Have hosts file support for DnsNameResolver, close #4074
Motivation: On contrary to `DefaultNameResolver`, `DnsNameResolver` doesn't currently honor hosts file. Modifications: * Introduce `HostsFileParser` that parses `/etc/hosts` or `C:\Windows\system32\drivers\etc\hosts` depending on the platform * Introduce `HostsFileEntriesResolver` that uses the former to resolve host names * Make `DnsNameResolver` check his `HostsFileEntriesResolver` prior to trying to resolve names against the DNS server * Introduce `DnsNameResolverBuilder` so we now have a builder for `DnsNameResolver`s * Additionally introduce a `CompositeNameResolver` that takes several `NameResolver`s and tries to resolve names by delegating sequentially * Change `DnsNameResolver.asAddressResolver` to return a composite and honor hosts file Result: Hosts file support when using `DnsNameResolver`. Consistent behavior with JDK implementation.
This commit is contained in:
parent
4e467f5c6f
commit
8d4db050f3
@ -32,4 +32,48 @@ public final class ObjectUtil {
|
||||
}
|
||||
return arg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the given argument is strictly positive. If it is, throws {@link IllegalArgumentException}.
|
||||
* Otherwise, returns the argument.
|
||||
*/
|
||||
public static int checkPositive(int i, String name) {
|
||||
if (i <= 0) {
|
||||
throw new IllegalArgumentException(name + ": " + i + " (expected: > 0)");
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the given argument is strictly positive. If it is, throws {@link IllegalArgumentException}.
|
||||
* Otherwise, returns the argument.
|
||||
*/
|
||||
public static long checkPositive(long i, String name) {
|
||||
if (i <= 0) {
|
||||
throw new IllegalArgumentException(name + ": " + i + " (expected: > 0)");
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the given argument is positive or zero. If it is, throws {@link IllegalArgumentException}.
|
||||
* Otherwise, returns the argument.
|
||||
*/
|
||||
public static int checkPositiveOrZero(int i, String name) {
|
||||
if (i < 0) {
|
||||
throw new IllegalArgumentException(name + ": " + i + " (expected: >= 0)");
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the given argument is neither null nor empty.
|
||||
* If it is, throws {@link NullPointerException} or {@link IllegalArgumentException}.
|
||||
* Otherwise, returns the argument.
|
||||
*/
|
||||
public static <T> T[] checkNonEmpty(T[] array, String name) {
|
||||
checkNotNull(array, name);
|
||||
checkPositive(array.length, name + ".length");
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
@ -81,7 +81,11 @@ public class DnsAddressResolverGroup extends AddressResolverGroup<InetSocketAddr
|
||||
EventLoop eventLoop, ChannelFactory<? extends DatagramChannel> channelFactory,
|
||||
InetSocketAddress localAddress, DnsServerAddresses nameServerAddresses) throws Exception {
|
||||
|
||||
return new DnsNameResolver(eventLoop, channelFactory, localAddress, nameServerAddresses)
|
||||
return new DnsNameResolverBuilder(eventLoop)
|
||||
.channelFactory(channelFactory)
|
||||
.localAddress(localAddress)
|
||||
.nameServerAddresses(nameServerAddresses)
|
||||
.build()
|
||||
.asAddressResolver();
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,6 @@ 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.DatagramDnsQueryEncoder;
|
||||
@ -33,6 +32,7 @@ import io.netty.handler.codec.dns.DatagramDnsResponse;
|
||||
import io.netty.handler.codec.dns.DatagramDnsResponseDecoder;
|
||||
import io.netty.handler.codec.dns.DnsQuestion;
|
||||
import io.netty.handler.codec.dns.DnsResponse;
|
||||
import io.netty.resolver.HostsFileEntriesResolver;
|
||||
import io.netty.resolver.InetNameResolver;
|
||||
import io.netty.util.NetUtil;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
@ -56,7 +56,7 @@ import java.util.Map.Entry;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||
import static io.netty.util.internal.ObjectUtil.*;
|
||||
|
||||
/**
|
||||
* A DNS-based {@link InetNameResolver}.
|
||||
@ -67,7 +67,7 @@ public class DnsNameResolver extends InetNameResolver {
|
||||
|
||||
static final InetSocketAddress ANY_LOCAL_ADDR = new InetSocketAddress(0);
|
||||
|
||||
private static final InternetProtocolFamily[] DEFAULT_RESOLVE_ADDRESS_TYPES = new InternetProtocolFamily[2];
|
||||
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.
|
||||
@ -97,7 +97,7 @@ public class DnsNameResolver extends InetNameResolver {
|
||||
/**
|
||||
* Cache for {@link #doResolve(String, Promise)} and {@link #doResolveAll(String, Promise)}.
|
||||
*/
|
||||
final ConcurrentMap<String, List<DnsCacheEntry>> resolveCache = PlatformDependent.newConcurrentHashMap();
|
||||
private final ConcurrentMap<String, List<DnsCacheEntry>> resolveCache = PlatformDependent.newConcurrentHashMap();
|
||||
|
||||
private final FastThreadLocal<DnsServerAddressStream> nameServerAddrStream =
|
||||
new FastThreadLocal<DnsServerAddressStream>() {
|
||||
@ -107,68 +107,18 @@ public class DnsNameResolver extends InetNameResolver {
|
||||
}
|
||||
};
|
||||
|
||||
private final DnsResponseHandler responseHandler = new DnsResponseHandler();
|
||||
|
||||
private volatile long queryTimeoutMillis = 5000;
|
||||
|
||||
private final long queryTimeoutMillis;
|
||||
// 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 maxQueriesPerResolve = 3;
|
||||
private volatile boolean traceEnabled = true;
|
||||
|
||||
private volatile InternetProtocolFamily[] resolveAddressTypes = DEFAULT_RESOLVE_ADDRESS_TYPES;
|
||||
private volatile boolean recursionDesired = true;
|
||||
|
||||
private volatile int maxPayloadSize;
|
||||
private volatile boolean optResourceEnabled = true;
|
||||
|
||||
/**
|
||||
* Creates a new DNS-based name resolver that communicates with the specified list of DNS servers.
|
||||
*
|
||||
* @param eventLoop the {@link EventLoop} which will perform the communication with the DNS servers
|
||||
* @param channelType the type of the {@link DatagramChannel} to create
|
||||
* @param nameServerAddresses the addresses of the DNS server. For each DNS query, a new stream is created from
|
||||
* this to determine which DNS server should be contacted for the next retry in case
|
||||
* of failure.
|
||||
*/
|
||||
public DnsNameResolver(
|
||||
EventLoop eventLoop, Class<? extends DatagramChannel> channelType,
|
||||
DnsServerAddresses 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 stream is created from
|
||||
* this 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, DnsServerAddresses 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 stream is created from
|
||||
* this to determine which DNS server should be contacted for the next retry in case
|
||||
* of failure.
|
||||
*/
|
||||
public DnsNameResolver(
|
||||
EventLoop eventLoop, ChannelFactory<? extends DatagramChannel> channelFactory,
|
||||
DnsServerAddresses nameServerAddresses) {
|
||||
this(eventLoop, channelFactory, ANY_LOCAL_ADDR, nameServerAddresses);
|
||||
}
|
||||
private final int minTtl;
|
||||
private final int maxTtl;
|
||||
private final int negativeTtl;
|
||||
private final int maxQueriesPerResolve;
|
||||
private final boolean traceEnabled;
|
||||
private final InternetProtocolFamily[] resolvedAddressTypes;
|
||||
private final boolean recursionDesired;
|
||||
private final int maxPayloadSize;
|
||||
private final boolean optResourceEnabled;
|
||||
private final HostsFileEntriesResolver hostsFileEntriesResolver;
|
||||
|
||||
/**
|
||||
* Creates a new DNS-based name resolver that communicates with the specified list of DNS servers.
|
||||
@ -179,22 +129,58 @@ public class DnsNameResolver extends InetNameResolver {
|
||||
* @param nameServerAddresses the addresses of the DNS server. For each DNS query, a new stream is created from
|
||||
* this to determine which DNS server should be contacted for the next retry in case
|
||||
* of failure.
|
||||
* @param minTtl the minimum TTL of cached DNS records
|
||||
* @param maxTtl the maximum TTL of cached DNS records
|
||||
* @param negativeTtl the TTL for failed cached queries
|
||||
* @param queryTimeoutMillis timeout of each DNS query in millis
|
||||
* @param resolvedAddressTypes list of the protocol families
|
||||
* @param recursionDesired if recursion desired flag must be set
|
||||
* @param maxQueriesPerResolve the maximum allowed number of DNS queries for a given name resolution
|
||||
* @param traceEnabled if trace is enabled
|
||||
* @param maxPayloadSize the capacity of the datagram packet buffer
|
||||
* @param optResourceEnabled if automatic inclusion of a optional records is enabled
|
||||
* @param hostsFileEntriesResolver the {@link HostsFileEntriesResolver} used to check for local aliases
|
||||
*/
|
||||
public DnsNameResolver(
|
||||
EventLoop eventLoop, ChannelFactory<? extends DatagramChannel> channelFactory,
|
||||
InetSocketAddress localAddress, DnsServerAddresses nameServerAddresses) {
|
||||
EventLoop eventLoop,
|
||||
ChannelFactory<? extends DatagramChannel> channelFactory,
|
||||
InetSocketAddress localAddress,
|
||||
DnsServerAddresses nameServerAddresses,
|
||||
int minTtl,
|
||||
int maxTtl,
|
||||
int negativeTtl,
|
||||
long queryTimeoutMillis,
|
||||
InternetProtocolFamily[] resolvedAddressTypes,
|
||||
boolean recursionDesired,
|
||||
int maxQueriesPerResolve,
|
||||
boolean traceEnabled,
|
||||
int maxPayloadSize,
|
||||
boolean optResourceEnabled,
|
||||
HostsFileEntriesResolver hostsFileEntriesResolver) {
|
||||
|
||||
super(eventLoop);
|
||||
|
||||
checkNotNull(channelFactory, "channelFactory");
|
||||
checkNotNull(nameServerAddresses, "nameServerAddresses");
|
||||
checkNotNull(localAddress, "localAddress");
|
||||
this.nameServerAddresses = checkNotNull(nameServerAddresses, "nameServerAddresses");
|
||||
this.minTtl = checkPositiveOrZero(minTtl, "minTtl");
|
||||
this.maxTtl = checkPositiveOrZero(maxTtl, "maxTtl");
|
||||
if (minTtl > maxTtl) {
|
||||
throw new IllegalArgumentException(
|
||||
"minTtl: " + minTtl + ", maxTtl: " + maxTtl + " (expected: 0 <= minTtl <= maxTtl)");
|
||||
}
|
||||
this.negativeTtl = checkPositiveOrZero(negativeTtl, "negativeTtl");
|
||||
this.queryTimeoutMillis = checkPositive(queryTimeoutMillis, "queryTimeoutMillis");
|
||||
this.resolvedAddressTypes = checkNonEmpty(resolvedAddressTypes, "resolvedAddressTypes");
|
||||
this.recursionDesired = recursionDesired;
|
||||
this.maxQueriesPerResolve = checkPositive(maxQueriesPerResolve, "maxQueriesPerResolve");
|
||||
this.traceEnabled = traceEnabled;
|
||||
this.maxPayloadSize = checkPositive(maxPayloadSize, "maxPayloadSize");
|
||||
this.optResourceEnabled = optResourceEnabled;
|
||||
this.hostsFileEntriesResolver = checkNotNull(hostsFileEntriesResolver, "hostsFileEntriesResolver");
|
||||
|
||||
this.nameServerAddresses = nameServerAddresses;
|
||||
bindFuture = newChannel(channelFactory, localAddress);
|
||||
ch = (DatagramChannel) bindFuture.channel();
|
||||
|
||||
setMaxPayloadSize(4096);
|
||||
ch.config().setRecvByteBufAllocator(new FixedRecvByteBufAllocator(maxPayloadSize));
|
||||
}
|
||||
|
||||
private ChannelFuture newChannel(
|
||||
@ -203,6 +189,7 @@ public class DnsNameResolver extends InetNameResolver {
|
||||
Bootstrap b = new Bootstrap();
|
||||
b.group(executor());
|
||||
b.channelFactory(channelFactory);
|
||||
final DnsResponseHandler responseHandler = new DnsResponseHandler();
|
||||
b.handler(new ChannelInitializer<DatagramChannel>() {
|
||||
@Override
|
||||
protected void initChannel(DatagramChannel ch) throws Exception {
|
||||
@ -225,7 +212,6 @@ public class DnsNameResolver extends InetNameResolver {
|
||||
* Returns the minimum TTL of the cached DNS resource records (in seconds).
|
||||
*
|
||||
* @see #maxTtl()
|
||||
* @see #setTtl(int, int)
|
||||
*/
|
||||
public int minTtl() {
|
||||
return minTtl;
|
||||
@ -235,236 +221,56 @@ public class DnsNameResolver extends InetNameResolver {
|
||||
* 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 list of the protocol families of the address resolved by {@link #resolve(String)}
|
||||
* in the order of preference.
|
||||
* The default value depends on the value of the system property {@code "java.net.preferIPv6Addresses"}.
|
||||
*
|
||||
* @see #setResolveAddressTypes(InternetProtocolFamily...)
|
||||
*/
|
||||
public List<InternetProtocolFamily> resolveAddressTypes() {
|
||||
return Arrays.asList(resolveAddressTypes);
|
||||
public List<InternetProtocolFamily> resolvedAddressTypes() {
|
||||
return Arrays.asList(resolvedAddressTypes);
|
||||
}
|
||||
|
||||
InternetProtocolFamily[] resolveAddressTypesUnsafe() {
|
||||
return resolveAddressTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of the protocol families of the address resolved by {@link #resolve(String)}.
|
||||
* 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) {
|
||||
checkNotNull(resolveAddressTypes, "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(String)}.
|
||||
* 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) {
|
||||
checkNotNull(resolveAddressTypes, "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;
|
||||
return resolvedAddressTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if and only if this resolver sends a DNS query with the RD (recursion desired) flag set.
|
||||
* The default value is {@code true}.
|
||||
*
|
||||
* @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 if this resolver should generate the detailed trace information in an exception message so that
|
||||
* it is easier to understand the cause of resolution failure. The default value if {@code true}.
|
||||
@ -473,57 +279,13 @@ public class DnsNameResolver extends InetNameResolver {
|
||||
return traceEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if this resolver should generate the detailed trace information in an exception message so that
|
||||
* it is easier to understand the cause of resolution failure.
|
||||
*/
|
||||
public DnsNameResolver setTraceEnabled(boolean traceEnabled) {
|
||||
this.traceEnabled = traceEnabled;
|
||||
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;
|
||||
ch.config().setRecvByteBufAllocator(new FixedRecvByteBufAllocator(maxPayloadSize));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the automatic inclusion of a optional records that tries to give the remote DNS server a hint about how
|
||||
* much data the resolver can read per response. Some DNSServer may not support this and so fail to answer
|
||||
* queries. If you find problems you may want to disable this.
|
||||
*/
|
||||
public DnsNameResolver setOptResourceEnabled(boolean optResourceEnabled) {
|
||||
this.optResourceEnabled = optResourceEnabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the automatic inclusion of a optional records that tries to give the remote DNS server a hint about how
|
||||
* much data the resolver can read per response is enabled.
|
||||
@ -532,6 +294,14 @@ public class DnsNameResolver extends InetNameResolver {
|
||||
return optResourceEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the component that tries to resolve hostnames against the hosts file prior to asking to
|
||||
* remotes DNS servers.
|
||||
*/
|
||||
public HostsFileEntriesResolver hostsFileEntriesResolver() {
|
||||
return hostsFileEntriesResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all the resolved addresses cached by this resolver.
|
||||
*
|
||||
@ -590,6 +360,10 @@ public class DnsNameResolver extends InetNameResolver {
|
||||
return (EventLoop) super.executor();
|
||||
}
|
||||
|
||||
private InetAddress resolveHostsFileEntry(String hostname) {
|
||||
return hostsFileEntriesResolver != null ? hostsFileEntriesResolver.address(hostname) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doResolve(String inetHost, Promise<InetAddress> promise) throws Exception {
|
||||
final byte[] bytes = NetUtil.createByteArrayFromIpAddressString(inetHost);
|
||||
@ -601,6 +375,12 @@ public class DnsNameResolver extends InetNameResolver {
|
||||
|
||||
final String hostname = hostname(inetHost);
|
||||
|
||||
InetAddress hostsFileEntry = resolveHostsFileEntry(hostname);
|
||||
if (hostsFileEntry != null) {
|
||||
promise.setSuccess(hostsFileEntry);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!doResolveCached(hostname, promise)) {
|
||||
doResolveUncached(hostname, promise);
|
||||
}
|
||||
@ -622,7 +402,7 @@ public class DnsNameResolver extends InetNameResolver {
|
||||
cause = cachedEntries.get(0).cause();
|
||||
} else {
|
||||
// Find the first entry with the preferred address type.
|
||||
for (InternetProtocolFamily f : resolveAddressTypes) {
|
||||
for (InternetProtocolFamily f : resolvedAddressTypes) {
|
||||
for (int i = 0; i < numEntries; i++) {
|
||||
final DnsCacheEntry e = cachedEntries.get(i);
|
||||
if (f.addressType().isInstance(e.address())) {
|
||||
@ -687,6 +467,12 @@ public class DnsNameResolver extends InetNameResolver {
|
||||
|
||||
final String hostname = hostname(inetHost);
|
||||
|
||||
InetAddress hostsFileEntry = resolveHostsFileEntry(hostname);
|
||||
if (hostsFileEntry != null) {
|
||||
promise.setSuccess(Collections.singletonList(hostsFileEntry));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!doResolveAllCached(hostname, promise)) {
|
||||
doResolveAllUncached(hostname, promise);
|
||||
}
|
||||
@ -707,7 +493,7 @@ public class DnsNameResolver extends InetNameResolver {
|
||||
if (cachedEntries.get(0).cause() != null) {
|
||||
cause = cachedEntries.get(0).cause();
|
||||
} else {
|
||||
for (InternetProtocolFamily f : resolveAddressTypes) {
|
||||
for (InternetProtocolFamily f : resolvedAddressTypes) {
|
||||
for (int i = 0; i < numEntries; i++) {
|
||||
final DnsCacheEntry e = cachedEntries.get(i);
|
||||
if (f.addressType().isInstance(e.address())) {
|
||||
|
@ -0,0 +1,312 @@
|
||||
/*
|
||||
* Copyright 2015 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.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.channel.socket.InternetProtocolFamily;
|
||||
import io.netty.resolver.HostsFileEntriesResolver;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||
|
||||
/**
|
||||
* A {@link DnsNameResolver} builder.
|
||||
*/
|
||||
public final class DnsNameResolverBuilder {
|
||||
|
||||
private final EventLoop eventLoop;
|
||||
private ChannelFactory<? extends DatagramChannel> channelFactory;
|
||||
private InetSocketAddress localAddress = DnsNameResolver.ANY_LOCAL_ADDR;
|
||||
private DnsServerAddresses nameServerAddresses;
|
||||
private int minTtl;
|
||||
private int maxTtl = Integer.MAX_VALUE;
|
||||
private int negativeTtl;
|
||||
private long queryTimeoutMillis = 5000;
|
||||
private InternetProtocolFamily[] resolvedAddressTypes = DnsNameResolver.DEFAULT_RESOLVE_ADDRESS_TYPES;
|
||||
private boolean recursionDesired = true;
|
||||
private int maxQueriesPerResolve = 3;
|
||||
private boolean traceEnabled;
|
||||
private int maxPayloadSize = 4096;
|
||||
private boolean optResourceEnabled = true;
|
||||
private HostsFileEntriesResolver hostsFileEntriesResolver = HostsFileEntriesResolver.DEFAULT;
|
||||
|
||||
/**
|
||||
* Creates a new builder.
|
||||
*
|
||||
* @param eventLoop the {@link EventLoop} the {@link EventLoop} which will perform the communication with the DNS
|
||||
* servers.
|
||||
*/
|
||||
public DnsNameResolverBuilder(EventLoop eventLoop) {
|
||||
this.eventLoop = eventLoop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link ChannelFactory} that will create a {@link DatagramChannel}.
|
||||
*
|
||||
* @param channelFactory the {@link ChannelFactory}
|
||||
* @return {@code this}
|
||||
*/
|
||||
public DnsNameResolverBuilder channelFactory(ChannelFactory<? extends DatagramChannel> channelFactory) {
|
||||
this.channelFactory = channelFactory;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link ChannelFactory} as a {@link ReflectiveChannelFactory} of this type.
|
||||
* Use as an alternative to {@link #channelFactory(ChannelFactory)}.
|
||||
*
|
||||
* @param channelType
|
||||
* @return {@code this}
|
||||
*/
|
||||
public DnsNameResolverBuilder channelType(Class<? extends DatagramChannel> channelType) {
|
||||
return channelFactory(new ReflectiveChannelFactory<DatagramChannel>(channelType));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the local address of the {@link DatagramChannel}
|
||||
*
|
||||
* @param localAddress the local address
|
||||
* @return {@code this}
|
||||
*/
|
||||
public DnsNameResolverBuilder localAddress(InetSocketAddress localAddress) {
|
||||
this.localAddress = localAddress;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the addresses of the DNS server.
|
||||
*
|
||||
* @param nameServerAddresses the DNS server addresses
|
||||
* @return {@code this}
|
||||
*/
|
||||
public DnsNameResolverBuilder nameServerAddresses(DnsServerAddresses nameServerAddresses) {
|
||||
this.nameServerAddresses = nameServerAddresses;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param minTtl the minimum TTL
|
||||
* @param maxTtl the maximum TTL
|
||||
* @return {@code this}
|
||||
*/
|
||||
public DnsNameResolverBuilder ttl(int minTtl, int maxTtl) {
|
||||
this.maxTtl = maxTtl;
|
||||
this.minTtl = minTtl;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the TTL of the cache for the failed DNS queries (in seconds).
|
||||
*
|
||||
* @param negativeTtl the TTL for failed cached queries
|
||||
* @return {@code this}
|
||||
*/
|
||||
public DnsNameResolverBuilder negativeTtl(int negativeTtl) {
|
||||
this.negativeTtl = negativeTtl;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timeout of each DNS query performed by this resolver (in milliseconds).
|
||||
*
|
||||
* @param queryTimeoutMillis the query timeout
|
||||
* @return {@code this}
|
||||
*/
|
||||
public DnsNameResolverBuilder queryTimeoutMillis(long queryTimeoutMillis) {
|
||||
this.queryTimeoutMillis = queryTimeoutMillis;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of the protocol families of the address resolved.
|
||||
* 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}.
|
||||
*
|
||||
* @param resolvedAddressTypes the address types
|
||||
* @return {@code this}
|
||||
*/
|
||||
public DnsNameResolverBuilder resolvedAddressTypes(InternetProtocolFamily... resolvedAddressTypes) {
|
||||
checkNotNull(resolvedAddressTypes, "resolvedAddressTypes");
|
||||
|
||||
final List<InternetProtocolFamily> list =
|
||||
new ArrayList<InternetProtocolFamily>(InternetProtocolFamily.values().length);
|
||||
|
||||
for (InternetProtocolFamily f : resolvedAddressTypes) {
|
||||
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.resolvedAddressTypes = list.toArray(new InternetProtocolFamily[list.size()]);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of the protocol families of the address resolved.
|
||||
* 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}.
|
||||
*
|
||||
* @param resolvedAddressTypes the address types
|
||||
* @return {@code this}
|
||||
*/
|
||||
public DnsNameResolverBuilder resolvedAddressTypes(Iterable<InternetProtocolFamily> resolvedAddressTypes) {
|
||||
checkNotNull(resolvedAddressTypes, "resolveAddressTypes");
|
||||
|
||||
final List<InternetProtocolFamily> list =
|
||||
new ArrayList<InternetProtocolFamily>(InternetProtocolFamily.values().length);
|
||||
|
||||
for (InternetProtocolFamily f : resolvedAddressTypes) {
|
||||
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.resolvedAddressTypes = list.toArray(new InternetProtocolFamily[list.size()]);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if this resolver has to send a DNS query with the RD (recursion desired) flag set.
|
||||
*
|
||||
* @param recursionDesired true if recursion is desired
|
||||
* @return {@code this}
|
||||
*/
|
||||
public DnsNameResolverBuilder recursionDesired(boolean recursionDesired) {
|
||||
this.recursionDesired = recursionDesired;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum allowed number of DNS queries to send when resolving a host name.
|
||||
*
|
||||
* @param maxQueriesPerResolve the max number of queries
|
||||
* @return {@code this}
|
||||
*/
|
||||
public DnsNameResolverBuilder maxQueriesPerResolve(int maxQueriesPerResolve) {
|
||||
this.maxQueriesPerResolve = maxQueriesPerResolve;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if this resolver should generate the detailed trace information in an exception message so that
|
||||
* it is easier to understand the cause of resolution failure.
|
||||
*
|
||||
* @param traceEnabled true if trace is enabled
|
||||
* @return {@code this}
|
||||
*/
|
||||
public DnsNameResolverBuilder traceEnabled(boolean traceEnabled) {
|
||||
this.traceEnabled = traceEnabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the capacity of the datagram packet buffer (in bytes). The default value is {@code 4096} bytes.
|
||||
*
|
||||
* @param maxPayloadSize the capacity of the datagram packet buffer
|
||||
* @return {@code this}
|
||||
*/
|
||||
public DnsNameResolverBuilder maxPayloadSize(int maxPayloadSize) {
|
||||
this.maxPayloadSize = maxPayloadSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the automatic inclusion of a optional records that tries to give the remote DNS server a hint about
|
||||
* how much data the resolver can read per response. Some DNSServer may not support this and so fail to answer
|
||||
* queries. If you find problems you may want to disable this.
|
||||
*
|
||||
* @param optResourceEnabled if optional records inclusion is enabled
|
||||
* @return {@code this}
|
||||
*/
|
||||
public DnsNameResolverBuilder optResourceEnabled(boolean optResourceEnabled) {
|
||||
this.optResourceEnabled = optResourceEnabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param hostsFileEntriesResolver the {@link HostsFileEntriesResolver} used to first check
|
||||
* if the hostname is locally aliased.
|
||||
* @param hostsFileEntriesResolver the {@link HostsFileEntriesResolver}
|
||||
* @return {@code this}
|
||||
*/
|
||||
public DnsNameResolverBuilder hostsFileEntriesResolver(HostsFileEntriesResolver hostsFileEntriesResolver) {
|
||||
this.hostsFileEntriesResolver = hostsFileEntriesResolver;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link DnsNameResolver} instance.
|
||||
*
|
||||
* @return a {@link DnsNameResolver}
|
||||
*/
|
||||
public DnsNameResolver build() {
|
||||
return new DnsNameResolver(
|
||||
eventLoop,
|
||||
channelFactory,
|
||||
localAddress,
|
||||
nameServerAddresses,
|
||||
minTtl,
|
||||
maxTtl,
|
||||
negativeTtl,
|
||||
queryTimeoutMillis,
|
||||
resolvedAddressTypes,
|
||||
recursionDesired,
|
||||
maxQueriesPerResolve,
|
||||
traceEnabled,
|
||||
maxPayloadSize,
|
||||
optResourceEnabled,
|
||||
hostsFileEntriesResolver);
|
||||
}
|
||||
}
|
@ -31,11 +31,6 @@ public final class DnsNameResolverException extends RuntimeException {
|
||||
private final InetSocketAddress remoteAddress;
|
||||
private final DnsQuestion question;
|
||||
|
||||
public DnsNameResolverException(InetSocketAddress remoteAddress, DnsQuestion question) {
|
||||
this.remoteAddress = validateRemoteAddress(remoteAddress);
|
||||
this.question = validateQuestion(question);
|
||||
}
|
||||
|
||||
public DnsNameResolverException(InetSocketAddress remoteAddress, DnsQuestion question, String message) {
|
||||
super(message);
|
||||
this.remoteAddress = validateRemoteAddress(remoteAddress);
|
||||
@ -49,12 +44,6 @@ public final class DnsNameResolverException extends RuntimeException {
|
||||
this.question = validateQuestion(question);
|
||||
}
|
||||
|
||||
public DnsNameResolverException(InetSocketAddress remoteAddress, DnsQuestion question, Throwable cause) {
|
||||
super(cause);
|
||||
this.remoteAddress = validateRemoteAddress(remoteAddress);
|
||||
this.question = validateQuestion(question);
|
||||
}
|
||||
|
||||
private static InetSocketAddress validateRemoteAddress(InetSocketAddress remoteAddress) {
|
||||
return ObjectUtil.checkNotNull(remoteAddress, "remoteAddress");
|
||||
}
|
||||
@ -63,6 +52,13 @@ public final class DnsNameResolverException extends RuntimeException {
|
||||
return ObjectUtil.checkNotNull(question, "question");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link InetSocketAddress} of the DNS query that has failed.
|
||||
*/
|
||||
public InetSocketAddress remoteAddress() {
|
||||
return remoteAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link DnsQuestion} of the DNS query that has failed.
|
||||
*/
|
||||
|
@ -58,7 +58,6 @@ import org.apache.mina.filter.codec.ProtocolEncoder;
|
||||
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
|
||||
import org.apache.mina.transport.socket.DatagramAcceptor;
|
||||
import org.apache.mina.transport.socket.DatagramSessionConfig;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
@ -265,15 +264,23 @@ public class DnsNameResolverTest {
|
||||
|
||||
private static final TestDnsServer dnsServer = new TestDnsServer();
|
||||
private static final EventLoopGroup group = new NioEventLoopGroup(1);
|
||||
private static DnsNameResolver resolver;
|
||||
|
||||
private DnsNameResolverBuilder newResolver() {
|
||||
return new DnsNameResolverBuilder(group.next())
|
||||
.channelType(NioDatagramChannel.class)
|
||||
.nameServerAddresses(DnsServerAddresses.singleton(dnsServer.localAddress()))
|
||||
.maxQueriesPerResolve(1)
|
||||
.optResourceEnabled(false);
|
||||
}
|
||||
|
||||
private DnsNameResolverBuilder newResolver(InternetProtocolFamily... resolvedAddressTypes) {
|
||||
return newResolver()
|
||||
.resolvedAddressTypes(resolvedAddressTypes);
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void init() throws Exception {
|
||||
dnsServer.start();
|
||||
resolver = new DnsNameResolver(group.next(), NioDatagramChannel.class,
|
||||
DnsServerAddresses.singleton(dnsServer.localAddress()));
|
||||
resolver.setMaxQueriesPerResolve(1);
|
||||
resolver.setOptResourceEnabled(false);
|
||||
}
|
||||
@AfterClass
|
||||
public static void destroy() {
|
||||
@ -281,37 +288,40 @@ public class DnsNameResolverTest {
|
||||
group.shutdownGracefully();
|
||||
}
|
||||
|
||||
@After
|
||||
public void reset() throws Exception {
|
||||
resolver.clearCache();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveAorAAAA() throws Exception {
|
||||
testResolve0(EXCLUSIONS_RESOLVE_A, InternetProtocolFamily.IPv4, InternetProtocolFamily.IPv6);
|
||||
DnsNameResolver resolver = newResolver(InternetProtocolFamily.IPv4, InternetProtocolFamily.IPv6).build();
|
||||
try {
|
||||
testResolve0(resolver, EXCLUSIONS_RESOLVE_A);
|
||||
} finally {
|
||||
resolver.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveAAAAorA() throws Exception {
|
||||
testResolve0(EXCLUSIONS_RESOLVE_A, InternetProtocolFamily.IPv6, InternetProtocolFamily.IPv4);
|
||||
DnsNameResolver resolver = newResolver(InternetProtocolFamily.IPv6, InternetProtocolFamily.IPv4).build();
|
||||
try {
|
||||
testResolve0(resolver, EXCLUSIONS_RESOLVE_A);
|
||||
} finally {
|
||||
resolver.close();
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
DnsNameResolver resolver = newResolver(InternetProtocolFamily.IPv4)
|
||||
// Cache for eternity
|
||||
.ttl(Integer.MAX_VALUE, Integer.MAX_VALUE)
|
||||
.build();
|
||||
try {
|
||||
final Map<String, InetAddress> resultA = testResolve0(EXCLUSIONS_RESOLVE_A, InternetProtocolFamily.IPv4);
|
||||
final Map<String, InetAddress> resultA = testResolve0(resolver, EXCLUSIONS_RESOLVE_A);
|
||||
|
||||
// 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);
|
||||
final Map<String, InetAddress> resultB = testResolve0(resolver, EXCLUSIONS_RESOLVE_A);
|
||||
|
||||
// Ensure the result from the cache is identical from the uncached one.
|
||||
assertThat(resultB.size(), is(resultA.size()));
|
||||
@ -325,61 +335,56 @@ public class DnsNameResolverTest {
|
||||
assertThat(actual, is(expected));
|
||||
}
|
||||
} finally {
|
||||
// Restore the TTL configuration.
|
||||
resolver.setTtl(oldMinTtl, oldMaxTtl);
|
||||
resolver.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveAAAA() throws Exception {
|
||||
testResolve0(EXCLUSIONS_RESOLVE_AAAA, InternetProtocolFamily.IPv6);
|
||||
DnsNameResolver resolver = newResolver(InternetProtocolFamily.IPv6).build();
|
||||
try {
|
||||
testResolve0(resolver, EXCLUSIONS_RESOLVE_AAAA);
|
||||
} finally {
|
||||
resolver.close();
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, InetAddress> testResolve0(
|
||||
Set<String> excludedDomains, InternetProtocolFamily... famililies) throws InterruptedException {
|
||||
|
||||
final List<InternetProtocolFamily> oldResolveAddressTypes = resolver.resolveAddressTypes();
|
||||
private Map<String, InetAddress> testResolve0(DnsNameResolver resolver, Set<String> excludedDomains)
|
||||
throws InterruptedException {
|
||||
|
||||
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<String, Future<InetAddress>> futures =
|
||||
new LinkedHashMap<String, Future<InetAddress>>();
|
||||
final Map<String, Future<InetAddress>> futures =
|
||||
new LinkedHashMap<String, Future<InetAddress>>();
|
||||
|
||||
for (String name : DOMAINS) {
|
||||
if (excludedDomains.contains(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
resolve(futures, name);
|
||||
for (String name : DOMAINS) {
|
||||
if (excludedDomains.contains(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Entry<String, Future<InetAddress>> e : futures.entrySet()) {
|
||||
String unresolved = e.getKey();
|
||||
InetAddress resolved = e.getValue().sync().getNow();
|
||||
resolve(resolver, futures, name);
|
||||
}
|
||||
|
||||
logger.info("{}: {}", unresolved, resolved.getHostAddress());
|
||||
for (Entry<String, Future<InetAddress>> e : futures.entrySet()) {
|
||||
String unresolved = e.getKey();
|
||||
InetAddress resolved = e.getValue().sync().getNow();
|
||||
|
||||
assertThat(resolved.getHostName(), is(unresolved));
|
||||
logger.info("{}: {}", unresolved, resolved.getHostAddress());
|
||||
|
||||
boolean typeMatches = false;
|
||||
for (InternetProtocolFamily f: famililies) {
|
||||
Class<?> resolvedType = resolved.getClass();
|
||||
if (f.addressType().isAssignableFrom(resolvedType)) {
|
||||
typeMatches = true;
|
||||
}
|
||||
assertThat(resolved.getHostName(), is(unresolved));
|
||||
|
||||
boolean typeMatches = false;
|
||||
for (InternetProtocolFamily f: resolver.resolvedAddressTypes()) {
|
||||
Class<?> resolvedType = resolved.getClass();
|
||||
if (f.addressType().isAssignableFrom(resolvedType)) {
|
||||
typeMatches = true;
|
||||
}
|
||||
|
||||
assertThat(typeMatches, is(true));
|
||||
|
||||
results.put(resolved.getHostName(), resolved);
|
||||
}
|
||||
} finally {
|
||||
resolver.setResolveAddressTypes(oldResolveAddressTypes);
|
||||
|
||||
assertThat(typeMatches, is(true));
|
||||
|
||||
results.put(resolved.getHostName(), resolved);
|
||||
}
|
||||
|
||||
return results;
|
||||
@ -387,61 +392,65 @@ public class DnsNameResolverTest {
|
||||
|
||||
@Test
|
||||
public void testQueryMx() throws Exception {
|
||||
assertThat(resolver.isRecursionDesired(), is(true));
|
||||
DnsNameResolver resolver = newResolver().build();
|
||||
try {
|
||||
assertThat(resolver.isRecursionDesired(), is(true));
|
||||
|
||||
Map<String, Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> futures =
|
||||
new LinkedHashMap<String, Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>>();
|
||||
for (String name: DOMAINS) {
|
||||
if (EXCLUSIONS_QUERY_MX.contains(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
queryMx(futures, name);
|
||||
}
|
||||
|
||||
for (Entry<String, Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> e: futures.entrySet()) {
|
||||
String hostname = e.getKey();
|
||||
Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> f = e.getValue().awaitUninterruptibly();
|
||||
|
||||
DnsResponse response = f.getNow().content();
|
||||
assertThat(response.code(), is(DnsResponseCode.NOERROR));
|
||||
|
||||
final int answerCount = response.count(DnsSection.ANSWER);
|
||||
final List<DnsRecord> mxList = new ArrayList<DnsRecord>(answerCount);
|
||||
for (int i = 0; i < answerCount; i ++) {
|
||||
final DnsRecord r = response.recordAt(DnsSection.ANSWER, i);
|
||||
if (r.type() == DnsRecordType.MX) {
|
||||
mxList.add(r);
|
||||
Map<String, Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> futures =
|
||||
new LinkedHashMap<String, Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>>();
|
||||
for (String name: DOMAINS) {
|
||||
if (EXCLUSIONS_QUERY_MX.contains(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
queryMx(resolver, futures, name);
|
||||
}
|
||||
|
||||
assertThat(mxList.size(), is(greaterThan(0)));
|
||||
StringBuilder buf = new StringBuilder();
|
||||
for (DnsRecord r: mxList) {
|
||||
ByteBuf recordContent = ((ByteBufHolder) r).content();
|
||||
for (Entry<String, Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> e: futures.entrySet()) {
|
||||
String hostname = e.getKey();
|
||||
Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> f = e.getValue().awaitUninterruptibly();
|
||||
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
buf.append('\t');
|
||||
buf.append(r.name());
|
||||
buf.append(' ');
|
||||
buf.append(r.type().name());
|
||||
buf.append(' ');
|
||||
buf.append(recordContent.readUnsignedShort());
|
||||
buf.append(' ');
|
||||
buf.append(DnsNameResolverContext.decodeDomainName(recordContent));
|
||||
DnsResponse response = f.getNow().content();
|
||||
assertThat(response.code(), is(DnsResponseCode.NOERROR));
|
||||
|
||||
final int answerCount = response.count(DnsSection.ANSWER);
|
||||
final List<DnsRecord> mxList = new ArrayList<DnsRecord>(answerCount);
|
||||
for (int i = 0; i < answerCount; i ++) {
|
||||
final DnsRecord r = response.recordAt(DnsSection.ANSWER, i);
|
||||
if (r.type() == DnsRecordType.MX) {
|
||||
mxList.add(r);
|
||||
}
|
||||
}
|
||||
|
||||
assertThat(mxList.size(), is(greaterThan(0)));
|
||||
StringBuilder buf = new StringBuilder();
|
||||
for (DnsRecord r: mxList) {
|
||||
ByteBuf recordContent = ((ByteBufHolder) r).content();
|
||||
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
buf.append('\t');
|
||||
buf.append(r.name());
|
||||
buf.append(' ');
|
||||
buf.append(r.type().name());
|
||||
buf.append(' ');
|
||||
buf.append(recordContent.readUnsignedShort());
|
||||
buf.append(' ');
|
||||
buf.append(DnsNameResolverContext.decodeDomainName(recordContent));
|
||||
}
|
||||
|
||||
logger.info("{} has the following MX records:{}", hostname, buf);
|
||||
response.release();
|
||||
}
|
||||
|
||||
logger.info("{} has the following MX records:{}", hostname, buf);
|
||||
response.release();
|
||||
} finally {
|
||||
resolver.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNegativeTtl() throws Exception {
|
||||
final int oldNegativeTtl = resolver.negativeTtl();
|
||||
resolver.setNegativeTtl(10);
|
||||
final DnsNameResolver resolver = newResolver().negativeTtl(10).build();
|
||||
try {
|
||||
resolveNonExistentDomain();
|
||||
resolveNonExistentDomain(resolver);
|
||||
|
||||
final int size = 10000;
|
||||
final List<UnknownHostException> exceptions = new ArrayList<UnknownHostException>();
|
||||
@ -451,7 +460,7 @@ public class DnsNameResolverTest {
|
||||
@Override
|
||||
public void run() {
|
||||
for (int i = 0; i < size; i++) {
|
||||
exceptions.add(resolveNonExistentDomain());
|
||||
exceptions.add(resolveNonExistentDomain(resolver));
|
||||
if (isInterrupted()) {
|
||||
break;
|
||||
}
|
||||
@ -469,11 +478,11 @@ public class DnsNameResolverTest {
|
||||
|
||||
assertThat(exceptions, hasSize(size));
|
||||
} finally {
|
||||
resolver.setNegativeTtl(oldNegativeTtl);
|
||||
resolver.close();
|
||||
}
|
||||
}
|
||||
|
||||
private static UnknownHostException resolveNonExistentDomain() {
|
||||
private UnknownHostException resolveNonExistentDomain(DnsNameResolver resolver) {
|
||||
try {
|
||||
resolver.resolve("non-existent.netty.io").sync();
|
||||
fail();
|
||||
@ -486,17 +495,23 @@ public class DnsNameResolverTest {
|
||||
|
||||
@Test
|
||||
public void testResolveIp() {
|
||||
InetAddress address = resolver.resolve("10.0.0.1").syncUninterruptibly().getNow();
|
||||
DnsNameResolver resolver = newResolver().build();
|
||||
try {
|
||||
InetAddress address = resolver.resolve("10.0.0.1").syncUninterruptibly().getNow();
|
||||
|
||||
assertEquals("10.0.0.1", address.getHostName());
|
||||
assertEquals("10.0.0.1", address.getHostName());
|
||||
} finally {
|
||||
resolver.close();
|
||||
}
|
||||
}
|
||||
|
||||
private static void resolve(Map<String, Future<InetAddress>> futures, String hostname) {
|
||||
private void resolve(DnsNameResolver resolver, Map<String, Future<InetAddress>> futures, String hostname) {
|
||||
|
||||
futures.put(hostname, resolver.resolve(hostname));
|
||||
}
|
||||
|
||||
private static void queryMx(
|
||||
DnsNameResolver resolver,
|
||||
Map<String, Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> futures,
|
||||
String hostname) throws Exception {
|
||||
futures.put(hostname, resolver.query(new DefaultDnsQuestion(hostname, DnsRecordType.MX)));
|
||||
|
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2015 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.resolver;
|
||||
|
||||
import io.netty.util.concurrent.EventExecutor;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.FutureListener;
|
||||
import io.netty.util.concurrent.Promise;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static io.netty.util.internal.ObjectUtil.*;
|
||||
|
||||
/**
|
||||
* A composite {@link SimpleNameResolver} that resolves a host name against a sequence of {@link NameResolver}s.
|
||||
*
|
||||
* In case of a failure, only the last one will be reported.
|
||||
*/
|
||||
public final class CompositeNameResolver<T> extends SimpleNameResolver<T> {
|
||||
|
||||
private final NameResolver<T>[] resolvers;
|
||||
|
||||
/**
|
||||
* @param executor the {@link EventExecutor} which is used to notify the listeners of the {@link Future} returned
|
||||
* by {@link #resolve(String)}
|
||||
* @param resolvers the {@link NameResolver}s to be tried sequentially
|
||||
*/
|
||||
public CompositeNameResolver(EventExecutor executor, NameResolver<T>... resolvers) {
|
||||
super(executor);
|
||||
checkNotNull(resolvers, "resolvers");
|
||||
for (int i = 0; i < resolvers.length; i++) {
|
||||
if (resolvers[i] == null) {
|
||||
throw new NullPointerException("resolvers[" + i + ']');
|
||||
}
|
||||
}
|
||||
if (resolvers.length < 2) {
|
||||
throw new IllegalArgumentException("resolvers: " + Arrays.asList(resolvers) +
|
||||
" (expected: at least 2 resolvers)");
|
||||
}
|
||||
this.resolvers = resolvers.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doResolve(String inetHost, Promise<T> promise) throws Exception {
|
||||
doResolveRec(inetHost, promise, 0, null);
|
||||
}
|
||||
|
||||
private void doResolveRec(final String inetHost,
|
||||
final Promise<T> promise,
|
||||
final int resolverIndex,
|
||||
Throwable lastFailure) throws Exception {
|
||||
if (resolverIndex >= resolvers.length) {
|
||||
promise.setFailure(lastFailure);
|
||||
} else {
|
||||
NameResolver resolver = resolvers[resolverIndex];
|
||||
resolver.resolve(inetHost).addListener(new FutureListener<T>() {
|
||||
@Override
|
||||
public void operationComplete(Future<T> future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
promise.setSuccess(future.getNow());
|
||||
} else {
|
||||
doResolveRec(inetHost, promise, resolverIndex + 1, future.cause());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doResolveAll(String inetHost, Promise<List<T>> promise) throws Exception {
|
||||
doResolveAllRec(inetHost, promise, 0, null);
|
||||
}
|
||||
|
||||
private void doResolveAllRec(final String inetHost,
|
||||
final Promise<List<T>> promise,
|
||||
final int resolverIndex,
|
||||
Throwable lastFailure) throws Exception {
|
||||
if (resolverIndex >= resolvers.length) {
|
||||
promise.setFailure(lastFailure);
|
||||
} else {
|
||||
NameResolver resolver = resolvers[resolverIndex];
|
||||
resolver.resolveAll(inetHost).addListener(new FutureListener<List<T>>() {
|
||||
@Override
|
||||
public void operationComplete(Future<List<T>> future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
promise.setSuccess(future.getNow());
|
||||
} else {
|
||||
doResolveAllRec(inetHost, promise, resolverIndex + 1, future.cause());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2015 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.resolver;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Default {@link HostsFileEntriesResolver} that resolves hosts file entries only once.
|
||||
*/
|
||||
public final class DefaultHostsFileEntriesResolver implements HostsFileEntriesResolver {
|
||||
|
||||
private final Map<String, InetAddress> entries = HostsFileParser.parseSilently();
|
||||
|
||||
@Override
|
||||
public InetAddress address(String inetHost) {
|
||||
return entries.get(inetHost);
|
||||
}
|
||||
}
|
@ -20,7 +20,6 @@ import io.netty.util.concurrent.EventExecutor;
|
||||
import io.netty.util.concurrent.Promise;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2015 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.resolver;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
/**
|
||||
* Resolves a hostname against the hosts file entries.
|
||||
*/
|
||||
public interface HostsFileEntriesResolver {
|
||||
|
||||
/**
|
||||
* Default instance: a {@link DefaultHostsFileEntriesResolver}.
|
||||
*/
|
||||
HostsFileEntriesResolver DEFAULT = new DefaultHostsFileEntriesResolver();
|
||||
|
||||
InetAddress address(String inetHost);
|
||||
}
|
171
resolver/src/main/java/io/netty/resolver/HostsFileParser.java
Normal file
171
resolver/src/main/java/io/netty/resolver/HostsFileParser.java
Normal file
@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright 2015 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.resolver;
|
||||
|
||||
import io.netty.util.NetUtil;
|
||||
import io.netty.util.internal.PlatformDependent;
|
||||
import io.netty.util.internal.logging.InternalLogger;
|
||||
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static io.netty.util.internal.ObjectUtil.*;
|
||||
|
||||
/**
|
||||
* A parser for hosts files.
|
||||
*/
|
||||
public final class HostsFileParser {
|
||||
|
||||
private static final String WINDOWS_DEFAULT_SYSTEM_ROOT = "C:\\Windows";
|
||||
private static final String WINDOWS_HOSTS_FILE_RELATIVE_PATH = "\\system32\\drivers\\etc\\hosts";
|
||||
private static final String X_PLATFORMS_HOSTS_FILE_PATH = "/etc/hosts";
|
||||
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(HostsFileParser.class);
|
||||
|
||||
private static File locateHostsFile() {
|
||||
File hostsFile;
|
||||
if (PlatformDependent.isWindows()) {
|
||||
hostsFile = new File(System.getenv("SystemRoot") + WINDOWS_HOSTS_FILE_RELATIVE_PATH);
|
||||
if (!hostsFile.exists()) {
|
||||
hostsFile = new File(WINDOWS_DEFAULT_SYSTEM_ROOT + WINDOWS_HOSTS_FILE_RELATIVE_PATH);
|
||||
}
|
||||
} else {
|
||||
hostsFile = new File(X_PLATFORMS_HOSTS_FILE_PATH);
|
||||
}
|
||||
return hostsFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse hosts file at standard OS location.
|
||||
*
|
||||
* @return a map of hostname or alias to {@link InetAddress}
|
||||
*/
|
||||
public static Map<String, InetAddress> parseSilently() {
|
||||
File hostsFile = locateHostsFile();
|
||||
try {
|
||||
return parse(hostsFile);
|
||||
} catch (IOException e) {
|
||||
logger.warn("Failed to load and parse hosts file at " + hostsFile.getPath(), e);
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse hosts file at standard OS location.
|
||||
*
|
||||
* @return a map of hostname or alias to {@link InetAddress}
|
||||
* @throws IOException file could not be read
|
||||
*/
|
||||
public static Map<String, InetAddress> parse() throws IOException {
|
||||
return parse(locateHostsFile());
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a hosts file.
|
||||
*
|
||||
* @param file the file to be parsed
|
||||
* @return a map of hostname or alias to {@link InetAddress}
|
||||
* @throws IOException file could not be read
|
||||
*/
|
||||
public static Map<String, InetAddress> parse(File file) throws IOException {
|
||||
checkNotNull(file, "file");
|
||||
if (file.exists() && file.isFile()) {
|
||||
return parse(new BufferedReader(new FileReader(file)));
|
||||
} else {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a reader of hosts file format.
|
||||
*
|
||||
* @param reader the file to be parsed
|
||||
* @return a map of hostname or alias to {@link InetAddress}
|
||||
* @throws IOException file could not be read
|
||||
*/
|
||||
public static Map<String, InetAddress> parse(Reader reader) throws IOException {
|
||||
checkNotNull(reader, "reader");
|
||||
BufferedReader buff = new BufferedReader(reader);
|
||||
try {
|
||||
Map<String, InetAddress> entries = new HashMap<String, InetAddress>();
|
||||
String line;
|
||||
while ((line = buff.readLine()) != null) {
|
||||
// remove comment
|
||||
int commentPosition = line.indexOf('#');
|
||||
if (commentPosition != -1) {
|
||||
line = line.substring(0, commentPosition);
|
||||
}
|
||||
// skip empty lines
|
||||
line = line.trim();
|
||||
if (line.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// split
|
||||
List<String> lineParts = new ArrayList<String>();
|
||||
for (String s: line.split("[ \t]+")) {
|
||||
if (!s.isEmpty()) {
|
||||
lineParts.add(s);
|
||||
}
|
||||
}
|
||||
|
||||
// a valid line should be [IP, hostname, alias*]
|
||||
if (lineParts.size() < 2) {
|
||||
// skip invalid line
|
||||
continue;
|
||||
}
|
||||
|
||||
byte[] ipBytes = NetUtil.createByteArrayFromIpAddressString(lineParts.get(0));
|
||||
|
||||
if (ipBytes == null) {
|
||||
// skip invalid IP
|
||||
continue;
|
||||
}
|
||||
|
||||
InetAddress inetAddress = InetAddress.getByAddress(ipBytes);
|
||||
|
||||
// loop over hostname and aliases
|
||||
for (int i = 1; i < lineParts.size(); i ++) {
|
||||
String hostname = lineParts.get(i);
|
||||
if (!entries.containsKey(hostname)) {
|
||||
// trying to map a host to multiple IPs is wrong
|
||||
// only the first entry is honored
|
||||
entries.put(hostname, inetAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
} finally {
|
||||
buff.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Can't be instantiated.
|
||||
*/
|
||||
private HostsFileParser() {
|
||||
}
|
||||
}
|
@ -37,7 +37,8 @@ public abstract class InetNameResolver extends SimpleNameResolver<InetAddress> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link AddressResolver} that will use this name resolver underneath.
|
||||
* Return a {@link AddressResolver} that will use this name resolver underneath.
|
||||
* It's cached internally, so the same instance is always returned.
|
||||
*/
|
||||
public AddressResolver<InetSocketAddress> asAddressResolver() {
|
||||
AddressResolver<InetSocketAddress> result = addressResolver;
|
||||
|
@ -17,7 +17,7 @@ package io.netty.resolver;
|
||||
|
||||
import io.netty.util.concurrent.EventExecutor;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.GenericFutureListener;
|
||||
import io.netty.util.concurrent.FutureListener;
|
||||
import io.netty.util.concurrent.Promise;
|
||||
|
||||
import java.net.InetAddress;
|
||||
@ -53,7 +53,7 @@ public class InetSocketAddressResolver extends AbstractAddressResolver<InetSocke
|
||||
// Note that InetSocketAddress.getHostName() will never incur a reverse lookup here,
|
||||
// because an unresolved address always has a host name.
|
||||
nameResolver.resolve(unresolvedAddress.getHostName())
|
||||
.addListener(new GenericFutureListener<Future<InetAddress>>() {
|
||||
.addListener(new FutureListener<InetAddress>() {
|
||||
@Override
|
||||
public void operationComplete(Future<InetAddress> future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
@ -71,7 +71,7 @@ public class InetSocketAddressResolver extends AbstractAddressResolver<InetSocke
|
||||
// Note that InetSocketAddress.getHostName() will never incur a reverse lookup here,
|
||||
// because an unresolved address always has a host name.
|
||||
nameResolver.resolveAll(unresolvedAddress.getHostName())
|
||||
.addListener(new GenericFutureListener<Future<List<InetAddress>>>() {
|
||||
.addListener(new FutureListener<List<InetAddress>>() {
|
||||
@Override
|
||||
public void operationComplete(Future<List<InetAddress>> future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2015 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.resolver;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.net.InetAddress;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class HostsFileParserTest {
|
||||
|
||||
@Test
|
||||
public void testParse() throws IOException {
|
||||
String hostsString = new StringBuilder()
|
||||
.append("127.0.0.1 host1").append("\n") // single hostname, separated with blanks
|
||||
.append("\n") // empty line
|
||||
.append("192.168.0.1\thost2").append("\n") // single hostname, separated with tabs
|
||||
.append("#comment").append("\n") // comment at the beginning of the line
|
||||
.append(" #comment ").append("\n") // comment in the middle of the line
|
||||
.append("192.168.0.2 host3 #comment").append("\n") // comment after hostname
|
||||
.append("192.168.0.3 host4 host5 host6").append("\n") // multiple aliases
|
||||
.append("192.168.0.4 host4").append("\n") // host mapped to a second address, must be ignored
|
||||
.toString();
|
||||
|
||||
Map<String, InetAddress> entries = HostsFileParser.parse(new BufferedReader(new StringReader(hostsString)));
|
||||
|
||||
assertEquals("Expected 6 entries", 6, entries.size());
|
||||
assertEquals("127.0.0.1", entries.get("host1").getHostAddress());
|
||||
assertEquals("192.168.0.1", entries.get("host2").getHostAddress());
|
||||
assertEquals("192.168.0.2", entries.get("host3").getHostAddress());
|
||||
assertEquals("192.168.0.3", entries.get("host4").getHostAddress());
|
||||
assertEquals("192.168.0.3", entries.get("host5").getHostAddress());
|
||||
assertEquals("192.168.0.3", entries.get("host6").getHostAddress());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user