HostsFileParser should allow both IPv4 and IPv6 for a given host

Motivation:

HostsFileParser only retains the first address for each given hostname.
This is wrong, and it’s allowed to have both an IPv4 and an IPv6.

Modifications:

* Have `HostsFileParser` now return a `HostsFileEntries` that contains IPv4 entries and IPv6 entries
* Introduce `ResolvedAddressTypes` to describe resolved address types preferences
* Add a new `ResolvedAddressTypes` parameter to `HostsFileEntriesResolver::address` to account for address types preferences
* Change `DnsNameResolver` constructor to take a `ResolvedAddressTypes`, allowing for a null value that would use default
* Change `DnsNameResolverBuilder::resolvedAddressTypes` to take a `ResolvedAddressTypes`
* Make `DnsNameResolver::resolvedAddressTypes` return a `ResolvedAddressTypes`
* Add a static `DnsNameResolverBuilder::computeResolvedAddressTypes` to ease converting from `InternetProtocolFamily`

Result:

We now support hosts files that contains IPv4 and IPv6 pairs for a same
hostname.
This commit is contained in:
Stephane Landelle 2017-02-08 16:43:15 +01:00 committed by Scott Mitchell
parent 64abef5f5b
commit 81f9de423c
11 changed files with 380 additions and 220 deletions

View File

