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> <scope>compile</scope>
<optional>true</optional> <optional>true</optional>
</dependency> </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> <dependency>
<groupId>${project.groupId}</groupId> <groupId>${project.groupId}</groupId>
<artifactId>netty-transport</artifactId> <artifactId>netty-transport</artifactId>

View File

@ -341,6 +341,8 @@
<module>codec-mqtt</module> <module>codec-mqtt</module>
<module>codec-socks</module> <module>codec-socks</module>
<module>codec-stomp</module> <module>codec-stomp</module>
<module>resolver</module>
<module>resolver-dns</module>
<module>transport</module> <module>transport</module>
<module>transport-rxtx</module> <module>transport-rxtx</module>
<module>transport-sctp</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.AbstractBootstrap;
import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ChannelFactory;
import io.netty.bootstrap.ServerBootstrap; import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelFactory;
import io.netty.channel.ChannelOption; import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup; import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup;

View File

@ -16,9 +16,9 @@
package io.netty.channel.epoll; package io.netty.channel.epoll;
import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ChannelFactory;
import io.netty.bootstrap.ServerBootstrap; import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelFactory;
import io.netty.channel.EventLoopGroup; import io.netty.channel.EventLoopGroup;
import io.netty.channel.socket.InternetProtocolFamily; import io.netty.channel.socket.InternetProtocolFamily;
import io.netty.channel.socket.nio.NioDatagramChannel; 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.SelectorProviderUDT;
import com.barchart.udt.nio.ServerSocketChannelUDT; import com.barchart.udt.nio.ServerSocketChannelUDT;
import com.barchart.udt.nio.SocketChannelUDT; import com.barchart.udt.nio.SocketChannelUDT;
import io.netty.bootstrap.ChannelFactory;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelException; 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.UdtChannel;
import io.netty.channel.udt.UdtServerChannel;
import java.io.IOException; import java.io.IOException;
import java.nio.channels.spi.SelectorProvider; import java.nio.channels.spi.SelectorProvider;

View File

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

View File

