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:
parent
ab2e80fbb1
commit
e848066cab
14
all/pom.xml
14
all/pom.xml
@ -189,6 +189,20 @@
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>netty-resolver</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>netty-resolver-dns</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>netty-transport</artifactId>
|
||||
|
2
pom.xml
2
pom.xml
@ -341,6 +341,8 @@
|
||||
<module>codec-mqtt</module>
|
||||
<module>codec-socks</module>
|
||||
<module>codec-stomp</module>
|
||||
<module>resolver</module>
|
||||
<module>resolver-dns</module>
|
||||
<module>transport</module>
|
||||
<module>transport-rxtx</module>
|
||||
<module>transport-sctp</module>
|
||||
|
49
resolver-dns/pom.xml
Normal file
49
resolver-dns/pom.xml
Normal 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>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
@ -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)));
|
||||
}
|
||||
}
|
@ -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
39
resolver/pom.xml
Normal 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>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
90
resolver/src/main/java/io/netty/resolver/NameResolver.java
Normal file
90
resolver/src/main/java/io/netty/resolver/NameResolver.java
Normal 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();
|
||||
}
|
113
resolver/src/main/java/io/netty/resolver/NameResolverGroup.java
Normal file
113
resolver/src/main/java/io/netty/resolver/NameResolverGroup.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
179
resolver/src/main/java/io/netty/resolver/SimpleNameResolver.java
Normal file
179
resolver/src/main/java/io/netty/resolver/SimpleNameResolver.java
Normal 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() { }
|
||||
}
|
20
resolver/src/main/java/io/netty/resolver/package-info.java
Normal file
20
resolver/src/main/java/io/netty/resolver/package-info.java
Normal 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;
|
@ -17,9 +17,9 @@ package io.netty.testsuite.transport.socket;
|
||||
|
||||
import io.netty.bootstrap.AbstractBootstrap;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.bootstrap.ChannelFactory;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFactory;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
|
@ -16,9 +16,9 @@
|
||||
package io.netty.channel.epoll;
|
||||
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.bootstrap.ChannelFactory;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFactory;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.socket.InternetProtocolFamily;
|
||||
import io.netty.channel.socket.nio.NioDatagramChannel;
|
||||
|
@ -23,11 +23,11 @@ import com.barchart.udt.nio.RendezvousChannelUDT;
|
||||
import com.barchart.udt.nio.SelectorProviderUDT;
|
||||
import com.barchart.udt.nio.ServerSocketChannelUDT;
|
||||
import com.barchart.udt.nio.SocketChannelUDT;
|
||||
import io.netty.bootstrap.ChannelFactory;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelException;
|
||||
import io.netty.channel.udt.UdtServerChannel;
|
||||
import io.netty.channel.ChannelFactory;
|
||||
import io.netty.channel.udt.UdtChannel;
|
||||
import io.netty.channel.udt.UdtServerChannel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.spi.SelectorProvider;
|
||||
|
@ -34,6 +34,11 @@
|
||||
<artifactId>netty-buffer</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>netty-resolver</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
|
@ -17,7 +17,6 @@
|
||||
package io.netty.bootstrap;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelException;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
@ -26,6 +25,7 @@ import io.netty.channel.ChannelPromise;
|
||||
import io.netty.channel.DefaultChannelPromise;
|
||||
import io.netty.channel.EventLoop;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.ReflectiveChannelFactory;
|
||||
import io.netty.util.AttributeKey;
|
||||
import io.netty.util.concurrent.EventExecutor;
|
||||
import io.netty.util.concurrent.GlobalEventExecutor;
|
||||
@ -47,6 +47,7 @@ import java.util.Map;
|
||||
public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable {
|
||||
|
||||
private volatile EventLoopGroup group;
|
||||
@SuppressWarnings("deprecation")
|
||||
private volatile ChannelFactory<? extends C> channelFactory;
|
||||
private volatile SocketAddress localAddress;
|
||||
private final Map<ChannelOption<?>, Object> options = new LinkedHashMap<ChannelOption<?>, Object>();
|
||||
@ -88,23 +89,20 @@ public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C ext
|
||||
|
||||
/**
|
||||
* The {@link Class} which is used to create {@link Channel} instances from.
|
||||
* You either use this or {@link #channelFactory(ChannelFactory)} if your
|
||||
* You either use this or {@link #channelFactory(io.netty.channel.ChannelFactory)} if your
|
||||
* {@link Channel} implementation has no no-args constructor.
|
||||
*/
|
||||
public B channel(Class<? extends C> channelClass) {
|
||||
if (channelClass == null) {
|
||||
throw new NullPointerException("channelClass");
|
||||
}
|
||||
return channelFactory(new BootstrapChannelFactory<C>(channelClass));
|
||||
return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ChannelFactory} which is used to create {@link Channel} instances from
|
||||
* when calling {@link #bind()}. This method is usually only used if {@link #channel(Class)}
|
||||
* is not working for you because of some more complex needs. If your {@link Channel} implementation
|
||||
* has a no-args constructor, its highly recommend to just use {@link #channel(Class)} for
|
||||
* simplify your code.
|
||||
* @deprecated Use {@link #channelFactory(io.netty.channel.ChannelFactory)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@SuppressWarnings("unchecked")
|
||||
public B channelFactory(ChannelFactory<? extends C> channelFactory) {
|
||||
if (channelFactory == null) {
|
||||
@ -118,9 +116,20 @@ public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C ext
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link io.netty.channel.ChannelFactory} which is used to create {@link Channel} instances from
|
||||
* when calling {@link #bind()}. This method is usually only used if {@link #channel(Class)}
|
||||
* is not working for you because of some more complex needs. If your {@link Channel} implementation
|
||||
* has a no-args constructor, its highly recommend to just use {@link #channel(Class)} for
|
||||
* simplify your code.
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "deprecation" })
|
||||
public B channelFactory(io.netty.channel.ChannelFactory<? extends C> channelFactory) {
|
||||
return channelFactory((ChannelFactory<C>) channelFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link SocketAddress} which is used to bind the local "end" to.
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public B localAddress(SocketAddress localAddress) {
|
||||
@ -371,6 +380,7 @@ public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C ext
|
||||
return localAddress;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
final ChannelFactory<? extends C> channelFactory() {
|
||||
return channelFactory;
|
||||
}
|
||||
@ -442,28 +452,6 @@ public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C ext
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static final class BootstrapChannelFactory<T extends Channel> implements ChannelFactory<T> {
|
||||
private final Class<? extends T> clazz;
|
||||
|
||||
BootstrapChannelFactory(Class<? extends T> clazz) {
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T newChannel() {
|
||||
try {
|
||||
return clazz.newInstance();
|
||||
} catch (Throwable t) {
|
||||
throw new ChannelException("Unable to create Channel from class " + clazz, t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return StringUtil.simpleClassName(clazz) + ".class";
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PendingRegistrationPromise extends DefaultChannelPromise {
|
||||
// Is set to the correct EventExecutor once the registration was successful. Otherwise it will
|
||||
// stay null and so the GlobalEventExecutor.INSTANCE will be used for notifications.
|
||||
|
@ -21,7 +21,13 @@ import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.channel.EventLoop;
|
||||
import io.netty.resolver.DefaultNameResolverGroup;
|
||||
import io.netty.resolver.NameResolver;
|
||||
import io.netty.resolver.NameResolverGroup;
|
||||
import io.netty.util.AttributeKey;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.FutureListener;
|
||||
import io.netty.util.internal.logging.InternalLogger;
|
||||
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||
|
||||
@ -42,6 +48,10 @@ public final class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
|
||||
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(Bootstrap.class);
|
||||
|
||||
private static final NameResolverGroup<?> DEFAULT_RESOLVER = DefaultNameResolverGroup.INSTANCE;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private volatile NameResolverGroup<SocketAddress> resolver = (NameResolverGroup<SocketAddress>) DEFAULT_RESOLVER;
|
||||
private volatile SocketAddress remoteAddress;
|
||||
|
||||
public Bootstrap() { }
|
||||
@ -51,6 +61,18 @@ public final class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
|
||||
remoteAddress = bootstrap.remoteAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link NameResolver} which will resolve the address of the unresolved named address.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public Bootstrap resolver(NameResolverGroup<?> resolver) {
|
||||
if (resolver == null) {
|
||||
throw new NullPointerException("resolver");
|
||||
}
|
||||
this.resolver = (NameResolverGroup<SocketAddress>) resolver;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link SocketAddress} to connect to once the {@link #connect()} method
|
||||
* is called.
|
||||
@ -64,7 +86,7 @@ public final class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
|
||||
* @see {@link #remoteAddress(SocketAddress)}
|
||||
*/
|
||||
public Bootstrap remoteAddress(String inetHost, int inetPort) {
|
||||
remoteAddress = new InetSocketAddress(inetHost, inetPort);
|
||||
remoteAddress = InetSocketAddress.createUnresolved(inetHost, inetPort);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -86,14 +108,14 @@ public final class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
|
||||
throw new IllegalStateException("remoteAddress not set");
|
||||
}
|
||||
|
||||
return doConnect(remoteAddress, localAddress());
|
||||
return doResolveAndConnect(remoteAddress, localAddress());
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect a {@link Channel} to the remote peer.
|
||||
*/
|
||||
public ChannelFuture connect(String inetHost, int inetPort) {
|
||||
return connect(new InetSocketAddress(inetHost, inetPort));
|
||||
return connect(InetSocketAddress.createUnresolved(inetHost, inetPort));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -112,7 +134,7 @@ public final class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
|
||||
}
|
||||
|
||||
validate();
|
||||
return doConnect(remoteAddress, localAddress());
|
||||
return doResolveAndConnect(remoteAddress, localAddress());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -123,52 +145,94 @@ public final class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
|
||||
throw new NullPointerException("remoteAddress");
|
||||
}
|
||||
validate();
|
||||
return doConnect(remoteAddress, localAddress);
|
||||
return doResolveAndConnect(remoteAddress, localAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link #connect()}
|
||||
*/
|
||||
private ChannelFuture doConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
|
||||
private ChannelFuture doResolveAndConnect(SocketAddress remoteAddress, final SocketAddress localAddress) {
|
||||
final ChannelFuture regFuture = initAndRegister();
|
||||
final Channel channel = regFuture.channel();
|
||||
if (regFuture.cause() != null) {
|
||||
return regFuture;
|
||||
}
|
||||
|
||||
final ChannelPromise promise = channel.newPromise();
|
||||
final Channel channel = regFuture.channel();
|
||||
final EventLoop eventLoop = channel.eventLoop();
|
||||
final NameResolver<SocketAddress> resolver = this.resolver.getResolver(eventLoop);
|
||||
|
||||
if (!resolver.isSupported(remoteAddress) || resolver.isResolved(remoteAddress)) {
|
||||
// Resolver has no idea about what to do with the specified remote address or it's resolved already.
|
||||
return doConnect(remoteAddress, localAddress, regFuture, channel.newPromise());
|
||||
}
|
||||
|
||||
final Future<SocketAddress> resolveFuture = resolver.resolve(remoteAddress);
|
||||
final Throwable resolveFailureCause = resolveFuture.cause();
|
||||
|
||||
if (resolveFailureCause != null) {
|
||||
// Failed to resolve immediately
|
||||
channel.close();
|
||||
return channel.newFailedFuture(resolveFailureCause);
|
||||
}
|
||||
|
||||
if (resolveFuture.isDone()) {
|
||||
// Succeeded to resolve immediately; cached? (or did a blocking lookup)
|
||||
return doConnect(resolveFuture.getNow(), localAddress, regFuture, channel.newPromise());
|
||||
}
|
||||
|
||||
// Wait until the name resolution is finished.
|
||||
final ChannelPromise connectPromise = channel.newPromise();
|
||||
resolveFuture.addListener(new FutureListener<SocketAddress>() {
|
||||
@Override
|
||||
public void operationComplete(Future<SocketAddress> future) throws Exception {
|
||||
if (future.cause() != null) {
|
||||
channel.close();
|
||||
connectPromise.setFailure(future.cause());
|
||||
} else {
|
||||
doConnect(future.getNow(), localAddress, regFuture, connectPromise);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return connectPromise;
|
||||
}
|
||||
|
||||
private static ChannelFuture doConnect(
|
||||
final SocketAddress remoteAddress, final SocketAddress localAddress,
|
||||
final ChannelFuture regFuture, final ChannelPromise connectPromise) {
|
||||
if (regFuture.isDone()) {
|
||||
doConnect0(regFuture, channel, remoteAddress, localAddress, promise);
|
||||
doConnect0(remoteAddress, localAddress, regFuture, connectPromise);
|
||||
} else {
|
||||
regFuture.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
doConnect0(regFuture, channel, remoteAddress, localAddress, promise);
|
||||
doConnect0(remoteAddress, localAddress, regFuture, connectPromise);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return promise;
|
||||
return connectPromise;
|
||||
}
|
||||
|
||||
private static void doConnect0(
|
||||
final ChannelFuture regFuture, final Channel channel,
|
||||
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
|
||||
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelFuture regFuture,
|
||||
final ChannelPromise connectPromise) {
|
||||
|
||||
// This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up
|
||||
// the pipeline in its channelRegistered() implementation.
|
||||
final Channel channel = connectPromise.channel();
|
||||
channel.eventLoop().execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (regFuture.isSuccess()) {
|
||||
if (localAddress == null) {
|
||||
channel.connect(remoteAddress, promise);
|
||||
channel.connect(remoteAddress, connectPromise);
|
||||
} else {
|
||||
channel.connect(remoteAddress, localAddress, promise);
|
||||
channel.connect(remoteAddress, localAddress, connectPromise);
|
||||
}
|
||||
promise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
|
||||
connectPromise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
|
||||
} else {
|
||||
promise.setFailure(regFuture.cause());
|
||||
connectPromise.setFailure(regFuture.cause());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -18,9 +18,9 @@ package io.netty.bootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
/**
|
||||
* Factory that creates a new {@link Channel} on {@link Bootstrap#bind()}, {@link Bootstrap#connect()}, and
|
||||
* {@link ServerBootstrap#bind()}.
|
||||
* @deprecated Use {@link io.netty.channel.ChannelFactory} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public interface ChannelFactory<T extends Channel> {
|
||||
/**
|
||||
* Creates a new channel.
|
||||
|
28
transport/src/main/java/io/netty/channel/ChannelFactory.java
Normal file
28
transport/src/main/java/io/netty/channel/ChannelFactory.java
Normal 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();
|
||||
}
|
@ -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";
|
||||
}
|
||||
}
|
@ -17,124 +17,124 @@
|
||||
package io.netty.bootstrap;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFactory;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandler.Sharable;
|
||||
import io.netty.channel.ChannelInboundHandler;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.channel.DefaultEventLoopGroup;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.channel.DefaultEventLoopGroup;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.ServerChannel;
|
||||
import io.netty.channel.local.LocalAddress;
|
||||
import io.netty.channel.local.LocalChannel;
|
||||
import io.netty.channel.local.LocalServerChannel;
|
||||
import io.netty.resolver.NameResolver;
|
||||
import io.netty.resolver.NameResolverGroup;
|
||||
import io.netty.resolver.SimpleNameResolver;
|
||||
import io.netty.util.concurrent.EventExecutor;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import org.junit.Assert;
|
||||
import io.netty.util.concurrent.Promise;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class BootstrapTest {
|
||||
|
||||
private static final EventLoopGroup groupA = new DefaultEventLoopGroup(1);
|
||||
private static final EventLoopGroup groupB = new DefaultEventLoopGroup(1);
|
||||
private static final ChannelInboundHandler dummyHandler = new DummyHandler();
|
||||
|
||||
@AfterClass
|
||||
public static void destroy() {
|
||||
groupA.shutdownGracefully();
|
||||
groupB.shutdownGracefully();
|
||||
groupA.terminationFuture().syncUninterruptibly();
|
||||
groupB.terminationFuture().syncUninterruptibly();
|
||||
}
|
||||
|
||||
@Test(timeout = 10000)
|
||||
public void testBindDeadLock() throws Exception {
|
||||
EventLoopGroup groupA = new DefaultEventLoopGroup(1);
|
||||
EventLoopGroup groupB = new DefaultEventLoopGroup(1);
|
||||
|
||||
try {
|
||||
ChannelInboundHandler dummyHandler = new DummyHandler();
|
||||
final Bootstrap bootstrapA = new Bootstrap();
|
||||
bootstrapA.group(groupA);
|
||||
bootstrapA.channel(LocalChannel.class);
|
||||
bootstrapA.handler(dummyHandler);
|
||||
|
||||
final Bootstrap bootstrapA = new Bootstrap();
|
||||
bootstrapA.group(groupA);
|
||||
bootstrapA.channel(LocalChannel.class);
|
||||
bootstrapA.handler(dummyHandler);
|
||||
final Bootstrap bootstrapB = new Bootstrap();
|
||||
bootstrapB.group(groupB);
|
||||
bootstrapB.channel(LocalChannel.class);
|
||||
bootstrapB.handler(dummyHandler);
|
||||
|
||||
final Bootstrap bootstrapB = new Bootstrap();
|
||||
bootstrapB.group(groupB);
|
||||
bootstrapB.channel(LocalChannel.class);
|
||||
bootstrapB.handler(dummyHandler);
|
||||
List<Future<?>> bindFutures = new ArrayList<Future<?>>();
|
||||
|
||||
List<Future<?>> bindFutures = new ArrayList<Future<?>>();
|
||||
// Try to bind from each other.
|
||||
for (int i = 0; i < 1024; i ++) {
|
||||
bindFutures.add(groupA.next().submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
bootstrapB.bind(LocalAddress.ANY);
|
||||
}
|
||||
}));
|
||||
|
||||
// Try to bind from each other.
|
||||
for (int i = 0; i < 1024; i ++) {
|
||||
bindFutures.add(groupA.next().submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
bootstrapB.bind(LocalAddress.ANY);
|
||||
}
|
||||
}));
|
||||
bindFutures.add(groupB.next().submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
bootstrapA.bind(LocalAddress.ANY);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
bindFutures.add(groupB.next().submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
bootstrapA.bind(LocalAddress.ANY);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
for (Future<?> f: bindFutures) {
|
||||
f.sync();
|
||||
}
|
||||
} finally {
|
||||
groupA.shutdownGracefully();
|
||||
groupB.shutdownGracefully();
|
||||
groupA.terminationFuture().sync();
|
||||
groupB.terminationFuture().sync();
|
||||
for (Future<?> f: bindFutures) {
|
||||
f.sync();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 10000)
|
||||
public void testConnectDeadLock() throws Exception {
|
||||
EventLoopGroup groupA = new DefaultEventLoopGroup(1);
|
||||
EventLoopGroup groupB = new DefaultEventLoopGroup(1);
|
||||
|
||||
try {
|
||||
ChannelInboundHandler dummyHandler = new DummyHandler();
|
||||
final Bootstrap bootstrapA = new Bootstrap();
|
||||
bootstrapA.group(groupA);
|
||||
bootstrapA.channel(LocalChannel.class);
|
||||
bootstrapA.handler(dummyHandler);
|
||||
|
||||
final Bootstrap bootstrapA = new Bootstrap();
|
||||
bootstrapA.group(groupA);
|
||||
bootstrapA.channel(LocalChannel.class);
|
||||
bootstrapA.handler(dummyHandler);
|
||||
final Bootstrap bootstrapB = new Bootstrap();
|
||||
bootstrapB.group(groupB);
|
||||
bootstrapB.channel(LocalChannel.class);
|
||||
bootstrapB.handler(dummyHandler);
|
||||
|
||||
final Bootstrap bootstrapB = new Bootstrap();
|
||||
bootstrapB.group(groupB);
|
||||
bootstrapB.channel(LocalChannel.class);
|
||||
bootstrapB.handler(dummyHandler);
|
||||
List<Future<?>> bindFutures = new ArrayList<Future<?>>();
|
||||
|
||||
List<Future<?>> bindFutures = new ArrayList<Future<?>>();
|
||||
// Try to connect from each other.
|
||||
for (int i = 0; i < 1024; i ++) {
|
||||
bindFutures.add(groupA.next().submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
bootstrapB.connect(LocalAddress.ANY);
|
||||
}
|
||||
}));
|
||||
|
||||
// Try to connect from each other.
|
||||
for (int i = 0; i < 1024; i ++) {
|
||||
bindFutures.add(groupA.next().submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
bootstrapB.connect(LocalAddress.ANY);
|
||||
}
|
||||
}));
|
||||
bindFutures.add(groupB.next().submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
bootstrapA.connect(LocalAddress.ANY);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
bindFutures.add(groupB.next().submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
bootstrapA.connect(LocalAddress.ANY);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
for (Future<?> f: bindFutures) {
|
||||
f.sync();
|
||||
}
|
||||
} finally {
|
||||
groupA.shutdownGracefully();
|
||||
groupB.shutdownGracefully();
|
||||
groupA.terminationFuture().sync();
|
||||
groupB.terminationFuture().sync();
|
||||
for (Future<?> f: bindFutures) {
|
||||
f.sync();
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,7 +148,7 @@ public class BootstrapTest {
|
||||
bootstrap.childHandler(new DummyHandler());
|
||||
bootstrap.localAddress(new LocalAddress("1"));
|
||||
ChannelFuture future = bootstrap.bind();
|
||||
Assert.assertFalse(future.isDone());
|
||||
assertFalse(future.isDone());
|
||||
group.promise.setSuccess();
|
||||
final BlockingQueue<Boolean> queue = new LinkedBlockingQueue<Boolean>();
|
||||
future.addListener(new ChannelFutureListener() {
|
||||
@ -158,8 +158,8 @@ public class BootstrapTest {
|
||||
queue.add(future.isSuccess());
|
||||
}
|
||||
});
|
||||
Assert.assertTrue(queue.take());
|
||||
Assert.assertTrue(queue.take());
|
||||
assertTrue(queue.take());
|
||||
assertTrue(queue.take());
|
||||
} finally {
|
||||
group.shutdownGracefully();
|
||||
group.terminationFuture().sync();
|
||||
@ -197,7 +197,7 @@ public class BootstrapTest {
|
||||
bootstrap.childHandler(new DummyHandler());
|
||||
bootstrap.localAddress(new LocalAddress("1"));
|
||||
ChannelFuture future = bootstrap.bind();
|
||||
Assert.assertFalse(future.isDone());
|
||||
assertFalse(future.isDone());
|
||||
group.promise.setSuccess();
|
||||
final BlockingQueue<Boolean> queue = new LinkedBlockingQueue<Boolean>();
|
||||
future.addListener(new ChannelFutureListener() {
|
||||
@ -207,16 +207,61 @@ public class BootstrapTest {
|
||||
queue.add(future.isSuccess());
|
||||
}
|
||||
});
|
||||
Assert.assertTrue(queue.take());
|
||||
Assert.assertFalse(queue.take());
|
||||
assertTrue(queue.take());
|
||||
assertFalse(queue.take());
|
||||
} finally {
|
||||
group.shutdownGracefully();
|
||||
group.terminationFuture().sync();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsyncResolutionSuccess() throws Exception {
|
||||
|
||||
final Bootstrap bootstrapA = new Bootstrap();
|
||||
bootstrapA.group(groupA);
|
||||
bootstrapA.channel(LocalChannel.class);
|
||||
bootstrapA.resolver(new TestNameResolverGroup(true));
|
||||
bootstrapA.handler(dummyHandler);
|
||||
|
||||
final ServerBootstrap bootstrapB = new ServerBootstrap();
|
||||
bootstrapB.group(groupB);
|
||||
bootstrapB.channel(LocalServerChannel.class);
|
||||
bootstrapB.childHandler(dummyHandler);
|
||||
SocketAddress localAddress = bootstrapB.bind(LocalAddress.ANY).sync().channel().localAddress();
|
||||
|
||||
// Connect to the server using the asynchronous resolver.
|
||||
bootstrapA.connect(localAddress).sync();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsyncResolutionFailure() throws Exception {
|
||||
|
||||
final Bootstrap bootstrapA = new Bootstrap();
|
||||
bootstrapA.group(groupA);
|
||||
bootstrapA.channel(LocalChannel.class);
|
||||
bootstrapA.resolver(new TestNameResolverGroup(false));
|
||||
bootstrapA.handler(dummyHandler);
|
||||
|
||||
final ServerBootstrap bootstrapB = new ServerBootstrap();
|
||||
bootstrapB.group(groupB);
|
||||
bootstrapB.channel(LocalServerChannel.class);
|
||||
bootstrapB.childHandler(dummyHandler);
|
||||
SocketAddress localAddress = bootstrapB.bind(LocalAddress.ANY).sync().channel().localAddress();
|
||||
|
||||
// Connect to the server using the asynchronous resolver.
|
||||
ChannelFuture connectFuture = bootstrapA.connect(localAddress);
|
||||
|
||||
// Should fail with the UnknownHostException.
|
||||
assertThat(connectFuture.await(10000), is(true));
|
||||
assertThat(connectFuture.cause(), is(instanceOf(UnknownHostException.class)));
|
||||
assertThat(connectFuture.channel().isOpen(), is(false));
|
||||
}
|
||||
|
||||
private static final class TestEventLoopGroup extends DefaultEventLoopGroup {
|
||||
|
||||
ChannelPromise promise;
|
||||
|
||||
TestEventLoopGroup() {
|
||||
super(1);
|
||||
}
|
||||
@ -236,4 +281,39 @@ public class BootstrapTest {
|
||||
|
||||
@Sharable
|
||||
private static final class DummyHandler extends ChannelInboundHandlerAdapter { }
|
||||
|
||||
private static final class TestNameResolverGroup extends NameResolverGroup<SocketAddress> {
|
||||
|
||||
private final boolean success;
|
||||
|
||||
TestNameResolverGroup(boolean success) {
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NameResolver<SocketAddress> newResolver(EventExecutor executor) throws Exception {
|
||||
return new SimpleNameResolver<SocketAddress>(executor) {
|
||||
|
||||
@Override
|
||||
protected boolean doIsResolved(SocketAddress address) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doResolve(
|
||||
final SocketAddress unresolvedAddress, final Promise<SocketAddress> promise) {
|
||||
executor().execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (success) {
|
||||
promise.setSuccess(unresolvedAddress);
|
||||
} else {
|
||||
promise.setFailure(new UnknownHostException());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user