Name resolver API and DNS-based name resolver

Motivation:

So far, we relied on the domain name resolution mechanism provided by
JDK.  It served its purpose very well, but had the following
shortcomings:

- Domain name resolution is performed in a blocking manner.
  This becomes a problem when a user has to connect to thousands of
  different hosts. e.g. web crawlers
- It is impossible to employ an alternative cache/retry policy.
  e.g. lower/upper bound in TTL, round-robin
- It is impossible to employ an alternative name resolution mechanism.
  e.g. Zookeeper-based name resolver

Modification:

- Add the resolver API in the new module: netty-resolver
- Implement the DNS-based resolver: netty-resolver-dns
  .. which uses netty-codec-dns
- Make ChannelFactory reusable because it's now used by
  io.netty.bootstrap, io.netty.resolver.dns, and potentially by other
  modules in the future
  - Move ChannelFactory from io.netty.bootstrap to io.netty.channel
  - Deprecate the old ChannelFactory
  - Add ReflectiveChannelFactory

Result:

It is trivial to resolve a large number of domain names asynchronously.
This commit is contained in:
Trustin Lee 2014-09-19 22:36:32 +09:00
parent ab2e80fbb1
commit e848066cab
30 changed files with 3627 additions and 136 deletions

View File

@ -189,6 +189,20 @@
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-resolver</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-resolver-dns</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-transport</artifactId>

View File

@ -341,6 +341,8 @@
<module>codec-mqtt</module>
<module>codec-socks</module>
<module>codec-stomp</module>
<module>resolver</module>
<module>resolver-dns</module>
<module>transport</module>
<module>transport-rxtx</module>
<module>transport-sctp</module>