@ -17,7 +17,6 @@
package io.netty.bootstrap; package io.netty.bootstrap;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelException;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandler;
@ -26,6 +25,7 @@ import io.netty.channel.ChannelPromise;
import io.netty.channel.DefaultChannelPromise; import io.netty.channel.DefaultChannelPromise;
import io.netty.channel.EventLoop; import io.netty.channel.EventLoop;
import io.netty.channel.EventLoopGroup; import io.netty.channel.EventLoopGroup;
import io.netty.channel.ReflectiveChannelFactory;
import io.netty.util.AttributeKey; import io.netty.util.AttributeKey;
import io.netty.util.concurrent.EventExecutor; import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.GlobalEventExecutor; 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 { public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable {
private volatile EventLoopGroup group; private volatile EventLoopGroup group;
@SuppressWarnings("deprecation")
private volatile ChannelFactory<? extends C> channelFactory; private volatile ChannelFactory<? extends C> channelFactory;
private volatile SocketAddress localAddress; private volatile SocketAddress localAddress;
private final Map<ChannelOption<?>, Object> options = new LinkedHashMap<ChannelOption<?>, Object>(); 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. * 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. * {@link Channel} implementation has no no-args constructor.
*/ */
public B channel(Class<? extends C> channelClass) { public B channel(Class<? extends C> channelClass) {
if (channelClass == null) { if (channelClass == null) {
throw new NullPointerException("channelClass"); 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 * @deprecated Use {@link #channelFactory(io.netty.channel.ChannelFactory)} instead.
* 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
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public B channelFactory(ChannelFactory<? extends C> channelFactory) { public B channelFactory(ChannelFactory<? extends C> channelFactory) {
if (channelFactory == null) { if (channelFactory == null) {
@ -118,9 +116,20 @@ public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C ext
return (B) this; 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. * The {@link SocketAddress} which is used to bind the local "end" to.
*
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public B localAddress(SocketAddress localAddress) { public B localAddress(SocketAddress localAddress) {
@ -371,6 +380,7 @@ public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C ext
return localAddress; return localAddress;
} }
@SuppressWarnings("deprecation")
final ChannelFactory<? extends C> channelFactory() { final ChannelFactory<? extends C> channelFactory() {
return channelFactory; return channelFactory;
} }
@ -442,28 +452,6 @@ public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C ext
return buf.toString(); 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 { private static final class PendingRegistrationPromise extends DefaultChannelPromise {
// Is set to the correct EventExecutor once the registration was successful. Otherwise it will // 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. // 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.ChannelOption;
import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise; 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.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.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory; 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 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; private volatile SocketAddress remoteAddress;
public Bootstrap() { } public Bootstrap() { }
@ -51,6 +61,18 @@ public final class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
remoteAddress = bootstrap.remoteAddress; 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 * The {@link SocketAddress} to connect to once the {@link #connect()} method
* is called. * is called.
@ -64,7 +86,7 @@ public final class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
* @see {@link #remoteAddress(SocketAddress)} * @see {@link #remoteAddress(SocketAddress)}
*/ */
public Bootstrap remoteAddress(String inetHost, int inetPort) { public Bootstrap remoteAddress(String inetHost, int inetPort) {
remoteAddress = new InetSocketAddress(inetHost, inetPort); remoteAddress = InetSocketAddress.createUnresolved(inetHost, inetPort);
return this; return this;
} }
@ -86,14 +108,14 @@ public final class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
throw new IllegalStateException("remoteAddress not set"); throw new IllegalStateException("remoteAddress not set");
} }
return doConnect(remoteAddress, localAddress()); return doResolveAndConnect(remoteAddress, localAddress());
} }
/** /**
* Connect a {@link Channel} to the remote peer. * Connect a {@link Channel} to the remote peer.
*/ */
public ChannelFuture connect(String inetHost, int inetPort) { 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(); 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"); throw new NullPointerException("remoteAddress");
} }
validate(); validate();
return doConnect(remoteAddress, localAddress); return doResolveAndConnect(remoteAddress, localAddress);
} }
/** /**
* @see {@link #connect()} * @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 ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) { if (regFuture.cause() != null) {
return regFuture; 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()) { if (regFuture.isDone()) {
doConnect0(regFuture, channel, remoteAddress, localAddress, promise); doConnect0(remoteAddress, localAddress, regFuture, connectPromise);
} else { } else {
regFuture.addListener(new ChannelFutureListener() { regFuture.addListener(new ChannelFutureListener() {
@Override @Override
public void operationComplete(ChannelFuture future) throws Exception { 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( private static void doConnect0(
final ChannelFuture regFuture, final Channel channel, final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelFuture regFuture,
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) { final ChannelPromise connectPromise) {
// This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up // This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up
// the pipeline in its channelRegistered() implementation. // the pipeline in its channelRegistered() implementation.
final Channel channel = connectPromise.channel();
channel.eventLoop().execute(new Runnable() { channel.eventLoop().execute(new Runnable() {
@Override @Override
public void run() { public void run() {
if (regFuture.isSuccess()) { if (regFuture.isSuccess()) {
if (localAddress == null) { if (localAddress == null) {
channel.connect(remoteAddress, promise); channel.connect(remoteAddress, connectPromise);
} else { } else {
channel.connect(remoteAddress, localAddress, promise); channel.connect(remoteAddress, localAddress, connectPromise);
} }
promise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE); connectPromise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else { } else {
promise.setFailure(regFuture.cause()); connectPromise.setFailure(regFuture.cause());
} }
} }
}); });

View File

@ -18,9 +18,9 @@ package io.netty.bootstrap;
import io.netty.channel.Channel; import io.netty.channel.Channel;
/** /**
* Factory that creates a new {@link Channel} on {@link Bootstrap#bind()}, {@link Bootstrap#connect()}, and * @deprecated Use {@link io.netty.channel.ChannelFactory} instead.
* {@link ServerBootstrap#bind()}.
*/ */
@Deprecated
public interface ChannelFactory<T extends Channel> { public interface ChannelFactory<T extends Channel> {
/** /**
* Creates a new 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; package io.netty.bootstrap;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelFactory;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler.Sharable; import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelInboundHandler; import io.netty.channel.ChannelInboundHandler;
import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.DefaultEventLoopGroup;
import io.netty.channel.ChannelPromise; import io.netty.channel.ChannelPromise;
import io.netty.channel.DefaultEventLoopGroup;
import io.netty.channel.EventLoopGroup; import io.netty.channel.EventLoopGroup;
import io.netty.channel.ServerChannel; import io.netty.channel.ServerChannel;
import io.netty.channel.local.LocalAddress; import io.netty.channel.local.LocalAddress;
import io.netty.channel.local.LocalChannel; import io.netty.channel.local.LocalChannel;
import io.netty.channel.local.LocalServerChannel; 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 io.netty.util.concurrent.Future;
import org.junit.Assert; import io.netty.util.concurrent.Promise;
import org.junit.AfterClass;
import org.junit.Test; import org.junit.Test;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.net.SocketException; import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
public class BootstrapTest { 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) @Test(timeout = 10000)
public void testBindDeadLock() throws Exception { public void testBindDeadLock() throws Exception {
EventLoopGroup groupA = new DefaultEventLoopGroup(1);
EventLoopGroup groupB = new DefaultEventLoopGroup(1);
try { final Bootstrap bootstrapA = new Bootstrap();
ChannelInboundHandler dummyHandler = new DummyHandler(); bootstrapA.group(groupA);
bootstrapA.channel(LocalChannel.class);
bootstrapA.handler(dummyHandler);
final Bootstrap bootstrapA = new Bootstrap(); final Bootstrap bootstrapB = new Bootstrap();
bootstrapA.group(groupA); bootstrapB.group(groupB);
bootstrapA.channel(LocalChannel.class); bootstrapB.channel(LocalChannel.class);
bootstrapA.handler(dummyHandler); bootstrapB.handler(dummyHandler);
final Bootstrap bootstrapB = new Bootstrap(); List<Future<?>> bindFutures = new ArrayList<Future<?>>();
bootstrapB.group(groupB);
bootstrapB.channel(LocalChannel.class);
bootstrapB.handler(dummyHandler);
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. bindFutures.add(groupB.next().submit(new Runnable() {
for (int i = 0; i < 1024; i ++) { @Override
bindFutures.add(groupA.next().submit(new Runnable() { public void run() {
@Override bootstrapA.bind(LocalAddress.ANY);
public void run() { }
bootstrapB.bind(LocalAddress.ANY); }));
} }
}));
bindFutures.add(groupB.next().submit(new Runnable() { for (Future<?> f: bindFutures) {
@Override f.sync();
public void run() {
bootstrapA.bind(LocalAddress.ANY);
}
}));
}
for (Future<?> f: bindFutures) {
f.sync();
}
} finally {
groupA.shutdownGracefully();
groupB.shutdownGracefully();
groupA.terminationFuture().sync();
groupB.terminationFuture().sync();
} }
} }
@Test(timeout = 10000) @Test(timeout = 10000)
public void testConnectDeadLock() throws Exception { public void testConnectDeadLock() throws Exception {
EventLoopGroup groupA = new DefaultEventLoopGroup(1);
EventLoopGroup groupB = new DefaultEventLoopGroup(1);
try { final Bootstrap bootstrapA = new Bootstrap();
ChannelInboundHandler dummyHandler = new DummyHandler(); bootstrapA.group(groupA);
bootstrapA.channel(LocalChannel.class);
bootstrapA.handler(dummyHandler);
final Bootstrap bootstrapA = new Bootstrap(); final Bootstrap bootstrapB = new Bootstrap();
bootstrapA.group(groupA); bootstrapB.group(groupB);
bootstrapA.channel(LocalChannel.class); bootstrapB.channel(LocalChannel.class);
bootstrapA.handler(dummyHandler); bootstrapB.handler(dummyHandler);
final Bootstrap bootstrapB = new Bootstrap(); List<Future<?>> bindFutures = new ArrayList<Future<?>>();
bootstrapB.group(groupB);
bootstrapB.channel(LocalChannel.class);
bootstrapB.handler(dummyHandler);
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. bindFutures.add(groupB.next().submit(new Runnable() {
for (int i = 0; i < 1024; i ++) { @Override
bindFutures.add(groupA.next().submit(new Runnable() { public void run() {
@Override bootstrapA.connect(LocalAddress.ANY);
public void run() { }
bootstrapB.connect(LocalAddress.ANY); }));
} }
}));
bindFutures.add(groupB.next().submit(new Runnable() { for (Future<?> f: bindFutures) {
@Override f.sync();
public void run() {
bootstrapA.connect(LocalAddress.ANY);
}
}));
}
for (Future<?> f: bindFutures) {
f.sync();
}
} finally {
groupA.shutdownGracefully();
groupB.shutdownGracefully();
groupA.terminationFuture().sync();
groupB.terminationFuture().sync();
} }
} }
@ -148,7 +148,7 @@ public class BootstrapTest {
bootstrap.childHandler(new DummyHandler()); bootstrap.childHandler(new DummyHandler());
bootstrap.localAddress(new LocalAddress("1")); bootstrap.localAddress(new LocalAddress("1"));
ChannelFuture future = bootstrap.bind(); ChannelFuture future = bootstrap.bind();
Assert.assertFalse(future.isDone()); assertFalse(future.isDone());
group.promise.setSuccess(); group.promise.setSuccess();
final BlockingQueue<Boolean> queue = new LinkedBlockingQueue<Boolean>(); final BlockingQueue<Boolean> queue = new LinkedBlockingQueue<Boolean>();
future.addListener(new ChannelFutureListener() { future.addListener(new ChannelFutureListener() {
@ -158,8 +158,8 @@ public class BootstrapTest {
queue.add(future.isSuccess()); queue.add(future.isSuccess());
} }
}); });
Assert.assertTrue(queue.take()); assertTrue(queue.take());
Assert.assertTrue(queue.take()); assertTrue(queue.take());
} finally { } finally {
group.shutdownGracefully(); group.shutdownGracefully();
group.terminationFuture().sync(); group.terminationFuture().sync();
@ -197,7 +197,7 @@ public class BootstrapTest {
bootstrap.childHandler(new DummyHandler()); bootstrap.childHandler(new DummyHandler());
bootstrap.localAddress(new LocalAddress("1")); bootstrap.localAddress(new LocalAddress("1"));
ChannelFuture future = bootstrap.bind(); ChannelFuture future = bootstrap.bind();
Assert.assertFalse(future.isDone()); assertFalse(future.isDone());
group.promise.setSuccess(); group.promise.setSuccess();
final BlockingQueue<Boolean> queue = new LinkedBlockingQueue<Boolean>(); final BlockingQueue<Boolean> queue = new LinkedBlockingQueue<Boolean>();
future.addListener(new ChannelFutureListener() { future.addListener(new ChannelFutureListener() {
@ -207,16 +207,61 @@ public class BootstrapTest {
queue.add(future.isSuccess()); queue.add(future.isSuccess());
} }
}); });
Assert.assertTrue(queue.take()); assertTrue(queue.take());
Assert.assertFalse(queue.take()); assertFalse(queue.take());
} finally { } finally {
group.shutdownGracefully(); group.shutdownGracefully();
group.terminationFuture().sync(); 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 { private static final class TestEventLoopGroup extends DefaultEventLoopGroup {
ChannelPromise promise; ChannelPromise promise;
TestEventLoopGroup() { TestEventLoopGroup() {
super(1); super(1);
} }
@ -236,4 +281,39 @@ public class BootstrapTest {
@Sharable @Sharable
private static final class DummyHandler extends ChannelInboundHandlerAdapter { } 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());
}
}
});
}
};
}
}
} }