@ -39,6 +39,7 @@ import io.netty.handler.codec.dns.DnsRecordType;
import io.netty.handler.codec.dns.DnsResponse; import io.netty.handler.codec.dns.DnsResponse;
import io.netty.resolver.HostsFileEntriesResolver; import io.netty.resolver.HostsFileEntriesResolver;
import io.netty.resolver.InetNameResolver; import io.netty.resolver.InetNameResolver;
import io.netty.resolver.ResolvedAddressTypes;
import io.netty.util.NetUtil; import io.netty.util.NetUtil;
import io.netty.util.ReferenceCountUtil; import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.FastThreadLocal; import io.netty.util.concurrent.FastThreadLocal;
@ -56,13 +57,10 @@ import java.net.IDN;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import static io.netty.util.internal.ObjectUtil.checkNonEmpty; import static io.netty.util.internal.ObjectUtil.checkNonEmpty;
import static io.netty.util.internal.ObjectUtil.checkNotNull; import static io.netty.util.internal.ObjectUtil.checkNotNull;
@ -78,24 +76,37 @@ public class DnsNameResolver extends InetNameResolver {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(DnsNameResolver.class); private static final InternalLogger logger = InternalLoggerFactory.getInstance(DnsNameResolver.class);
private static final String LOCALHOST = "localhost"; private static final String LOCALHOST = "localhost";
private static final InetAddress LOCALHOST_ADDRESS; private static final InetAddress LOCALHOST_ADDRESS;
private static final DnsRecord[] EMTPY_ADDITIONALS = new DnsRecord[0]; private static final DnsRecord[] EMPTY_ADDITIONALS = new DnsRecord[0];
private static final DnsRecordType[] IPV4_ONLY_RESOLVED_RECORD_TYPES =
new DnsRecordType[] {DnsRecordType.A};
private static final InternetProtocolFamily[] IPV4_ONLY_RESOLVED_PROTOCOL_FAMILIES =
new InternetProtocolFamily[] {InternetProtocolFamily.IPv4};
private static final DnsRecordType[] IPV4_PREFERRED_RESOLVED_RECORD_TYPES =
new DnsRecordType[] {DnsRecordType.A, DnsRecordType.AAAA};
private static final InternetProtocolFamily[] IPV4_PREFERRED_RESOLVED_PROTOCOL_FAMILIES =
new InternetProtocolFamily[] {InternetProtocolFamily.IPv4, InternetProtocolFamily.IPv6};
private static final DnsRecordType[] IPV6_ONLY_RESOLVED_RECORD_TYPES =
new DnsRecordType[] {DnsRecordType.AAAA};
private static final InternetProtocolFamily[] IPV6_ONLY_RESOLVED_PROTOCOL_FAMILIES =
new InternetProtocolFamily[] {InternetProtocolFamily.IPv6};
private static final DnsRecordType[] IPV6_PREFERRED_RESOLVED_RECORD_TYPES =
new DnsRecordType[] {DnsRecordType.AAAA, DnsRecordType.A};
private static final InternetProtocolFamily[] IPV6_PREFERRED_RESOLVED_PROTOCOL_FAMILIES =
new InternetProtocolFamily[] {InternetProtocolFamily.IPv6, InternetProtocolFamily.IPv4};
static final InternetProtocolFamily[] DEFAULT_RESOLVE_ADDRESS_TYPES; static final ResolvedAddressTypes DEFAULT_RESOLVE_ADDRESS_TYPES;
static final String[] DEFAULT_SEACH_DOMAINS; static final String[] DEFAULT_SEARCH_DOMAINS;
static { static {
if (NetUtil.isIpV4StackPreferred()) { if (NetUtil.isIpV4StackPreferred()) {
DEFAULT_RESOLVE_ADDRESS_TYPES = new InternetProtocolFamily[] { InternetProtocolFamily.IPv4 }; DEFAULT_RESOLVE_ADDRESS_TYPES = ResolvedAddressTypes.IPV4_ONLY;
LOCALHOST_ADDRESS = NetUtil.LOCALHOST4; LOCALHOST_ADDRESS = NetUtil.LOCALHOST4;
} else { } else {
DEFAULT_RESOLVE_ADDRESS_TYPES = new InternetProtocolFamily[2];
if (NetUtil.isIpV6AddressesPreferred()) { if (NetUtil.isIpV6AddressesPreferred()) {
DEFAULT_RESOLVE_ADDRESS_TYPES[0] = InternetProtocolFamily.IPv6; DEFAULT_RESOLVE_ADDRESS_TYPES = ResolvedAddressTypes.IPV6_PREFERRED;
DEFAULT_RESOLVE_ADDRESS_TYPES[1] = InternetProtocolFamily.IPv4;
LOCALHOST_ADDRESS = NetUtil.LOCALHOST6; LOCALHOST_ADDRESS = NetUtil.LOCALHOST6;
} else { } else {
DEFAULT_RESOLVE_ADDRESS_TYPES[0] = InternetProtocolFamily.IPv4; DEFAULT_RESOLVE_ADDRESS_TYPES = ResolvedAddressTypes.IPV4_PREFERRED;
DEFAULT_RESOLVE_ADDRESS_TYPES[1] = InternetProtocolFamily.IPv6;
LOCALHOST_ADDRESS = NetUtil.LOCALHOST4; LOCALHOST_ADDRESS = NetUtil.LOCALHOST4;
} }
} }
@ -116,7 +127,7 @@ public class DnsNameResolver extends InetNameResolver {
// Failed to get the system name search domain list. // Failed to get the system name search domain list.
searchDomains = EmptyArrays.EMPTY_STRINGS; searchDomains = EmptyArrays.EMPTY_STRINGS;
} }
DEFAULT_SEACH_DOMAINS = searchDomains; DEFAULT_SEARCH_DOMAINS = searchDomains;
} }
private static final DatagramDnsResponseDecoder DECODER = new DatagramDnsResponseDecoder(); private static final DatagramDnsResponseDecoder DECODER = new DatagramDnsResponseDecoder();
@ -148,7 +159,8 @@ public class DnsNameResolver extends InetNameResolver {
private final long queryTimeoutMillis; private final long queryTimeoutMillis;
private final int maxQueriesPerResolve; private final int maxQueriesPerResolve;
private final boolean traceEnabled; private final boolean traceEnabled;
private final InternetProtocolFamily[] resolvedAddressTypes; private final ResolvedAddressTypes resolvedAddressTypes;
private final InternetProtocolFamily[] resolvedInternetProtocolFamilies;
private final boolean recursionDesired; private final boolean recursionDesired;
private final int maxPayloadSize; private final int maxPayloadSize;
private final boolean optResourceEnabled; private final boolean optResourceEnabled;
@ -161,49 +173,6 @@ public class DnsNameResolver extends InetNameResolver {
private final DnsRecordType[] resolveRecordTypes; private final DnsRecordType[] resolveRecordTypes;
private final boolean decodeIdn; private final boolean decodeIdn;
/**
* Creates a new DNS-based name resolver that communicates with the specified list of DNS servers.
*
* @param eventLoop the {@link EventLoop} which will perform the communication with the DNS servers
* @param channelFactory the {@link ChannelFactory} that will create a {@link DatagramChannel}
* @param nameServerAddresses the addresses of the DNS server. For each DNS query, a new stream is created from
* this to determine which DNS server should be contacted for the next retry in case
* of failure.
* @param resolveCache the DNS resolved entries cache
* @param queryTimeoutMillis timeout of each DNS query in millis
* @param resolvedAddressTypes list of the protocol families
* @param recursionDesired if recursion desired flag must be set
* @param maxQueriesPerResolve the maximum allowed number of DNS queries for a given name resolution
* @param traceEnabled if trace is enabled
* @param maxPayloadSize the capacity of the datagram packet buffer
* @param optResourceEnabled if automatic inclusion of a optional records is enabled
* @param hostsFileEntriesResolver the {@link HostsFileEntriesResolver} used to check for local aliases
* @param searchDomains the list of search domain
* @param ndots the ndots value
* @deprecated use {@link DnsNameResolver#DnsNameResolver(EventLoop, ChannelFactory, DnsServerAddresses, DnsCache,
* DnsCache, long, InternetProtocolFamily[], boolean, int, boolean, int, boolean,
* HostsFileEntriesResolver, String[], int, boolean)}
*/
@Deprecated
public DnsNameResolver(
EventLoop eventLoop,
ChannelFactory<? extends DatagramChannel> channelFactory,
DnsServerAddresses nameServerAddresses,
final DnsCache resolveCache,
long queryTimeoutMillis,
InternetProtocolFamily[] resolvedAddressTypes,
boolean recursionDesired,
int maxQueriesPerResolve,
boolean traceEnabled,
int maxPayloadSize,
boolean optResourceEnabled,
HostsFileEntriesResolver hostsFileEntriesResolver,
String[] searchDomains,
int ndots) {
this(eventLoop, channelFactory, nameServerAddresses, resolveCache, NoopDnsCache.INSTANCE, queryTimeoutMillis,
resolvedAddressTypes, recursionDesired, maxQueriesPerResolve, traceEnabled, maxPayloadSize,
optResourceEnabled, hostsFileEntriesResolver, searchDomains, ndots, true);
}
/** /**
* Creates a new DNS-based name resolver that communicates with the specified list of DNS servers. * Creates a new DNS-based name resolver that communicates with the specified list of DNS servers.
* *
@ -215,7 +184,7 @@ public class DnsNameResolver extends InetNameResolver {
* @param resolveCache the DNS resolved entries cache * @param resolveCache the DNS resolved entries cache
* @param authoritativeDnsServerCache the cache used to find the authoritative DNS server for a domain * @param authoritativeDnsServerCache the cache used to find the authoritative DNS server for a domain
* @param queryTimeoutMillis timeout of each DNS query in millis * @param queryTimeoutMillis timeout of each DNS query in millis
* @param resolvedAddressTypes list of the protocol families * @param resolvedAddressTypes the preferred address types
* @param recursionDesired if recursion desired flag must be set * @param recursionDesired if recursion desired flag must be set
* @param maxQueriesPerResolve the maximum allowed number of DNS queries for a given name resolution * @param maxQueriesPerResolve the maximum allowed number of DNS queries for a given name resolution
* @param traceEnabled if trace is enabled * @param traceEnabled if trace is enabled
@ -234,7 +203,7 @@ public class DnsNameResolver extends InetNameResolver {
final DnsCache resolveCache, final DnsCache resolveCache,
DnsCache authoritativeDnsServerCache, DnsCache authoritativeDnsServerCache,
long queryTimeoutMillis, long queryTimeoutMillis,
InternetProtocolFamily[] resolvedAddressTypes, ResolvedAddressTypes resolvedAddressTypes,
boolean recursionDesired, boolean recursionDesired,
int maxQueriesPerResolve, int maxQueriesPerResolve,
boolean traceEnabled, boolean traceEnabled,
@ -248,7 +217,7 @@ public class DnsNameResolver extends InetNameResolver {
checkNotNull(channelFactory, "channelFactory"); checkNotNull(channelFactory, "channelFactory");
this.nameServerAddresses = checkNotNull(nameServerAddresses, "nameServerAddresses"); this.nameServerAddresses = checkNotNull(nameServerAddresses, "nameServerAddresses");
this.queryTimeoutMillis = checkPositive(queryTimeoutMillis, "queryTimeoutMillis"); this.queryTimeoutMillis = checkPositive(queryTimeoutMillis, "queryTimeoutMillis");
this.resolvedAddressTypes = checkNonEmpty(resolvedAddressTypes, "resolvedAddressTypes"); this.resolvedAddressTypes = resolvedAddressTypes != null ? resolvedAddressTypes : DEFAULT_RESOLVE_ADDRESS_TYPES;
this.recursionDesired = recursionDesired; this.recursionDesired = recursionDesired;
this.maxQueriesPerResolve = checkPositive(maxQueriesPerResolve, "maxQueriesPerResolve"); this.maxQueriesPerResolve = checkPositive(maxQueriesPerResolve, "maxQueriesPerResolve");
this.traceEnabled = traceEnabled; this.traceEnabled = traceEnabled;
@ -261,31 +230,38 @@ public class DnsNameResolver extends InetNameResolver {
this.ndots = checkPositiveOrZero(ndots, "ndots"); this.ndots = checkPositiveOrZero(ndots, "ndots");
this.decodeIdn = decodeIdn; this.decodeIdn = decodeIdn;
boolean supportsARecords = false; switch (this.resolvedAddressTypes) {
boolean supportsAAAARecords = false; case IPV4_ONLY:
// Use LinkedHashSet to maintain correct ordering. supportsAAAARecords = false;
Set<DnsRecordType> recordTypes = new LinkedHashSet<DnsRecordType>(resolvedAddressTypes.length);
for (InternetProtocolFamily family: resolvedAddressTypes) {
switch (family) {
case IPv4:
supportsARecords = true; supportsARecords = true;
recordTypes.add(DnsRecordType.A); resolveRecordTypes = IPV4_ONLY_RESOLVED_RECORD_TYPES;
resolvedInternetProtocolFamilies = IPV4_ONLY_RESOLVED_PROTOCOL_FAMILIES;
preferredAddressType = InternetProtocolFamily.IPv4;
break; break;
case IPv6: case IPV4_PREFERRED:
supportsAAAARecords = true; supportsAAAARecords = true;
recordTypes.add(DnsRecordType.AAAA); supportsARecords = true;
resolveRecordTypes = IPV4_PREFERRED_RESOLVED_RECORD_TYPES;
resolvedInternetProtocolFamilies = IPV4_PREFERRED_RESOLVED_PROTOCOL_FAMILIES;
preferredAddressType = InternetProtocolFamily.IPv4;
break;
case IPV6_ONLY:
supportsAAAARecords = true;
supportsARecords = false;
resolveRecordTypes = IPV6_ONLY_RESOLVED_RECORD_TYPES;
resolvedInternetProtocolFamilies = IPV6_ONLY_RESOLVED_PROTOCOL_FAMILIES;
preferredAddressType = InternetProtocolFamily.IPv6;
break;
case IPV6_PREFERRED:
supportsAAAARecords = true;
supportsARecords = true;
resolveRecordTypes = IPV6_PREFERRED_RESOLVED_RECORD_TYPES;
resolvedInternetProtocolFamilies = IPV6_PREFERRED_RESOLVED_PROTOCOL_FAMILIES;
preferredAddressType = InternetProtocolFamily.IPv6;
break; break;
default: default:
throw new Error(); throw new IllegalArgumentException("Unknown ResolvedAddressTypes " + resolvedAddressTypes);
} }
}
// One of both must be always true.
assert supportsARecords || supportsAAAARecords;
this.supportsAAAARecords = supportsAAAARecords;
this.supportsARecords = supportsARecords;
resolveRecordTypes = recordTypes.toArray(new DnsRecordType[recordTypes.size()]);
preferredAddressType = resolvedAddressTypes[0];
Bootstrap b = new Bootstrap(); Bootstrap b = new Bootstrap();
b.group(executor()); b.group(executor());
@ -339,16 +315,15 @@ public class DnsNameResolver extends InetNameResolver {
} }
/** /**
* Returns the list of the protocol families of the address resolved by {@link #resolve(String)} * Returns the {@link ResolvedAddressTypes} 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"}. * The default value depends on the value of the system property {@code "java.net.preferIPv6Addresses"}.
*/ */
public List<InternetProtocolFamily> resolvedAddressTypes() { public ResolvedAddressTypes resolvedAddressTypes() {
return Arrays.asList(resolvedAddressTypes); return resolvedAddressTypes;
} }
InternetProtocolFamily[] resolveAddressTypesUnsafe() { InternetProtocolFamily[] resolvedInternetProtocolFamiliesUnsafe() {
return resolvedAddressTypes; return resolvedInternetProtocolFamilies;
} }
final String[] searchDomains() { final String[] searchDomains() {
@ -447,7 +422,7 @@ public class DnsNameResolver extends InetNameResolver {
if (hostsFileEntriesResolver == null) { if (hostsFileEntriesResolver == null) {
return null; return null;
} else { } else {
InetAddress address = hostsFileEntriesResolver.address(hostname); InetAddress address = hostsFileEntriesResolver.address(hostname, resolvedAddressTypes);
if (address == null && PlatformDependent.isWindows() && LOCALHOST.equalsIgnoreCase(hostname)) { if (address == null && PlatformDependent.isWindows() && LOCALHOST.equalsIgnoreCase(hostname)) {
// If we tried to resolve localhost we need workaround that windows removed localhost from its // If we tried to resolve localhost we need workaround that windows removed localhost from its
// hostfile in later versions. // hostfile in later versions.
@ -526,7 +501,7 @@ public class DnsNameResolver extends InetNameResolver {
@Override @Override
protected void doResolve(String inetHost, Promise<InetAddress> promise) throws Exception { protected void doResolve(String inetHost, Promise<InetAddress> promise) throws Exception {
doResolve(inetHost, EMTPY_ADDITIONALS, promise, resolveCache); doResolve(inetHost, EMPTY_ADDITIONALS, promise, resolveCache);
} }
private static DnsRecord[] toArray(Iterable<DnsRecord> additionals, boolean validateType) { private static DnsRecord[] toArray(Iterable<DnsRecord> additionals, boolean validateType) {
@ -541,7 +516,7 @@ public class DnsNameResolver extends InetNameResolver {
Iterator<DnsRecord> additionalsIt = additionals.iterator(); Iterator<DnsRecord> additionalsIt = additionals.iterator();
if (!additionalsIt.hasNext()) { if (!additionalsIt.hasNext()) {
return EMTPY_ADDITIONALS; return EMPTY_ADDITIONALS;
} }
List<DnsRecord> records = new ArrayList<DnsRecord>(); List<DnsRecord> records = new ArrayList<DnsRecord>();
do { do {
@ -617,7 +592,7 @@ public class DnsNameResolver extends InetNameResolver {
cause = cachedEntries.get(0).cause(); cause = cachedEntries.get(0).cause();
} else { } else {
// Find the first entry with the preferred address type. // Find the first entry with the preferred address type.
for (InternetProtocolFamily f : resolvedAddressTypes) { for (InternetProtocolFamily f : resolvedInternetProtocolFamilies) {
for (int i = 0; i < numEntries; i++) { for (int i = 0; i < numEntries; i++) {
final DnsCacheEntry e = cachedEntries.get(i); final DnsCacheEntry e = cachedEntries.get(i);
if (f.addressType().isInstance(e.address())) { if (f.addressType().isInstance(e.address())) {
@ -692,7 +667,7 @@ public class DnsNameResolver extends InetNameResolver {
@Override @Override
protected void doResolveAll(String inetHost, Promise<List<InetAddress>> promise) throws Exception { protected void doResolveAll(String inetHost, Promise<List<InetAddress>> promise) throws Exception {
doResolveAll(inetHost, EMTPY_ADDITIONALS, promise, resolveCache); doResolveAll(inetHost, EMPTY_ADDITIONALS, promise, resolveCache);
} }
/** /**
@ -746,7 +721,7 @@ public class DnsNameResolver extends InetNameResolver {
if (cachedEntries.get(0).cause() != null) { if (cachedEntries.get(0).cause() != null) {
cause = cachedEntries.get(0).cause(); cause = cachedEntries.get(0).cause();
} else { } else {
for (InternetProtocolFamily f : resolvedAddressTypes) { for (InternetProtocolFamily f : resolvedInternetProtocolFamilies) {
for (int i = 0; i < numEntries; i++) { for (int i = 0; i < numEntries; i++) {
final DnsCacheEntry e = cachedEntries.get(i); final DnsCacheEntry e = cachedEntries.get(i);
if (f.addressType().isInstance(e.address())) { if (f.addressType().isInstance(e.address())) {
@ -859,7 +834,7 @@ public class DnsNameResolver extends InetNameResolver {
public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query( public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(
InetSocketAddress nameServerAddr, DnsQuestion question) { InetSocketAddress nameServerAddr, DnsQuestion question) {
return query0(nameServerAddr, question, EMTPY_ADDITIONALS, return query0(nameServerAddr, question, EMPTY_ADDITIONALS,
ch.eventLoop().<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>>newPromise()); ch.eventLoop().<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>>newPromise());
} }
@ -880,7 +855,7 @@ public class DnsNameResolver extends InetNameResolver {
InetSocketAddress nameServerAddr, DnsQuestion question, InetSocketAddress nameServerAddr, DnsQuestion question,
Promise<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>> promise) { Promise<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>> promise) {
return query0(nameServerAddr, question, EMTPY_ADDITIONALS, promise); return query0(nameServerAddr, question, EMPTY_ADDITIONALS, promise);
} }
/** /**

View File

@ -23,6 +23,7 @@ import io.netty.channel.ReflectiveChannelFactory;
import io.netty.channel.socket.DatagramChannel; import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.InternetProtocolFamily; import io.netty.channel.socket.InternetProtocolFamily;
import io.netty.resolver.HostsFileEntriesResolver; import io.netty.resolver.HostsFileEntriesResolver;
import io.netty.resolver.ResolvedAddressTypes;
import io.netty.util.internal.UnstableApi; import io.netty.util.internal.UnstableApi;
import java.util.ArrayList; import java.util.ArrayList;
@ -45,14 +46,14 @@ public final class DnsNameResolverBuilder {
private Integer maxTtl; private Integer maxTtl;
private Integer negativeTtl; private Integer negativeTtl;
private long queryTimeoutMillis = 5000; private long queryTimeoutMillis = 5000;
private InternetProtocolFamily[] resolvedAddressTypes = DnsNameResolver.DEFAULT_RESOLVE_ADDRESS_TYPES; private ResolvedAddressTypes resolvedAddressTypes = DnsNameResolver.DEFAULT_RESOLVE_ADDRESS_TYPES;
private boolean recursionDesired = true; private boolean recursionDesired = true;
private int maxQueriesPerResolve = 16; private int maxQueriesPerResolve = 16;
private boolean traceEnabled; private boolean traceEnabled;
private int maxPayloadSize = 4096; private int maxPayloadSize = 4096;
private boolean optResourceEnabled = true; private boolean optResourceEnabled = true;
private HostsFileEntriesResolver hostsFileEntriesResolver = HostsFileEntriesResolver.DEFAULT; private HostsFileEntriesResolver hostsFileEntriesResolver = HostsFileEntriesResolver.DEFAULT;
private String[] searchDomains = DnsNameResolver.DEFAULT_SEACH_DOMAINS; private String[] searchDomains = DnsNameResolver.DEFAULT_SEARCH_DOMAINS;
private int ndots = 1; private int ndots = 1;
private boolean decodeIdn = true; private boolean decodeIdn = true;
@ -162,76 +163,45 @@ public final class DnsNameResolverBuilder {
} }
/** /**
* Sets the list of the protocol families of the address resolved. * Compute a {@link ResolvedAddressTypes} from some {@link InternetProtocolFamily}s.
* Usually, both {@link InternetProtocolFamily#IPv4} and {@link InternetProtocolFamily#IPv6} are specified in * An empty input will return the default value, based on "java.net" System properties.
* the order of preference. To enforce the resolve to retrieve the address of a specific protocol family, * Valid inputs are (), (IPv4), (IPv6), (Ipv4, IPv6) and (IPv6, IPv4).
* specify only a single {@link InternetProtocolFamily}. * @param internetProtocolFamilies a valid sequence of {@link InternetProtocolFamily}s
* * @return a {@link ResolvedAddressTypes}
* @param resolvedAddressTypes the address types
* @return {@code this}
*/ */
public DnsNameResolverBuilder resolvedAddressTypes(InternetProtocolFamily... resolvedAddressTypes) { public static ResolvedAddressTypes computeResolvedAddressTypes(InternetProtocolFamily... internetProtocolFamilies) {
checkNotNull(resolvedAddressTypes, "resolvedAddressTypes"); if (internetProtocolFamilies == null || internetProtocolFamilies.length == 0) {
return DnsNameResolver.DEFAULT_RESOLVE_ADDRESS_TYPES;
final List<InternetProtocolFamily> list = new ArrayList<InternetProtocolFamily>( }
InternetProtocolFamily.values().length); if (internetProtocolFamilies.length > 2) {
throw new IllegalArgumentException("No more than 2 InternetProtocolFamilies");
for (InternetProtocolFamily f : resolvedAddressTypes) {
if (f == null) {
break;
} }
// Avoid duplicate entries. switch(internetProtocolFamilies[0]) {
if (list.contains(f)) { case IPv4:
continue; return (internetProtocolFamilies.length >= 2
&& internetProtocolFamilies[1] == InternetProtocolFamily.IPv6) ?
ResolvedAddressTypes.IPV4_PREFERRED: ResolvedAddressTypes.IPV4_ONLY;
case IPv6:
return (internetProtocolFamilies.length >= 2
&& internetProtocolFamilies[1] == InternetProtocolFamily.IPv4) ?
ResolvedAddressTypes.IPV6_PREFERRED: ResolvedAddressTypes.IPV6_ONLY;
default:
throw new IllegalArgumentException(
"Couldn't resolve ResolvedAddressTypes from InternetProtocolFamily array");
} }
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. * Sets the list of the protocol families of the address resolved.
* Usually, both {@link InternetProtocolFamily#IPv4} and {@link InternetProtocolFamily#IPv6} are specified in * You can use {@link DnsNameResolverBuilder#computeResolvedAddressTypes(InternetProtocolFamily...)}
* the order of preference. To enforce the resolve to retrieve the address of a specific protocol family, * to get a {@link ResolvedAddressTypes} out of some {@link InternetProtocolFamily}s.
* specify only a single {@link InternetProtocolFamily}.
* *
* @param resolvedAddressTypes the address types * @param resolvedAddressTypes the address types
* @return {@code this} * @return {@code this}
*/ */
public DnsNameResolverBuilder resolvedAddressTypes(Iterable<InternetProtocolFamily> resolvedAddressTypes) { public DnsNameResolverBuilder resolvedAddressTypes(ResolvedAddressTypes resolvedAddressTypes) {
checkNotNull(resolvedAddressTypes, "resolveAddressTypes"); this.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; return this;
} }

View File

@ -74,7 +74,7 @@ abstract class DnsNameResolverContext<T> {
private final DnsCache resolveCache; private final DnsCache resolveCache;
private final boolean traceEnabled; private final boolean traceEnabled;
private final int maxAllowedQueries; private final int maxAllowedQueries;
private final InternetProtocolFamily[] resolveAddressTypes; private final InternetProtocolFamily[] resolvedInternetProtocolFamilies;
private final DnsRecord[] additionals; private final DnsRecord[] additionals;
private final Set<Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> queriesInProgress = private final Set<Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> queriesInProgress =
@ -97,7 +97,7 @@ abstract class DnsNameResolverContext<T> {
nameServerAddrs = parent.nameServerAddresses.stream(); nameServerAddrs = parent.nameServerAddresses.stream();
maxAllowedQueries = parent.maxQueriesPerResolve(); maxAllowedQueries = parent.maxQueriesPerResolve();
resolveAddressTypes = parent.resolveAddressTypesUnsafe(); resolvedInternetProtocolFamilies = parent.resolvedInternetProtocolFamiliesUnsafe();
traceEnabled = parent.isTraceEnabled(); traceEnabled = parent.isTraceEnabled();
allowedQueries = maxAllowedQueries; allowedQueries = maxAllowedQueries;
} }
@ -611,7 +611,7 @@ abstract class DnsNameResolverContext<T> {
if (resolvedEntries != null) { if (resolvedEntries != null) {
// Found at least one resolved address. // Found at least one resolved address.
for (InternetProtocolFamily f: resolveAddressTypes) { for (InternetProtocolFamily f: resolvedInternetProtocolFamilies) {
if (finishResolve(f.addressType(), resolvedEntries, promise)) { if (finishResolve(f.addressType(), resolvedEntries, promise)) {
return; return;
} }

View File

@ -31,6 +31,7 @@ import io.netty.handler.codec.dns.DnsRecordType;
import io.netty.handler.codec.dns.DnsResponse; import io.netty.handler.codec.dns.DnsResponse;
import io.netty.handler.codec.dns.DnsResponseCode; import io.netty.handler.codec.dns.DnsResponseCode;
import io.netty.handler.codec.dns.DnsSection; import io.netty.handler.codec.dns.DnsSection;
import io.netty.resolver.ResolvedAddressTypes;
import io.netty.util.NetUtil; import io.netty.util.NetUtil;
import io.netty.resolver.HostsFileEntriesResolver; import io.netty.resolver.HostsFileEntriesResolver;
import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Future;
@ -65,7 +66,6 @@ import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.hasToString;
import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@ -283,12 +283,12 @@ public class DnsNameResolverTest {
return newResolver(true); return newResolver(true);
} }
private static DnsNameResolverBuilder newResolver(InternetProtocolFamily... resolvedAddressTypes) { private static DnsNameResolverBuilder newResolver(ResolvedAddressTypes resolvedAddressTypes) {
return newResolver() return newResolver()
.resolvedAddressTypes(resolvedAddressTypes); .resolvedAddressTypes(resolvedAddressTypes);
} }
private static DnsNameResolverBuilder newNonCachedResolver(InternetProtocolFamily... resolvedAddressTypes) { private static DnsNameResolverBuilder newNonCachedResolver(ResolvedAddressTypes resolvedAddressTypes) {
return newResolver() return newResolver()
.resolveCache(NoopDnsCache.INSTANCE) .resolveCache(NoopDnsCache.INSTANCE)
.resolvedAddressTypes(resolvedAddressTypes); .resolvedAddressTypes(resolvedAddressTypes);
@ -306,7 +306,7 @@ public class DnsNameResolverTest {
@Test @Test
public void testResolveAorAAAA() throws Exception { public void testResolveAorAAAA() throws Exception {
DnsNameResolver resolver = newResolver(InternetProtocolFamily.IPv4, InternetProtocolFamily.IPv6).build(); DnsNameResolver resolver = newResolver(ResolvedAddressTypes.IPV4_PREFERRED).build();
try { try {
testResolve0(resolver, EXCLUSIONS_RESOLVE_A); testResolve0(resolver, EXCLUSIONS_RESOLVE_A);
} finally { } finally {
@ -316,7 +316,7 @@ public class DnsNameResolverTest {
@Test @Test
public void testResolveAAAAorA() throws Exception { public void testResolveAAAAorA() throws Exception {
DnsNameResolver resolver = newResolver(InternetProtocolFamily.IPv6, InternetProtocolFamily.IPv4).build(); DnsNameResolver resolver = newResolver(ResolvedAddressTypes.IPV6_PREFERRED).build();
try { try {
testResolve0(resolver, EXCLUSIONS_RESOLVE_A); testResolve0(resolver, EXCLUSIONS_RESOLVE_A);
} finally { } finally {
@ -326,7 +326,7 @@ public class DnsNameResolverTest {
@Test @Test
public void testResolveA() throws Exception { public void testResolveA() throws Exception {
DnsNameResolver resolver = newResolver(InternetProtocolFamily.IPv4) DnsNameResolver resolver = newResolver(ResolvedAddressTypes.IPV4_ONLY)
// Cache for eternity // Cache for eternity
.ttl(Integer.MAX_VALUE, Integer.MAX_VALUE) .ttl(Integer.MAX_VALUE, Integer.MAX_VALUE)
.build(); .build();
@ -357,7 +357,7 @@ public class DnsNameResolverTest {
@Test @Test
public void testResolveAAAA() throws Exception { public void testResolveAAAA() throws Exception {
DnsNameResolver resolver = newResolver(InternetProtocolFamily.IPv6).build(); DnsNameResolver resolver = newResolver(ResolvedAddressTypes.IPV6_ONLY).build();
try { try {
testResolve0(resolver, EXCLUSIONS_RESOLVE_AAAA); testResolve0(resolver, EXCLUSIONS_RESOLVE_AAAA);
} finally { } finally {
@ -367,7 +367,7 @@ public class DnsNameResolverTest {
@Test @Test
public void testNonCachedResolve() throws Exception { public void testNonCachedResolve() throws Exception {
DnsNameResolver resolver = newNonCachedResolver(InternetProtocolFamily.IPv4).build(); DnsNameResolver resolver = newNonCachedResolver(ResolvedAddressTypes.IPV4_ONLY).build();
try { try {
testResolve0(resolver, EXCLUSIONS_RESOLVE_A); testResolve0(resolver, EXCLUSIONS_RESOLVE_A);
} finally { } finally {
@ -442,7 +442,7 @@ public class DnsNameResolverTest {
assertThat(resolved.getHostName(), is(unresolved)); assertThat(resolved.getHostName(), is(unresolved));
boolean typeMatches = false; boolean typeMatches = false;
for (InternetProtocolFamily f: resolver.resolvedAddressTypes()) { for (InternetProtocolFamily f: resolver.resolvedInternetProtocolFamiliesUnsafe()) {
Class<?> resolvedType = resolved.getClass(); Class<?> resolvedType = resolved.getClass();
if (f.addressType().isAssignableFrom(resolvedType)) { if (f.addressType().isAssignableFrom(resolvedType)) {
typeMatches = true; typeMatches = true;
@ -574,26 +574,26 @@ public class DnsNameResolverTest {
@Test @Test
public void testResolveEmptyIpv4() { public void testResolveEmptyIpv4() {
testResolve0(InternetProtocolFamily.IPv4, NetUtil.LOCALHOST4, StringUtil.EMPTY_STRING); testResolve0(ResolvedAddressTypes.IPV4_ONLY, NetUtil.LOCALHOST4, StringUtil.EMPTY_STRING);
} }
@Test @Test
public void testResolveEmptyIpv6() { public void testResolveEmptyIpv6() {
testResolve0(InternetProtocolFamily.IPv6, NetUtil.LOCALHOST6, StringUtil.EMPTY_STRING); testResolve0(ResolvedAddressTypes.IPV6_ONLY, NetUtil.LOCALHOST6, StringUtil.EMPTY_STRING);
} }
@Test @Test
public void testResolveNullIpv4() { public void testResolveNullIpv4() {
testResolve0(InternetProtocolFamily.IPv4, NetUtil.LOCALHOST4, null); testResolve0(ResolvedAddressTypes.IPV4_ONLY, NetUtil.LOCALHOST4, null);
} }
@Test @Test
public void testResolveNullIpv6() { public void testResolveNullIpv6() {
testResolve0(InternetProtocolFamily.IPv6, NetUtil.LOCALHOST6, null); testResolve0(ResolvedAddressTypes.IPV6_ONLY, NetUtil.LOCALHOST6, null);
} }
private static void testResolve0(InternetProtocolFamily family, InetAddress expectedAddr, String name) { private static void testResolve0(ResolvedAddressTypes addressTypes, InetAddress expectedAddr, String name) {
DnsNameResolver resolver = newResolver(family).build(); DnsNameResolver resolver = newResolver(addressTypes).build();
try { try {
InetAddress address = resolver.resolve(name).syncUninterruptibly().getNow(); InetAddress address = resolver.resolve(name).syncUninterruptibly().getNow();
assertEquals(expectedAddr, address); assertEquals(expectedAddr, address);
@ -604,26 +604,26 @@ public class DnsNameResolverTest {
@Test @Test
public void testResolveAllEmptyIpv4() { public void testResolveAllEmptyIpv4() {
testResolveAll0(InternetProtocolFamily.IPv4, NetUtil.LOCALHOST4, StringUtil.EMPTY_STRING); testResolveAll0(ResolvedAddressTypes.IPV4_ONLY, NetUtil.LOCALHOST4, StringUtil.EMPTY_STRING);
} }
@Test @Test
public void testResolveAllEmptyIpv6() { public void testResolveAllEmptyIpv6() {
testResolveAll0(InternetProtocolFamily.IPv6, NetUtil.LOCALHOST6, StringUtil.EMPTY_STRING); testResolveAll0(ResolvedAddressTypes.IPV6_ONLY, NetUtil.LOCALHOST6, StringUtil.EMPTY_STRING);
} }
@Test @Test
public void testResolveAllNullIpv4() { public void testResolveAllNullIpv4() {
testResolveAll0(InternetProtocolFamily.IPv4, NetUtil.LOCALHOST4, null); testResolveAll0(ResolvedAddressTypes.IPV4_ONLY, NetUtil.LOCALHOST4, null);
} }
@Test @Test
public void testResolveAllNullIpv6() { public void testResolveAllNullIpv6() {
testResolveAll0(InternetProtocolFamily.IPv6, NetUtil.LOCALHOST6, null); testResolveAll0(ResolvedAddressTypes.IPV6_ONLY, NetUtil.LOCALHOST6, null);
} }
private static void testResolveAll0(InternetProtocolFamily family, InetAddress expectedAddr, String name) { private static void testResolveAll0(ResolvedAddressTypes addressTypes, InetAddress expectedAddr, String name) {
DnsNameResolver resolver = newResolver(family).build(); DnsNameResolver resolver = newResolver(addressTypes).build();
try { try {
List<InetAddress> addresses = resolver.resolveAll(name).syncUninterruptibly().getNow(); List<InetAddress> addresses = resolver.resolveAll(name).syncUninterruptibly().getNow();
assertEquals(1, addresses.size()); assertEquals(1, addresses.size());
@ -682,9 +682,9 @@ public class DnsNameResolverTest {
DnsNameResolver resolver = new DnsNameResolver( DnsNameResolver resolver = new DnsNameResolver(
group.next(), new ReflectiveChannelFactory<DatagramChannel>(NioDatagramChannel.class), group.next(), new ReflectiveChannelFactory<DatagramChannel>(NioDatagramChannel.class),
DnsServerAddresses.singleton(dnsServer.localAddress()), NoopDnsCache.INSTANCE, nsCache, DnsServerAddresses.singleton(dnsServer.localAddress()), NoopDnsCache.INSTANCE, nsCache,
3000, new InternetProtocolFamily[] { InternetProtocolFamily.IPv4 }, true, 10, 3000, ResolvedAddressTypes.IPV4_ONLY, true, 10,
true, 4096, false, HostsFileEntriesResolver.DEFAULT, true, 4096, false, HostsFileEntriesResolver.DEFAULT,
DnsNameResolver.DEFAULT_SEACH_DOMAINS, 0, true) { DnsNameResolver.DEFAULT_SEARCH_DOMAINS, 0, true) {
@Override @Override
int dnsRedirectPort(InetAddress server) { int dnsRedirectPort(InetAddress server) {
return server.equals(dnsServerAuthority.localAddress().getAddress()) ? return server.equals(dnsServerAuthority.localAddress().getAddress()) ?

View File

@ -15,6 +15,10 @@
*/ */
package io.netty.resolver; package io.netty.resolver;
import io.netty.util.internal.UnstableApi;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
@ -22,13 +26,39 @@ import java.util.Map;
/** /**
* Default {@link HostsFileEntriesResolver} that resolves hosts file entries only once. * Default {@link HostsFileEntriesResolver} that resolves hosts file entries only once.
*/ */
@UnstableApi
public final class DefaultHostsFileEntriesResolver implements HostsFileEntriesResolver { public final class DefaultHostsFileEntriesResolver implements HostsFileEntriesResolver {
private final Map<String, InetAddress> entries = HostsFileParser.parseSilently(); private final Map<String, Inet4Address> inet4Entries;
private final Map<String, Inet6Address> inet6Entries;
public DefaultHostsFileEntriesResolver() {
this(HostsFileParser.parseSilently());
}
// for testing purpose only
DefaultHostsFileEntriesResolver(HostsFileEntries entries) {
inet4Entries = entries.inet4Entries();
inet6Entries = entries.inet6Entries();
}
@Override @Override
public InetAddress address(String inetHost) { public InetAddress address(String inetHost, ResolvedAddressTypes resolvedAddressTypes) {
return entries.get(normalize(inetHost)); String normalized = normalize(inetHost);
switch (resolvedAddressTypes) {
case IPV4_ONLY:
return inet4Entries.get(normalized);
case IPV6_ONLY:
return inet6Entries.get(normalized);
case IPV4_PREFERRED:
Inet4Address inet4Address = inet4Entries.get(normalized);
return inet4Address != null? inet4Address : inet6Entries.get(normalized);
case IPV6_PREFERRED:
Inet6Address inet6Address = inet6Entries.get(normalized);
return inet6Address != null? inet6Address : inet4Entries.get(normalized);
default:
throw new IllegalArgumentException("Unknown ResolvedAddressTypes " + resolvedAddressTypes);
}
} }
// package-private for testing purposes // package-private for testing purposes

View File

@ -0,0 +1,63 @@
/*
* Copyright 2017 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.internal.UnstableApi;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* A container of hosts file entries
*/
@UnstableApi
public final class HostsFileEntries {
/**
* Empty entries
*/
static final HostsFileEntries EMPTY =
new HostsFileEntries(
Collections.<String, Inet4Address>emptyMap(),
Collections.<String, Inet6Address>emptyMap());
private final Map<String, Inet4Address> inet4Entries;
private final Map<String, Inet6Address> inet6Entries;
public HostsFileEntries(Map<String, Inet4Address> inet4Entries, Map<String, Inet6Address> inet6Entries) {
this.inet4Entries = Collections.unmodifiableMap(new HashMap<String, Inet4Address>(inet4Entries));
this.inet6Entries = Collections.unmodifiableMap(new HashMap<String, Inet6Address>(inet6Entries));
}
/**
* The IPv4 entries
* @return the IPv4 entries
*/
public Map<String, Inet4Address> inet4Entries() {
return inet4Entries;
}
/**
* The IPv6 entries
* @return the IPv6 entries
*/
public Map<String, Inet6Address> inet6Entries() {
return inet6Entries;
}
}

View File

@ -15,11 +15,14 @@
*/ */
package io.netty.resolver; package io.netty.resolver;
import io.netty.util.internal.UnstableApi;
import java.net.InetAddress; import java.net.InetAddress;
/** /**
* Resolves a hostname against the hosts file entries. * Resolves a hostname against the hosts file entries.
*/ */
@UnstableApi
public interface HostsFileEntriesResolver { public interface HostsFileEntriesResolver {
/** /**
@ -27,5 +30,11 @@ public interface HostsFileEntriesResolver {
*/ */
HostsFileEntriesResolver DEFAULT = new DefaultHostsFileEntriesResolver(); HostsFileEntriesResolver DEFAULT = new DefaultHostsFileEntriesResolver();
InetAddress address(String inetHost); /**
* Resolve the address of a hostname against the entries in a hosts file, depending on some address types.
* @param inetHost the hostname to resolve
* @param resolvedAddressTypes the address types to resolve
* @return the first matching address
*/
InetAddress address(String inetHost, ResolvedAddressTypes resolvedAddressTypes);
} }

View File

@ -17,6 +17,7 @@ package io.netty.resolver;
import io.netty.util.NetUtil; import io.netty.util.NetUtil;
import io.netty.util.internal.PlatformDependent; import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.UnstableApi;
import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory; import io.netty.util.internal.logging.InternalLoggerFactory;
@ -25,9 +26,10 @@ import java.io.File;
import java.io.FileReader; import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.io.Reader; import java.io.Reader;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.HashMap; import java.util.HashMap;
@ -39,6 +41,7 @@ import static io.netty.util.internal.ObjectUtil.*;
/** /**
* A parser for hosts files. * A parser for hosts files.
*/ */
@UnstableApi
public final class HostsFileParser { public final class HostsFileParser {
private static final String WINDOWS_DEFAULT_SYSTEM_ROOT = "C:\\Windows"; private static final String WINDOWS_DEFAULT_SYSTEM_ROOT = "C:\\Windows";
@ -65,25 +68,25 @@ public final class HostsFileParser {
/** /**
* Parse hosts file at standard OS location. * Parse hosts file at standard OS location.
* *
* @return a map of hostname or alias to {@link InetAddress} * @return a {@link HostsFileEntries}
*/ */
public static Map<String, InetAddress> parseSilently() { public static HostsFileEntries parseSilently() {
File hostsFile = locateHostsFile(); File hostsFile = locateHostsFile();
try { try {
return parse(hostsFile); return parse(hostsFile);
} catch (IOException e) { } catch (IOException e) {
logger.warn("Failed to load and parse hosts file at " + hostsFile.getPath(), e); logger.warn("Failed to load and parse hosts file at " + hostsFile.getPath(), e);
return Collections.emptyMap(); return HostsFileEntries.EMPTY;
} }
} }
/** /**
* Parse hosts file at standard OS location. * Parse hosts file at standard OS location.
* *
* @return a map of hostname or alias to {@link InetAddress} * @return a {@link HostsFileEntries}
* @throws IOException file could not be read * @throws IOException file could not be read
*/ */
public static Map<String, InetAddress> parse() throws IOException { public static HostsFileEntries parse() throws IOException {
return parse(locateHostsFile()); return parse(locateHostsFile());
} }
@ -91,15 +94,15 @@ public final class HostsFileParser {
* Parse a hosts file. * Parse a hosts file.
* *
* @param file the file to be parsed * @param file the file to be parsed
* @return a map of hostname or alias to {@link InetAddress} * @return a {@link HostsFileEntries}
* @throws IOException file could not be read * @throws IOException file could not be read
*/ */
public static Map<String, InetAddress> parse(File file) throws IOException { public static HostsFileEntries parse(File file) throws IOException {
checkNotNull(file, "file"); checkNotNull(file, "file");
if (file.exists() && file.isFile()) { if (file.exists() && file.isFile()) {
return parse(new BufferedReader(new FileReader(file))); return parse(new BufferedReader(new FileReader(file)));
} else { } else {
return Collections.emptyMap(); return HostsFileEntries.EMPTY;
} }
} }
@ -107,14 +110,15 @@ public final class HostsFileParser {
* Parse a reader of hosts file format. * Parse a reader of hosts file format.
* *
* @param reader the file to be parsed * @param reader the file to be parsed
* @return a map of hostname or alias to {@link InetAddress} * @return a {@link HostsFileEntries}
* @throws IOException file could not be read * @throws IOException file could not be read
*/ */
public static Map<String, InetAddress> parse(Reader reader) throws IOException { public static HostsFileEntries parse(Reader reader) throws IOException {
checkNotNull(reader, "reader"); checkNotNull(reader, "reader");
BufferedReader buff = new BufferedReader(reader); BufferedReader buff = new BufferedReader(reader);
try { try {
Map<String, InetAddress> entries = new HashMap<String, InetAddress>(); Map<String, Inet4Address> ipv4Entries = new HashMap<String, Inet4Address>();
Map<String, Inet6Address> ipv6Entries = new HashMap<String, Inet6Address>();
String line; String line;
while ((line = buff.readLine()) != null) { while ((line = buff.readLine()) != null) {
// remove comment // remove comment
@ -153,14 +157,25 @@ public final class HostsFileParser {
for (int i = 1; i < lineParts.size(); i ++) { for (int i = 1; i < lineParts.size(); i ++) {
String hostname = lineParts.get(i); String hostname = lineParts.get(i);
String hostnameLower = hostname.toLowerCase(Locale.ENGLISH); String hostnameLower = hostname.toLowerCase(Locale.ENGLISH);
if (!entries.containsKey(hostnameLower)) { InetAddress address = InetAddress.getByAddress(hostname, ipBytes);
// trying to map a host to multiple IPs is wrong if (address instanceof Inet4Address) {
// only the first entry is honored Inet4Address previous = ipv4Entries.put(hostnameLower, (Inet4Address) address);
entries.put(hostnameLower, InetAddress.getByAddress(hostname, ipBytes)); if (previous != null) {
// restore, we want to keep the first entry
ipv4Entries.put(hostnameLower, previous);
}
} else {
Inet6Address previous = ipv6Entries.put(hostnameLower, (Inet6Address) address);
if (previous != null) {
// restore, we want to keep the first entry
ipv6Entries.put(hostnameLower, previous);
} }
} }
} }
return entries; }
return ipv4Entries.isEmpty() && ipv6Entries.isEmpty() ?
HostsFileEntries.EMPTY :
new HostsFileEntries(ipv4Entries, ipv6Entries);
} finally { } finally {
try { try {
buff.close(); buff.close();

View File

@ -0,0 +1,41 @@
/*
* Copyright 2017 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.internal.UnstableApi;
/**
* Defined resolved address types.
*/
@UnstableApi
public enum ResolvedAddressTypes {
/**
* Only resolve IPv4 addresses
*/
IPV4_ONLY,
/**
* Only resolve IPv6 addresses
*/
IPV6_ONLY,
/**
* Prefer IPv4 addresses over IPv6 ones
*/
IPV4_PREFERRED,
/**
* Prefer IPv6 addresses over IPv4 ones
*/
IPV6_PREFERRED
}

View File

@ -13,21 +13,72 @@
* License for the specific language governing permissions and limitations * License for the specific language governing permissions and limitations
* under the License. * under the License.
*/ */
/**
* show issue https://github.com/netty/netty/issues/5182
* HostsFileParser tries to resolve hostnames as case-sensitive
*/
package io.netty.resolver; package io.netty.resolver;
import io.netty.util.NetUtil;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Map;
public class DefaultHostsFileEntriesResolverTest { public class DefaultHostsFileEntriesResolverTest {
/**
* show issue https://github.com/netty/netty/issues/5182
* HostsFileParser tries to resolve hostnames as case-sensitive
*/
@Test @Test
public void testCaseInsensitivity() throws Exception { public void testCaseInsensitivity() throws Exception {
DefaultHostsFileEntriesResolver resolver = new DefaultHostsFileEntriesResolver(); DefaultHostsFileEntriesResolver resolver = new DefaultHostsFileEntriesResolver();
//normalized somehow //normalized somehow
Assert.assertEquals(resolver.normalize("localhost"), resolver.normalize("LOCALHOST")); Assert.assertEquals(resolver.normalize("localhost"), resolver.normalize("LOCALHOST"));
} }
@Test
public void shouldntFindWhenAddressTypeDoesntMatch() {
Map<String, Inet4Address> inet4Entries = new HashMap<String, Inet4Address>();
Map<String, Inet6Address> inet6Entries = new HashMap<String, Inet6Address>();
inet4Entries.put("localhost", NetUtil.LOCALHOST4);
DefaultHostsFileEntriesResolver resolver =
new DefaultHostsFileEntriesResolver(new HostsFileEntries(inet4Entries, inet6Entries));
InetAddress address = resolver.address("localhost", ResolvedAddressTypes.IPV6_ONLY);
Assert.assertNull("Should pick an IPv6 address", address);
}
@Test
public void shouldPickIpv4WhenBothAreDefinedButIpv4IsPreferred() {
Map<String, Inet4Address> inet4Entries = new HashMap<String, Inet4Address>();
Map<String, Inet6Address> inet6Entries = new HashMap<String, Inet6Address>();
inet4Entries.put("localhost", NetUtil.LOCALHOST4);
inet6Entries.put("localhost", NetUtil.LOCALHOST6);
DefaultHostsFileEntriesResolver resolver =
new DefaultHostsFileEntriesResolver(new HostsFileEntries(inet4Entries, inet6Entries));
InetAddress address = resolver.address("localhost", ResolvedAddressTypes.IPV4_PREFERRED);
Assert.assertTrue("Should pick an IPv4 address", address instanceof Inet4Address);
}
@Test
public void shouldPickIpv6WhenBothAreDefinedButIpv6IsPreferred() {
Map<String, Inet4Address> inet4Entries = new HashMap<String, Inet4Address>();
Map<String, Inet6Address> inet6Entries = new HashMap<String, Inet6Address>();
inet4Entries.put("localhost", NetUtil.LOCALHOST4);
inet6Entries.put("localhost", NetUtil.LOCALHOST6);
DefaultHostsFileEntriesResolver resolver =
new DefaultHostsFileEntriesResolver(new HostsFileEntries(inet4Entries, inet6Entries));
InetAddress address = resolver.address("localhost", ResolvedAddressTypes.IPV6_PREFERRED);
Assert.assertTrue("Should pick an IPv6 address", address instanceof Inet6Address);
}
} }

View File

@ -20,7 +20,8 @@ import org.junit.Test;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.StringReader; import java.io.StringReader;
import java.net.InetAddress; import java.net.Inet4Address;
import java.net.Inet6Address;
import java.util.Map; import java.util.Map;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@ -31,6 +32,7 @@ public class HostsFileParserTest {
public void testParse() throws IOException { public void testParse() throws IOException {
String hostsString = new StringBuilder() String hostsString = new StringBuilder()
.append("127.0.0.1 host1").append("\n") // single hostname, separated with blanks .append("127.0.0.1 host1").append("\n") // single hostname, separated with blanks
.append("::1 host1").append("\n") // same as above, but IPv6
.append("\n") // empty line .append("\n") // empty line
.append("192.168.0.1\thost2").append("\n") // single hostname, separated with tabs .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 at the beginning of the line
@ -42,16 +44,20 @@ public class HostsFileParserTest {
.append("192.168.0.6 host7").append("\n") // should be ignored since we have the uppercase host already .append("192.168.0.6 host7").append("\n") // should be ignored since we have the uppercase host already
.toString(); .toString();
Map<String, InetAddress> entries = HostsFileParser.parse(new BufferedReader(new StringReader(hostsString))); HostsFileEntries entries = HostsFileParser.parse(new BufferedReader(new StringReader(hostsString)));
Map<String, Inet4Address> inet4Entries = entries.inet4Entries();
Map<String, Inet6Address> inet6Entries = entries.inet6Entries();
assertEquals("Expected 7 entries", 7, entries.size()); assertEquals("Expected 7 IPv4 entries", 7, inet4Entries.size());
assertEquals("127.0.0.1", entries.get("host1").getHostAddress()); assertEquals("Expected 1 IPv6 entries", 1, inet6Entries.size());
assertEquals("192.168.0.1", entries.get("host2").getHostAddress()); assertEquals("127.0.0.1", inet4Entries.get("host1").getHostAddress());
assertEquals("192.168.0.2", entries.get("host3").getHostAddress()); assertEquals("192.168.0.1", inet4Entries.get("host2").getHostAddress());
assertEquals("192.168.0.3", entries.get("host4").getHostAddress()); assertEquals("192.168.0.2", inet4Entries.get("host3").getHostAddress());
assertEquals("192.168.0.3", entries.get("host5").getHostAddress()); assertEquals("192.168.0.3", inet4Entries.get("host4").getHostAddress());
assertEquals("192.168.0.3", entries.get("host6").getHostAddress()); assertEquals("192.168.0.3", inet4Entries.get("host5").getHostAddress());
assertNotNull("uppercase host doesn't resolve", entries.get("host7")); assertEquals("192.168.0.3", inet4Entries.get("host6").getHostAddress());
assertEquals("192.168.0.5", entries.get("host7").getHostAddress()); assertNotNull("uppercase host doesn't resolve", inet4Entries.get("host7"));
assertEquals("192.168.0.5", inet4Entries.get("host7").getHostAddress());
assertEquals("0:0:0:0:0:0:0:1", inet6Entries.get("host1").getHostAddress());
} }
} }