49
resolver-dns/pom.xml Normal file
View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.netty</groupId>
<artifactId>netty-parent</artifactId>
<version>4.1.0.Beta4-SNAPSHOT</version>
</parent>
<artifactId>netty-resolver-dns</artifactId>
<packaging>jar</packaging>
<name>Netty/Resolver/DNS</name>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-resolver</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-codec-dns</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-transport</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,864 @@
/*
* 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.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.EventLoop;
import io.netty.channel.FixedRecvByteBufAllocator;
import io.netty.channel.ReflectiveChannelFactory;
import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.InternetProtocolFamily;
import io.netty.handler.codec.dns.DnsClass;
import io.netty.handler.codec.dns.DnsQueryEncoder;
import io.netty.handler.codec.dns.DnsQuestion;
import io.netty.handler.codec.dns.DnsResource;
import io.netty.handler.codec.dns.DnsResponse;
import io.netty.handler.codec.dns.DnsResponseCode;
import io.netty.handler.codec.dns.DnsResponseDecoder;
import io.netty.resolver.NameResolver;
import io.netty.resolver.SimpleNameResolver;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.collection.IntObjectHashMap;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.Promise;
import io.netty.util.concurrent.ScheduledFuture;
import io.netty.util.internal.OneTimeTask;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.SystemPropertyUtil;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.net.IDN;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReferenceArray;
/**
* A DNS-based {@link NameResolver}.
*/
public class DnsNameResolver extends SimpleNameResolver<InetSocketAddress> {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(DnsNameResolver.class);
static final InetSocketAddress ANY_LOCAL_ADDR = new InetSocketAddress(0);
private 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 ("true".equalsIgnoreCase(SystemPropertyUtil.get("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 DnsResponseDecoder DECODER = new DnsResponseDecoder();
private static final DnsQueryEncoder ENCODER = new DnsQueryEncoder();
final Iterable<InetSocketAddress> nameServerAddresses;
final DatagramChannel ch;
/**
* An array whose index is the ID of a DNS query and whose value is the promise of the corresponsing response. We
* don't use {@link IntObjectHashMap} or map-like data structure here because 64k elements are fairly small, which
* is only about 512KB.
*/
final AtomicReferenceArray<DnsQueryContext> promises = new AtomicReferenceArray<DnsQueryContext>(65536);
/**
* The cache for {@link #query(DnsQuestion)}
*/
final ConcurrentMap<DnsQuestion, DnsCacheEntry> queryCache = PlatformDependent.newConcurrentHashMap();
private final DnsResponseHandler responseHandler = new DnsResponseHandler();
private volatile long queryTimeoutMillis = 5000;
// The default TTL values here respect the TTL returned by the DNS server and do not cache the negative response.
private volatile int minTtl;
private volatile int maxTtl = Integer.MAX_VALUE;
private volatile int negativeTtl;
private volatile int maxTriesPerQuery = 2;
private volatile InternetProtocolFamily[] resolveAddressTypes = DEFAULT_RESOLVE_ADDRESS_TYPES;
private volatile boolean recursionDesired = true;
private volatile int maxQueriesPerResolve = 8;
private volatile int maxPayloadSize;
private volatile DnsClass maxPayloadSizeClass; // EDNS uses the CLASS field as the payload size field.
/**
* Creates a new DNS-based name resolver that communicates with a single DNS server.
*
* @param eventLoop the {@link EventLoop} which will perform the communication with the DNS servers
* @param channelType the type of the {@link DatagramChannel} to create
* @param nameServerAddress the address of the DNS server
*/
public DnsNameResolver(
EventLoop eventLoop, Class<? extends DatagramChannel> channelType,
InetSocketAddress nameServerAddress) {
this(eventLoop, channelType, ANY_LOCAL_ADDR, nameServerAddress);
}
/**
* Creates a new DNS-based name resolver that communicates with a single DNS server.
*
* @param eventLoop the {@link EventLoop} which will perform the communication with the DNS servers
* @param channelType the type of the {@link DatagramChannel} to create
* @param localAddress the local address of the {@link DatagramChannel}
* @param nameServerAddress the address of the DNS server
*/
public DnsNameResolver(
EventLoop eventLoop, Class<? extends DatagramChannel> channelType,
InetSocketAddress localAddress, InetSocketAddress nameServerAddress) {
this(eventLoop, new ReflectiveChannelFactory<DatagramChannel>(channelType), localAddress, nameServerAddress);
}
/**
* Creates a new DNS-based name resolver that communicates with a single DNS server.
*
* @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 nameServerAddress the address of the DNS server
*/
public DnsNameResolver(
EventLoop eventLoop, ChannelFactory<? extends DatagramChannel> channelFactory,
InetSocketAddress nameServerAddress) {
this(eventLoop, channelFactory, ANY_LOCAL_ADDR, nameServerAddress);
}
/**
* Creates a new DNS-based name resolver that communicates with a single DNS server.
*
* @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 localAddress the local address of the {@link DatagramChannel}
* @param nameServerAddress the address of the DNS server
*/
public DnsNameResolver(
EventLoop eventLoop, ChannelFactory<? extends DatagramChannel> channelFactory,
InetSocketAddress localAddress, InetSocketAddress nameServerAddress) {
this(eventLoop, channelFactory, localAddress, DnsServerAddresses.singleton(nameServerAddress));
}
/**
* 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 channelType the type of the {@link DatagramChannel} to create
* @param nameServerAddresses the addresses of the DNS server. For each DNS query, a new {@link Iterator} is
* created from this {@link Iterable} to determine which DNS server should be contacted
* for the next retry in case of failure.
*/
public DnsNameResolver(
EventLoop eventLoop, Class<? extends DatagramChannel> channelType,
Iterable<InetSocketAddress> nameServerAddresses) {
this(eventLoop, channelType, ANY_LOCAL_ADDR, nameServerAddresses);
}
/**
* 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 channelType the type of the {@link DatagramChannel} to create
* @param localAddress the local address of the {@link DatagramChannel}
* @param nameServerAddresses the addresses of the DNS server. For each DNS query, a new {@link Iterator} is
* created from this {@link Iterable} to determine which DNS server should be contacted
* for the next retry in case of failure.
*/
public DnsNameResolver(
EventLoop eventLoop, Class<? extends DatagramChannel> channelType,
InetSocketAddress localAddress, Iterable<InetSocketAddress> nameServerAddresses) {
this(eventLoop, new ReflectiveChannelFactory<DatagramChannel>(channelType), localAddress, nameServerAddresses);
}
/**
* 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 {@link Iterator} is
* created from this {@link Iterable} to determine which DNS server should be contacted
* for the next retry in case of failure.
*/
public DnsNameResolver(
EventLoop eventLoop, ChannelFactory<? extends DatagramChannel> channelFactory,
Iterable<InetSocketAddress> nameServerAddresses) {
this(eventLoop, channelFactory, ANY_LOCAL_ADDR, nameServerAddresses);
}
/**
* 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 localAddress the local address of the {@link DatagramChannel}
* @param nameServerAddresses the addresses of the DNS server. For each DNS query, a new {@link Iterator} is
* created from this {@link Iterable} to determine which DNS server should be contacted
* for the next retry in case of failure.
*/
public DnsNameResolver(
EventLoop eventLoop, ChannelFactory<? extends DatagramChannel> channelFactory,
InetSocketAddress localAddress, Iterable<InetSocketAddress> nameServerAddresses) {
super(eventLoop);
if (channelFactory == null) {
throw new NullPointerException("channelFactory");
}
if (nameServerAddresses == null) {
throw new NullPointerException("nameServerAddresses");
}
if (!nameServerAddresses.iterator().hasNext()) {
throw new NullPointerException("nameServerAddresses is empty");
}
if (localAddress == null) {
throw new NullPointerException("localAddress");
}
this.nameServerAddresses = nameServerAddresses;
ch = newChannel(channelFactory, localAddress);
setMaxPayloadSize(4096);
}
private DatagramChannel newChannel(
ChannelFactory<? extends DatagramChannel> channelFactory, InetSocketAddress localAddress) {
Bootstrap b = new Bootstrap();
b.group(executor());
b.channelFactory(channelFactory);
b.handler(new ChannelInitializer<DatagramChannel>() {
@Override
protected void initChannel(DatagramChannel ch) throws Exception {
ch.pipeline().addLast(DECODER, ENCODER, responseHandler);
}
});
DatagramChannel ch = (DatagramChannel) b.bind(localAddress).channel();
ch.closeFuture().addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
clearCache();
}
});
return ch;
}
/**
* Returns the minimum TTL of the cached DNS resource records (in seconds).
*
* @see #maxTtl()
* @see #setTtl(int, int)
*/
public int minTtl() {
return minTtl;
}
/**
* Returns the maximum TTL of the cached DNS resource records (in seconds).
*
* @see #minTtl()
* @see #setTtl(int, int)
*/
public int maxTtl() {
return maxTtl;
}
/**
* Sets the minimum and maximum TTL of the cached DNS resource records (in seconds). If the TTL of the DNS resource
* record returned by the DNS server is less than the minimum TTL or greater than the maximum TTL, this resolver
* will ignore the TTL from the DNS server and use the minimum TTL or the maximum TTL instead respectively.
* The default value is {@code 0} and {@link Integer#MAX_VALUE}, which practically tells this resolver to respect
* the TTL from the DNS server.
*
* @return {@code this}
*
* @see #minTtl()
* @see #maxTtl()
*/
public DnsNameResolver setTtl(int minTtl, int maxTtl) {
if (minTtl < 0) {
throw new IllegalArgumentException("minTtl: " + minTtl + " (expected: >= 0)");
}
if (maxTtl < 0) {
throw new IllegalArgumentException("maxTtl: " + maxTtl + " (expected: >= 0)");
}
if (minTtl > maxTtl) {
throw new IllegalArgumentException(
"minTtl: " + minTtl + ", maxTtl: " + maxTtl + " (expected: 0 <= minTtl <= maxTtl)");
}
this.maxTtl = maxTtl;
this.minTtl = minTtl;
return this;
}
/**
* Returns the TTL of the cache for the failed DNS queries (in seconds). The default value is {@code 0}, which
* disables the cache for negative results.
*
* @see #setNegativeTtl(int)
*/
public int negativeTtl() {
return negativeTtl;
}
/**
* Sets the TTL of the cache for the failed DNS queries (in seconds).
*
* @return {@code this}
*
* @see #negativeTtl()
*/
public DnsNameResolver setNegativeTtl(int negativeTtl) {
if (negativeTtl < 0) {
throw new IllegalArgumentException("negativeTtl: " + negativeTtl + " (expected: >= 0)");
}
this.negativeTtl = negativeTtl;
return this;
}
/**
* Returns the timeout of each DNS query performed by this resolver (in milliseconds).
* The default value is 5 seconds.
*
* @see #setQueryTimeoutMillis(long)
*/
public long queryTimeoutMillis() {
return queryTimeoutMillis;
}
/**
* Sets the timeout of each DNS query performed by this resolver (in milliseconds).
*
* @return {@code this}
*
* @see #queryTimeoutMillis()
*/
public DnsNameResolver setQueryTimeoutMillis(long queryTimeoutMillis) {
if (queryTimeoutMillis < 0) {
throw new IllegalArgumentException("queryTimeoutMillis: " + queryTimeoutMillis + " (expected: >= 0)");
}
this.queryTimeoutMillis = queryTimeoutMillis;
return this;
}
/**
* Returns the maximum number of tries for each query. The default value is 2 times.
*
* @see #setMaxTriesPerQuery(int)
*/
public int maxTriesPerQuery() {
return maxTriesPerQuery;
}
/**
* Sets the maximum number of tries for each query.
*
* @return {@code this}
*
* @see #maxTriesPerQuery()
*/
public DnsNameResolver setMaxTriesPerQuery(int maxTriesPerQuery) {
if (maxTriesPerQuery < 1) {
throw new IllegalArgumentException("maxTries: " + maxTriesPerQuery + " (expected: > 0)");
}
this.maxTriesPerQuery = maxTriesPerQuery;
return this;
}
/**
* Returns the list of the protocol families of the address resolved by {@link #resolve(SocketAddress)}
* in the order of preference.
* The default value depends on the value of the system property {@code "java.net.preferIPv6Addresses"}.
*
* @see #setResolveAddressTypes(InternetProtocolFamily...)
*/
public List<InternetProtocolFamily> resolveAddressTypes() {
return Arrays.asList(resolveAddressTypes);
}
InternetProtocolFamily[] resolveAddressTypesUnsafe() {
return resolveAddressTypes;
}
/**
* Sets the list of the protocol families of the address resolved by {@link #resolve(SocketAddress)}.
* Usually, both {@link InternetProtocolFamily#IPv4} and {@link InternetProtocolFamily#IPv6} are specified in the
* order of preference. To enforce the resolve to retrieve the address of a specific protocol family, specify
* only a single {@link InternetProtocolFamily}.
*
* @return {@code this}
*
* @see #resolveAddressTypes()
*/
public DnsNameResolver setResolveAddressTypes(InternetProtocolFamily... resolveAddressTypes) {
if (resolveAddressTypes == null) {
throw new NullPointerException("resolveAddressTypes");
}
final List<InternetProtocolFamily> list =
new ArrayList<InternetProtocolFamily>(InternetProtocolFamily.values().length);
for (InternetProtocolFamily f: resolveAddressTypes) {
if (f == null) {
break;
}
// Avoid duplicate entries.
if (list.contains(f)) {
continue;
}
list.add(f);
}
if (list.isEmpty()) {
throw new IllegalArgumentException("no protocol family specified");
}
this.resolveAddressTypes = list.toArray(new InternetProtocolFamily[list.size()]);
return this;
}
/**
* Sets the list of the protocol families of the address resolved by {@link #resolve(SocketAddress)}.
* Usually, both {@link InternetProtocolFamily#IPv4} and {@link InternetProtocolFamily#IPv6} are specified in the
* order of preference. To enforce the resolve to retrieve the address of a specific protocol family, specify
* only a single {@link InternetProtocolFamily}.
*
* @return {@code this}
*
* @see #resolveAddressTypes()
*/
public DnsNameResolver setResolveAddressTypes(Iterable<InternetProtocolFamily> resolveAddressTypes) {
if (resolveAddressTypes == null) {
throw new NullPointerException("resolveAddressTypes");
}
final List<InternetProtocolFamily> list =
new ArrayList<InternetProtocolFamily>(InternetProtocolFamily.values().length);
for (InternetProtocolFamily f: resolveAddressTypes) {
if (f == null) {
break;
}
// Avoid duplicate entries.
if (list.contains(f)) {
continue;
}
list.add(f);
}
if (list.isEmpty()) {
throw new IllegalArgumentException("no protocol family specified");
}
this.resolveAddressTypes = list.toArray(new InternetProtocolFamily[list.size()]);
return this;
}
/**
* 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}.
*
* @see #setRecursionDesired(boolean)
*/
public boolean isRecursionDesired() {
return recursionDesired;
}
/**
* Sets if this resolver has to send a DNS query with the RD (recursion desired) flag set.
*
* @return {@code this}
*
* @see #isRecursionDesired()
*/
public DnsNameResolver setRecursionDesired(boolean recursionDesired) {
this.recursionDesired = recursionDesired;
return this;
}
/**
* Returns the maximum allowed number of DNS queries to send when resolving a host name.
* The default value is {@code 8}.
*
* @see #setMaxQueriesPerResolve(int)
*/
public int maxQueriesPerResolve() {
return maxQueriesPerResolve;
}
/**
* Sets the maximum allowed number of DNS queries to send when resolving a host name.
*
* @return {@code this}
*
* @see #maxQueriesPerResolve()
*/
public DnsNameResolver setMaxQueriesPerResolve(int maxQueriesPerResolve) {
if (maxQueriesPerResolve <= 0) {
throw new IllegalArgumentException("maxQueriesPerResolve: " + maxQueriesPerResolve + " (expected: > 0)");
}
this.maxQueriesPerResolve = maxQueriesPerResolve;
return this;
}
/**
* Returns the capacity of the datagram packet buffer (in bytes). The default value is {@code 4096} bytes.
*
* @see #setMaxPayloadSize(int)
*/
public int maxPayloadSize() {
return maxPayloadSize;
}
/**
* Sets the capacity of the datagram packet buffer (in bytes). The default value is {@code 4096} bytes.
*
* @return {@code this}
*
* @see #maxPayloadSize()
*/
public DnsNameResolver setMaxPayloadSize(int maxPayloadSize) {
if (maxPayloadSize <= 0) {
throw new IllegalArgumentException("maxPayloadSize: " + maxPayloadSize + " (expected: > 0)");
}
if (this.maxPayloadSize == maxPayloadSize) {
// Same value; no need to instantiate DnsClass and RecvByteBufAllocator again.
return this;
}
this.maxPayloadSize = maxPayloadSize;
maxPayloadSizeClass = DnsClass.valueOf(maxPayloadSize);
ch.config().setRecvByteBufAllocator(new FixedRecvByteBufAllocator(maxPayloadSize));
return this;
}
DnsClass maxPayloadSizeClass() {
return maxPayloadSizeClass;
}
/**
* Clears all the DNS resource records cached by this resolver.
*
* @return {@code this}
*
* @see #clearCache(DnsQuestion)
*/
public DnsNameResolver clearCache() {
for (Iterator<Entry<DnsQuestion, DnsCacheEntry>> i = queryCache.entrySet().iterator(); i.hasNext();) {
Entry<DnsQuestion, DnsCacheEntry> e = i.next();
i.remove();
e.getValue().release();
}
return this;
}
/**
* Clears the DNS resource record of the specified DNS question from the cache of this resolver.
*/
public boolean clearCache(DnsQuestion question) {
DnsCacheEntry e = queryCache.remove(question);
if (e != null) {
e.release();
return true;
} else {
return false;
}
}
/**
* 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();
}
@Override
protected boolean doIsResolved(InetSocketAddress address) {
return !address.isUnresolved();
}
@Override
protected void doResolve(InetSocketAddress unresolvedAddress, Promise<InetSocketAddress> promise) throws Exception {
final String hostname = IDN.toASCII(unresolvedAddress.getHostString());
final int port = unresolvedAddress.getPort();
final DnsNameResolverContext ctx = new DnsNameResolverContext(this, hostname, port, promise);
ctx.resolve();
}
/**
* Sends a DNS query with the specified question.
*/
public Future<DnsResponse> query(DnsQuestion question) {
return query(nameServerAddresses, question);
}
/**
* Sends a DNS query with the specified question.
*/
public Future<DnsResponse> query(DnsQuestion question, Promise<DnsResponse> promise) {
return query(nameServerAddresses, question, promise);
}
/**
* Sends a DNS query with the specified question using the specified name server list.
*/
public Future<DnsResponse> query(Iterable<InetSocketAddress> nameServerAddresses, DnsQuestion question) {
if (nameServerAddresses == null) {
throw new NullPointerException("nameServerAddresses");
}
if (question == null) {
throw new NullPointerException("question");
}
final EventLoop eventLoop = ch.eventLoop();
final DnsCacheEntry cachedResult = queryCache.get(question);
if (cachedResult != null) {
if (cachedResult.response != null) {
return eventLoop.newSucceededFuture(cachedResult.response.retain());
} else {
return eventLoop.newFailedFuture(cachedResult.cause);
}
} else {
return query0(nameServerAddresses, question, eventLoop.<DnsResponse>newPromise());
}
}
/**
* Sends a DNS query with the specified question using the specified name server list.
*/
public Future<DnsResponse> query(
Iterable<InetSocketAddress> nameServerAddresses, DnsQuestion question, Promise<DnsResponse> promise) {
if (nameServerAddresses == null) {
throw new NullPointerException("nameServerAddresses");
}
if (question == null) {
throw new NullPointerException("question");
}
if (promise == null) {
throw new NullPointerException("promise");
}
final DnsCacheEntry cachedResult = queryCache.get(question);
if (cachedResult != null) {
if (cachedResult.response != null) {
return promise.setSuccess(cachedResult.response.retain());
} else {
return promise.setFailure(cachedResult.cause);
}
} else {
return query0(nameServerAddresses, question, promise);
}
}
private Future<DnsResponse> query0(
Iterable<InetSocketAddress> nameServerAddresses, DnsQuestion question, Promise<DnsResponse> promise) {
try {
new DnsQueryContext(this, nameServerAddresses, question, promise).query();
return promise;
} catch (Exception e) {
return promise.setFailure(e);
}
}
void cache(final DnsQuestion question, DnsCacheEntry entry, long delaySeconds) {
queryCache.put(question, entry);
boolean scheduled = false;
try {
entry.expirationFuture = ch.eventLoop().schedule(new OneTimeTask() {
@Override
public void run() {
Object response = queryCache.remove(question);
ReferenceCountUtil.safeRelease(response);
}
}, delaySeconds, TimeUnit.SECONDS);
scheduled = true;
} finally {
if (!scheduled) {
// If failed to schedule the expiration task,
// remove the entry from the cache so that it does not leak.
queryCache.remove(question);
entry.release();
}
}
}
private final class DnsResponseHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
boolean success = false;
try {
final DnsResponse res = (DnsResponse) msg;
final int queryId = res.header().id();
if (logger.isDebugEnabled()) {
logger.debug("{} RECEIVED: [{}: {}], {}", ch, queryId, res.sender(), res);
}
final DnsQueryContext qCtx = promises.get(queryId);
if (qCtx == null) {
if (logger.isWarnEnabled()) {
logger.warn("Received a DNS response with an unknown ID: {}", queryId);
}
return;
}
final List<DnsQuestion> questions = res.questions();
if (questions.size() != 1) {
logger.warn("Received a DNS response with invalid number of questions: {}", res);
return;
}
final DnsQuestion q = qCtx.question();
if (!q.equals(questions.get(0))) {
logger.warn("Received a mismatching DNS response: {}", res);
return;
}
// Cancel the timeout task.
final ScheduledFuture<?> timeoutFuture = qCtx.timeoutFuture();
if (timeoutFuture != null) {
timeoutFuture.cancel(false);
}
if (res.header().responseCode() == DnsResponseCode.NOERROR) {
cache(q, res);
promises.set(queryId, null);
qCtx.promise().trySuccess(res);
success = true;
} else {
qCtx.retry(res.sender(),
"response code: " + res.header().responseCode() +
" with " + res.answers().size() + " answer(s) and " +
res.authorityResources().size() + " authority resource(s)");
}
} finally {
if (!success) {
ReferenceCountUtil.safeRelease(msg);
}
}
}
private void cache(DnsQuestion question, DnsResponse res) {
final int maxTtl = maxTtl();
if (maxTtl == 0) {
return;
}
long ttl = Long.MAX_VALUE;
// Find the smallest TTL value returned by the server.
for (DnsResource r: res.answers()) {
long rTtl = r.timeToLive();
if (ttl > rTtl) {
ttl = rTtl;
}
}
// Ensure that the found TTL is between minTtl and maxTtl.
ttl = Math.max(minTtl(), Math.min(maxTtl, ttl));
res.retain();
DnsNameResolver.this.cache(question, new DnsCacheEntry(res), ttl);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
logger.warn("Unexpected exception: ", cause);
}
}
static final class DnsCacheEntry {
final DnsResponse response;
final Throwable cause;
volatile ScheduledFuture<?> expirationFuture;
DnsCacheEntry(DnsResponse response) {
this.response = response;
cause = null;
}
DnsCacheEntry(Throwable cause) {
this.cause = cause;
response = null;
}
void release() {
DnsResponse response = this.response;
if (response != null) {
ReferenceCountUtil.safeRelease(response);
}
ScheduledFuture<?> expirationFuture = this.expirationFuture;
if (expirationFuture != null) {
expirationFuture.cancel(false);
}
}
}
}

View File

