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.resolver.HostsFileEntriesResolver;
import io.netty.resolver.InetNameResolver;
import io.netty.resolver.ResolvedAddressTypes;
import io.netty.util.NetUtil;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.FastThreadLocal;
@ -56,13 +57,10 @@ import java.net.IDN;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import static io.netty.util.internal.ObjectUtil.checkNonEmpty;
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 String LOCALHOST = "localhost";
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 String[] DEFAULT_SEACH_DOMAINS;
static final ResolvedAddressTypes DEFAULT_RESOLVE_ADDRESS_TYPES;
static final String[] DEFAULT_SEARCH_DOMAINS;
static {
if (NetUtil.isIpV4StackPreferred()) {
DEFAULT_RESOLVE_ADDRESS_TYPES = new InternetProtocolFamily[] { InternetProtocolFamily.IPv4 };
DEFAULT_RESOLVE_ADDRESS_TYPES = ResolvedAddressTypes.IPV4_ONLY;
LOCALHOST_ADDRESS = NetUtil.LOCALHOST4;
} else {
DEFAULT_RESOLVE_ADDRESS_TYPES = new InternetProtocolFamily[2];
if (NetUtil.isIpV6AddressesPreferred()) {
DEFAULT_RESOLVE_ADDRESS_TYPES[0] = InternetProtocolFamily.IPv6;
DEFAULT_RESOLVE_ADDRESS_TYPES[1] = InternetProtocolFamily.IPv4;
DEFAULT_RESOLVE_ADDRESS_TYPES = ResolvedAddressTypes.IPV6_PREFERRED;
LOCALHOST_ADDRESS = NetUtil.LOCALHOST6;
} else {
DEFAULT_RESOLVE_ADDRESS_TYPES[0] = InternetProtocolFamily.IPv4;
DEFAULT_RESOLVE_ADDRESS_TYPES[1] = InternetProtocolFamily.IPv6;
DEFAULT_RESOLVE_ADDRESS_TYPES = ResolvedAddressTypes.IPV4_PREFERRED;
LOCALHOST_ADDRESS = NetUtil.LOCALHOST4;
}
}
@ -116,7 +127,7 @@ public class DnsNameResolver extends InetNameResolver {
// Failed to get the system name search domain list.
searchDomains = EmptyArrays.EMPTY_STRINGS;
}
DEFAULT_SEACH_DOMAINS = searchDomains;
DEFAULT_SEARCH_DOMAINS = searchDomains;
}
private static final DatagramDnsResponseDecoder DECODER = new DatagramDnsResponseDecoder();
@ -148,7 +159,8 @@ public class DnsNameResolver extends InetNameResolver {
private final long queryTimeoutMillis;
private final int maxQueriesPerResolve;
private final boolean traceEnabled;
private final InternetProtocolFamily[] resolvedAddressTypes;
private final ResolvedAddressTypes resolvedAddressTypes;
private final InternetProtocolFamily[] resolvedInternetProtocolFamilies;
private final boolean recursionDesired;
private final int maxPayloadSize;
private final boolean optResourceEnabled;
@ -161,49 +173,6 @@ public class DnsNameResolver extends InetNameResolver {
private final DnsRecordType[] resolveRecordTypes;
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.
*
@ -215,7 +184,7 @@ public class DnsNameResolver extends InetNameResolver {
* @param resolveCache the DNS resolved entries cache
* @param authoritativeDnsServerCache the cache used to find the authoritative DNS server for a domain
* @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 maxQueriesPerResolve the maximum allowed number of DNS queries for a given name resolution
* @param traceEnabled if trace is enabled
@ -234,7 +203,7 @@ public class DnsNameResolver extends InetNameResolver {
final DnsCache resolveCache,
DnsCache authoritativeDnsServerCache,
long queryTimeoutMillis,
InternetProtocolFamily[] resolvedAddressTypes,
ResolvedAddressTypes resolvedAddressTypes,
boolean recursionDesired,
int maxQueriesPerResolve,
boolean traceEnabled,
@ -248,7 +217,7 @@ public class DnsNameResolver extends InetNameResolver {
checkNotNull(channelFactory, "channelFactory");
this.nameServerAddresses = checkNotNull(nameServerAddresses, "nameServerAddresses");
this.queryTimeoutMillis = checkPositive(queryTimeoutMillis, "queryTimeoutMillis");
this.resolvedAddressTypes = checkNonEmpty(resolvedAddressTypes, "resolvedAddressTypes");
this.resolvedAddressTypes = resolvedAddressTypes != null ? resolvedAddressTypes : DEFAULT_RESOLVE_ADDRESS_TYPES;
this.recursionDesired = recursionDesired;
this.maxQueriesPerResolve = checkPositive(maxQueriesPerResolve, "maxQueriesPerResolve");
this.traceEnabled = traceEnabled;
@ -261,32 +230,39 @@ public class DnsNameResolver extends InetNameResolver {
this.ndots = checkPositiveOrZero(ndots, "ndots");
this.decodeIdn = decodeIdn;
boolean supportsARecords = false;
boolean supportsAAAARecords = false;
// Use LinkedHashSet to maintain correct ordering.
Set<DnsRecordType> recordTypes = new LinkedHashSet<DnsRecordType>(resolvedAddressTypes.length);
for (InternetProtocolFamily family: resolvedAddressTypes) {
switch (family) {
case IPv4:
supportsARecords = true;
recordTypes.add(DnsRecordType.A);
break;
case IPv6:
supportsAAAARecords = true;
recordTypes.add(DnsRecordType.AAAA);
break;
default:
throw new Error();
}
switch (this.resolvedAddressTypes) {
case IPV4_ONLY:
supportsAAAARecords = false;
supportsARecords = true;
resolveRecordTypes = IPV4_ONLY_RESOLVED_RECORD_TYPES;
resolvedInternetProtocolFamilies = IPV4_ONLY_RESOLVED_PROTOCOL_FAMILIES;
preferredAddressType = InternetProtocolFamily.IPv4;
break;
case IPV4_PREFERRED:
supportsAAAARecords = true;
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;
default:
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();
b.group(executor());
b.channelFactory(channelFactory);
@ -339,16 +315,15 @@ public class DnsNameResolver extends InetNameResolver {
}
/**
* Returns the list of the protocol families of the address resolved by {@link #resolve(String)}
* in the order of preference.
* Returns the {@link ResolvedAddressTypes} resolved by {@link #resolve(String)}.
* The default value depends on the value of the system property {@code "java.net.preferIPv6Addresses"}.
*/
public List<InternetProtocolFamily> resolvedAddressTypes() {
return Arrays.asList(resolvedAddressTypes);
public ResolvedAddressTypes resolvedAddressTypes() {
return resolvedAddressTypes;
}
InternetProtocolFamily[] resolveAddressTypesUnsafe() {
return resolvedAddressTypes;
InternetProtocolFamily[] resolvedInternetProtocolFamiliesUnsafe() {
return resolvedInternetProtocolFamilies;
}
final String[] searchDomains() {
@ -447,7 +422,7 @@ public class DnsNameResolver extends InetNameResolver {
if (hostsFileEntriesResolver == null) {
return null;
} else {
InetAddress address = hostsFileEntriesResolver.address(hostname);
InetAddress address = hostsFileEntriesResolver.address(hostname, resolvedAddressTypes);
if (address == null && PlatformDependent.isWindows() && LOCALHOST.equalsIgnoreCase(hostname)) {
// If we tried to resolve localhost we need workaround that windows removed localhost from its
// hostfile in later versions.
@ -526,7 +501,7 @@ public class DnsNameResolver extends InetNameResolver {
@Override
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) {
@ -541,7 +516,7 @@ public class DnsNameResolver extends InetNameResolver {
Iterator<DnsRecord> additionalsIt = additionals.iterator();
if (!additionalsIt.hasNext()) {
return EMTPY_ADDITIONALS;
return EMPTY_ADDITIONALS;
}
List<DnsRecord> records = new ArrayList<DnsRecord>();
do {
@ -617,7 +592,7 @@ public class DnsNameResolver extends InetNameResolver {
cause = cachedEntries.get(0).cause();
} else {
// Find the first entry with the preferred address type.
for (InternetProtocolFamily f : resolvedAddressTypes) {
for (InternetProtocolFamily f : resolvedInternetProtocolFamilies) {
for (int i = 0; i < numEntries; i++) {
final DnsCacheEntry e = cachedEntries.get(i);
if (f.addressType().isInstance(e.address())) {
@ -692,7 +667,7 @@ public class DnsNameResolver extends InetNameResolver {
@Override
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) {
cause = cachedEntries.get(0).cause();
} else {
for (InternetProtocolFamily f : resolvedAddressTypes) {
for (InternetProtocolFamily f : resolvedInternetProtocolFamilies) {
for (int i = 0; i < numEntries; i++) {
final DnsCacheEntry e = cachedEntries.get(i);
if (f.addressType().isInstance(e.address())) {
@ -859,7 +834,7 @@ public class DnsNameResolver extends InetNameResolver {
public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(
InetSocketAddress nameServerAddr, DnsQuestion question) {
return query0(nameServerAddr, question, EMTPY_ADDITIONALS,
return query0(nameServerAddr, question, EMPTY_ADDITIONALS,
ch.eventLoop().<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>>newPromise());
}
@ -880,7 +855,7 @@ public class DnsNameResolver extends InetNameResolver {
InetSocketAddress nameServerAddr, DnsQuestion question,
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.InternetProtocolFamily;
import io.netty.resolver.HostsFileEntriesResolver;
import io.netty.resolver.ResolvedAddressTypes;
import io.netty.util.internal.UnstableApi;
import java.util.ArrayList;
@ -45,14 +46,14 @@ public final class DnsNameResolverBuilder {
private Integer maxTtl;
private Integer negativeTtl;
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 int maxQueriesPerResolve = 16;
private boolean traceEnabled;
private int maxPayloadSize = 4096;
private boolean optResourceEnabled = true;
private HostsFileEntriesResolver hostsFileEntriesResolver = HostsFileEntriesResolver.DEFAULT;
private String[] searchDomains = DnsNameResolver.DEFAULT_SEACH_DOMAINS;
private String[] searchDomains = DnsNameResolver.DEFAULT_SEARCH_DOMAINS;
private int ndots = 1;
private boolean decodeIdn = true;
@ -162,76 +163,45 @@ public final class DnsNameResolverBuilder {
}
/**
* 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}
* Compute a {@link ResolvedAddressTypes} from some {@link InternetProtocolFamily}s.
* An empty input will return the default value, based on "java.net" System properties.
* Valid inputs are (), (IPv4), (IPv6), (Ipv4, IPv6) and (IPv6, IPv4).
* @param internetProtocolFamilies a valid sequence of {@link InternetProtocolFamily}s
* @return a {@link ResolvedAddressTypes}
*/
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);
public static ResolvedAddressTypes computeResolvedAddressTypes(InternetProtocolFamily... internetProtocolFamilies) {
if (internetProtocolFamilies == null || internetProtocolFamilies.length == 0) {
return DnsNameResolver.DEFAULT_RESOLVE_ADDRESS_TYPES;
}
if (internetProtocolFamilies.length > 2) {
throw new IllegalArgumentException("No more than 2 InternetProtocolFamilies");
}
if (list.isEmpty()) {
throw new IllegalArgumentException("no protocol family specified");
switch(internetProtocolFamilies[0]) {
case IPv4:
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");
}
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}.
* You can use {@link DnsNameResolverBuilder#computeResolvedAddressTypes(InternetProtocolFamily...)}
* to get a {@link ResolvedAddressTypes} out of some {@link InternetProtocolFamily}s.
*
* @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()]);
public DnsNameResolverBuilder resolvedAddressTypes(ResolvedAddressTypes resolvedAddressTypes) {
this.resolvedAddressTypes = resolvedAddressTypes;
return this;
}

View File

@ -74,7 +74,7 @@ abstract class DnsNameResolverContext<T> {
private final DnsCache resolveCache;
private final boolean traceEnabled;
private final int maxAllowedQueries;
private final InternetProtocolFamily[] resolveAddressTypes;
private final InternetProtocolFamily[] resolvedInternetProtocolFamilies;
private final DnsRecord[] additionals;
private final Set<Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> queriesInProgress =
@ -97,7 +97,7 @@ abstract class DnsNameResolverContext<T> {
nameServerAddrs = parent.nameServerAddresses.stream();
maxAllowedQueries = parent.maxQueriesPerResolve();
resolveAddressTypes = parent.resolveAddressTypesUnsafe();
resolvedInternetProtocolFamilies = parent.resolvedInternetProtocolFamiliesUnsafe();
traceEnabled = parent.isTraceEnabled();
allowedQueries = maxAllowedQueries;
}
@ -611,7 +611,7 @@ abstract class DnsNameResolverContext<T> {
if (resolvedEntries != null) {
// Found at least one resolved address.
for (InternetProtocolFamily f: resolveAddressTypes) {
for (InternetProtocolFamily f: resolvedInternetProtocolFamilies) {
if (finishResolve(f.addressType(), resolvedEntries, promise)) {
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.DnsResponseCode;
import io.netty.handler.codec.dns.DnsSection;
import io.netty.resolver.ResolvedAddressTypes;
import io.netty.util.NetUtil;
import io.netty.resolver.HostsFileEntriesResolver;
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.hasSize;
import static org.hamcrest.Matchers.hasToString;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
@ -283,12 +283,12 @@ public class DnsNameResolverTest {
return newResolver(true);
}
private static DnsNameResolverBuilder newResolver(InternetProtocolFamily... resolvedAddressTypes) {
private static DnsNameResolverBuilder newResolver(ResolvedAddressTypes resolvedAddressTypes) {
return newResolver()
.resolvedAddressTypes(resolvedAddressTypes);
}
private static DnsNameResolverBuilder newNonCachedResolver(InternetProtocolFamily... resolvedAddressTypes) {
private static DnsNameResolverBuilder newNonCachedResolver(ResolvedAddressTypes resolvedAddressTypes) {
return newResolver()
.resolveCache(NoopDnsCache.INSTANCE)
.resolvedAddressTypes(resolvedAddressTypes);
@ -306,7 +306,7 @@ public class DnsNameResolverTest {
@Test
public void testResolveAorAAAA() throws Exception {
DnsNameResolver resolver = newResolver(InternetProtocolFamily.IPv4, InternetProtocolFamily.IPv6).build();
DnsNameResolver resolver = newResolver(ResolvedAddressTypes.IPV4_PREFERRED).build();
try {
testResolve0(resolver, EXCLUSIONS_RESOLVE_A);
} finally {
@ -316,7 +316,7 @@ public class DnsNameResolverTest {
@Test
public void testResolveAAAAorA() throws Exception {
DnsNameResolver resolver = newResolver(InternetProtocolFamily.IPv6, InternetProtocolFamily.IPv4).build();
DnsNameResolver resolver = newResolver(ResolvedAddressTypes.IPV6_PREFERRED).build();
try {
testResolve0(resolver, EXCLUSIONS_RESOLVE_A);
} finally {
@ -326,7 +326,7 @@ public class DnsNameResolverTest {
@Test
public void testResolveA() throws Exception {
DnsNameResolver resolver = newResolver(InternetProtocolFamily.IPv4)
DnsNameResolver resolver = newResolver(ResolvedAddressTypes.IPV4_ONLY)
// Cache for eternity
.ttl(Integer.MAX_VALUE, Integer.MAX_VALUE)
.build();
@ -357,7 +357,7 @@ public class DnsNameResolverTest {
@Test
public void testResolveAAAA() throws Exception {
DnsNameResolver resolver = newResolver(InternetProtocolFamily.IPv6).build();
DnsNameResolver resolver = newResolver(ResolvedAddressTypes.IPV6_ONLY).build();
try {
testResolve0(resolver, EXCLUSIONS_RESOLVE_AAAA);
} finally {
@ -367,7 +367,7 @@ public class DnsNameResolverTest {
@Test
public void testNonCachedResolve() throws Exception {
DnsNameResolver resolver = newNonCachedResolver(InternetProtocolFamily.IPv4).build();
DnsNameResolver resolver = newNonCachedResolver(ResolvedAddressTypes.IPV4_ONLY).build();
try {
testResolve0(resolver, EXCLUSIONS_RESOLVE_A);
} finally {
@ -442,7 +442,7 @@ public class DnsNameResolverTest {
assertThat(resolved.getHostName(), is(unresolved));
boolean typeMatches = false;
for (InternetProtocolFamily f: resolver.resolvedAddressTypes()) {
for (InternetProtocolFamily f: resolver.resolvedInternetProtocolFamiliesUnsafe()) {
Class<?> resolvedType = resolved.getClass();
if (f.addressType().isAssignableFrom(resolvedType)) {
typeMatches = true;
@ -574,26 +574,26 @@ public class DnsNameResolverTest {
@Test
public void testResolveEmptyIpv4() {
testResolve0(InternetProtocolFamily.IPv4, NetUtil.LOCALHOST4, StringUtil.EMPTY_STRING);
testResolve0(ResolvedAddressTypes.IPV4_ONLY, NetUtil.LOCALHOST4, StringUtil.EMPTY_STRING);
}
@Test
public void testResolveEmptyIpv6() {
testResolve0(InternetProtocolFamily.IPv6, NetUtil.LOCALHOST6, StringUtil.EMPTY_STRING);
testResolve0(ResolvedAddressTypes.IPV6_ONLY, NetUtil.LOCALHOST6, StringUtil.EMPTY_STRING);
}
@Test
public void testResolveNullIpv4() {
testResolve0(InternetProtocolFamily.IPv4, NetUtil.LOCALHOST4, null);
testResolve0(ResolvedAddressTypes.IPV4_ONLY, NetUtil.LOCALHOST4, null);
}
@Test
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) {
DnsNameResolver resolver = newResolver(family).build();
private static void testResolve0(ResolvedAddressTypes addressTypes, InetAddress expectedAddr, String name) {
DnsNameResolver resolver = newResolver(addressTypes).build();
try {
InetAddress address = resolver.resolve(name).syncUninterruptibly().getNow();
assertEquals(expectedAddr, address);
@ -604,26 +604,26 @@ public class DnsNameResolverTest {
@Test
public void testResolveAllEmptyIpv4() {
testResolveAll0(InternetProtocolFamily.IPv4, NetUtil.LOCALHOST4, StringUtil.EMPTY_STRING);
testResolveAll0(ResolvedAddressTypes.IPV4_ONLY, NetUtil.LOCALHOST4, StringUtil.EMPTY_STRING);
}
@Test
public void testResolveAllEmptyIpv6() {
testResolveAll0(InternetProtocolFamily.IPv6, NetUtil.LOCALHOST6, StringUtil.EMPTY_STRING);
testResolveAll0(ResolvedAddressTypes.IPV6_ONLY, NetUtil.LOCALHOST6, StringUtil.EMPTY_STRING);
}
@Test
public void testResolveAllNullIpv4() {
testResolveAll0(InternetProtocolFamily.IPv4, NetUtil.LOCALHOST4, null);
testResolveAll0(ResolvedAddressTypes.IPV4_ONLY, NetUtil.LOCALHOST4, null);
}
@Test
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) {
DnsNameResolver resolver = newResolver(family).build();
private static void testResolveAll0(ResolvedAddressTypes addressTypes, InetAddress expectedAddr, String name) {
DnsNameResolver resolver = newResolver(addressTypes).build();
try {
List<InetAddress> addresses = resolver.resolveAll(name).syncUninterruptibly().getNow();
assertEquals(1, addresses.size());
@ -682,9 +682,9 @@ public class DnsNameResolverTest {
DnsNameResolver resolver = new DnsNameResolver(
group.next(), new ReflectiveChannelFactory<DatagramChannel>(NioDatagramChannel.class),
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,
DnsNameResolver.DEFAULT_SEACH_DOMAINS, 0, true) {
DnsNameResolver.DEFAULT_SEARCH_DOMAINS, 0, true) {
@Override
int dnsRedirectPort(InetAddress server) {
return server.equals(dnsServerAuthority.localAddress().getAddress()) ?

View File

@ -15,6 +15,10 @@
*/
package io.netty.resolver;
import io.netty.util.internal.UnstableApi;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.Locale;
import java.util.Map;
@ -22,13 +26,39 @@ import java.util.Map;
/**
* Default {@link HostsFileEntriesResolver} that resolves hosts file entries only once.
*/
@UnstableApi
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
public InetAddress address(String inetHost) {
return entries.get(normalize(inetHost));
public InetAddress address(String inetHost, ResolvedAddressTypes resolvedAddressTypes) {
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

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;
import io.netty.util.internal.UnstableApi;
import java.net.InetAddress;
/**
* Resolves a hostname against the hosts file entries.
*/
@UnstableApi
public interface HostsFileEntriesResolver {
/**
@ -27,5 +30,11 @@ public interface HostsFileEntriesResolver {
*/
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.internal.PlatformDependent;
import io.netty.util.internal.UnstableApi;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
@ -25,9 +26,10 @@ import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.HashMap;
@ -39,6 +41,7 @@ import static io.netty.util.internal.ObjectUtil.*;
/**
* A parser for hosts files.
*/
@UnstableApi
public final class HostsFileParser {
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.
*
* @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();
try {
return parse(hostsFile);
} catch (IOException 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.
*
* @return a map of hostname or alias to {@link InetAddress}
* @return a {@link HostsFileEntries}
* @throws IOException file could not be read
*/
public static Map<String, InetAddress> parse() throws IOException {
public static HostsFileEntries parse() throws IOException {
return parse(locateHostsFile());
}
@ -91,15 +94,15 @@ public final class HostsFileParser {
* Parse a hosts file.
*
* @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
*/
public static Map<String, InetAddress> parse(File file) throws IOException {
public static HostsFileEntries parse(File file) throws IOException {
checkNotNull(file, "file");
if (file.exists() && file.isFile()) {
return parse(new BufferedReader(new FileReader(file)));
} else {
return Collections.emptyMap();
return HostsFileEntries.EMPTY;
}
}
@ -107,14 +110,15 @@ public final class HostsFileParser {
* Parse a reader of hosts file format.
*
* @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
*/
public static Map<String, InetAddress> parse(Reader reader) throws IOException {
public static HostsFileEntries parse(Reader reader) throws IOException {
checkNotNull(reader, "reader");
BufferedReader buff = new BufferedReader(reader);
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;
while ((line = buff.readLine()) != null) {
// remove comment
@ -153,14 +157,25 @@ public final class HostsFileParser {
for (int i = 1; i < lineParts.size(); i ++) {
String hostname = lineParts.get(i);
String hostnameLower = hostname.toLowerCase(Locale.ENGLISH);
if (!entries.containsKey(hostnameLower)) {
// trying to map a host to multiple IPs is wrong
// only the first entry is honored
entries.put(hostnameLower, InetAddress.getByAddress(hostname, ipBytes));
InetAddress address = InetAddress.getByAddress(hostname, ipBytes);
if (address instanceof Inet4Address) {
Inet4Address previous = ipv4Entries.put(hostnameLower, (Inet4Address) address);
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 {
try {
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
* under the License.
*/
/**
* show issue https://github.com/netty/netty/issues/5182
* HostsFileParser tries to resolve hostnames as case-sensitive
*/
package io.netty.resolver;
import io.netty.util.NetUtil;
import org.junit.Assert;
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 {
/**
* show issue https://github.com/netty/netty/issues/5182
* HostsFileParser tries to resolve hostnames as case-sensitive
*/
@Test
public void testCaseInsensitivity() throws Exception {
DefaultHostsFileEntriesResolver resolver = new DefaultHostsFileEntriesResolver();
//normalized somehow
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.IOException;
import java.io.StringReader;
import java.net.InetAddress;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.util.Map;
import static org.junit.Assert.*;
@ -31,6 +32,7 @@ public class HostsFileParserTest {
public void testParse() throws IOException {
String hostsString = new StringBuilder()
.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("192.168.0.1\thost2").append("\n") // single hostname, separated with tabs
.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
.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("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());
assertNotNull("uppercase host doesn't resolve", entries.get("host7"));
assertEquals("192.168.0.5", entries.get("host7").getHostAddress());
assertEquals("Expected 7 IPv4 entries", 7, inet4Entries.size());
assertEquals("Expected 1 IPv6 entries", 1, inet6Entries.size());
assertEquals("127.0.0.1", inet4Entries.get("host1").getHostAddress());
assertEquals("192.168.0.1", inet4Entries.get("host2").getHostAddress());
assertEquals("192.168.0.2", inet4Entries.get("host3").getHostAddress());
assertEquals("192.168.0.3", inet4Entries.get("host4").getHostAddress());
assertEquals("192.168.0.3", inet4Entries.get("host5").getHostAddress());
assertEquals("192.168.0.3", inet4Entries.get("host6").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());
}
}