@ -0,0 +1,477 @@
/*
* 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.buffer.ByteBuf;
import io.netty.channel.socket.InternetProtocolFamily;
import io.netty.handler.codec.dns.DnsClass;
import io.netty.handler.codec.dns.DnsQuestion;
import io.netty.handler.codec.dns.DnsResource;
import io.netty.handler.codec.dns.DnsResponse;
import io.netty.handler.codec.dns.DnsResponseDecoder;
import io.netty.handler.codec.dns.DnsType;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.Promise;
import io.netty.util.internal.StringUtil;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
final class DnsNameResolverContext {
private static final int INADDRSZ4 = 4;
private static final int INADDRSZ6 = 16;
private static final FutureListener<DnsResponse> RELEASE_RESPONSE = new FutureListener<DnsResponse>() {
@Override
public void operationComplete(Future<DnsResponse> future) {
if (future.isSuccess()) {
future.getNow().release();
}
}
};
private final DnsNameResolver parent;
private final Promise<InetSocketAddress> promise;
private final String hostname;
private final int port;
private final int maxAllowedQueries;
private final InternetProtocolFamily[] resolveAddressTypes;
private final Set<Future<DnsResponse>> queriesInProgress =
Collections.newSetFromMap(new IdentityHashMap<Future<DnsResponse>, Boolean>());
private List<InetAddress> resolvedAddresses;
private StringBuilder trace;
private int allowedQueries;
private boolean triedCNAME;
DnsNameResolverContext(DnsNameResolver parent, String hostname, int port, Promise<InetSocketAddress> promise) {
this.parent = parent;
this.promise = promise;
this.hostname = hostname;
this.port = port;
maxAllowedQueries = parent.maxQueriesPerResolve();
resolveAddressTypes = parent.resolveAddressTypesUnsafe();
allowedQueries = maxAllowedQueries;
}
void resolve() {
for (InternetProtocolFamily f: resolveAddressTypes) {
final DnsType type;
switch (f) {
case IPv4:
type = DnsType.A;
break;
case IPv6:
type = DnsType.AAAA;
break;
default:
throw new Error();
}
query(parent.nameServerAddresses, new DnsQuestion(hostname, type));
}
}
private void query(Iterable<InetSocketAddress> nameServerAddresses, final DnsQuestion question) {
if (allowedQueries == 0 || promise.isCancelled()) {
return;
}
allowedQueries --;
final Future<DnsResponse> f = parent.query(nameServerAddresses, question);
queriesInProgress.add(f);
f.addListener(new FutureListener<DnsResponse>() {
@Override
public void operationComplete(Future<DnsResponse> future) throws Exception {
queriesInProgress.remove(future);
if (promise.isDone()) {
return;
}
try {
if (future.isSuccess()) {
onResponse(question, future.getNow());
} else {
addTrace(future.cause());
}
} finally {
tryToFinishResolve();
}
}
});
}
void onResponse(final DnsQuestion question, final DnsResponse response) {
final DnsType type = question.type();
try {
if (type == DnsType.A || type == DnsType.AAAA) {
onResponseAorAAAA(type, question, response);
} else if (type == DnsType.CNAME) {
onResponseCNAME(question, response);
}
} finally {
ReferenceCountUtil.safeRelease(response);
}
}
private void onResponseAorAAAA(DnsType qType, DnsQuestion question, DnsResponse response) {
// We often get a bunch of CNAMES as well when we asked for A/AAAA.
final Map<String, String> cnames = buildAliasMap(response);
boolean found = false;
for (DnsResource r: response.answers()) {
final DnsType type = r.type();
if (type != DnsType.A && type != DnsType.AAAA) {
continue;
}
final String qName = question.name().toLowerCase(Locale.US);
final String rName = r.name().toLowerCase(Locale.US);
// Make sure the record is for the questioned domain.
if (!rName.equals(qName)) {
// Even if the record's name is not exactly same, it might be an alias defined in the CNAME records.
String resolved = qName;
do {
resolved = cnames.get(resolved);
if (rName.equals(resolved)) {
break;
}
} while (resolved != null);
if (resolved == null) {
continue;
}
}
final ByteBuf content = r.content();
final int contentLen = content.readableBytes();
if (contentLen != INADDRSZ4 && contentLen != INADDRSZ6) {
continue;
}
final byte[] addrBytes = new byte[contentLen];
content.getBytes(content.readerIndex(), addrBytes);
try {
InetAddress resolved = InetAddress.getByAddress(hostname, addrBytes);
if (resolvedAddresses == null) {
resolvedAddresses = new ArrayList<InetAddress>();
}
resolvedAddresses.add(resolved);
found = true;
} catch (UnknownHostException e) {
// Should never reach here.
throw new Error(e);
}
}
if (found) {
return;
}
addTrace(response.sender(), "no matching " + qType + " record found");
// We aked for A/AAAA but we got only CNAME.
if (!cnames.isEmpty()) {
onResponseCNAME(question, response, cnames, false);
}
}
private void onResponseCNAME(DnsQuestion question, DnsResponse response) {
onResponseCNAME(question, response, buildAliasMap(response), true);
}
private void onResponseCNAME(
DnsQuestion question, DnsResponse response, Map<String, String> cnames, boolean trace) {
// Resolve the host name in the question into the real host name.
final String name = question.name().toLowerCase(Locale.US);
String resolved = name;
boolean found = false;
for (;;) {
String next = cnames.get(resolved);
if (next != null) {
found = true;
resolved = next;
} else {
break;
}
}
if (found) {
followCname(response.sender(), name, resolved);
} else if (trace) {
addTrace(response.sender(), "no matching CNAME record found");
}
}
private static Map<String, String> buildAliasMap(DnsResponse response) {
Map<String, String> cnames = null;
for (DnsResource r: response.answers()) {
final DnsType type = r.type();
if (type != DnsType.CNAME) {
continue;
}
String content = decodeDomainName(r.content());
if (content == null) {
continue;
}
if (cnames == null) {
cnames = new HashMap<String, String>();
}
cnames.put(r.name().toLowerCase(Locale.US), content.toLowerCase(Locale.US));
}
return cnames != null? cnames : Collections.<String, String>emptyMap();
}
void tryToFinishResolve() {
if (!queriesInProgress.isEmpty()) {
// There are still some queries we did not receive responses for.
if (gotPreferredAddress()) {
// But it's OK to finish the resolution process if we got a resolved address of the preferred type.
finishResolve();
}
// We did not get any resolved address of the preferred type, so we can't finish the resolution process.
return;
}
// There are no queries left to try.
if (resolvedAddresses == null) {
// .. and we could not find any A/AAAA records.
if (!triedCNAME) {
// As the last resort, try to query CNAME, just in case the name server has it.
triedCNAME = true;
query(parent.nameServerAddresses, new DnsQuestion(hostname, DnsType.CNAME, DnsClass.IN));
return;
}
}
// We have at least one resolved address or tried CNAME as the last resort..
finishResolve();
}
private boolean gotPreferredAddress() {
if (resolvedAddresses == null) {
return false;
}
final int size = resolvedAddresses.size();
switch (resolveAddressTypes[0]) {
case IPv4:
for (int i = 0; i < size; i ++) {
if (resolvedAddresses.get(i) instanceof Inet4Address) {
return true;
}
}
break;
case IPv6:
for (int i = 0; i < size; i ++) {
if (resolvedAddresses.get(i) instanceof Inet6Address) {
return true;
}
}
break;
}
return false;
}
private void finishResolve() {
if (!queriesInProgress.isEmpty()) {
// If there are queries in progress, we should cancel it because we already finished the resolution.
for (Iterator<Future<DnsResponse>> i = queriesInProgress.iterator(); i.hasNext();) {
Future<DnsResponse> f = i.next();
i.remove();
if (!f.cancel(false)) {
f.addListener(RELEASE_RESPONSE);
}
}
}
if (resolvedAddresses != null) {
// Found at least one resolved address.
for (InternetProtocolFamily f: resolveAddressTypes) {
switch (f) {
case IPv4:
if (finishResolveWithIPv4()) {
return;
}
break;
case IPv6:
if (finishResolveWithIPv6()) {
return;
}
break;
}
}
}
// No resolved address found.
int tries = maxAllowedQueries - allowedQueries;
UnknownHostException cause;
if (tries > 1) {
cause = new UnknownHostException(
"failed to resolve " + hostname + " after " + tries + " queries:" +
trace);
} else {
cause = new UnknownHostException("failed to resolve " + hostname + ':' + trace);
}
promise.tryFailure(cause);
}
private boolean finishResolveWithIPv4() {
final List<InetAddress> resolvedAddresses = this.resolvedAddresses;
final int size = resolvedAddresses.size();
for (int i = 0; i < size; i ++) {
InetAddress a = resolvedAddresses.get(i);
if (a instanceof Inet4Address) {
promise.trySuccess(new InetSocketAddress(a, port));
return true;
}
}
return false;
}
private boolean finishResolveWithIPv6() {
final List<InetAddress> resolvedAddresses = this.resolvedAddresses;
final int size = resolvedAddresses.size();
for (int i = 0; i < size; i ++) {
InetAddress a = resolvedAddresses.get(i);
if (a instanceof Inet6Address) {
promise.trySuccess(new InetSocketAddress(a, port));
return true;
}
}
return false;
}
/**
* Adapted from {@link DnsResponseDecoder#readName(ByteBuf)}.
*/
static String decodeDomainName(ByteBuf buf) {
buf.markReaderIndex();
try {
int position = -1;
int checked = 0;
int length = buf.writerIndex();
StringBuilder name = new StringBuilder(64);
for (int len = buf.readUnsignedByte(); buf.isReadable() && len != 0; len = buf.readUnsignedByte()) {
boolean pointer = (len & 0xc0) == 0xc0;
if (pointer) {
if (position == -1) {
position = buf.readerIndex() + 1;
}
buf.readerIndex((len & 0x3f) << 8 | buf.readUnsignedByte());
// check for loops
checked += 2;
if (checked >= length) {
// Name contains a loop; give up.
return null;
}
} else {
name.append(buf.toString(buf.readerIndex(), len, CharsetUtil.UTF_8)).append('.');
buf.skipBytes(len);
}
}
if (position != -1) {
buf.readerIndex(position);
}
if (name.length() == 0) {
return null;
}
return name.substring(0, name.length() - 1);
} finally {
buf.resetReaderIndex();
}
}
private void followCname(
InetSocketAddress nameServerAddr, String name, String cname) {
if (trace == null) {
trace = new StringBuilder(128);
}
trace.append(StringUtil.NEWLINE);
trace.append("\tfrom ");
trace.append(nameServerAddr);
trace.append(": ");
trace.append(name);
trace.append(" CNAME ");
trace.append(cname);
query(parent.nameServerAddresses, new DnsQuestion(cname, DnsType.A, DnsClass.IN));
query(parent.nameServerAddresses, new DnsQuestion(cname, DnsType.AAAA, DnsClass.IN));
}
private void addTrace(InetSocketAddress nameServerAddr, String msg) {
if (trace == null) {
trace = new StringBuilder(128);
}
trace.append(StringUtil.NEWLINE);
trace.append("\tfrom ");
trace.append(nameServerAddr);
trace.append(": ");
trace.append(msg);
}
private void addTrace(Throwable cause) {
if (trace == null) {
trace = new StringBuilder(128);
}
trace.append(StringUtil.NEWLINE);
trace.append("Caused by: ");
trace.append(cause);
}
}

View File

@ -0,0 +1,101 @@
/*
* 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.channel.ChannelFactory;
import io.netty.channel.EventLoop;
import io.netty.channel.ReflectiveChannelFactory;
import io.netty.channel.socket.DatagramChannel;
import io.netty.resolver.NameResolver;
import io.netty.resolver.NameResolverGroup;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.internal.StringUtil;
import java.net.InetSocketAddress;
import static io.netty.resolver.dns.DnsNameResolver.*;
/**
* A {@link NameResolverGroup} of {@link DnsNameResolver}s.
*/
public final class DnsNameResolverGroup extends NameResolverGroup<InetSocketAddress> {
private final ChannelFactory<? extends DatagramChannel> channelFactory;
private final InetSocketAddress localAddress;
private final Iterable<InetSocketAddress> nameServerAddresses;
public DnsNameResolverGroup(
Class<? extends DatagramChannel> channelType,
InetSocketAddress nameServerAddress) {
this(channelType, ANY_LOCAL_ADDR, nameServerAddress);
}
public DnsNameResolverGroup(
Class<? extends DatagramChannel> channelType,
InetSocketAddress localAddress, InetSocketAddress nameServerAddress) {
this(new ReflectiveChannelFactory<DatagramChannel>(channelType), localAddress, nameServerAddress);
}
public DnsNameResolverGroup(
ChannelFactory<? extends DatagramChannel> channelFactory,
InetSocketAddress nameServerAddress) {
this(channelFactory, ANY_LOCAL_ADDR, nameServerAddress);
}
public DnsNameResolverGroup(
ChannelFactory<? extends DatagramChannel> channelFactory,
InetSocketAddress localAddress, InetSocketAddress nameServerAddress) {
this(channelFactory, localAddress, DnsServerAddresses.singleton(nameServerAddress));
}
public DnsNameResolverGroup(
Class<? extends DatagramChannel> channelType,
Iterable<InetSocketAddress> nameServerAddresses) {
this(channelType, ANY_LOCAL_ADDR, nameServerAddresses);
}
public DnsNameResolverGroup(
Class<? extends DatagramChannel> channelType,
InetSocketAddress localAddress, Iterable<InetSocketAddress> nameServerAddresses) {
this(new ReflectiveChannelFactory<DatagramChannel>(channelType), localAddress, nameServerAddresses);
}
public DnsNameResolverGroup(
ChannelFactory<? extends DatagramChannel> channelFactory,
Iterable<InetSocketAddress> nameServerAddresses) {
this(channelFactory, ANY_LOCAL_ADDR, nameServerAddresses);
}
public DnsNameResolverGroup(
ChannelFactory<? extends DatagramChannel> channelFactory,
InetSocketAddress localAddress, Iterable<InetSocketAddress> nameServerAddresses) {
this.channelFactory = channelFactory;
this.localAddress = localAddress;
this.nameServerAddresses = nameServerAddresses;
}
@Override
protected NameResolver<InetSocketAddress> newResolver(EventExecutor executor) throws Exception {
if (!(executor instanceof EventLoop)) {
throw new IllegalStateException(
"unsupported executor type: " + StringUtil.simpleClassName(executor) +
" (expected: " + StringUtil.simpleClassName(EventLoop.class));
}
return new DnsNameResolver((EventLoop) executor, channelFactory, localAddress, nameServerAddresses);
}
}

View File

@ -0,0 +1,201 @@
/*
* 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.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.socket.DatagramChannel;
import io.netty.handler.codec.dns.DnsQuery;
import io.netty.handler.codec.dns.DnsQuestion;
import io.netty.handler.codec.dns.DnsResource;
import io.netty.handler.codec.dns.DnsResponse;
import io.netty.handler.codec.dns.DnsType;
import io.netty.resolver.dns.DnsNameResolver.DnsCacheEntry;
import io.netty.util.concurrent.Promise;
import io.netty.util.concurrent.ScheduledFuture;
import io.netty.util.internal.OneTimeTask;
import io.netty.util.internal.StringUtil;
import io.netty.util.internal.ThreadLocalRandom;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.Iterator;
import java.util.concurrent.TimeUnit;
final class DnsQueryContext {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(DnsQueryContext.class);
private final DnsNameResolver parent;
private final Promise<DnsResponse> promise;
private final int id;
private final DnsQuestion question;
private final DnsResource optResource;
private final Iterator<InetSocketAddress> nameServerAddresses;
private final boolean recursionDesired;
private final int maxTries;
private int remainingTries;
private volatile ScheduledFuture<?> timeoutFuture;
private StringBuilder trace;
DnsQueryContext(DnsNameResolver parent,
Iterable<InetSocketAddress> nameServerAddresses,
DnsQuestion question, Promise<DnsResponse> promise) {
this.parent = parent;
this.promise = promise;
this.question = question;
id = allocateId();
recursionDesired = parent.isRecursionDesired();
maxTries = parent.maxTriesPerQuery();
remainingTries = maxTries;
optResource = new DnsResource("", DnsType.OPT, parent.maxPayloadSizeClass(), 0, Unpooled.EMPTY_BUFFER);
this.nameServerAddresses = nameServerAddresses.iterator();
}
private int allocateId() {
int id = ThreadLocalRandom.current().nextInt(parent.promises.length());
final int maxTries = parent.promises.length() << 1;
int tries = 0;
for (;;) {
if (parent.promises.compareAndSet(id, null, this)) {
return id;
}
id = id + 1 & 0xFFFF;
if (++ tries >= maxTries) {
throw new IllegalStateException("query ID space exhausted: " + question);
}
}
}
Promise<DnsResponse> promise() {
return promise;
}
DnsQuestion question() {
return question;
}
ScheduledFuture<?> timeoutFuture() {
return timeoutFuture;
}
void query() {
final DnsQuestion question = this.question;
if (remainingTries <= 0 || !nameServerAddresses.hasNext()) {
parent.promises.set(id, null);
int tries = maxTries - remainingTries;
UnknownHostException cause;
if (tries > 1) {
cause = new UnknownHostException(
"failed to resolve " + question + " after " + tries + " attempts:" +
trace);
} else {
cause = new UnknownHostException("failed to resolve " + question + ':' + trace);
}
cache(question, cause);
promise.tryFailure(cause);
return;
}
remainingTries --;
final InetSocketAddress nameServerAddr = nameServerAddresses.next();
final DnsQuery query = new DnsQuery(id, nameServerAddr);
query.addQuestion(question);
query.header().setRecursionDesired(recursionDesired);
query.addAdditionalResource(optResource);
final DatagramChannel ch = parent.ch;
if (logger.isDebugEnabled()) {
logger.debug("{} WRITE: [{}: {}], {}", ch, id, nameServerAddr, question);
}
final ChannelFuture writeFuture = ch.writeAndFlush(query);
if (writeFuture.isDone()) {
onQueryWriteCompletion(writeFuture, nameServerAddr);
} else {
writeFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
onQueryWriteCompletion(writeFuture, nameServerAddr);
}
});
}
}
private void onQueryWriteCompletion(ChannelFuture writeFuture, final InetSocketAddress nameServerAddr) {
if (!writeFuture.isSuccess()) {
retry(nameServerAddr, "failed to send a query: " + writeFuture.cause());
return;
}
// Schedule a query timeout task if necessary.
final long queryTimeoutMillis = parent.queryTimeoutMillis();
if (queryTimeoutMillis > 0) {
timeoutFuture = parent.ch.eventLoop().schedule(new OneTimeTask() {
@Override
public void run() {
if (promise.isDone()) {
// Received a response before the query times out.
return;
}
retry(nameServerAddr, "query timed out after " + queryTimeoutMillis + " milliseconds");
}
}, queryTimeoutMillis, TimeUnit.MILLISECONDS);
}
}
void retry(InetSocketAddress nameServerAddr, String message) {
if (promise.isCancelled()) {
return;
}
if (trace == null) {
trace = new StringBuilder(128);
}
trace.append(StringUtil.NEWLINE);
trace.append("\tfrom ");
trace.append(nameServerAddr);
trace.append(": ");
trace.append(message);
query();
}
private void cache(final DnsQuestion question, Throwable cause) {
final int negativeTtl = parent.negativeTtl();
if (negativeTtl == 0) {
return;
}
parent.cache(question, new DnsCacheEntry(cause), negativeTtl);
}
}

View File

@ -0,0 +1,395 @@
/*
* 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.util.internal.PlatformDependent;
import io.netty.util.internal.ThreadLocalRandom;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Random;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
/**
* Provides a sequence of DNS server addresses to {@link DnsNameResolver}. The {@link Iterator} created by the
* {@link Iterable}s returned by the factory methods of this class is infinite, which means {@link Iterator#hasNext()}
* will never return {@code false} and {@link Iterator#next()} will never raise a {@link NoSuchElementException}.
*/
@SuppressWarnings("IteratorNextCanNotThrowNoSuchElementException")
public final class DnsServerAddresses {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(DnsServerAddresses.class);
private static final List<InetSocketAddress> DEFAULT_NAME_SERVER_LIST;
private static final InetSocketAddress[] DEFAULT_NAME_SERVER_ARRAY;
static {
final int DNS_PORT = 53;
final List<InetSocketAddress> defaultNameServers = new ArrayList<InetSocketAddress>(2);
try {
Class<?> configClass = Class.forName("sun.net.dns.ResolverConfiguration");
Method open = configClass.getMethod("open");
Method nameservers = configClass.getMethod("nameservers");
Object instance = open.invoke(null);
@SuppressWarnings("unchecked")
final List<String> list = (List<String>) nameservers.invoke(instance);
final int size = list.size();
for (int i = 0; i < size; i ++) {
String dnsAddr = list.get(i);
if (dnsAddr != null) {
defaultNameServers.add(new InetSocketAddress(InetAddress.getByName(dnsAddr), DNS_PORT));
}
}
} catch (Exception ignore) {
// Failed to get the system name server list.
// Will add the default name servers afterwards.
}
if (!defaultNameServers.isEmpty()) {
if (logger.isDebugEnabled()) {
logger.debug(
"Default DNS servers: {} (sun.net.dns.ResolverConfiguration)", defaultNameServers);
}
} else {
Collections.addAll(
defaultNameServers,
new InetSocketAddress("8.8.8.8", DNS_PORT),
new InetSocketAddress("8.8.4.4", DNS_PORT));
if (logger.isWarnEnabled()) {
logger.warn(
"Default DNS servers: {} (Google Public DNS as a fallback)", defaultNameServers);
}
}
DEFAULT_NAME_SERVER_LIST = Collections.unmodifiableList(defaultNameServers);
DEFAULT_NAME_SERVER_ARRAY = defaultNameServers.toArray(new InetSocketAddress[defaultNameServers.size()]);
}
/**
* Returns the list of the system DNS server addresses. If it failed to retrieve the list of the system DNS server
* addresses from the environment, it will return {@code "8.8.8.8"} and {@code "8.8.4.4"}, the addresses of the
* Google public DNS servers. Note that the {@code Iterator} of the returned list is not infinite unlike other
* factory methods in this class. To make the returned list infinite, pass it to the other factory method. e.g.
* <pre>
* addresses = {@link #sequential(Iterable) sequential}({@link #defaultAddresses()});
* </pre>
*/
public static List<InetSocketAddress> defaultAddresses() {
return DEFAULT_NAME_SERVER_LIST;
}
/**
* Returns an infinite {@link Iterable} of the specified DNS server addresses, whose {@link Iterator} iterates
* the DNS server addresses in a sequential order.
*/
public static Iterable<InetSocketAddress> sequential(Iterable<? extends InetSocketAddress> addresses) {
return sequential0(sanitize(addresses));
}
/**
* Returns an infinite {@link Iterable} of the specified DNS server addresses, whose {@link Iterator} iterates
* the DNS server addresses in a sequential order.
*/
public static Iterable<InetSocketAddress> sequential(InetSocketAddress... addresses) {
return sequential0(sanitize(addresses));
}
private static Iterable<InetSocketAddress> sequential0(final InetSocketAddress[] addresses) {
return new Iterable<InetSocketAddress>() {
@Override
public Iterator<InetSocketAddress> iterator() {
return new SequentialAddressIterator(addresses, 0);
}
};
}
/**
* Returns an infinite {@link Iterable} of the specified DNS server addresses, whose {@link Iterator} iterates
* the DNS server addresses in a shuffled order.
*/
public static Iterable<InetSocketAddress> shuffled(Iterable<? extends InetSocketAddress> addresses) {
return shuffled0(sanitize(addresses));
}
/**
* Returns an infinite {@link Iterable} of the specified DNS server addresses, whose {@link Iterator} iterates
* the DNS server addresses in a shuffled order.
*/
public static Iterable<InetSocketAddress> shuffled(InetSocketAddress... addresses) {
return shuffled0(sanitize(addresses));
}
private static Iterable<InetSocketAddress> shuffled0(final InetSocketAddress[] addresses) {
if (addresses.length == 1) {
return singleton(addresses[0]);
}
return new Iterable<InetSocketAddress>() {
@Override
public Iterator<InetSocketAddress> iterator() {
return new ShuffledAddressIterator(addresses);
}
};
}
/**
* Returns an infinite {@link Iterable} of the specified DNS server addresses, whose {@link Iterator} iterates
* the DNS server addresses in a rotational order. It is similar to {@link #sequential(Iterable)}, but each
* {@link Iterator} starts from a different starting point. For example, the first {@link Iterable#iterator()}
* will iterate from the first DNS server address, the second one will iterate from the second DNS server address,
* and so on.
*/
public static Iterable<InetSocketAddress> rotational(Iterable<? extends InetSocketAddress> addresses) {
return rotational0(sanitize(addresses));
}
/**
* Returns an infinite {@link Iterable} of the specified DNS server addresses, whose {@link Iterator} iterates
* the DNS server addresses in a rotational order. It is similar to {@link #sequential(Iterable)}, but each
* {@link Iterator} starts from a different starting point. For example, the first {@link Iterable#iterator()}
* will iterate from the first DNS server address, the second one will iterate from the second DNS server address,
* and so on.
*/
public static Iterable<InetSocketAddress> rotational(InetSocketAddress... addresses) {
return rotational0(sanitize(addresses));
}
private static Iterable<InetSocketAddress> rotational0(final InetSocketAddress[] addresses) {
return new RotationalAddresses(addresses);
}
/**
* Returns an infinite {@link Iterable} of the specified DNS server address, whose {@link Iterator} always
* return the same DNS server address.
*/
public static Iterable<InetSocketAddress> singleton(final InetSocketAddress address) {
if (address == null) {
throw new NullPointerException("address");
}
if (address.isUnresolved()) {
throw new IllegalArgumentException("cannot use an unresolved DNS server address: " + address);
}
return new Iterable<InetSocketAddress>() {
private final Iterator<InetSocketAddress> iterator = new Iterator<InetSocketAddress>() {
@Override
public boolean hasNext() {
return true;
}
@Override
public InetSocketAddress next() {
return address;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
@Override
public Iterator<InetSocketAddress> iterator() {
return iterator;
}
};
}
private static InetSocketAddress[] sanitize(Iterable<? extends InetSocketAddress> addresses) {
if (addresses == null) {
throw new NullPointerException("addresses");
}
final List<InetSocketAddress> list;
if (addresses instanceof Collection) {
list = new ArrayList<InetSocketAddress>(((Collection<?>) addresses).size());
} else {
list = new ArrayList<InetSocketAddress>(4);
}
for (InetSocketAddress a : addresses) {
if (a == null) {
break;
}
if (a.isUnresolved()) {
throw new IllegalArgumentException("cannot use an unresolved DNS server address: " + a);
}
list.add(a);
}
if (list.isEmpty()) {
return DEFAULT_NAME_SERVER_ARRAY;
}
return list.toArray(new InetSocketAddress[list.size()]);
}
private static InetSocketAddress[] sanitize(InetSocketAddress[] addresses) {
if (addresses == null) {
throw new NullPointerException("addresses");
}
List<InetSocketAddress> list = new ArrayList<InetSocketAddress>(addresses.length);
for (InetSocketAddress a: addresses) {
if (a == null) {
break;
}
if (a.isUnresolved()) {
throw new IllegalArgumentException("cannot use an unresolved DNS server address: " + a);
}
list.add(a);
}
if (list.isEmpty()) {
return DEFAULT_NAME_SERVER_ARRAY;
}
return list.toArray(new InetSocketAddress[list.size()]);
}
private DnsServerAddresses() { }
private static final class SequentialAddressIterator implements Iterator<InetSocketAddress> {
private final InetSocketAddress[] addresses;
private int i;
SequentialAddressIterator(InetSocketAddress[] addresses, int startIdx) {
this.addresses = addresses;
i = startIdx;
}
@Override
public boolean hasNext() {
return true;
}
@Override
public InetSocketAddress next() {
int i = this.i;
InetSocketAddress next = addresses[i];
if (++ i < addresses.length) {
this.i = i;
} else {
this.i = 0;
}
return next;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
private static final class ShuffledAddressIterator implements Iterator<InetSocketAddress> {
private final InetSocketAddress[] addresses;
private int i;
ShuffledAddressIterator(InetSocketAddress[] addresses) {
this.addresses = addresses.clone();
shuffle();
}
private void shuffle() {
final InetSocketAddress[] addresses = this.addresses;
final Random r = ThreadLocalRandom.current();
for (int i = addresses.length - 1; i >= 0; i --) {
InetSocketAddress tmp = addresses[i];
int j = r.nextInt(i + 1);
addresses[i] = addresses[j];
addresses[j] = tmp;
}
}
@Override
public boolean hasNext() {
return true;
}
@Override
public InetSocketAddress next() {
int i = this.i;
InetSocketAddress next = addresses[i];
if (++ i < addresses.length) {
this.i = i;
} else {
this.i = 0;
shuffle();
}
return next;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
private static final class RotationalAddresses implements Iterable<InetSocketAddress> {
private static final AtomicIntegerFieldUpdater<RotationalAddresses> startIdxUpdater;
static {
AtomicIntegerFieldUpdater<RotationalAddresses> updater =
PlatformDependent.newAtomicIntegerFieldUpdater(RotationalAddresses.class, "startIdx");
if (updater == null) {
updater = AtomicIntegerFieldUpdater.newUpdater(RotationalAddresses.class, "startIdx");
}
startIdxUpdater = updater;
}
private final InetSocketAddress[] addresses;
@SuppressWarnings("UnusedDeclaration")
private volatile int startIdx;
RotationalAddresses(InetSocketAddress[] addresses) {
this.addresses = addresses;
}
@Override
public Iterator<InetSocketAddress> iterator() {
for (;;) {
int curStartIdx = startIdx;
int nextStartIdx = curStartIdx + 1;
if (nextStartIdx >= addresses.length) {
nextStartIdx = 0;
}
if (startIdxUpdater.compareAndSet(this, curStartIdx, nextStartIdx)) {
return new SequentialAddressIterator(addresses, curStartIdx);
}
}
}
}
}

View File

@ -0,0 +1,21 @@
/*
* 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.
*/
/**
* An alternative to Java's built-in domain name lookup mechanism that resolves a domain name asynchronously,
* which supports the queries of an arbitrary DNS record type as well.
*/
package io.netty.resolver.dns;

View File

@ -0,0 +1,417 @@
/*
* 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.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.InternetProtocolFamily;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.handler.codec.dns.DnsQuestion;
import io.netty.handler.codec.dns.DnsResource;
import io.netty.handler.codec.dns.DnsResponse;
import io.netty.handler.codec.dns.DnsResponseCode;
import io.netty.handler.codec.dns.DnsType;
import io.netty.util.concurrent.Future;
import io.netty.util.internal.StringUtil;
import io.netty.util.internal.ThreadLocalRandom;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
public class DnsNameResolverTest {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(DnsNameResolver.class);
private static final List<InetSocketAddress> SERVERS = Arrays.asList(
new InetSocketAddress("8.8.8.8", 53), // Google Public DNS
new InetSocketAddress("8.8.4.4", 53),
new InetSocketAddress("208.67.222.222", 53), // OpenDNS
new InetSocketAddress("208.67.220.220", 53),
new InetSocketAddress("37.235.1.174", 53), // FreeDNS
new InetSocketAddress("37.235.1.177", 53)
);
// Using the top-100 web sites ranked in Alexa.com (Oct 2014)
// Please use the following series of shell commands to get this up-to-date:
// $ curl -O http://s3.amazonaws.com/alexa-static/top-1m.csv.zip
// $ unzip -o top-1m.csv.zip top-1m.csv
// $ head -100 top-1m.csv | cut -d, -f2 | cut -d/ -f1 | while read L; do echo '"'"$L"'",'; done > topsites.txt
private static final String[] DOMAINS = {
"google.com",
"facebook.com",
"youtube.com",
"yahoo.com",
"baidu.com",
"wikipedia.org",
"amazon.com",
"twitter.com",
"qq.com",
"taobao.com",
"linkedin.com",
"google.co.in",
"live.com",
"hao123.com",
"sina.com.cn",
"blogspot.com",
"weibo.com",
"yahoo.co.jp",
"tmall.com",
"yandex.ru",
"sohu.com",
"bing.com",
"ebay.com",
"pinterest.com",
"vk.com",
"google.de",
"wordpress.com",
"apple.com",
"google.co.jp",
"google.co.uk",
"360.cn",
"instagram.com",
"google.fr",
"msn.com",
"ask.com",
"soso.com",
"google.com.br",
"tumblr.com",
"paypal.com",
"mail.ru",
"xvideos.com",
"microsoft.com",
"google.ru",
"reddit.com",
"google.it",
"imgur.com",
"163.com",
"google.es",
"imdb.com",
"aliexpress.com",
"t.co",
"go.com",
"adcash.com",
"craigslist.org",
"amazon.co.jp",
"alibaba.com",
"google.com.mx",
"stackoverflow.com",
"xhamster.com",
"fc2.com",
"google.ca",
"bbc.co.uk",
"espn.go.com",
"cnn.com",
"google.co.id",
"people.com.cn",
"gmw.cn",
"pornhub.com",
"blogger.com",
"huffingtonpost.com",
"flipkart.com",
"akamaihd.net",
"google.com.tr",
"amazon.de",
"netflix.com",
"onclickads.net",
"googleusercontent.com",
"kickass.to",
"google.com.au",
"google.pl",
"xinhuanet.com",
"ebay.de",
"wordpress.org",
"odnoklassniki.ru",
"google.com.hk",
"adobe.com",
"dailymotion.com",
"dailymail.co.uk",
"indiatimes.com",
"thepiratebay.se",
"amazon.co.uk",
"xnxx.com",
"rakuten.co.jp",
"dropbox.com",
"tudou.com",
"about.com",
"cnet.com",
"vimeo.com",
"redtube.com",
"blogspot.in",
};
/**
* The list of the domain names to exclude from {@link #testResolveAorAAAA()}.
*/
private static final Set<String> EXCLUSIONS_RESOLVE_A = new HashSet<String>();
static {
Collections.addAll(
EXCLUSIONS_RESOLVE_A,
"akamaihd.net",
"googleusercontent.com",
"");
}
/**
* The list of the domain names to exclude from {@link #testResolveAAAA()}.
* Unfortunately, there are only handful of domain names with IPv6 addresses.
*/
private static final Set<String> EXCLUSIONS_RESOLVE_AAAA = new HashSet<String>();
static {
EXCLUSIONS_RESOLVE_AAAA.addAll(EXCLUSIONS_RESOLVE_A);
Collections.addAll(EXCLUSIONS_RESOLVE_AAAA, DOMAINS);
EXCLUSIONS_RESOLVE_AAAA.removeAll(Arrays.asList(
"google.com",
"facebook.com",
"youtube.com",
"wikipedia.org",
"google.co.in",
"blogspot.com",
"vk.com",
"google.de",
"google.co.jp",
"google.co.uk",
"google.fr",
"google.com.br",
"google.ru",
"google.it",
"google.es",
"google.com.mx",
"xhamster.com",
"google.ca",
"google.co.id",
"blogger.com",
"flipkart.com",
"google.com.tr",
"google.com.au",
"google.pl",
"google.com.hk",
"blogspot.in"
));
}
/**
* The list of the domain names to exclude from {@link #testQueryMx()}.
*/
private static final Set<String> EXCLUSIONS_QUERY_MX = new HashSet<String>();
static {
Collections.addAll(
EXCLUSIONS_QUERY_MX,
"hao123.com",
"blogspot.com",
"t.co",
"espn.go.com",
"people.com.cn",
"googleusercontent.com",
"blogspot.in",
"");
}
private static final EventLoopGroup group = new NioEventLoopGroup(1);
private static final DnsNameResolver resolver = new DnsNameResolver(
group.next(), NioDatagramChannel.class, DnsServerAddresses.shuffled(SERVERS));
static {
resolver.setMaxTriesPerQuery(SERVERS.size());
}
@AfterClass
public static void destroy() {
group.shutdownGracefully();
}
@Before
public void reset() {
resolver.clearCache();
}
@Test
public void testResolveAorAAAA() throws Exception {
testResolve0(EXCLUSIONS_RESOLVE_A, InternetProtocolFamily.IPv4, InternetProtocolFamily.IPv6);
}
@Test
public void testResolveAAAAorA() throws Exception {
testResolve0(EXCLUSIONS_RESOLVE_A, InternetProtocolFamily.IPv6, InternetProtocolFamily.IPv4);
}
@Test
public void testResolveA() throws Exception {
final int oldMinTtl = resolver.minTtl();
final int oldMaxTtl = resolver.maxTtl();
// Cache for eternity.
resolver.setTtl(Integer.MAX_VALUE, Integer.MAX_VALUE);
try {
final Map<String, InetAddress> resultA = testResolve0(EXCLUSIONS_RESOLVE_A, InternetProtocolFamily.IPv4);
// Now, try to resolve again to see if it's cached.
// This test works because the DNS servers usually randomizes the order of the records in a response.
// If cached, the resolved addresses must be always same, because we reuse the same response.
final Map<String, InetAddress> resultB = testResolve0(EXCLUSIONS_RESOLVE_A, InternetProtocolFamily.IPv4);
assertThat(resultA, is(resultB));
} finally {
// Restore the TTL configuration.
resolver.setTtl(oldMinTtl, oldMaxTtl);
}
}
@Test
public void testResolveAAAA() throws Exception {
testResolve0(EXCLUSIONS_RESOLVE_AAAA, InternetProtocolFamily.IPv6);
}
private static Map<String, InetAddress> testResolve0(
Set<String> excludedDomains, InternetProtocolFamily... famililies) throws InterruptedException {
final List<InternetProtocolFamily> oldResolveAddressTypes = resolver.resolveAddressTypes();
assertThat(resolver.isRecursionDesired(), is(true));
assertThat(oldResolveAddressTypes.size(), is(InternetProtocolFamily.values().length));
resolver.setResolveAddressTypes(famililies);
final Map<String, InetAddress> results = new HashMap<String, InetAddress>();
try {
final Map<InetSocketAddress, Future<InetSocketAddress>> futures =
new LinkedHashMap<InetSocketAddress, Future<InetSocketAddress>>();
for (String name : DOMAINS) {
if (excludedDomains.contains(name)) {
continue;
}
resolve(futures, name);
}
for (Entry<InetSocketAddress, Future<InetSocketAddress>> e : futures.entrySet()) {
InetSocketAddress unresolved = e.getKey();
InetSocketAddress resolved = e.getValue().sync().getNow();
logger.info("{}: {}", unresolved.getHostString(), resolved.getAddress().getHostAddress());
assertThat(resolved.isUnresolved(), is(false));
assertThat(resolved.getHostString(), is(unresolved.getHostString()));
assertThat(resolved.getPort(), is(unresolved.getPort()));
boolean typeMatches = false;
for (InternetProtocolFamily f: famililies) {
Class<?> resolvedType = resolved.getAddress().getClass();
switch (f) {
case IPv4:
if (Inet4Address.class.isAssignableFrom(resolvedType)) {
typeMatches = true;
}
break;
case IPv6:
if (Inet6Address.class.isAssignableFrom(resolvedType)) {
typeMatches = true;
}
break;
}
}
assertThat(typeMatches, is(true));
results.put(resolved.getHostString(), resolved.getAddress());
}
} finally {
resolver.setResolveAddressTypes(oldResolveAddressTypes);
}
return results;
}
@Test
public void testQueryMx() throws Exception {
assertThat(resolver.isRecursionDesired(), is(true));
Map<String, Future<DnsResponse>> futures =
new LinkedHashMap<String, Future<DnsResponse>>();
for (String name: DOMAINS) {
if (EXCLUSIONS_QUERY_MX.contains(name)) {
continue;
}
queryMx(futures, name);
}
for (Entry<String, Future<DnsResponse>> e: futures.entrySet()) {
String hostname = e.getKey();
DnsResponse response = e.getValue().sync().getNow();
assertThat(response.header().responseCode(), is(DnsResponseCode.NOERROR));
List<DnsResource> mxList = new ArrayList<DnsResource>();
for (DnsResource r: response.answers()) {
if (r.type() == DnsType.MX) {
mxList.add(r);
}
}
assertThat(mxList.size(), is(greaterThan(0)));
StringBuilder buf = new StringBuilder();
for (DnsResource r: mxList) {
buf.append(StringUtil.NEWLINE);
buf.append('\t');
buf.append(r.name());
buf.append(' ');
buf.append(r.type());
buf.append(' ');
buf.append(r.content().readUnsignedShort());
buf.append(' ');
buf.append(DnsNameResolverContext.decodeDomainName(r.content()));
}
logger.info("{} has the following MX records:{}", hostname, buf);
response.release();
}
}
private static void resolve(Map<InetSocketAddress, Future<InetSocketAddress>> futures, String hostname) {
InetSocketAddress unresolved =
InetSocketAddress.createUnresolved(hostname, ThreadLocalRandom.current().nextInt(65536));
futures.put(unresolved, resolver.resolve(unresolved));
}
private static void queryMx(Map<String, Future<DnsResponse>> futures, String hostname) throws Exception {
futures.put(hostname, resolver.query(new DnsQuestion(hostname, DnsType.MX)));
}
}

View File

@ -0,0 +1,129 @@
/*
* 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.util.NetUtil;
import org.junit.Test;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.Set;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
public class DnsServerAddressesTest {
private static final InetSocketAddress ADDR1 = new InetSocketAddress(NetUtil.LOCALHOST, 1);
private static final InetSocketAddress ADDR2 = new InetSocketAddress(NetUtil.LOCALHOST, 2);
private static final InetSocketAddress ADDR3 = new InetSocketAddress(NetUtil.LOCALHOST, 3);
@Test
public void testDefaultAddresses() {
assertThat(DnsServerAddresses.defaultAddresses().size(), is(greaterThan(0)));
}
@Test
public void testSequential() {
Iterable<InetSocketAddress> seq = DnsServerAddresses.sequential(ADDR1, ADDR2, ADDR3);
assertThat(seq.iterator(), is(not(sameInstance(seq.iterator()))));
for (int j = 0; j < 2; j ++) {
Iterator<InetSocketAddress> i = seq.iterator();
assertNext(i, ADDR1);
assertNext(i, ADDR2);
assertNext(i, ADDR3);
assertNext(i, ADDR1);
assertNext(i, ADDR2);
assertNext(i, ADDR3);
}
}
@Test
public void testRotational() {
Iterable<InetSocketAddress> seq = DnsServerAddresses.rotational(ADDR1, ADDR2, ADDR3);
Iterator<InetSocketAddress> i = seq.iterator();
assertNext(i, ADDR1);
assertNext(i, ADDR2);
assertNext(i, ADDR3);
assertNext(i, ADDR1);
assertNext(i, ADDR2);
assertNext(i, ADDR3);
i = seq.iterator();
assertNext(i, ADDR2);
assertNext(i, ADDR3);
assertNext(i, ADDR1);
assertNext(i, ADDR2);
assertNext(i, ADDR3);
assertNext(i, ADDR1);
i = seq.iterator();
assertNext(i, ADDR3);
assertNext(i, ADDR1);
assertNext(i, ADDR2);
assertNext(i, ADDR3);
assertNext(i, ADDR1);
assertNext(i, ADDR2);
i = seq.iterator();
assertNext(i, ADDR1);
assertNext(i, ADDR2);
assertNext(i, ADDR3);
assertNext(i, ADDR1);
assertNext(i, ADDR2);
assertNext(i, ADDR3);
}
@Test
public void testShuffled() {
Iterable<InetSocketAddress> seq = DnsServerAddresses.shuffled(ADDR1, ADDR2, ADDR3);
// Ensure that all three addresses are returned by the iterator.
// In theory, this test can fail at extremely low chance, but we don't really care.
Set<InetSocketAddress> set = Collections.newSetFromMap(new IdentityHashMap<InetSocketAddress, Boolean>());
Iterator<InetSocketAddress> i = seq.iterator();
for (int j = 0; j < 1048576; j ++) {
assertThat(i.hasNext(), is(true));
set.add(i.next());
}
assertThat(set.size(), is(3));
assertThat(seq.iterator(), is(not(sameInstance(seq.iterator()))));
}
@Test
public void testSingleton() {
Iterable<InetSocketAddress> seq = DnsServerAddresses.singleton(ADDR1);
// Should return the same iterator instance for least possible footprint.
assertThat(seq.iterator(), is(sameInstance(seq.iterator())));
Iterator<InetSocketAddress> i = seq.iterator();
assertNext(i, ADDR1);
assertNext(i, ADDR1);
assertNext(i, ADDR1);
}
private static void assertNext(Iterator<InetSocketAddress> i, InetSocketAddress addr) {
assertThat(i.hasNext(), is(true));
assertThat(i.next(), is(sameInstance(addr)));
}
}

39
resolver/pom.xml Normal file
View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.netty</groupId>
<artifactId>netty-parent</artifactId>
<version>4.1.0.Beta4-SNAPSHOT</version>
</parent>
<artifactId>netty-resolver</artifactId>
<packaging>jar</packaging>
<name>Netty/Resolver</name>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-common</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,52 @@
/*
* 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;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.Promise;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
/**
* A {@link NameResolver} that resolves an {@link InetSocketAddress} using JDK's built-in domain name lookup mechanism.
* Note that this resolver performs a blocking name lookup from the caller thread.
*/
public class DefaultNameResolver extends SimpleNameResolver<InetSocketAddress> {
public DefaultNameResolver(EventExecutor executor) {
super(executor);
}
@Override
protected boolean doIsResolved(InetSocketAddress address) {
return !address.isUnresolved();
}
@Override
protected void doResolve(InetSocketAddress unresolvedAddress, Promise<InetSocketAddress> promise) throws Exception {
try {
promise.setSuccess(
new InetSocketAddress(
InetAddress.getByName(unresolvedAddress.getHostString()),
unresolvedAddress.getPort()));
} catch (UnknownHostException e) {
promise.setFailure(e);
}
}
}

View File

@ -0,0 +1,36 @@
/*
* 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;
import io.netty.util.concurrent.EventExecutor;
import java.net.InetSocketAddress;
/**
* A {@link NameResolverGroup} of {@link DefaultNameResolver}s.
*/
public final class DefaultNameResolverGroup extends NameResolverGroup<InetSocketAddress> {
public static final DefaultNameResolverGroup INSTANCE = new DefaultNameResolverGroup();
private DefaultNameResolverGroup() { }
@Override
protected NameResolver<InetSocketAddress> newResolver(EventExecutor executor) throws Exception {
return new DefaultNameResolver(executor);
}
}

View File

@ -0,0 +1,90 @@
/*
* 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;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.Promise;
import java.io.Closeable;
import java.net.SocketAddress;
import java.nio.channels.UnsupportedAddressTypeException;
/**
* Resolves an arbitrary string that represents the name of an endpoint into a {@link SocketAddress}.
*/
public interface NameResolver<T extends SocketAddress> extends Closeable {
/**
* Returns {@code true} if and only if the specified address is supported by this resolved.
*/
boolean isSupported(SocketAddress address);
/**
* Returns {@code true} if and only if the specified address has been resolved.
*
* @throws UnsupportedAddressTypeException if the specified address is not supported by this resolver
*/
boolean isResolved(SocketAddress address);
/**
* Resolves the specified name into a {@link SocketAddress}.
*
* @param inetHost the name to resolve
* @param inetPort the port number
*
* @return the {@link SocketAddress} as the result of the resolution
*/
Future<T> resolve(String inetHost, int inetPort);
/**
* Resolves the specified name into a {@link SocketAddress}.
*
* @param inetHost the name to resolve
* @param inetPort the port number
* @param promise the {@link Promise} which will be fulfilled when the name resolution is finished
*
* @return the {@link SocketAddress} as the result of the resolution
*/
Future<T> resolve(String inetHost, int inetPort, Promise<T> promise);
/**
* Resolves the specified address. If the specified address is resolved already, this method does nothing
* but returning the original address.
*
* @param address the address to resolve
*
* @return the {@link SocketAddress} as the result of the resolution
*/
Future<T> resolve(SocketAddress address);
/**
* Resolves the specified address. If the specified address is resolved already, this method does nothing
* but returning the original address.
*
* @param address the address to resolve
* @param promise the {@link Promise} which will be fulfilled when the name resolution is finished
*
* @return the {@link SocketAddress} as the result of the resolution
*/
Future<T> resolve(SocketAddress address, Promise<T> promise);
/**
* Closes all the resources allocated and used by this resolver.
*/
@Override
void close();
}

View File

@ -0,0 +1,113 @@
/*
* 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;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.io.Closeable;
import java.net.SocketAddress;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
/**
* Creates and manages {@link NameResolver}s so that each {@link EventExecutor} has its own resolver instance.
*/
public abstract class NameResolverGroup<T extends SocketAddress> implements Closeable {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(NameResolverGroup.class);
/**
* Note that we do not use a {@link ConcurrentMap} here because it is usually expensive to instantiate a resolver.
*/
private final Map<EventExecutor, NameResolver<T>> resolvers =
new IdentityHashMap<EventExecutor, NameResolver<T>>();
protected NameResolverGroup() { }
/**
* Returns the {@link NameResolver} associated with the specified {@link EventExecutor}. If there's no associated
* resolved found, this method creates and returns a new resolver instance created by
* {@link #newResolver(EventExecutor)} so that the new resolver is reused on another
* {@link #getResolver(EventExecutor)} call with the same {@link EventExecutor}.
*/
public NameResolver<T> getResolver(final EventExecutor executor) {
if (executor == null) {
throw new NullPointerException("executor");
}
if (executor.isShuttingDown()) {
throw new IllegalStateException("executor not accepting a task");
}
NameResolver<T> r;
synchronized (resolvers) {
r = resolvers.get(executor);
if (r == null) {
final NameResolver<T> newResolver;
try {
newResolver = newResolver(executor);
} catch (Exception e) {
throw new IllegalStateException("failed to create a new resolver", e);
}
resolvers.put(executor, newResolver);
executor.terminationFuture().addListener(new FutureListener<Object>() {
@Override
public void operationComplete(Future<Object> future) throws Exception {
resolvers.remove(executor);
newResolver.close();
}
});
r = newResolver;
}
}
return r;
}
/**
* Invoked by {@link #getResolver(EventExecutor)} to create a new {@link NameResolver}.
*/
protected abstract NameResolver<T> newResolver(EventExecutor executor) throws Exception;
/**
* Closes all {@link NameResolver}s created by this group.
*/
@Override
@SuppressWarnings({ "unchecked", "SuspiciousToArrayCall" })
public void close() {
final NameResolver<T>[] rArray;
synchronized (resolvers) {
rArray = (NameResolver<T>[]) resolvers.values().toArray(new NameResolver[resolvers.size()]);
resolvers.clear();
}
for (NameResolver<T> r: rArray) {
try {
r.close();
} catch (Throwable t) {
logger.warn("Failed to close a resolver:", t);
}
}
}
}

View File

@ -0,0 +1,43 @@
/*
* 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;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.Promise;
import java.net.SocketAddress;
/**
* A {@link NameResolver} that does not perform any resolution but always reports successful resolution.
* This resolver is useful when name resolution is performed by a handler in a pipeline, such as a proxy handler.
*/
public class NoopNameResolver extends SimpleNameResolver<SocketAddress> {
public NoopNameResolver(EventExecutor executor) {
super(executor);
}
@Override
protected boolean doIsResolved(SocketAddress address) {
return true;
}
@Override
protected void doResolve(SocketAddress unresolvedAddress, Promise<SocketAddress> promise) throws Exception {
promise.setSuccess(unresolvedAddress);
}
}

View File

@ -0,0 +1,36 @@
/*
* 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;
import io.netty.util.concurrent.EventExecutor;
import java.net.SocketAddress;
/**
* A {@link NameResolverGroup} of {@link NoopNameResolver}s.
*/
public final class NoopNameResolverGroup extends NameResolverGroup<SocketAddress> {
public static final NoopNameResolverGroup INSTANCE = new NoopNameResolverGroup();
private NoopNameResolverGroup() { }
@Override
protected NameResolver<SocketAddress> newResolver(EventExecutor executor) throws Exception {
return new NoopNameResolver(executor);
}
}

View File

@ -0,0 +1,179 @@
/*
* 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;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.Promise;
import io.netty.util.internal.TypeParameterMatcher;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.UnsupportedAddressTypeException;
/**
* A skeletal {@link NameResolver} implementation.
*/
public abstract class SimpleNameResolver<T extends SocketAddress> implements NameResolver<T> {
private final EventExecutor executor;
private final TypeParameterMatcher matcher;
/**
* @param executor the {@link EventExecutor} which is used to notify the listeners of the {@link Future} returned
* by {@link #resolve(SocketAddress)}
*/
protected SimpleNameResolver(EventExecutor executor) {
if (executor == null) {
throw new NullPointerException("executor");
}
this.executor = executor;
matcher = TypeParameterMatcher.find(this, SimpleNameResolver.class, "T");
}
/**
* @param executor the {@link EventExecutor} which is used to notify the listeners of the {@link Future} returned
* by {@link #resolve(SocketAddress)}
* @param addressType the type of the {@link SocketAddress} supported by this resolver
*/
protected SimpleNameResolver(EventExecutor executor, Class<? extends T> addressType) {
if (executor == null) {
throw new NullPointerException("executor");
}
this.executor = executor;
matcher = TypeParameterMatcher.get(addressType);
}
/**
* Returns the {@link EventExecutor} which is used to notify the listeners of the {@link Future} returned
* by {@link #resolve(SocketAddress)}.
*/
protected EventExecutor executor() {
return executor;
}
@Override
public boolean isSupported(SocketAddress address) {
return matcher.match(address);
}
@Override
public final boolean isResolved(SocketAddress address) {
if (!isSupported(address)) {
throw new UnsupportedAddressTypeException();
}
@SuppressWarnings("unchecked")
final T castAddress = (T) address;
return doIsResolved(castAddress);
}
/**
* Invoked by {@link #isResolved(SocketAddress)} to check if the specified {@code address} has been resolved
* already.
*/
protected abstract boolean doIsResolved(T address);
@Override
public final Future<T> resolve(String inetHost, int inetPort) {
if (inetHost == null) {
throw new NullPointerException("inetHost");
}
return resolve(InetSocketAddress.createUnresolved(inetHost, inetPort));
}
@Override
public Future<T> resolve(String inetHost, int inetPort, Promise<T> promise) {
if (inetHost == null) {
throw new NullPointerException("inetHost");
}
return resolve(InetSocketAddress.createUnresolved(inetHost, inetPort), promise);
}
@Override
public final Future<T> resolve(SocketAddress address) {
if (address == null) {
throw new NullPointerException("unresolvedAddress");
}
if (!isSupported(address)) {
// Address type not supported by the resolver
return executor().newFailedFuture(new UnsupportedAddressTypeException());
}
if (isResolved(address)) {
// Resolved already; no need to perform a lookup
@SuppressWarnings("unchecked")
final T cast = (T) address;
return executor.newSucceededFuture(cast);
}
try {
@SuppressWarnings("unchecked")
final T cast = (T) address;
final Promise<T> promise = executor().newPromise();
doResolve(cast, promise);
return promise;
} catch (Exception e) {
return executor().newFailedFuture(e);
}
}
@Override
public final Future<T> resolve(SocketAddress address, Promise<T> promise) {
if (address == null) {
throw new NullPointerException("unresolvedAddress");
}
if (promise == null) {
throw new NullPointerException("promise");
}
if (!isSupported(address)) {
// Address type not supported by the resolver
return promise.setFailure(new UnsupportedAddressTypeException());
}
if (isResolved(address)) {
// Resolved already; no need to perform a lookup
@SuppressWarnings("unchecked")
final T cast = (T) address;
return promise.setSuccess(cast);
}
try {
@SuppressWarnings("unchecked")
final T cast = (T) address;
doResolve(cast, promise);
return promise;
} catch (Exception e) {
return promise.setFailure(e);
}
}
/**
* Invoked by {@link #resolve(SocketAddress)} and {@link #resolve(String, int)} to perform the actual name
* resolution.
*/
protected abstract void doResolve(T unresolvedAddress, Promise<T> promise) throws Exception;
@Override
public void close() { }
}

View File

@ -0,0 +1,20 @@
/*
* 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.
*/
/**
* Resolves an arbitrary string that represents the name of an endpoint into a {@link java.net.SocketAddress}.
*/
package io.netty.resolver;

View File

@ -17,9 +17,9 @@ package io.netty.testsuite.transport.socket;
import io.netty.bootstrap.AbstractBootstrap;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ChannelFactory;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFactory;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;

View File

@ -16,9 +16,9 @@
package io.netty.channel.epoll;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ChannelFactory;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFactory;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.socket.InternetProtocolFamily;
import io.netty.channel.socket.nio.NioDatagramChannel;

View File

@ -23,11 +23,11 @@ import com.barchart.udt.nio.RendezvousChannelUDT;
import com.barchart.udt.nio.SelectorProviderUDT;
import com.barchart.udt.nio.ServerSocketChannelUDT;
import com.barchart.udt.nio.SocketChannelUDT;
import io.netty.bootstrap.ChannelFactory;
import io.netty.channel.Channel;
import io.netty.channel.ChannelException;
import io.netty.channel.udt.UdtServerChannel;
import io.netty.channel.ChannelFactory;
import io.netty.channel.udt.UdtChannel;
import io.netty.channel.udt.UdtServerChannel;
import java.io.IOException;
import java.nio.channels.spi.SelectorProvider;

View File

@ -34,6 +34,11 @@
<artifactId>netty-buffer</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-resolver</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -17,7 +17,6 @@
package io.netty.bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelException;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
@ -26,6 +25,7 @@ import io.netty.channel.ChannelPromise;
import io.netty.channel.DefaultChannelPromise;
import io.netty.channel.EventLoop;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.ReflectiveChannelFactory;
import io.netty.util.AttributeKey;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.GlobalEventExecutor;
@ -47,6 +47,7 @@ import java.util.Map;
public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable {
private volatile EventLoopGroup group;
@SuppressWarnings("deprecation")
private volatile ChannelFactory<? extends C> channelFactory;
private volatile SocketAddress localAddress;
private final Map<ChannelOption<?>, Object> options = new LinkedHashMap<ChannelOption<?>, Object>();
@ -88,23 +89,20 @@ public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C ext
/**
* The {@link Class} which is used to create {@link Channel} instances from.
* You either use this or {@link #channelFactory(ChannelFactory)} if your
* You either use this or {@link #channelFactory(io.netty.channel.ChannelFactory)} if your
* {@link Channel} implementation has no no-args constructor.
*/
public B channel(Class<? extends C> channelClass) {
if (channelClass == null) {
throw new NullPointerException("channelClass");
}
return channelFactory(new BootstrapChannelFactory<C>(channelClass));
return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
}
/**
* {@link ChannelFactory} which is used to create {@link Channel} instances from
* when calling {@link #bind()}. This method is usually only used if {@link #channel(Class)}
* is not working for you because of some more complex needs. If your {@link Channel} implementation
* has a no-args constructor, its highly recommend to just use {@link #channel(Class)} for
* simplify your code.
* @deprecated Use {@link #channelFactory(io.netty.channel.ChannelFactory)} instead.
*/
@Deprecated
@SuppressWarnings("unchecked")
public B channelFactory(ChannelFactory<? extends C> channelFactory) {
if (channelFactory == null) {
@ -118,9 +116,20 @@ public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C ext
return (B) this;
}
/**
* {@link io.netty.channel.ChannelFactory} which is used to create {@link Channel} instances from
* when calling {@link #bind()}. This method is usually only used if {@link #channel(Class)}
* is not working for you because of some more complex needs. If your {@link Channel} implementation
* has a no-args constructor, its highly recommend to just use {@link #channel(Class)} for
* simplify your code.
*/
@SuppressWarnings({ "unchecked", "deprecation" })
public B channelFactory(io.netty.channel.ChannelFactory<? extends C> channelFactory) {
return channelFactory((ChannelFactory<C>) channelFactory);
}
/**
* The {@link SocketAddress} which is used to bind the local "end" to.
*
*/
@SuppressWarnings("unchecked")
public B localAddress(SocketAddress localAddress) {
@ -371,6 +380,7 @@ public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C ext
return localAddress;
}
@SuppressWarnings("deprecation")
final ChannelFactory<? extends C> channelFactory() {
return channelFactory;
}
@ -442,28 +452,6 @@ public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C ext
return buf.toString();
}
private static final class BootstrapChannelFactory<T extends Channel> implements ChannelFactory<T> {
private final Class<? extends T> clazz;
BootstrapChannelFactory(Class<? extends T> clazz) {
this.clazz = clazz;
}
@Override
public T newChannel() {
try {
return clazz.newInstance();
} catch (Throwable t) {
throw new ChannelException("Unable to create Channel from class " + clazz, t);
}
}
@Override
public String toString() {
return StringUtil.simpleClassName(clazz) + ".class";
}
}
private static final class PendingRegistrationPromise extends DefaultChannelPromise {
// Is set to the correct EventExecutor once the registration was successful. Otherwise it will
// stay null and so the GlobalEventExecutor.INSTANCE will be used for notifications.

View File

@ -21,7 +21,13 @@ import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import io.netty.channel.EventLoop;
import io.netty.resolver.DefaultNameResolverGroup;
import io.netty.resolver.NameResolver;
import io.netty.resolver.NameResolverGroup;
import io.netty.util.AttributeKey;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
@ -42,6 +48,10 @@ public final class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(Bootstrap.class);
private static final NameResolverGroup<?> DEFAULT_RESOLVER = DefaultNameResolverGroup.INSTANCE;
@SuppressWarnings("unchecked")
private volatile NameResolverGroup<SocketAddress> resolver = (NameResolverGroup<SocketAddress>) DEFAULT_RESOLVER;
private volatile SocketAddress remoteAddress;
public Bootstrap() { }
@ -51,6 +61,18 @@ public final class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
remoteAddress = bootstrap.remoteAddress;
}
/**
* Sets the {@link NameResolver} which will resolve the address of the unresolved named address.
*/
@SuppressWarnings("unchecked")
public Bootstrap resolver(NameResolverGroup<?> resolver) {
if (resolver == null) {
throw new NullPointerException("resolver");
}
this.resolver = (NameResolverGroup<SocketAddress>) resolver;
return this;
}
/**
* The {@link SocketAddress} to connect to once the {@link #connect()} method
* is called.
@ -64,7 +86,7 @@ public final class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
* @see {@link #remoteAddress(SocketAddress)}
*/
public Bootstrap remoteAddress(String inetHost, int inetPort) {
remoteAddress = new InetSocketAddress(inetHost, inetPort);
remoteAddress = InetSocketAddress.createUnresolved(inetHost, inetPort);
return this;
}
@ -86,14 +108,14 @@ public final class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
throw new IllegalStateException("remoteAddress not set");
}
return doConnect(remoteAddress, localAddress());
return doResolveAndConnect(remoteAddress, localAddress());
}
/**
* Connect a {@link Channel} to the remote peer.
*/
public ChannelFuture connect(String inetHost, int inetPort) {
return connect(new InetSocketAddress(inetHost, inetPort));
return connect(InetSocketAddress.createUnresolved(inetHost, inetPort));
}
/**
@ -112,7 +134,7 @@ public final class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
}
validate();
return doConnect(remoteAddress, localAddress());
return doResolveAndConnect(remoteAddress, localAddress());
}
/**
@ -123,52 +145,94 @@ public final class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
throw new NullPointerException("remoteAddress");
}
validate();
return doConnect(remoteAddress, localAddress);
return doResolveAndConnect(remoteAddress, localAddress);
}
/**
* @see {@link #connect()}
*/
private ChannelFuture doConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
private ChannelFuture doResolveAndConnect(SocketAddress remoteAddress, final SocketAddress localAddress) {
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
}
final ChannelPromise promise = channel.newPromise();
final Channel channel = regFuture.channel();
final EventLoop eventLoop = channel.eventLoop();
final NameResolver<SocketAddress> resolver = this.resolver.getResolver(eventLoop);
if (!resolver.isSupported(remoteAddress) || resolver.isResolved(remoteAddress)) {
// Resolver has no idea about what to do with the specified remote address or it's resolved already.
return doConnect(remoteAddress, localAddress, regFuture, channel.newPromise());
}
final Future<SocketAddress> resolveFuture = resolver.resolve(remoteAddress);
final Throwable resolveFailureCause = resolveFuture.cause();
if (resolveFailureCause != null) {
// Failed to resolve immediately
channel.close();
return channel.newFailedFuture(resolveFailureCause);
}
if (resolveFuture.isDone()) {
// Succeeded to resolve immediately; cached? (or did a blocking lookup)
return doConnect(resolveFuture.getNow(), localAddress, regFuture, channel.newPromise());
}
// Wait until the name resolution is finished.
final ChannelPromise connectPromise = channel.newPromise();
resolveFuture.addListener(new FutureListener<SocketAddress>() {
@Override
public void operationComplete(Future<SocketAddress> future) throws Exception {
if (future.cause() != null) {
channel.close();
connectPromise.setFailure(future.cause());
} else {
doConnect(future.getNow(), localAddress, regFuture, connectPromise);
}
}
});
return connectPromise;
}
private static ChannelFuture doConnect(
final SocketAddress remoteAddress, final SocketAddress localAddress,
final ChannelFuture regFuture, final ChannelPromise connectPromise) {
if (regFuture.isDone()) {
doConnect0(regFuture, channel, remoteAddress, localAddress, promise);
doConnect0(remoteAddress, localAddress, regFuture, connectPromise);
} else {
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
doConnect0(regFuture, channel, remoteAddress, localAddress, promise);
doConnect0(remoteAddress, localAddress, regFuture, connectPromise);
}
});
}
return promise;
return connectPromise;
}
private static void doConnect0(
final ChannelFuture regFuture, final Channel channel,
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelFuture regFuture,
final ChannelPromise connectPromise) {
// This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up
// the pipeline in its channelRegistered() implementation.
final Channel channel = connectPromise.channel();
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
if (regFuture.isSuccess()) {
if (localAddress == null) {
channel.connect(remoteAddress, promise);
channel.connect(remoteAddress, connectPromise);
} else {
channel.connect(remoteAddress, localAddress, promise);
channel.connect(remoteAddress, localAddress, connectPromise);
}
promise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
connectPromise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else {
promise.setFailure(regFuture.cause());
connectPromise.setFailure(regFuture.cause());
}
}
});

View File

@ -18,9 +18,9 @@ package io.netty.bootstrap;
import io.netty.channel.Channel;
/**
* Factory that creates a new {@link Channel} on {@link Bootstrap#bind()}, {@link Bootstrap#connect()}, and
* {@link ServerBootstrap#bind()}.
* @deprecated Use {@link io.netty.channel.ChannelFactory} instead.
*/
@Deprecated
public interface ChannelFactory<T extends Channel> {
/**
* Creates a new channel.

View File

@ -0,0 +1,28 @@
/*
* 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.channel;
/**
* Creates a new {@link Channel}.
*/
@SuppressWarnings({ "ClassNameSameAsAncestorName", "deprecation" })
public interface ChannelFactory<T extends Channel> extends io.netty.bootstrap.ChannelFactory<T> {
/**
* Creates a new channel.
*/
@Override
T newChannel();
}

View File

@ -0,0 +1,48 @@
/*
* 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.channel;
import io.netty.util.internal.StringUtil;
/**
* A {@link ChannelFactory} that instantiates a new {@link Channel} by invoking its default constructor reflectively.
*/
public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {
private final Class<? extends T> clazz;
public ReflectiveChannelFactory(Class<? extends T> clazz) {
if (clazz == null) {
throw new NullPointerException("clazz");
}
this.clazz = clazz;
}
@Override
public T newChannel() {
try {
return clazz.newInstance();
} catch (Throwable t) {
throw new ChannelException("Unable to create Channel from class " + clazz, t);
}
}
@Override
public String toString() {
return StringUtil.simpleClassName(clazz) + ".class";
}
}

View File

@ -17,124 +17,124 @@
package io.netty.bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFactory;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelInboundHandler;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.DefaultEventLoopGroup;
import io.netty.channel.ChannelPromise;
import io.netty.channel.DefaultEventLoopGroup;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.ServerChannel;
import io.netty.channel.local.LocalAddress;
import io.netty.channel.local.LocalChannel;
import io.netty.channel.local.LocalServerChannel;
import io.netty.resolver.NameResolver;
import io.netty.resolver.NameResolverGroup;
import io.netty.resolver.SimpleNameResolver;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.Future;
import org.junit.Assert;
import io.netty.util.concurrent.Promise;
import org.junit.AfterClass;
import org.junit.Test;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
public class BootstrapTest {
private static final EventLoopGroup groupA = new DefaultEventLoopGroup(1);
private static final EventLoopGroup groupB = new DefaultEventLoopGroup(1);
private static final ChannelInboundHandler dummyHandler = new DummyHandler();
@AfterClass
public static void destroy() {
groupA.shutdownGracefully();
groupB.shutdownGracefully();
groupA.terminationFuture().syncUninterruptibly();
groupB.terminationFuture().syncUninterruptibly();
}
@Test(timeout = 10000)
public void testBindDeadLock() throws Exception {
EventLoopGroup groupA = new DefaultEventLoopGroup(1);
EventLoopGroup groupB = new DefaultEventLoopGroup(1);
try {
ChannelInboundHandler dummyHandler = new DummyHandler();
final Bootstrap bootstrapA = new Bootstrap();
bootstrapA.group(groupA);
bootstrapA.channel(LocalChannel.class);
bootstrapA.handler(dummyHandler);
final Bootstrap bootstrapA = new Bootstrap();
bootstrapA.group(groupA);
bootstrapA.channel(LocalChannel.class);
bootstrapA.handler(dummyHandler);
final Bootstrap bootstrapB = new Bootstrap();
bootstrapB.group(groupB);
bootstrapB.channel(LocalChannel.class);
bootstrapB.handler(dummyHandler);
final Bootstrap bootstrapB = new Bootstrap();
bootstrapB.group(groupB);
bootstrapB.channel(LocalChannel.class);
bootstrapB.handler(dummyHandler);
List<Future<?>> bindFutures = new ArrayList<Future<?>>();
List<Future<?>> bindFutures = new ArrayList<Future<?>>();
// Try to bind from each other.
for (int i = 0; i < 1024; i ++) {
bindFutures.add(groupA.next().submit(new Runnable() {
@Override
public void run() {
bootstrapB.bind(LocalAddress.ANY);
}
}));
// Try to bind from each other.
for (int i = 0; i < 1024; i ++) {
bindFutures.add(groupA.next().submit(new Runnable() {
@Override
public void run() {
bootstrapB.bind(LocalAddress.ANY);
}
}));
bindFutures.add(groupB.next().submit(new Runnable() {
@Override
public void run() {
bootstrapA.bind(LocalAddress.ANY);
}
}));
}
bindFutures.add(groupB.next().submit(new Runnable() {
@Override
public void run() {
bootstrapA.bind(LocalAddress.ANY);
}
}));
}
for (Future<?> f: bindFutures) {
f.sync();
}
} finally {
groupA.shutdownGracefully();
groupB.shutdownGracefully();
groupA.terminationFuture().sync();
groupB.terminationFuture().sync();
for (Future<?> f: bindFutures) {
f.sync();
}
}
@Test(timeout = 10000)
public void testConnectDeadLock() throws Exception {
EventLoopGroup groupA = new DefaultEventLoopGroup(1);
EventLoopGroup groupB = new DefaultEventLoopGroup(1);
try {
ChannelInboundHandler dummyHandler = new DummyHandler();
final Bootstrap bootstrapA = new Bootstrap();
bootstrapA.group(groupA);
bootstrapA.channel(LocalChannel.class);
bootstrapA.handler(dummyHandler);
final Bootstrap bootstrapA = new Bootstrap();
bootstrapA.group(groupA);
bootstrapA.channel(LocalChannel.class);
bootstrapA.handler(dummyHandler);
final Bootstrap bootstrapB = new Bootstrap();
bootstrapB.group(groupB);
bootstrapB.channel(LocalChannel.class);
bootstrapB.handler(dummyHandler);
final Bootstrap bootstrapB = new Bootstrap();
bootstrapB.group(groupB);
bootstrapB.channel(LocalChannel.class);
bootstrapB.handler(dummyHandler);
List<Future<?>> bindFutures = new ArrayList<Future<?>>();
List<Future<?>> bindFutures = new ArrayList<Future<?>>();
// Try to connect from each other.
for (int i = 0; i < 1024; i ++) {
bindFutures.add(groupA.next().submit(new Runnable() {
@Override
public void run() {
bootstrapB.connect(LocalAddress.ANY);
}
}));
// Try to connect from each other.
for (int i = 0; i < 1024; i ++) {
bindFutures.add(groupA.next().submit(new Runnable() {
@Override
public void run() {
bootstrapB.connect(LocalAddress.ANY);
}
}));
bindFutures.add(groupB.next().submit(new Runnable() {
@Override
public void run() {
bootstrapA.connect(LocalAddress.ANY);
}
}));
}
bindFutures.add(groupB.next().submit(new Runnable() {
@Override
public void run() {
bootstrapA.connect(LocalAddress.ANY);
}
}));
}
for (Future<?> f: bindFutures) {
f.sync();
}
} finally {
groupA.shutdownGracefully();
groupB.shutdownGracefully();
groupA.terminationFuture().sync();
groupB.terminationFuture().sync();
for (Future<?> f: bindFutures) {
f.sync();
}
}
@ -148,7 +148,7 @@ public class BootstrapTest {
bootstrap.childHandler(new DummyHandler());
bootstrap.localAddress(new LocalAddress("1"));
ChannelFuture future = bootstrap.bind();
Assert.assertFalse(future.isDone());
assertFalse(future.isDone());
group.promise.setSuccess();
final BlockingQueue<Boolean> queue = new LinkedBlockingQueue<Boolean>();
future.addListener(new ChannelFutureListener() {
@ -158,8 +158,8 @@ public class BootstrapTest {
queue.add(future.isSuccess());
}
});
Assert.assertTrue(queue.take());
Assert.assertTrue(queue.take());
assertTrue(queue.take());
assertTrue(queue.take());
} finally {
group.shutdownGracefully();
group.terminationFuture().sync();
@ -197,7 +197,7 @@ public class BootstrapTest {
bootstrap.childHandler(new DummyHandler());
bootstrap.localAddress(new LocalAddress("1"));
ChannelFuture future = bootstrap.bind();
Assert.assertFalse(future.isDone());
assertFalse(future.isDone());
group.promise.setSuccess();
final BlockingQueue<Boolean> queue = new LinkedBlockingQueue<Boolean>();
future.addListener(new ChannelFutureListener() {
@ -207,16 +207,61 @@ public class BootstrapTest {
queue.add(future.isSuccess());
}
});
Assert.assertTrue(queue.take());
Assert.assertFalse(queue.take());
assertTrue(queue.take());
assertFalse(queue.take());
} finally {
group.shutdownGracefully();
group.terminationFuture().sync();
}
}
@Test
public void testAsyncResolutionSuccess() throws Exception {
final Bootstrap bootstrapA = new Bootstrap();
bootstrapA.group(groupA);
bootstrapA.channel(LocalChannel.class);
bootstrapA.resolver(new TestNameResolverGroup(true));
bootstrapA.handler(dummyHandler);
final ServerBootstrap bootstrapB = new ServerBootstrap();
bootstrapB.group(groupB);
bootstrapB.channel(LocalServerChannel.class);
bootstrapB.childHandler(dummyHandler);
SocketAddress localAddress = bootstrapB.bind(LocalAddress.ANY).sync().channel().localAddress();
// Connect to the server using the asynchronous resolver.
bootstrapA.connect(localAddress).sync();
}
@Test
public void testAsyncResolutionFailure() throws Exception {
final Bootstrap bootstrapA = new Bootstrap();
bootstrapA.group(groupA);
bootstrapA.channel(LocalChannel.class);
bootstrapA.resolver(new TestNameResolverGroup(false));
bootstrapA.handler(dummyHandler);
final ServerBootstrap bootstrapB = new ServerBootstrap();
bootstrapB.group(groupB);
bootstrapB.channel(LocalServerChannel.class);
bootstrapB.childHandler(dummyHandler);
SocketAddress localAddress = bootstrapB.bind(LocalAddress.ANY).sync().channel().localAddress();
// Connect to the server using the asynchronous resolver.
ChannelFuture connectFuture = bootstrapA.connect(localAddress);
// Should fail with the UnknownHostException.
assertThat(connectFuture.await(10000), is(true));
assertThat(connectFuture.cause(), is(instanceOf(UnknownHostException.class)));
assertThat(connectFuture.channel().isOpen(), is(false));
}
private static final class TestEventLoopGroup extends DefaultEventLoopGroup {
ChannelPromise promise;
TestEventLoopGroup() {
super(1);
}
@ -236,4 +281,39 @@ public class BootstrapTest {
@Sharable
private static final class DummyHandler extends ChannelInboundHandlerAdapter { }
private static final class TestNameResolverGroup extends NameResolverGroup<SocketAddress> {
private final boolean success;
TestNameResolverGroup(boolean success) {
this.success = success;
}
@Override
protected NameResolver<SocketAddress> newResolver(EventExecutor executor) throws Exception {
return new SimpleNameResolver<SocketAddress>(executor) {
@Override
protected boolean doIsResolved(SocketAddress address) {
return false;
}
@Override
protected void doResolve(
final SocketAddress unresolvedAddress, final Promise<SocketAddress> promise) {
executor().execute(new Runnable() {
@Override
public void run() {
if (success) {
promise.setSuccess(unresolvedAddress);
} else {
promise.setFailure(new UnknownHostException());
}
}
});
}
};
}
}
}