Correctly handle DNS redirects for NS servers that have no ADDITIONAL record (#8177)

Motiviation:

We incorrectly did ignore NS servers during redirect which had no ADDITIONAL record. This could at worse have the affect that we failed the query completely as none of the NS servers had a ADDITIONAL record. Beside this using a DnsCache to cache authoritative nameservers does not work in practise as we we need different features and semantics when cache these servers (for example we also want to cache unresolved nameservers and resolve these on the fly when needed).

Modifications:

- Correctly take NS records into account that have no matching ADDITIONAL record
- Correctly handle multiple ADDITIONAL records for the same NS record
- Introduce AuthoritativeDnsServerCache as a replacement of the DnsCache when caching authoritative nameservers + adding default implementation
- Add an adapter layer to reduce API breakage as much as possible
- Replace DnsNameResolver.uncachedRedirectDnsServerStream(...) with newRedirectDnsServerStream(...)
- Add unit tests

Result:

Our DnsResolver now correctly handle redirects in all cases.
This commit is contained in:
Norman Maurer 2018-08-22 17:49:22 +02:00 committed by GitHub
parent ea4c315b45
commit bbb6e126b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 2178 additions and 470 deletions

View File

@ -0,0 +1,63 @@
/*
* Copyright 2018 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.resolver.dns;
import io.netty.channel.EventLoop;
import io.netty.util.internal.UnstableApi;
import java.net.InetSocketAddress;
/**
* Cache which stores the nameservers that should be used to resolve a specific hostname.
*/
@UnstableApi
public interface AuthoritativeDnsServerCache {
/**
* Returns the cached nameservers that should be used to resolve the given hostname. The returned
* {@link DnsServerAddressStream} may contain unresolved {@link InetSocketAddress}es that will be resolved
* when needed while resolving other domain names.
*
* @param hostname the hostname
* @return the cached entries or an {@code null} if none.
*/
DnsServerAddressStream get(String hostname);
/**
* Caches a nameserver that should be used to resolve the given hostname.
*
* @param hostname the hostname
* @param address the nameserver address (which may be unresolved).
* @param originalTtl the TTL as returned by the DNS server
* @param loop the {@link EventLoop} used to register the TTL timeout
*/
void cache(String hostname, InetSocketAddress address, long originalTtl, EventLoop loop);
/**
* Clears all cached nameservers.
*
* @see #clear(String)
*/
void clear();
/**
* Clears the cached nameservers for the specified hostname.
*
* @return {@code true} if and only if there was an entry for the specified host name in the cache and
* it has been removed by this method
*/
boolean clear(String hostname);
}

View File

@ -0,0 +1,80 @@
/*
* Copyright 2018 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.resolver.dns;
import io.netty.channel.EventLoop;
import io.netty.handler.codec.dns.DnsRecord;
import io.netty.util.internal.UnstableApi;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
/**
* {@link AuthoritativeDnsServerCache} implementation which delegates all operations to a wrapped {@link DnsCache}.
* This implementation is only present to preserve a upgrade story.
*/
@UnstableApi
final class AuthoritativeDnsServerCacheAdapter implements AuthoritativeDnsServerCache {
private static final DnsRecord[] EMPTY = new DnsRecord[0];
private final DnsCache cache;
AuthoritativeDnsServerCacheAdapter(DnsCache cache) {
this.cache = checkNotNull(cache, "cache");
}
@Override
public DnsServerAddressStream get(String hostname) {
List<? extends DnsCacheEntry> entries = cache.get(hostname, EMPTY);
if (entries == null || entries.isEmpty()) {
return null;
}
if (entries.get(0).cause() != null) {
return null;
}
List<InetSocketAddress> addresses = new ArrayList<InetSocketAddress>(entries.size());
int i = 0;
do {
InetAddress addr = entries.get(i).address();
addresses.add(new InetSocketAddress(addr, DefaultDnsServerAddressStreamProvider.DNS_PORT));
} while (++i < entries.size());
return new SequentialDnsServerAddressStream(addresses, 0);
}
@Override
public void cache(String hostname, InetSocketAddress address, long originalTtl, EventLoop loop) {
// We only cache resolved addresses.
if (!address.isUnresolved()) {
cache.cache(hostname, EMPTY, address.getAddress(), originalTtl, loop);
}
}
@Override
public void clear() {
cache.clear();
}
@Override
public boolean clear(String hostname) {
return cache.clear(hostname);
}
}

View File

@ -0,0 +1,292 @@
/*
* Copyright 2018 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.resolver.dns;
import io.netty.channel.EventLoop;
import io.netty.util.internal.PlatformDependent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Delayed;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import static java.util.Collections.singletonList;
/**
* Abstract cache that automatically removes entries for a hostname once the TTL for an entry is reached.
*
* @param <E>
*/
abstract class Cache<E> {
private static final AtomicReferenceFieldUpdater<Cache.Entries, ScheduledFuture> FUTURE_UPDATER =
AtomicReferenceFieldUpdater.newUpdater(Cache.Entries.class, ScheduledFuture.class, "expirationFuture");
private static final ScheduledFuture<?> CANCELLED = new ScheduledFuture<Object>() {
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return false;
}
@Override
public long getDelay(TimeUnit unit) {
// We ignore unit and always return the minimum value to ensure the TTL of the cancelled marker is
// the smallest.
return Long.MIN_VALUE;
}
@Override
public int compareTo(Delayed o) {
throw new UnsupportedOperationException();
}
@Override
public boolean isCancelled() {
return true;
}
@Override
public boolean isDone() {
return true;
}
@Override
public Object get() {
throw new UnsupportedOperationException();
}
@Override
public Object get(long timeout, TimeUnit unit) {
throw new UnsupportedOperationException();
}
};
// Two years are supported by all our EventLoop implementations and so safe to use as maximum.
// See also: https://github.com/netty/netty/commit/b47fb817991b42ec8808c7d26538f3f2464e1fa6
static final int MAX_SUPPORTED_TTL_SECS = (int) TimeUnit.DAYS.toSeconds(365 * 2);
private final ConcurrentMap<String, Entries> resolveCache = PlatformDependent.newConcurrentHashMap();
/**
* Remove everything from the cache.
*/
final void clear() {
while (!resolveCache.isEmpty()) {
for (Iterator<Entry<String, Entries>> i = resolveCache.entrySet().iterator(); i.hasNext();) {
Map.Entry<String, Entries> e = i.next();
i.remove();
e.getValue().clearAndCancel();
}
}
}
/**
* Clear all entries (if anything exists) for the given hostname and return {@code true} if anything was removed.
*/
final boolean clear(String hostname) {
Entries entries = resolveCache.remove(hostname);
return entries != null && entries.clearAndCancel();
}
/**
* Returns all caches entries for the given hostname.
*/
final List<? extends E> get(String hostname) {
Entries entries = resolveCache.get(hostname);
return entries == null ? null : entries.get();
}
/**
* Cache a value for the given hostname that will automatically expire once the TTL is reached.
*/
final void cache(String hostname, E value, int ttl, EventLoop loop) {
Entries entries = resolveCache.get(hostname);
if (entries == null) {
entries = new Entries(hostname);
Entries oldEntries = resolveCache.putIfAbsent(hostname, entries);
if (oldEntries != null) {
entries = oldEntries;
}
}
entries.add(value, ttl, loop);
}
/**
* Return the number of hostames for which we have cached something.
*/
final int size() {
return resolveCache.size();
}
/**
* Returns {@code true} if this entry should replace all other entries that are already cached for the hostname.
*/
protected abstract boolean shouldReplaceAll(E entry);
/**
* Sort the {@link List} for a {@code hostname} before caching these.
*/
protected void sortEntries(
@SuppressWarnings("unused") String hostname, @SuppressWarnings("unused") List<E> entries) {
// NOOP.
}
/**
* Returns {@code true} if both entries are equal.
*/
protected abstract boolean equals(E entry, E otherEntry);
// Directly extend AtomicReference for intrinsics and also to keep memory overhead low.
private final class Entries extends AtomicReference<List<E>> implements Runnable {
private final String hostname;
// Needs to be package-private to be able to access it via the AtomicReferenceFieldUpdater
volatile ScheduledFuture<?> expirationFuture;
Entries(String hostname) {
super(Collections.<E>emptyList());
this.hostname = hostname;
}
void add(E e, int ttl, EventLoop loop) {
if (!shouldReplaceAll(e)) {
for (;;) {
List<E> entries = get();
if (!entries.isEmpty()) {
final E firstEntry = entries.get(0);
if (shouldReplaceAll(firstEntry)) {
assert entries.size() == 1;
if (compareAndSet(entries, singletonList(e))) {
scheduleCacheExpirationIfNeeded(ttl, loop);
return;
} else {
// Need to try again as CAS failed
continue;
}
}
// Create a new List for COW semantics
List<E> newEntries = new ArrayList<E>(entries.size() + 1);
int i = 0;
E replacedEntry = null;
do {
E entry = entries.get(i);
// Only add old entry if the address is not the same as the one we try to add as well.
// In this case we will skip it and just add the new entry as this may have
// more up-to-date data and cancel the old after we were able to update the cache.
if (!Cache.this.equals(e, entry)) {
newEntries.add(entry);
} else {
replacedEntry = entry;
newEntries.add(e);
++i;
for (; i < entries.size(); ++i) {
newEntries.add(entries.get(i));
}
break;
}
} while (++i < entries.size());
if (replacedEntry == null) {
newEntries.add(e);
}
sortEntries(hostname, newEntries);
if (compareAndSet(entries, Collections.unmodifiableList(newEntries))) {
scheduleCacheExpirationIfNeeded(ttl, loop);
return;
}
} else if (compareAndSet(entries, singletonList(e))) {
scheduleCacheExpirationIfNeeded(ttl, loop);
return;
}
}
} else {
set(singletonList(e));
scheduleCacheExpirationIfNeeded(ttl, loop);
}
}
private void scheduleCacheExpirationIfNeeded(int ttl, EventLoop loop) {
for (;;) {
// We currently don't calculate a new TTL when we need to retry the CAS as we don't expect this to
// be invoked very concurrently and also we use SECONDS anyway. If this ever becomes a problem
// we can reconsider.
ScheduledFuture<?> oldFuture = FUTURE_UPDATER.get(this);
if (oldFuture == null || oldFuture.getDelay(TimeUnit.SECONDS) > ttl) {
ScheduledFuture<?> newFuture = loop.schedule(this, ttl, TimeUnit.SECONDS);
// It is possible that
// 1. task will fire in between this line, or
// 2. multiple timers may be set if there is concurrency
// (1) Shouldn't be a problem because we will fail the CAS and then the next loop will see CANCELLED
// so the ttl will not be less, and we will bail out of the loop.
// (2) This is a trade-off to avoid concurrency resulting in contention on a synchronized block.
if (FUTURE_UPDATER.compareAndSet(this, oldFuture, newFuture)) {
if (oldFuture != null) {
oldFuture.cancel(true);
}
break;
} else {
// There was something else scheduled in the meantime... Cancel and try again.
newFuture.cancel(true);
}
} else {
break;
}
}
}
boolean clearAndCancel() {
List<E> entries = getAndSet(Collections.<E>emptyList());
if (entries.isEmpty()) {
return false;
}
ScheduledFuture<?> expirationFuture = FUTURE_UPDATER.getAndSet(this, CANCELLED);
if (expirationFuture != null) {
expirationFuture.cancel(false);
}
return true;
}
@Override
public void run() {
// We always remove all entries for a hostname once one entry expire. This is not the
// most efficient to do but this way we can guarantee that if a DnsResolver
// be configured to prefer one ip family over the other we will not return unexpected
// results to the enduser if one of the A or AAAA records has different TTL settings.
//
// As a TTL is just a hint of the maximum time a cache is allowed to cache stuff it's
// completely fine to remove the entry even if the TTL is not reached yet.
//
// See https://github.com/netty/netty/issues/7329
resolveCache.remove(hostname, this);
clearAndCancel();
}
}
}

View File

@ -0,0 +1,130 @@
/*
* Copyright 2018 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.resolver.dns;
import io.netty.channel.EventLoop;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.UnstableApi;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import static io.netty.util.internal.ObjectUtil.*;
/**
* Default implementation of {@link AuthoritativeDnsServerCache}, backed by a {@link ConcurrentMap}.
*/
@UnstableApi
public class DefaultAuthoritativeDnsServerCache implements AuthoritativeDnsServerCache {
private final int minTtl;
private final int maxTtl;
private final Comparator<InetSocketAddress> comparator;
private final Cache<InetSocketAddress> resolveCache = new Cache<InetSocketAddress>() {
@Override
protected boolean shouldReplaceAll(InetSocketAddress entry) {
return false;
}
@Override
protected boolean equals(InetSocketAddress entry, InetSocketAddress otherEntry) {
if (PlatformDependent.javaVersion() >= 7) {
return entry.getHostString().equalsIgnoreCase(otherEntry.getHostString());
}
return entry.getHostName().equalsIgnoreCase(otherEntry.getHostName());
}
@Override
protected void sortEntries(String hostname, List<InetSocketAddress> entries) {
if (comparator != null) {
Collections.sort(entries, comparator);
}
}
};
/**
* Create a cache that respects the TTL returned by the DNS server.
*/
public DefaultAuthoritativeDnsServerCache() {
this(0, Cache.MAX_SUPPORTED_TTL_SECS, null);
}
/**
* Create a cache.
*
* @param minTtl the minimum TTL
* @param maxTtl the maximum TTL
* @param comparator the {@link Comparator} to order the {@link InetSocketAddress} for a hostname or {@code null}
* if insertion order should be used.
*/
public DefaultAuthoritativeDnsServerCache(int minTtl, int maxTtl, Comparator<InetSocketAddress> comparator) {
this.minTtl = Math.min(Cache.MAX_SUPPORTED_TTL_SECS, checkPositiveOrZero(minTtl, "minTtl"));
this.maxTtl = Math.min(Cache.MAX_SUPPORTED_TTL_SECS, checkPositive(maxTtl, "maxTtl"));
if (minTtl > maxTtl) {
throw new IllegalArgumentException(
"minTtl: " + minTtl + ", maxTtl: " + maxTtl + " (expected: 0 <= minTtl <= maxTtl)");
}
this.comparator = comparator;
}
@SuppressWarnings("unchecked")
@Override
public DnsServerAddressStream get(String hostname) {
checkNotNull(hostname, "hostname");
List<? extends InetSocketAddress> addresses = resolveCache.get(hostname);
if (addresses == null || addresses.isEmpty()) {
return null;
}
return new SequentialDnsServerAddressStream(addresses, 0);
}
@Override
public void cache(String hostname, InetSocketAddress address, long originalTtl, EventLoop loop) {
checkNotNull(hostname, "hostname");
checkNotNull(address, "address");
checkNotNull(loop, "loop");
if (PlatformDependent.javaVersion() >= 7 && address.getHostString() == null) {
// We only cache addresses that have also a host string as we will need it later when trying to replace
// unresolved entries in the cache.
return;
}
resolveCache.cache(hostname, address, Math.max(minTtl, (int) Math.min(maxTtl, originalTtl)), loop);
}
@Override
public void clear() {
resolveCache.clear();
}
@Override
public boolean clear(String hostname) {
checkNotNull(hostname, "hostname");
return resolveCache.clear(hostname);
}
@Override
public String toString() {
return "DefaultAuthoritativeDnsServerCache(minTtl=" + minTtl + ", maxTtl=" + maxTtl + ", cached nameservers=" +
resolveCache.size() + ')';
}
}

View File

@ -17,22 +17,13 @@ package io.netty.resolver.dns;
import io.netty.channel.EventLoop;
import io.netty.handler.codec.dns.DnsRecord;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.StringUtil;
import io.netty.util.internal.UnstableApi;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Delayed;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
@ -43,55 +34,26 @@ import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
*/
@UnstableApi
public class DefaultDnsCache implements DnsCache {
// Two years are supported by all our EventLoop implementations and so safe to use as maximum.
// See also: https://github.com/netty/netty/commit/b47fb817991b42ec8808c7d26538f3f2464e1fa6
private static final int MAX_SUPPORTED_TTL_SECS = (int) TimeUnit.DAYS.toSeconds(365 * 2);
private static final ScheduledFuture<?> CANCELLED = new ScheduledFuture<Object>() {
private final Cache<DefaultDnsCacheEntry> resolveCache = new Cache<DefaultDnsCacheEntry>() {
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return false;
protected boolean shouldReplaceAll(DefaultDnsCacheEntry entry) {
return entry.cause() != null;
}
@Override
public long getDelay(TimeUnit unit) {
// We ignore unit and always return the minimum value to ensure the TTL of the cancelled marker is
// the smallest.
return Long.MIN_VALUE;
}
@Override
public int compareTo(Delayed o) {
throw new UnsupportedOperationException();
}
@Override
public boolean isCancelled() {
return true;
}
@Override
public boolean isDone() {
return true;
}
@Override
public Object get() {
throw new UnsupportedOperationException();
}
@Override
public Object get(long timeout, TimeUnit unit) {
throw new UnsupportedOperationException();
protected boolean equals(DefaultDnsCacheEntry entry, DefaultDnsCacheEntry otherEntry) {
if (entry.address() != null) {
return entry.address().equals(otherEntry.address());
}
if (otherEntry.address() != null) {
return false;
}
return entry.cause().equals(otherEntry.cause());
}
};
private static final AtomicReferenceFieldUpdater<DefaultDnsCache.Entries, ScheduledFuture> FUTURE_UPDATER =
AtomicReferenceFieldUpdater.newUpdater(
DefaultDnsCache.Entries.class, ScheduledFuture.class, "expirationFuture");
private final ConcurrentMap<String, Entries> resolveCache = PlatformDependent.newConcurrentHashMap();
private final int minTtl;
private final int maxTtl;
private final int negativeTtl;
@ -101,7 +63,7 @@ public class DefaultDnsCache implements DnsCache {
* and doesn't cache negative responses.
*/
public DefaultDnsCache() {
this(0, MAX_SUPPORTED_TTL_SECS, 0);
this(0, Cache.MAX_SUPPORTED_TTL_SECS, 0);
}
/**
@ -111,8 +73,8 @@ public class DefaultDnsCache implements DnsCache {
* @param negativeTtl the TTL for failed queries
*/
public DefaultDnsCache(int minTtl, int maxTtl, int negativeTtl) {
this.minTtl = Math.min(MAX_SUPPORTED_TTL_SECS, checkPositiveOrZero(minTtl, "minTtl"));
this.maxTtl = Math.min(MAX_SUPPORTED_TTL_SECS, checkPositiveOrZero(maxTtl, "maxTtl"));
this.minTtl = Math.min(Cache.MAX_SUPPORTED_TTL_SECS, checkPositiveOrZero(minTtl, "minTtl"));
this.maxTtl = Math.min(Cache.MAX_SUPPORTED_TTL_SECS, checkPositiveOrZero(maxTtl, "maxTtl"));
if (minTtl > maxTtl) {
throw new IllegalArgumentException(
"minTtl: " + minTtl + ", maxTtl: " + maxTtl + " (expected: 0 <= minTtl <= maxTtl)");
@ -148,21 +110,13 @@ public class DefaultDnsCache implements DnsCache {
@Override
public void clear() {
while (!resolveCache.isEmpty()) {
for (Iterator<Map.Entry<String, Entries>> i = resolveCache.entrySet().iterator(); i.hasNext();) {
Map.Entry<String, Entries> e = i.next();
i.remove();
e.getValue().clearAndCancel();
}
}
resolveCache.clear();
}
@Override
public boolean clear(String hostname) {
checkNotNull(hostname, "hostname");
Entries entries = resolveCache.remove(appendDot(hostname));
return entries != null && entries.clearAndCancel();
return resolveCache.clear(appendDot(hostname));
}
private static boolean emptyAdditionals(DnsRecord[] additionals) {
@ -176,8 +130,7 @@ public class DefaultDnsCache implements DnsCache {
return Collections.<DnsCacheEntry>emptyList();
}
Entries entries = resolveCache.get(appendDot(hostname));
return entries == null ? null : entries.get();
return resolveCache.get(appendDot(hostname));
}
@Override
@ -186,12 +139,11 @@ public class DefaultDnsCache implements DnsCache {
checkNotNull(hostname, "hostname");
checkNotNull(address, "address");
checkNotNull(loop, "loop");
final DefaultDnsCacheEntry e = new DefaultDnsCacheEntry(hostname, address);
DefaultDnsCacheEntry e = new DefaultDnsCacheEntry(hostname, address);
if (maxTtl == 0 || !emptyAdditionals(additionals)) {
return e;
}
cache0(appendDot(hostname), e,
Math.max(minTtl, (int) Math.min(maxTtl, originalTtl)), loop);
resolveCache.cache(appendDot(hostname), e, Math.max(minTtl, (int) Math.min(maxTtl, originalTtl)), loop);
return e;
}
@ -201,27 +153,15 @@ public class DefaultDnsCache implements DnsCache {
checkNotNull(cause, "cause");
checkNotNull(loop, "loop");
final DefaultDnsCacheEntry e = new DefaultDnsCacheEntry(hostname, cause);
DefaultDnsCacheEntry e = new DefaultDnsCacheEntry(hostname, cause);
if (negativeTtl == 0 || !emptyAdditionals(additionals)) {
return e;
}
cache0(appendDot(hostname), e, negativeTtl, loop);
resolveCache.cache(appendDot(hostname), e, negativeTtl, loop);
return e;
}
private void cache0(String hostname, DefaultDnsCacheEntry e, int ttl, EventLoop loop) {
Entries entries = resolveCache.get(hostname);
if (entries == null) {
entries = new Entries(hostname);
Entries oldEntries = resolveCache.putIfAbsent(hostname, entries);
if (oldEntries != null) {
entries = oldEntries;
}
}
entries.add(e, ttl, loop);
}
@Override
public String toString() {
return new StringBuilder()
@ -229,7 +169,7 @@ public class DefaultDnsCache implements DnsCache {
.append(minTtl).append(", maxTtl=")
.append(maxTtl).append(", negativeTtl=")
.append(negativeTtl).append(", cached resolved hostname=")
.append(resolveCache.size()).append(")")
.append(resolveCache.size()).append(')')
.toString();
}
@ -239,14 +179,14 @@ public class DefaultDnsCache implements DnsCache {
private final Throwable cause;
DefaultDnsCacheEntry(String hostname, InetAddress address) {
this.hostname = checkNotNull(hostname, "hostname");
this.address = checkNotNull(address, "address");
this.hostname = hostname;
this.address = address;
cause = null;
}
DefaultDnsCacheEntry(String hostname, Throwable cause) {
this.hostname = checkNotNull(hostname, "hostname");
this.cause = checkNotNull(cause, "cause");
this.hostname = hostname;
this.cause = cause;
address = null;
}
@ -274,124 +214,6 @@ public class DefaultDnsCache implements DnsCache {
}
}
// Directly extend AtomicReference for intrinsics and also to keep memory overhead low.
private final class Entries extends AtomicReference<List<DefaultDnsCacheEntry>> implements Runnable {
private final String hostname;
// Needs to be package-private to be able to access it via the AtomicReferenceFieldUpdater
volatile ScheduledFuture<?> expirationFuture;
Entries(String hostname) {
super(Collections.<DefaultDnsCacheEntry>emptyList());
this.hostname = hostname;
}
void add(DefaultDnsCacheEntry e, int ttl, EventLoop loop) {
if (e.cause() == null) {
for (;;) {
List<DefaultDnsCacheEntry> entries = get();
if (!entries.isEmpty()) {
final DefaultDnsCacheEntry firstEntry = entries.get(0);
if (firstEntry.cause() != null) {
assert entries.size() == 1;
if (compareAndSet(entries, Collections.singletonList(e))) {
scheduleCacheExpirationIfNeeded(ttl, loop);
return;
} else {
// Need to try again as CAS failed
continue;
}
}
// Create a new List for COW semantics
List<DefaultDnsCacheEntry> newEntries = new ArrayList<DefaultDnsCacheEntry>(entries.size() + 1);
int i = 0;
do {
DefaultDnsCacheEntry entry = entries.get(i);
// Only add old entry if the address is not the same as the one we try to add as well.
// In this case we will skip it and just add the new entry as this may have
// more up-to-date data and cancel the old after we were able to update the cache.
if (!e.address().equals(entry.address())) {
newEntries.add(entry);
}
} while (++i < entries.size());
newEntries.add(e);
if (compareAndSet(entries, Collections.unmodifiableList(newEntries))) {
scheduleCacheExpirationIfNeeded(ttl, loop);
return;
}
} else if (compareAndSet(entries, Collections.singletonList(e))) {
scheduleCacheExpirationIfNeeded(ttl, loop);
return;
}
}
} else {
set(Collections.singletonList(e));
scheduleCacheExpirationIfNeeded(ttl, loop);
}
}
private void scheduleCacheExpirationIfNeeded(int ttl, EventLoop loop) {
for (;;) {
// We currently don't calculate a new TTL when we need to retry the CAS as we don't expect this to
// be invoked very concurrently and also we use SECONDS anyway. If this ever becomes a problem
// we can reconsider.
ScheduledFuture<?> oldFuture = FUTURE_UPDATER.get(this);
if (oldFuture == null || oldFuture.getDelay(TimeUnit.SECONDS) > ttl) {
ScheduledFuture<?> newFuture = loop.schedule(this, ttl, TimeUnit.SECONDS);
// It is possible that
// 1. task will fire in between this line, or
// 2. multiple timers may be set if there is concurrency
// (1) Shouldn't be a problem because we will fail the CAS and then the next loop will see CANCELLED
// so the ttl will not be less, and we will bail out of the loop.
// (2) This is a trade-off to avoid concurrency resulting in contention on a synchronized block.
if (FUTURE_UPDATER.compareAndSet(this, oldFuture, newFuture)) {
if (oldFuture != null) {
oldFuture.cancel(true);
}
break;
} else {
// There was something else scheduled in the meantime... Cancel and try again.
newFuture.cancel(true);
}
} else {
break;
}
}
}
boolean clearAndCancel() {
List<DefaultDnsCacheEntry> entries = getAndSet(Collections.<DefaultDnsCacheEntry>emptyList());
if (entries.isEmpty()) {
return false;
}
ScheduledFuture<?> expirationFuture = FUTURE_UPDATER.getAndSet(this, CANCELLED);
if (expirationFuture != null) {
expirationFuture.cancel(false);
}
return true;
}
@Override
public void run() {
// We always remove all entries for a hostname once one entry expire. This is not the
// most efficient to do but this way we can guarantee that if a DnsResolver
// be configured to prefer one ip family over the other we will not return unexpected
// results to the enduser if one of the A or AAAA records has different TTL settings.
//
// As a TTL is just a hint of the maximum time a cache is allowed to cache stuff it's
// completely fine to remove the entry even if the TTL is not reached yet.
//
// See https://github.com/netty/netty/issues/7329
resolveCache.remove(hostname, this);
clearAndCancel();
}
}
private static String appendDot(String hostname) {
return StringUtil.endsWith(hostname, '.') ? hostname : hostname + '.';
}

View File

@ -50,7 +50,6 @@ public final class DefaultDnsServerAddressStreamProvider implements DnsServerAdd
public static final DefaultDnsServerAddressStreamProvider INSTANCE = new DefaultDnsServerAddressStreamProvider();
private static final List<InetSocketAddress> DEFAULT_NAME_SERVER_LIST;
private static final InetSocketAddress[] DEFAULT_NAME_SERVER_ARRAY;
private static final DnsServerAddresses DEFAULT_NAME_SERVERS;
static final int DNS_PORT = 53;
@ -142,8 +141,7 @@ public final class DefaultDnsServerAddressStreamProvider implements DnsServerAdd
}
DEFAULT_NAME_SERVER_LIST = Collections.unmodifiableList(defaultNameServers);
DEFAULT_NAME_SERVER_ARRAY = defaultNameServers.toArray(new InetSocketAddress[0]);
DEFAULT_NAME_SERVERS = sequential(DEFAULT_NAME_SERVER_ARRAY);
DEFAULT_NAME_SERVERS = sequential(DEFAULT_NAME_SERVER_LIST);
}
private DefaultDnsServerAddressStreamProvider() {
@ -177,12 +175,4 @@ public final class DefaultDnsServerAddressStreamProvider implements DnsServerAdd
public static DnsServerAddresses defaultAddresses() {
return DEFAULT_NAME_SERVERS;
}
/**
* Get the array form of {@link #defaultAddressList()}.
* @return The array form of {@link #defaultAddressList()}.
*/
static InetSocketAddress[] defaultAddressArray() {
return DEFAULT_NAME_SERVER_ARRAY.clone();
}
}

View File

@ -17,16 +17,17 @@
package io.netty.resolver.dns;
import java.net.InetSocketAddress;
import java.util.List;
abstract class DefaultDnsServerAddresses extends DnsServerAddresses {
protected final InetSocketAddress[] addresses;
protected final List<InetSocketAddress> addresses;
private final String strVal;
DefaultDnsServerAddresses(String type, InetSocketAddress[] addresses) {
DefaultDnsServerAddresses(String type, List<InetSocketAddress> addresses) {
this.addresses = addresses;
final StringBuilder buf = new StringBuilder(type.length() + 2 + addresses.length * 16);
final StringBuilder buf = new StringBuilder(type.length() + 2 + addresses.size() * 16);
buf.append(type).append('(');
for (InetSocketAddress a: addresses) {

View File

@ -30,11 +30,14 @@ import io.netty.util.concurrent.Promise;
final class DnsAddressResolveContext extends DnsResolveContext<InetAddress> {
private final DnsCache resolveCache;
private final AuthoritativeDnsServerCache authoritativeDnsServerCache;
DnsAddressResolveContext(DnsNameResolver parent, String hostname, DnsRecord[] additionals,
DnsServerAddressStream nameServerAddrs, DnsCache resolveCache) {
DnsServerAddressStream nameServerAddrs, DnsCache resolveCache,
AuthoritativeDnsServerCache authoritativeDnsServerCache) {
super(parent, hostname, DnsRecord.CLASS_IN, parent.resolveRecordTypes(), additionals, nameServerAddrs);
this.resolveCache = resolveCache;
this.authoritativeDnsServerCache = authoritativeDnsServerCache;
}
@Override
@ -42,7 +45,8 @@ final class DnsAddressResolveContext extends DnsResolveContext<InetAddress> {
int dnsClass, DnsRecordType[] expectedTypes,
DnsRecord[] additionals,
DnsServerAddressStream nameServerAddrs) {
return new DnsAddressResolveContext(parent, hostname, additionals, nameServerAddrs, resolveCache);
return new DnsAddressResolveContext(parent, hostname, additionals, nameServerAddrs, resolveCache,
authoritativeDnsServerCache);
}
@Override
@ -89,8 +93,19 @@ final class DnsAddressResolveContext extends DnsResolveContext<InetAddress> {
@Override
void doSearchDomainQuery(String hostname, Promise<List<InetAddress>> nextPromise) {
// Query the cache for the hostname first and only do a query if we could not find it in the cache.
if (!parent.doResolveAllCached(hostname, additionals, nextPromise, resolveCache)) {
if (!DnsNameResolver.doResolveAllCached(
hostname, additionals, nextPromise, resolveCache, parent.resolvedInternetProtocolFamiliesUnsafe())) {
super.doSearchDomainQuery(hostname, nextPromise);
}
}
@Override
DnsCache resolveCache() {
return resolveCache;
}
@Override
AuthoritativeDnsServerCache authoritativeDnsServerCache() {
return authoritativeDnsServerCache;
}
}

View File

@ -67,6 +67,7 @@ import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
@ -153,6 +154,8 @@ public class DnsNameResolver extends InetNameResolver {
final Future<Channel> channelFuture;
final DatagramChannel ch;
// Comparator that ensures we will try first to use the nameservers that use our preferred address type.
private final Comparator<InetSocketAddress> nameServerComparator;
/**
* Manages the {@link DnsQueryContext}s in progress and their query IDs.
*/
@ -162,12 +165,12 @@ public class DnsNameResolver extends InetNameResolver {
* Cache for {@link #doResolve(String, Promise)} and {@link #doResolveAll(String, Promise)}.
*/
private final DnsCache resolveCache;
private final DnsCache authoritativeDnsServerCache;
private final AuthoritativeDnsServerCache authoritativeDnsServerCache;
private final FastThreadLocal<DnsServerAddressStream> nameServerAddrStream =
new FastThreadLocal<DnsServerAddressStream>() {
@Override
protected DnsServerAddressStream initialValue() throws Exception {
protected DnsServerAddressStream initialValue() {
return dnsServerAddressStreamProvider.nameServerAddressStream("");
}
};
@ -214,7 +217,11 @@ public class DnsNameResolver extends InetNameResolver {
* @param ndots the ndots value
* @param decodeIdn {@code true} if domain / host names should be decoded to unicode when received.
* See <a href="https://tools.ietf.org/html/rfc3492">rfc3492</a>.
* @deprecated Use {@link DnsNameResolver(EventLoop, ChannelFactory, DnsCache, AuthoritativeDnsServerCache,
* DnsQueryLifecycleObserverFactory, long, ResolvedAddressTypes, boolean, int, boolean, int, boolean,
* HostsFileEntriesResolver, DnsServerAddressStreamProvider, String[], int, boolean)}
*/
@Deprecated
public DnsNameResolver(
EventLoop eventLoop,
ChannelFactory<? extends DatagramChannel> channelFactory,
@ -233,6 +240,56 @@ public class DnsNameResolver extends InetNameResolver {
String[] searchDomains,
int ndots,
boolean decodeIdn) {
this(eventLoop, channelFactory, resolveCache,
new AuthoritativeDnsServerCacheAdapter(authoritativeDnsServerCache), dnsQueryLifecycleObserverFactory,
queryTimeoutMillis, resolvedAddressTypes, recursionDesired, maxQueriesPerResolve, traceEnabled,
maxPayloadSize, optResourceEnabled, hostsFileEntriesResolver, dnsServerAddressStreamProvider,
searchDomains, ndots, 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 resolveCache the DNS resolved entries cache
* @param authoritativeDnsServerCache the cache used to find the authoritative DNS server for a domain
* @param dnsQueryLifecycleObserverFactory used to generate new instances of {@link DnsQueryLifecycleObserver} which
* can be used to track metrics for DNS servers.
* @param queryTimeoutMillis timeout of each DNS query in millis
* @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
* @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 dnsServerAddressStreamProvider The {@link DnsServerAddressStreamProvider} used to determine the name
* servers for each hostname lookup.
* @param searchDomains the list of search domain
* (can be null, if so, will try to default to the underlying platform ones)
* @param ndots the ndots value
* @param decodeIdn {@code true} if domain / host names should be decoded to unicode when received.
* See <a href="https://tools.ietf.org/html/rfc3492">rfc3492</a>.
*/
public DnsNameResolver(
EventLoop eventLoop,
ChannelFactory<? extends DatagramChannel> channelFactory,
final DnsCache resolveCache,
final AuthoritativeDnsServerCache authoritativeDnsServerCache,
DnsQueryLifecycleObserverFactory dnsQueryLifecycleObserverFactory,
long queryTimeoutMillis,
ResolvedAddressTypes resolvedAddressTypes,
boolean recursionDesired,
int maxQueriesPerResolve,
boolean traceEnabled,
int maxPayloadSize,
boolean optResourceEnabled,
HostsFileEntriesResolver hostsFileEntriesResolver,
DnsServerAddressStreamProvider dnsServerAddressStreamProvider,
String[] searchDomains,
int ndots,
boolean decodeIdn) {
super(eventLoop);
this.queryTimeoutMillis = checkPositive(queryTimeoutMillis, "queryTimeoutMillis");
this.resolvedAddressTypes = resolvedAddressTypes != null ? resolvedAddressTypes : DEFAULT_RESOLVE_ADDRESS_TYPES;
@ -244,12 +301,11 @@ public class DnsNameResolver extends InetNameResolver {
this.dnsServerAddressStreamProvider =
checkNotNull(dnsServerAddressStreamProvider, "dnsServerAddressStreamProvider");
this.resolveCache = checkNotNull(resolveCache, "resolveCache");
this.authoritativeDnsServerCache = checkNotNull(authoritativeDnsServerCache, "authoritativeDnsServerCache");
this.dnsQueryLifecycleObserverFactory = traceEnabled ?
dnsQueryLifecycleObserverFactory instanceof NoopDnsQueryLifecycleObserverFactory ?
new TraceDnsQueryLifeCycleObserverFactory() :
new BiDnsQueryLifecycleObserverFactory(new TraceDnsQueryLifeCycleObserverFactory(),
dnsQueryLifecycleObserverFactory) :
dnsQueryLifecycleObserverFactory instanceof NoopDnsQueryLifecycleObserverFactory ?
new TraceDnsQueryLifeCycleObserverFactory() :
new BiDnsQueryLifecycleObserverFactory(new TraceDnsQueryLifeCycleObserverFactory(),
dnsQueryLifecycleObserverFactory) :
checkNotNull(dnsQueryLifecycleObserverFactory, "dnsQueryLifecycleObserverFactory");
this.searchDomains = searchDomains != null ? searchDomains.clone() : DEFAULT_SEARCH_DOMAINS;
this.ndots = ndots >= 0 ? ndots : DEFAULT_NDOTS;
@ -261,32 +317,31 @@ public class DnsNameResolver extends InetNameResolver {
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);
}
preferredAddressType = preferredAddressType(resolvedAddressTypes);
this.authoritativeDnsServerCache = checkNotNull(authoritativeDnsServerCache, "authoritativeDnsServerCache");
nameServerComparator = new NameServerComparator(preferredAddressType.addressType());
Bootstrap b = new Bootstrap();
b.group(executor());
@ -313,9 +368,22 @@ public class DnsNameResolver extends InetNameResolver {
});
}
static InternetProtocolFamily preferredAddressType(ResolvedAddressTypes resolvedAddressTypes) {
switch (resolvedAddressTypes) {
case IPV4_ONLY:
case IPV4_PREFERRED:
return InternetProtocolFamily.IPv4;
case IPV6_ONLY:
case IPV6_PREFERRED:
return InternetProtocolFamily.IPv6;
default:
throw new IllegalArgumentException("Unknown ResolvedAddressTypes " + resolvedAddressTypes);
}
}
// Only here to override in unit tests.
int dnsRedirectPort(@SuppressWarnings("unused") InetAddress server) {
return DNS_PORT;
InetSocketAddress newRedirectServerAddress(InetAddress server) {
return new InetSocketAddress(server, DNS_PORT);
}
final DnsQueryLifecycleObserverFactory dnsQueryLifecycleObserverFactory() {
@ -323,12 +391,20 @@ public class DnsNameResolver extends InetNameResolver {
}
/**
* Provides the opportunity to sort the name servers before following a redirected DNS query.
* @param nameServers The addresses of the DNS servers which are used in the event of a redirect.
* @return A {@link DnsServerAddressStream} which will be used to follow the DNS redirect.
* Creates a new {@link DnsServerAddressStream} to following a redirected DNS query. By overriding this
* it provides the opportunity to sort the name servers before following a redirected DNS query.
*
* @param hostname the hostname.
* @param nameservers The addresses of the DNS servers which are used in the event of a redirect. This may
* contain resolved and unresolved addresses so the used {@link DnsServerAddressStream} must
* allow unresolved addresses if you want to include these as well.
* @return A {@link DnsServerAddressStream} which will be used to follow the DNS redirect or {@code null} if
* none should be followed.
*/
protected DnsServerAddressStream uncachedRedirectDnsServerStream(List<InetSocketAddress> nameServers) {
return DnsServerAddresses.sequential(nameServers).stream();
protected DnsServerAddressStream newRedirectDnsServerStream(
@SuppressWarnings("unused") String hostname, List<InetSocketAddress> nameservers) {
Collections.sort(nameservers, nameServerComparator);
return new SequentialDnsServerAddressStream(nameservers, 0);
}
/**
@ -341,7 +417,7 @@ public class DnsNameResolver extends InetNameResolver {
/**
* Returns the cache used for authoritative DNS servers for a domain.
*/
public DnsCache authoritativeDnsServerCache() {
public AuthoritativeDnsServerCache authoritativeDnsServerCache() {
return authoritativeDnsServerCache;
}
@ -785,15 +861,16 @@ public class DnsNameResolver extends InetNameResolver {
return;
}
if (!doResolveAllCached(hostname, additionals, promise, resolveCache)) {
if (!doResolveAllCached(hostname, additionals, promise, resolveCache, resolvedInternetProtocolFamilies)) {
doResolveAllUncached(hostname, additionals, promise, resolveCache);
}
}
boolean doResolveAllCached(String hostname,
DnsRecord[] additionals,
Promise<List<InetAddress>> promise,
DnsCache resolveCache) {
static boolean doResolveAllCached(String hostname,
DnsRecord[] additionals,
Promise<List<InetAddress>> promise,
DnsCache resolveCache,
InternetProtocolFamily[] resolvedInternetProtocolFamilies) {
final List<? extends DnsCacheEntry> cachedEntries = resolveCache.get(hostname, additionals);
if (cachedEntries == null || cachedEntries.isEmpty()) {
return false;
@ -831,7 +908,8 @@ public class DnsNameResolver extends InetNameResolver {
DnsCache resolveCache) {
final DnsServerAddressStream nameServerAddrs =
dnsServerAddressStreamProvider.nameServerAddressStream(hostname);
new DnsAddressResolveContext(this, hostname, additionals, nameServerAddrs, resolveCache).resolve(promise);
new DnsAddressResolveContext(this, hostname, additionals, nameServerAddrs,
resolveCache, authoritativeDnsServerCache).resolve(promise);
}
private static String hostname(String inetHost) {
@ -958,6 +1036,10 @@ public class DnsNameResolver extends InetNameResolver {
return (Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>>) promise;
}
final DnsServerAddressStream newNameServerAddressStream(String hostname) {
return dnsServerAddressStreamProvider.nameServerAddressStream(hostname);
}
private final class DnsResponseHandler extends ChannelInboundHandlerAdapter {
private final Promise<Channel> channelActivePromise;
@ -967,7 +1049,7 @@ public class DnsNameResolver extends InetNameResolver {
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
public void channelRead(ChannelHandlerContext ctx, Object msg) {
try {
final DatagramDnsResponse res = (DatagramDnsResponse) msg;
final int queryId = res.id();
@ -995,7 +1077,7 @@ public class DnsNameResolver extends InetNameResolver {
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
logger.warn("{} Unexpected exception: ", ch, cause);
}
}

View File

@ -40,7 +40,7 @@ public final class DnsNameResolverBuilder {
private EventLoop eventLoop;
private ChannelFactory<? extends DatagramChannel> channelFactory;
private DnsCache resolveCache;
private DnsCache authoritativeDnsServerCache;
private AuthoritativeDnsServerCache authoritativeDnsServerCache;
private Integer minTtl;
private Integer maxTtl;
private Integer negativeTtl;
@ -139,8 +139,21 @@ public final class DnsNameResolverBuilder {
*
* @param authoritativeDnsServerCache the authoritative NS servers cache
* @return {@code this}
* @deprecated Use {@link #authoritativeDnsServerCache(AuthoritativeDnsServerCache)}
*/
@Deprecated
public DnsNameResolverBuilder authoritativeDnsServerCache(DnsCache authoritativeDnsServerCache) {
this.authoritativeDnsServerCache = new AuthoritativeDnsServerCacheAdapter(authoritativeDnsServerCache);
return this;
}
/**
* Sets the cache for authoritative NS servers
*
* @param authoritativeDnsServerCache the authoritative NS servers cache
* @return {@code this}
*/
public DnsNameResolverBuilder authoritativeDnsServerCache(AuthoritativeDnsServerCache authoritativeDnsServerCache) {
this.authoritativeDnsServerCache = authoritativeDnsServerCache;
return this;
}
@ -355,6 +368,14 @@ public final class DnsNameResolverBuilder {
return new DefaultDnsCache(intValue(minTtl, 0), intValue(maxTtl, Integer.MAX_VALUE), intValue(negativeTtl, 0));
}
private AuthoritativeDnsServerCache newAuthoritativeDnsServerCache() {
return new DefaultAuthoritativeDnsServerCache(
intValue(minTtl, 0), intValue(maxTtl, Integer.MAX_VALUE),
// Let us use the sane ordering as DnsNameResolver will be used when returning
// nameservers from the cache.
new NameServerComparator(DnsNameResolver.preferredAddressType(resolvedAddressTypes).addressType()));
}
/**
* Set if domain / host names should be decoded to unicode when received.
* See <a href="https://tools.ietf.org/html/rfc3492">rfc3492</a>.
@ -386,8 +407,8 @@ public final class DnsNameResolverBuilder {
}
DnsCache resolveCache = this.resolveCache != null ? this.resolveCache : newCache();
DnsCache authoritativeDnsServerCache = this.authoritativeDnsServerCache != null ?
this.authoritativeDnsServerCache : newCache();
AuthoritativeDnsServerCache authoritativeDnsServerCache = this.authoritativeDnsServerCache != null ?
this.authoritativeDnsServerCache : newAuthoritativeDnsServerCache();
return new DnsNameResolver(
eventLoop,
channelFactory,

View File

@ -44,6 +44,7 @@ import io.netty.util.internal.ThrowableUtil;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@ -52,12 +53,11 @@ import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import static io.netty.resolver.dns.DnsAddressDecoder.decodeAddress;
import static io.netty.resolver.dns.DnsNameResolver.trySuccess;
import static java.lang.Math.min;
import static java.util.Collections.unmodifiableList;
abstract class DnsResolveContext<T> {
@ -124,6 +124,20 @@ abstract class DnsResolveContext<T> {
allowedQueries = maxAllowedQueries;
}
/**
* The {@link DnsCache} to use while resolving.
*/
DnsCache resolveCache() {
return parent.resolveCache();
}
/**
* The {@link AuthoritativeDnsServerCache} to use while resolving.
*/
AuthoritativeDnsServerCache authoritativeDnsServerCache() {
return parent.authoritativeDnsServerCache();
}
/**
* Creates a new context with the given parameters.
*/
@ -158,7 +172,7 @@ abstract class DnsResolveContext<T> {
void resolve(final Promise<List<T>> promise) {
final String[] searchDomains = parent.searchDomains();
if (searchDomains.length == 0 || parent.ndots() == 0 || StringUtil.endsWith(hostname, '.')) {
internalResolve(promise);
internalResolve(hostname, promise);
} else {
final boolean startWithoutSearchDomain = hasNDots();
final String initialHostname = startWithoutSearchDomain ? hostname : hostname + '.' + searchDomains[0];
@ -180,7 +194,7 @@ abstract class DnsResolveContext<T> {
newPromise.addListener(this);
doSearchDomainQuery(hostname + '.' + searchDomains[searchDomainIdx++], newPromise);
} else if (!startWithoutSearchDomain) {
internalResolve(promise);
internalResolve(hostname, promise);
} else {
promise.tryFailure(new SearchDomainUnknownHostException(cause, hostname));
}
@ -220,31 +234,19 @@ abstract class DnsResolveContext<T> {
void doSearchDomainQuery(String hostname, Promise<List<T>> nextPromise) {
DnsResolveContext<T> nextContext = newResolverContext(parent, hostname, dnsClass, expectedTypes,
additionals, nameServerAddrs);
nextContext.internalResolve(nextPromise);
nextContext.internalResolve(hostname, nextPromise);
}
private void internalResolve(Promise<List<T>> promise) {
DnsServerAddressStream nameServerAddressStream = getNameServers(hostname);
private void internalResolve(String name, Promise<List<T>> promise) {
DnsServerAddressStream nameServerAddressStream = getNameServers(name);
final int end = expectedTypes.length - 1;
for (int i = 0; i < end; ++i) {
if (!query(hostname, expectedTypes[i], nameServerAddressStream.duplicate(), promise)) {
if (!query(name, expectedTypes[i], nameServerAddressStream.duplicate(), promise)) {
return;
}
}
query(hostname, expectedTypes[end], nameServerAddressStream, promise);
}
/**
* Add an authoritative nameserver to the cache if its not a root server.
*/
private void addNameServerToCache(
AuthoritativeNameServer name, InetAddress resolved, long ttl) {
if (!name.isRootServer()) {
// Cache NS record if not for a root server as we should never cache for root servers.
parent.authoritativeDnsServerCache().cache(name.domainName(),
additionals, resolved, ttl, parent.ch.eventLoop());
}
query(name, expectedTypes[end], nameServerAddressStream, promise);
}
/**
@ -282,44 +284,15 @@ abstract class DnsResolveContext<T> {
}
idx = idx2;
List<? extends DnsCacheEntry> entries = parent.authoritativeDnsServerCache().get(hostname, additionals);
if (entries != null && !entries.isEmpty()) {
return DnsServerAddresses.sequential(new DnsCacheIterable(entries)).stream();
DnsServerAddressStream entries = authoritativeDnsServerCache().get(hostname);
if (entries != null) {
// The returned List may contain unresolved InetSocketAddress instances that will be
// resolved on the fly in query(....).
return entries;
}
}
}
private final class DnsCacheIterable implements Iterable<InetSocketAddress> {
private final List<? extends DnsCacheEntry> entries;
DnsCacheIterable(List<? extends DnsCacheEntry> entries) {
this.entries = entries;
}
@Override
public Iterator<InetSocketAddress> iterator() {
return new Iterator<InetSocketAddress>() {
Iterator<? extends DnsCacheEntry> entryIterator = entries.iterator();
@Override
public boolean hasNext() {
return entryIterator.hasNext();
}
@Override
public InetSocketAddress next() {
InetAddress address = entryIterator.next().address();
return new InetSocketAddress(address, parent.dnsRedirectPort(address));
}
@Override
public void remove() {
entryIterator.remove();
}
};
}
}
private void query(final DnsServerAddressStream nameServerAddrStream, final int nameServerAddrStreamIndex,
final DnsQuestion question,
final Promise<List<T>> promise, Throwable cause) {
@ -340,7 +313,13 @@ abstract class DnsResolveContext<T> {
}
--allowedQueries;
final InetSocketAddress nameServerAddr = nameServerAddrStream.next();
if (nameServerAddr.isUnresolved()) {
queryUnresolvedNameserver(nameServerAddr, nameServerAddrStream, nameServerAddrStreamIndex, question,
queryLifecycleObserver, promise, cause);
return;
}
final ChannelPromise writePromise = parent.ch.newPromise();
final Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> f = parent.query0(
nameServerAddr, question, additionals, writePromise,
@ -387,10 +366,77 @@ abstract class DnsResolveContext<T> {
});
}
private void queryUnresolvedNameserver(final InetSocketAddress nameServerAddr,
final DnsServerAddressStream nameServerAddrStream,
final int nameServerAddrStreamIndex,
final DnsQuestion question,
final DnsQueryLifecycleObserver queryLifecycleObserver,
final Promise<List<T>> promise,
final Throwable cause) {
final String nameServerName = PlatformDependent.javaVersion() >= 7 ?
nameServerAddr.getHostString() : nameServerAddr.getHostName();
assert nameServerName != null;
// Placeholder so we will not try to finish the original query yet.
final Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> resolveFuture = parent.executor()
.newSucceededFuture(null);
queriesInProgress.add(resolveFuture);
Promise<List<InetAddress>> resolverPromise = parent.executor().newPromise();
resolverPromise.addListener(new FutureListener<List<InetAddress>>() {
@Override
public void operationComplete(final Future<List<InetAddress>> future) {
// Remove placeholder.
queriesInProgress.remove(resolveFuture);
if (future.isSuccess()) {
List<InetAddress> resolvedAddresses = future.getNow();
DnsServerAddressStream addressStream = new CombinedDnsServerAddressStream(
nameServerAddr, resolvedAddresses, nameServerAddrStream);
query(addressStream, nameServerAddrStreamIndex, question,
queryLifecycleObserver, promise, cause);
} else {
// Ignore the server and try the next one...
query(nameServerAddrStream, nameServerAddrStreamIndex + 1,
question, queryLifecycleObserver, promise, cause);
}
}
});
if (!DnsNameResolver.doResolveAllCached(nameServerName, additionals, resolverPromise, resolveCache(),
parent.resolvedInternetProtocolFamiliesUnsafe())) {
final AuthoritativeDnsServerCache authoritativeDnsServerCache = authoritativeDnsServerCache();
new DnsAddressResolveContext(parent, nameServerName, additionals,
parent.newNameServerAddressStream(nameServerName),
resolveCache(), new AuthoritativeDnsServerCache() {
@Override
public DnsServerAddressStream get(String hostname) {
// To not risk falling into any loop, we will not use the cache while following redirects but only
// on the initial query.
return null;
}
@Override
public void cache(String hostname, InetSocketAddress address, long originalTtl, EventLoop loop) {
authoritativeDnsServerCache.cache(hostname, address, originalTtl, loop);
}
@Override
public void clear() {
authoritativeDnsServerCache.clear();
}
@Override
public boolean clear(String hostname) {
return authoritativeDnsServerCache.clear(hostname);
}
}).resolve(resolverPromise);
}
}
private void onResponse(final DnsServerAddressStream nameServerAddrStream, final int nameServerAddrStreamIndex,
final DnsQuestion question, AddressedEnvelope<DnsResponse, InetSocketAddress> envelope,
final DnsQueryLifecycleObserver queryLifecycleObserver,
Promise<List<T>> promise) {
final DnsQuestion question, AddressedEnvelope<DnsResponse, InetSocketAddress> envelope,
final DnsQueryLifecycleObserver queryLifecycleObserver,
Promise<List<T>> promise) {
try {
final DnsResponse res = envelope.content();
final DnsResponseCode code = res.code();
@ -440,11 +486,10 @@ abstract class DnsResolveContext<T> {
// Check if we have answers, if not this may be an non authority NS and so redirects must be handled.
if (res.count(DnsSection.ANSWER) == 0) {
AuthoritativeNameServerList serverNames = extractAuthoritativeNameServers(question.name(), res);
if (serverNames != null) {
List<InetSocketAddress> nameServers = new ArrayList<InetSocketAddress>(serverNames.size());
int additionalCount = res.count(DnsSection.ADDITIONAL);
AuthoritativeDnsServerCache authoritativeDnsServerCache = authoritativeDnsServerCache();
for (int i = 0; i < additionalCount; i++) {
final DnsRecord r = res.recordAt(DnsSection.ADDITIONAL, i);
@ -453,27 +498,24 @@ abstract class DnsResolveContext<T> {
continue;
}
final String recordName = r.name();
final AuthoritativeNameServer authoritativeNameServer = serverNames.remove(recordName);
if (authoritativeNameServer == null) {
// Not a server we are interested in.
continue;
}
InetAddress resolved = decodeAddress(r, recordName, parent.isDecodeIdn());
if (resolved == null) {
// Could not parse it, move to the next.
continue;
}
nameServers.add(new InetSocketAddress(resolved, parent.dnsRedirectPort(resolved)));
addNameServerToCache(authoritativeNameServer, resolved, r.timeToLive());
// We may have multiple ADDITIONAL entries for the same nameserver name. For example one AAAA and
// one A record.
serverNames.handleWithAdditional(parent, r, authoritativeDnsServerCache);
}
if (!nameServers.isEmpty()) {
query(parent.uncachedRedirectDnsServerStream(nameServers), 0, question,
queryLifecycleObserver.queryRedirected(unmodifiableList(nameServers)), promise, null);
// Process all unresolved nameservers as well.
serverNames.handleWithoutAdditionals(parent, resolveCache(), authoritativeDnsServerCache);
List<InetSocketAddress> addresses = serverNames.addressList();
// Give the user the chance to sort or filter the used servers for the query.
DnsServerAddressStream serverStream = parent.newRedirectDnsServerStream(
question.name(), addresses);
if (serverStream != null) {
query(serverStream, 0, question,
queryLifecycleObserver.queryRedirected(new DnsAddressStreamList(serverStream)),
promise, null);
return true;
}
}
@ -481,6 +523,60 @@ abstract class DnsResolveContext<T> {
return false;
}
private static final class DnsAddressStreamList extends AbstractList<InetSocketAddress> {
private final DnsServerAddressStream duplicate;
private List<InetSocketAddress> addresses;
DnsAddressStreamList(DnsServerAddressStream stream) {
duplicate = stream.duplicate();
}
@Override
public InetSocketAddress get(int index) {
if (addresses == null) {
DnsServerAddressStream stream = duplicate.duplicate();
addresses = new ArrayList<InetSocketAddress>(size());
for (int i = 0; i < stream.size(); i++) {
addresses.add(stream.next());
}
}
return addresses.get(index);
}
@Override
public int size() {
return duplicate.size();
}
@Override
public Iterator<InetSocketAddress> iterator() {
return new Iterator<InetSocketAddress>() {
private final DnsServerAddressStream stream = duplicate.duplicate();
private int i;
@Override
public boolean hasNext() {
return i < stream.size();
}
@Override
public InetSocketAddress next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
i++;
return stream.next();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}
/**
* Returns the {@code {@link AuthoritativeNameServerList} which were included in {@link DnsSection#AUTHORITY}
* or {@code null} if non are found.
@ -495,7 +591,7 @@ abstract class DnsResolveContext<T> {
for (int i = 0; i < authorityCount; i++) {
serverNames.add(res.recordAt(DnsSection.AUTHORITY, i));
}
return serverNames;
return serverNames.isEmpty() ? null : serverNames;
}
private void onExpectedResponse(
@ -542,7 +638,7 @@ abstract class DnsResolveContext<T> {
}
}
final T converted = convertRecord(r, hostname, additionals, parent.ch.eventLoop());
final T converted = convertRecord(r, hostname, additionals, parent.executor());
if (converted == null) {
continue;
}
@ -630,11 +726,11 @@ abstract class DnsResolveContext<T> {
}
private void tryToFinishResolve(final DnsServerAddressStream nameServerAddrStream,
final int nameServerAddrStreamIndex,
final DnsQuestion question,
final DnsQueryLifecycleObserver queryLifecycleObserver,
final Promise<List<T>> promise,
final Throwable cause) {
final int nameServerAddrStreamIndex,
final DnsQuestion question,
final DnsQueryLifecycleObserver queryLifecycleObserver,
final Promise<List<T>> promise,
final Throwable cause) {
// There are no queries left to try.
if (!queriesInProgress.isEmpty()) {
@ -697,7 +793,7 @@ abstract class DnsResolveContext<T> {
if (finalResult != null) {
// Found at least one resolved record.
trySuccess(promise, filterResults(finalResult));
DnsNameResolver.trySuccess(promise, filterResults(finalResult));
return;
}
@ -776,6 +872,48 @@ abstract class DnsResolveContext<T> {
return true;
}
private final class CombinedDnsServerAddressStream implements DnsServerAddressStream {
private final InetSocketAddress replaced;
private final DnsServerAddressStream originalStream;
private final List<InetAddress> resolvedAddresses;
private Iterator<InetAddress> resolved;
CombinedDnsServerAddressStream(InetSocketAddress replaced, List<InetAddress> resolvedAddresses,
DnsServerAddressStream originalStream) {
this.replaced = replaced;
this.resolvedAddresses = resolvedAddresses;
this.originalStream = originalStream;
resolved = resolvedAddresses.iterator();
}
@Override
public InetSocketAddress next() {
if (resolved.hasNext()) {
return nextResolved0();
}
InetSocketAddress address = originalStream.next();
if (address.equals(replaced)) {
resolved = resolvedAddresses.iterator();
return nextResolved0();
}
return address;
}
private InetSocketAddress nextResolved0() {
return parent.newRedirectServerAddress(resolved.next());
}
@Override
public int size() {
return originalStream.size() + resolvedAddresses.size() - 1;
}
@Override
public DnsServerAddressStream duplicate() {
return new CombinedDnsServerAddressStream(replaced, resolvedAddresses, originalStream.duplicate());
}
}
/**
* Holds the closed DNS Servers for a domain.
*/
@ -785,7 +923,8 @@ abstract class DnsResolveContext<T> {
// We not expect the linked-list to be very long so a double-linked-list is overkill.
private AuthoritativeNameServer head;
private int count;
private int nameServerCount;
AuthoritativeNameServerList(String questionName) {
this.questionName = questionName.toLowerCase(Locale.US);
@ -829,50 +968,164 @@ abstract class DnsResolveContext<T> {
// We are only interested in preserving the nameservers which are the closest to our qName, so ensure
// we drop servers that have a smaller dots count.
if (head == null || head.dots < dots) {
count = 1;
head = new AuthoritativeNameServer(dots, recordName, domainName);
nameServerCount = 1;
head = new AuthoritativeNameServer(dots, r.timeToLive(), recordName, domainName);
} else if (head.dots == dots) {
AuthoritativeNameServer serverName = head;
while (serverName.next != null) {
serverName = serverName.next;
}
serverName.next = new AuthoritativeNameServer(dots, recordName, domainName);
count++;
serverName.next = new AuthoritativeNameServer(dots, r.timeToLive(), recordName, domainName);
nameServerCount++;
}
}
// Just walk the linked-list and mark the entry as removed when matched, so next lookup will need to process
// one node less.
AuthoritativeNameServer remove(String nsName) {
void handleWithAdditional(
DnsNameResolver parent, DnsRecord r, AuthoritativeDnsServerCache authoritativeCache) {
// Just walk the linked-list and mark the entry as handled when matched.
AuthoritativeNameServer serverName = head;
String nsName = r.name();
InetAddress resolved = decodeAddress(r, nsName, parent.isDecodeIdn());
if (resolved == null) {
// Could not parse the address, just ignore.
return;
}
while (serverName != null) {
if (!serverName.removed && serverName.nsName.equalsIgnoreCase(nsName)) {
serverName.removed = true;
return serverName;
if (serverName.nsName.equalsIgnoreCase(nsName)) {
if (serverName.address != null) {
// We received multiple ADDITIONAL records for the same name.
// Search for the last we insert before and then append a new one.
while (serverName.next != null && serverName.next.isCopy) {
serverName = serverName.next;
}
AuthoritativeNameServer server = new AuthoritativeNameServer(serverName);
server.next = serverName.next;
serverName.next = server;
serverName = server;
nameServerCount++;
}
// We should replace the TTL if needed with the one of the ADDITIONAL record so we use
// the smallest for caching.
serverName.update(parent.newRedirectServerAddress(resolved), r.timeToLive());
// Cache the server now.
cache(serverName, authoritativeCache, parent.executor());
return;
}
serverName = serverName.next;
}
return null;
}
int size() {
return count;
// Now handle all AuthoritativeNameServer for which we had no ADDITIONAL record
void handleWithoutAdditionals(
DnsNameResolver parent, DnsCache cache, AuthoritativeDnsServerCache authoritativeCache) {
AuthoritativeNameServer serverName = head;
while (serverName != null) {
if (serverName.address == null) {
// These will be resolved on the fly if needed.
cacheUnresolved(serverName, authoritativeCache, parent.executor());
// Try to resolve via cache as we had no ADDITIONAL entry for the server.
List<? extends DnsCacheEntry> entries = cache.get(serverName.nsName, null);
if (entries != null && !entries.isEmpty()) {
InetAddress address = entries.get(0).address();
// If address is null we have a resolution failure cached so just use an unresolved address.
if (address != null) {
serverName.update(parent.newRedirectServerAddress(address));
for (int i = 1; i < entries.size(); i++) {
address = entries.get(i).address();
assert address != null :
"Cache returned a cached failure, should never return anything else";
AuthoritativeNameServer server = new AuthoritativeNameServer(serverName);
server.next = serverName.next;
serverName.next = server;
serverName = server;
serverName.update(parent.newRedirectServerAddress(address));
nameServerCount++;
}
}
}
}
serverName = serverName.next;
}
}
private static void cacheUnresolved(
AuthoritativeNameServer server, AuthoritativeDnsServerCache authoritativeCache, EventLoop loop) {
// We still want to cached the unresolved address
server.address = InetSocketAddress.createUnresolved(
server.nsName, DefaultDnsServerAddressStreamProvider.DNS_PORT);
// Cache the server now.
cache(server, authoritativeCache, loop);
}
private static void cache(AuthoritativeNameServer server, AuthoritativeDnsServerCache cache, EventLoop loop) {
// Cache NS record if not for a root server as we should never cache for root servers.
if (!server.isRootServer()) {
cache.cache(server.domainName, server.address, server.ttl, loop);
}
}
/**
* Returns {@code true} if empty, {@code false} otherwise.
*/
boolean isEmpty() {
return nameServerCount == 0;
}
/**
* Creates a new {@link List} which holds the {@link InetSocketAddress}es.
*/
List<InetSocketAddress> addressList() {
List<InetSocketAddress> addressList = new ArrayList<InetSocketAddress>(nameServerCount);
AuthoritativeNameServer server = head;
while (server != null) {
if (server.address != null) {
addressList.add(server.address);
}
server = server.next;
}
return addressList;
}
}
static final class AuthoritativeNameServer {
final int dots;
private static final class AuthoritativeNameServer {
private final int dots;
private final String domainName;
final boolean isCopy;
final String nsName;
final String domainName;
private long ttl;
private InetSocketAddress address;
AuthoritativeNameServer next;
boolean removed;
AuthoritativeNameServer(int dots, String domainName, String nsName) {
AuthoritativeNameServer(int dots, long ttl, String domainName, String nsName) {
this.dots = dots;
this.ttl = ttl;
this.nsName = nsName;
this.domainName = domainName;
isCopy = false;
}
AuthoritativeNameServer(AuthoritativeNameServer server) {
dots = server.dots;
ttl = server.ttl;
nsName = server.nsName;
domainName = server.domainName;
isCopy = true;
}
/**
@ -883,10 +1136,16 @@ abstract class DnsResolveContext<T> {
}
/**
* The domain for which the {@link AuthoritativeNameServer} is responsible.
* Update the server with the given address and TTL if needed.
*/
String domainName() {
return domainName;
void update(InetSocketAddress address, long ttl) {
assert this.address == null || this.address.isUnresolved();
this.address = address;
this.ttl = min(ttl, ttl);
}
void update(InetSocketAddress address) {
update(address, Long.MAX_VALUE);
}
}
}

View File

@ -23,8 +23,6 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import static io.netty.resolver.dns.DefaultDnsServerAddressStreamProvider.defaultAddressArray;
/**
* Provides an infinite sequence of DNS server addresses to {@link DnsNameResolver}.
*/
@ -77,9 +75,9 @@ public abstract class DnsServerAddresses {
return sequential0(sanitize(addresses));
}
private static DnsServerAddresses sequential0(final InetSocketAddress... addresses) {
if (addresses.length == 1) {
return singleton(addresses[0]);
private static DnsServerAddresses sequential0(final List<InetSocketAddress> addresses) {
if (addresses.size() == 1) {
return singleton(addresses.get(0));
}
return new DefaultDnsServerAddresses("sequential", addresses) {
@ -106,9 +104,9 @@ public abstract class DnsServerAddresses {
return shuffled0(sanitize(addresses));
}
private static DnsServerAddresses shuffled0(final InetSocketAddress[] addresses) {
if (addresses.length == 1) {
return singleton(addresses[0]);
private static DnsServerAddresses shuffled0(List<InetSocketAddress> addresses) {
if (addresses.size() == 1) {
return singleton(addresses.get(0));
}
return new DefaultDnsServerAddresses("shuffled", addresses) {
@ -139,9 +137,9 @@ public abstract class DnsServerAddresses {
return rotational0(sanitize(addresses));
}
private static DnsServerAddresses rotational0(final InetSocketAddress[] addresses) {
if (addresses.length == 1) {
return singleton(addresses[0]);
private static DnsServerAddresses rotational0(List<InetSocketAddress> addresses) {
if (addresses.size() == 1) {
return singleton(addresses.get(0));
}
return new RotationalDnsServerAddresses(addresses);
@ -161,7 +159,7 @@ public abstract class DnsServerAddresses {
return new SingletonDnsServerAddresses(address);
}
private static InetSocketAddress[] sanitize(Iterable<? extends InetSocketAddress> addresses) {
private static List<InetSocketAddress> sanitize(Iterable<? extends InetSocketAddress> addresses) {
if (addresses == null) {
throw new NullPointerException("addresses");
}
@ -187,10 +185,10 @@ public abstract class DnsServerAddresses {
throw new IllegalArgumentException("empty addresses");
}
return list.toArray(new InetSocketAddress[0]);
return list;
}
private static InetSocketAddress[] sanitize(InetSocketAddress[] addresses) {
private static List<InetSocketAddress> sanitize(InetSocketAddress[] addresses) {
if (addresses == null) {
throw new NullPointerException("addresses");
}
@ -207,10 +205,10 @@ public abstract class DnsServerAddresses {
}
if (list.isEmpty()) {
return defaultAddressArray();
return DefaultDnsServerAddressStreamProvider.defaultAddressList();
}
return list.toArray(new InetSocketAddress[0]);
return list;
}
/**

View File

@ -0,0 +1,61 @@
/*
* Copyright 2018 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.resolver.dns;
import io.netty.util.internal.ObjectUtil;
import java.io.Serializable;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Comparator;
import java.util.List;
/**
* Special {@link Comparator} implementation to sort the nameservers to use when follow redirects.
*
* This implementation follows all the semantics listed in the
* <a href="https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html">Comparator apidocs</a>
* with the limitation that {@link InetSocketAddress#equals(Object)} will not result in the same return value as
* {@link #compare(InetSocketAddress, InetSocketAddress)}. This is completely fine as this should only be used
* to sort {@link List}s.
*/
public final class NameServerComparator implements Comparator<InetSocketAddress>, Serializable {
private static final long serialVersionUID = 8372151874317596185L;
private final Class<? extends InetAddress> preferredAddressType;
public NameServerComparator(Class<? extends InetAddress> preferredAddressType) {
this.preferredAddressType = ObjectUtil.checkNotNull(preferredAddressType, "preferredAddressType");
}
@Override
public int compare(InetSocketAddress addr1, InetSocketAddress addr2) {
if (addr1.equals(addr2)) {
return 0;
}
if (!addr1.isUnresolved() && !addr2.isUnresolved()) {
if (addr1.getAddress().getClass() == addr2.getAddress().getClass()) {
return 0;
}
return preferredAddressType.isAssignableFrom(addr1.getAddress().getClass()) ? -1 : 1;
}
if (addr1.isUnresolved() && addr2.isUnresolved()) {
return 0;
}
return addr1.isUnresolved() ? 1 : -1;
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright 2018 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.resolver.dns;
import io.netty.channel.EventLoop;
import io.netty.util.internal.UnstableApi;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.List;
/**
* A noop {@link AuthoritativeDnsServerCache} that actually never caches anything.
*/
@UnstableApi
public final class NoopAuthoritativeDnsServerCache implements AuthoritativeDnsServerCache {
public static final NoopAuthoritativeDnsServerCache INSTANCE = new NoopAuthoritativeDnsServerCache();
private NoopAuthoritativeDnsServerCache() { }
@Override
public DnsServerAddressStream get(String hostname) {
return null;
}
@Override
public void cache(String hostname, InetSocketAddress address, long originalTtl, EventLoop loop) {
// NOOP
}
@Override
public void clear() {
// NOOP
}
@Override
public boolean clear(String hostname) {
return false;
}
}

View File

@ -17,6 +17,7 @@
package io.netty.resolver.dns;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
final class RotationalDnsServerAddresses extends DefaultDnsServerAddresses {
@ -27,7 +28,7 @@ final class RotationalDnsServerAddresses extends DefaultDnsServerAddresses {
@SuppressWarnings("UnusedDeclaration")
private volatile int startIdx;
RotationalDnsServerAddresses(InetSocketAddress[] addresses) {
RotationalDnsServerAddresses(List<InetSocketAddress> addresses) {
super("rotational", addresses);
}
@ -36,7 +37,7 @@ final class RotationalDnsServerAddresses extends DefaultDnsServerAddresses {
for (;;) {
int curStartIdx = startIdx;
int nextStartIdx = curStartIdx + 1;
if (nextStartIdx >= addresses.length) {
if (nextStartIdx >= addresses.size()) {
nextStartIdx = 0;
}
if (startIdxUpdater.compareAndSet(this, curStartIdx, nextStartIdx)) {

View File

@ -17,13 +17,15 @@
package io.netty.resolver.dns;
import java.net.InetSocketAddress;
import java.util.Collection;
import java.util.List;
final class SequentialDnsServerAddressStream implements DnsServerAddressStream {
private final InetSocketAddress[] addresses;
private final List<? extends InetSocketAddress> addresses;
private int i;
SequentialDnsServerAddressStream(InetSocketAddress[] addresses, int startIdx) {
SequentialDnsServerAddressStream(List<? extends InetSocketAddress> addresses, int startIdx) {
this.addresses = addresses;
i = startIdx;
}
@ -31,8 +33,8 @@ final class SequentialDnsServerAddressStream implements DnsServerAddressStream {
@Override
public InetSocketAddress next() {
int i = this.i;
InetSocketAddress next = addresses[i];
if (++ i < addresses.length) {
InetSocketAddress next = addresses.get(i);
if (++ i < addresses.size()) {
this.i = i;
} else {
this.i = 0;
@ -42,7 +44,7 @@ final class SequentialDnsServerAddressStream implements DnsServerAddressStream {
@Override
public int size() {
return addresses.length;
return addresses.size();
}
@Override
@ -55,8 +57,8 @@ final class SequentialDnsServerAddressStream implements DnsServerAddressStream {
return toString("sequential", i, addresses);
}
static String toString(String type, int index, InetSocketAddress[] addresses) {
final StringBuilder buf = new StringBuilder(type.length() + 2 + addresses.length * 16);
static String toString(String type, int index, Collection<? extends InetSocketAddress> addresses) {
final StringBuilder buf = new StringBuilder(type.length() + 2 + addresses.size() * 16);
buf.append(type).append("(index: ").append(index);
buf.append(", addrs: (");
for (InetSocketAddress a: addresses) {

View File

@ -19,11 +19,13 @@ package io.netty.resolver.dns;
import io.netty.util.internal.PlatformDependent;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.List;
import java.util.Random;
final class ShuffledDnsServerAddressStream implements DnsServerAddressStream {
private final InetSocketAddress[] addresses;
private final List<InetSocketAddress> addresses;
private int i;
/**
@ -31,34 +33,26 @@ final class ShuffledDnsServerAddressStream implements DnsServerAddressStream {
* @param addresses The addresses are not cloned. It is assumed the caller has cloned this array or otherwise will
* not modify the contents.
*/
ShuffledDnsServerAddressStream(InetSocketAddress[] addresses) {
ShuffledDnsServerAddressStream(List<InetSocketAddress> addresses) {
this.addresses = addresses;
shuffle();
}
private ShuffledDnsServerAddressStream(InetSocketAddress[] addresses, int startIdx) {
private ShuffledDnsServerAddressStream(List<InetSocketAddress> addresses, int startIdx) {
this.addresses = addresses;
i = startIdx;
}
private void shuffle() {
final InetSocketAddress[] addresses = this.addresses;
final Random r = PlatformDependent.threadLocalRandom();
for (int i = addresses.length - 1; i >= 0; i --) {
InetSocketAddress tmp = addresses[i];
int j = r.nextInt(i + 1);
addresses[i] = addresses[j];
addresses[j] = tmp;
}
Collections.shuffle(addresses, PlatformDependent.threadLocalRandom());
}
@Override
public InetSocketAddress next() {
int i = this.i;
InetSocketAddress next = addresses[i];
if (++ i < addresses.length) {
InetSocketAddress next = addresses.get(i);
if (++ i < addresses.size()) {
this.i = i;
} else {
this.i = 0;
@ -69,7 +63,7 @@ final class ShuffledDnsServerAddressStream implements DnsServerAddressStream {
@Override
public int size() {
return addresses.length;
return addresses.size();
}
@Override

View File

@ -0,0 +1,198 @@
/*
* Copyright 2018 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.resolver.dns;
import io.netty.channel.DefaultEventLoopGroup;
import io.netty.channel.EventLoop;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.util.NetUtil;
import org.junit.Test;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.*;
public class DefaultAuthoritativeDnsServerCacheTest {
@Test
public void testExpire() throws Throwable {
InetSocketAddress resolved1 = new InetSocketAddress(
InetAddress.getByAddress("ns1", new byte[] { 10, 0, 0, 1 }), 53);
InetSocketAddress resolved2 = new InetSocketAddress(
InetAddress.getByAddress("ns2", new byte[] { 10, 0, 0, 2 }), 53);
EventLoopGroup group = new DefaultEventLoopGroup(1);
try {
EventLoop loop = group.next();
final DefaultAuthoritativeDnsServerCache cache = new DefaultAuthoritativeDnsServerCache();
cache.cache("netty.io", resolved1, 1, loop);
cache.cache("netty.io", resolved2, 10000, loop);
Throwable error = loop.schedule(new Callable<Throwable>() {
@Override
public Throwable call() {
try {
assertNull(cache.get("netty.io"));
return null;
} catch (Throwable cause) {
return cause;
}
}
}, 1, TimeUnit.SECONDS).get();
if (error != null) {
throw error;
}
} finally {
group.shutdownGracefully();
}
}
@Test
public void testExpireWithDifferentTTLs() {
testExpireWithTTL0(1);
testExpireWithTTL0(1000);
testExpireWithTTL0(1000000);
}
private static void testExpireWithTTL0(int days) {
EventLoopGroup group = new NioEventLoopGroup(1);
try {
EventLoop loop = group.next();
final DefaultAuthoritativeDnsServerCache cache = new DefaultAuthoritativeDnsServerCache();
cache.cache("netty.io", new InetSocketAddress(NetUtil.LOCALHOST, 53), days, loop);
} finally {
group.shutdownGracefully();
}
}
@Test
public void testAddMultipleDnsServerForSameHostname() throws Exception {
InetSocketAddress resolved1 = new InetSocketAddress(
InetAddress.getByAddress("ns1", new byte[] { 10, 0, 0, 1 }), 53);
InetSocketAddress resolved2 = new InetSocketAddress(
InetAddress.getByAddress("ns2", new byte[] { 10, 0, 0, 2 }), 53);
EventLoopGroup group = new DefaultEventLoopGroup(1);
try {
EventLoop loop = group.next();
final DefaultAuthoritativeDnsServerCache cache = new DefaultAuthoritativeDnsServerCache();
cache.cache("netty.io", resolved1, 100, loop);
cache.cache("netty.io", resolved2, 10000, loop);
DnsServerAddressStream entries = cache.get("netty.io");
assertEquals(2, entries.size());
assertEquals(resolved1, entries.next());
assertEquals(resolved2, entries.next());
} finally {
group.shutdownGracefully();
}
}
@Test
public void testUnresolvedReplacedByResolved() throws Exception {
InetSocketAddress unresolved = InetSocketAddress.createUnresolved("ns1", 53);
InetSocketAddress resolved1 = new InetSocketAddress(
InetAddress.getByAddress("ns2", new byte[] { 10, 0, 0, 2 }), 53);
InetSocketAddress resolved2 = new InetSocketAddress(
InetAddress.getByAddress("ns1", new byte[] { 10, 0, 0, 1 }), 53);
EventLoopGroup group = new DefaultEventLoopGroup(1);
try {
EventLoop loop = group.next();
final DefaultAuthoritativeDnsServerCache cache = new DefaultAuthoritativeDnsServerCache();
cache.cache("netty.io", unresolved, 100, loop);
cache.cache("netty.io", resolved1, 10000, loop);
DnsServerAddressStream entries = cache.get("netty.io");
assertEquals(2, entries.size());
assertEquals(unresolved, entries.next());
assertEquals(resolved1, entries.next());
cache.cache("netty.io", resolved2, 100, loop);
DnsServerAddressStream entries2 = cache.get("netty.io");
assertEquals(2, entries2.size());
assertEquals(resolved2, entries2.next());
assertEquals(resolved1, entries2.next());
} finally {
group.shutdownGracefully();
}
}
@Test
public void testUseNoComparator() throws Exception {
testUseComparator0(true);
}
@Test
public void testUseComparator() throws Exception {
testUseComparator0(false);
}
private static void testUseComparator0(boolean noComparator) throws Exception {
InetSocketAddress unresolved = InetSocketAddress.createUnresolved("ns1", 53);
InetSocketAddress resolved = new InetSocketAddress(
InetAddress.getByAddress("ns2", new byte[] { 10, 0, 0, 2 }), 53);
EventLoopGroup group = new DefaultEventLoopGroup(1);
try {
EventLoop loop = group.next();
final DefaultAuthoritativeDnsServerCache cache;
if (noComparator) {
cache = new DefaultAuthoritativeDnsServerCache(10000, 10000, null);
} else {
cache = new DefaultAuthoritativeDnsServerCache(10000, 10000,
new Comparator<InetSocketAddress>() {
@Override
public int compare(InetSocketAddress o1, InetSocketAddress o2) {
if (o1.equals(o2)) {
return 0;
}
if (o1.isUnresolved()) {
return 1;
} else {
return -1;
}
}
});
}
cache.cache("netty.io", unresolved, 100, loop);
cache.cache("netty.io", resolved, 10000, loop);
DnsServerAddressStream entries = cache.get("netty.io");
assertEquals(2, entries.size());
if (noComparator) {
assertEquals(unresolved, entries.next());
assertEquals(resolved, entries.next());
} else {
assertEquals(resolved, entries.next());
assertEquals(unresolved, entries.next());
}
} finally {
group.shutdownGracefully();
}
}
}

View File

@ -63,6 +63,7 @@ import org.junit.Test;
import org.junit.rules.ExpectedException;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
@ -82,14 +83,15 @@ import java.util.Map.Entry;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import static io.netty.handler.codec.dns.DnsRecordType.A;
import static io.netty.handler.codec.dns.DnsRecordType.AAAA;
import static io.netty.handler.codec.dns.DnsRecordType.CNAME;
import static io.netty.resolver.dns.DefaultDnsServerAddressStreamProvider.DNS_PORT;
import static io.netty.resolver.dns.DnsServerAddresses.sequential;
import static java.util.Collections.singletonList;
import static org.hamcrest.Matchers.greaterThan;
@ -212,12 +214,14 @@ public class DnsNameResolverTest {
"localhost")));
private static final Map<String, String> DOMAINS_PUNYCODE = new HashMap<String, String>();
static {
DOMAINS_PUNYCODE.put("büchner.de", "xn--bchner-3ya.de");
DOMAINS_PUNYCODE.put("müller.de", "xn--mller-kva.de");
}
private static final Set<String> DOMAINS_ALL;
static {
Set<String> all = new HashSet<String>(DOMAINS.size() + DOMAINS_PUNYCODE.size());
all.addAll(DOMAINS);
@ -229,6 +233,7 @@ public class DnsNameResolverTest {
* The list of the domain names to exclude from {@link #testResolveAorAAAA()}.
*/
private static final Set<String> EXCLUSIONS_RESOLVE_A = new HashSet<String>();
static {
Collections.addAll(
EXCLUSIONS_RESOLVE_A,
@ -242,6 +247,7 @@ public class DnsNameResolverTest {
* Unfortunately, there are only handful of domain names with IPv6 addresses.
*/
private static final Set<String> EXCLUSIONS_RESOLVE_AAAA = new HashSet<String>();
static {
EXCLUSIONS_RESOLVE_AAAA.addAll(EXCLUSIONS_RESOLVE_A);
EXCLUSIONS_RESOLVE_AAAA.addAll(DOMAINS);
@ -279,6 +285,7 @@ public class DnsNameResolverTest {
* The list of the domain names to exclude from {@link #testQueryMx()}.
*/
private static final Set<String> EXCLUSIONS_QUERY_MX = new HashSet<String>();
static {
Collections.addAll(
EXCLUSIONS_QUERY_MX,
@ -342,6 +349,7 @@ public class DnsNameResolverTest {
public static void init() throws Exception {
dnsServer.start();
}
@AfterClass
public static void destroy() {
dnsServer.stop();
@ -407,7 +415,7 @@ public class DnsNameResolverTest {
@Override
public DnsServerAddressStream nameServerAddressStream(String hostname) {
return overridenHostnames.contains(hostname) ? sequential(dnsServer2.localAddress()).stream() :
null;
null;
}
}).build();
try {
@ -449,7 +457,7 @@ public class DnsNameResolverTest {
// Ensure the result from the cache is identical from the uncached one.
assertThat(resultB.size(), is(resultA.size()));
for (Entry<String, InetAddress> e: resultA.entrySet()) {
for (Entry<String, InetAddress> e : resultA.entrySet()) {
InetAddress expected = e.getValue();
InetAddress actual = resultB.get(e.getKey());
if (!actual.equals(expected)) {
@ -551,7 +559,7 @@ public class DnsNameResolverTest {
assertThat(resolved.getHostName(), is(unresolved));
boolean typeMatches = false;
for (InternetProtocolFamily f: resolver.resolvedInternetProtocolFamiliesUnsafe()) {
for (InternetProtocolFamily f : resolver.resolvedInternetProtocolFamiliesUnsafe()) {
Class<?> resolvedType = resolved.getClass();
if (f.addressType().isAssignableFrom(resolvedType)) {
typeMatches = true;
@ -576,7 +584,7 @@ public class DnsNameResolverTest {
Map<String, Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> futures =
new LinkedHashMap<String, Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>>();
for (String name: DOMAINS) {
for (String name : DOMAINS) {
if (EXCLUSIONS_QUERY_MX.contains(name)) {
continue;
}
@ -584,7 +592,7 @@ public class DnsNameResolverTest {
queryMx(resolver, futures, name);
}
for (Entry<String, Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> e: futures.entrySet()) {
for (Entry<String, Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> e : futures.entrySet()) {
String hostname = e.getKey();
Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> f = e.getValue().awaitUninterruptibly();
@ -593,7 +601,7 @@ public class DnsNameResolverTest {
final int answerCount = response.count(DnsSection.ANSWER);
final List<DnsRecord> mxList = new ArrayList<DnsRecord>(answerCount);
for (int i = 0; i < answerCount; i ++) {
for (int i = 0; i < answerCount; i++) {
final DnsRecord r = response.recordAt(DnsSection.ANSWER, i);
if (r.type() == DnsRecordType.MX) {
mxList.add(r);
@ -602,7 +610,7 @@ public class DnsNameResolverTest {
assertThat(mxList.size(), is(greaterThan(0)));
StringBuilder buf = new StringBuilder();
for (DnsRecord r: mxList) {
for (DnsRecord r : mxList) {
ByteBuf recordContent = ((ByteBufHolder) r).content();
buf.append(StringUtil.NEWLINE);
@ -887,7 +895,7 @@ public class DnsNameResolverTest {
dnsServer3.start();
DnsNameResolver resolver = null;
try {
DnsCache nsCache = new DefaultDnsCache();
AuthoritativeDnsServerCache nsCache = new DefaultAuthoritativeDnsServerCache();
// What we want to test is the following:
// 1. Do a DNS query.
// 2. CNAME is returned, we want to lookup that CNAME on multiple DNS servers
@ -897,7 +905,7 @@ public class DnsNameResolverTest {
// The DnsCache is used for the name server cache, but doesn't provide a InetSocketAddress (only InetAddress
// so no port), so we only specify the name server in the cache, and then specify both name servers in the
// fallback name server provider.
nsCache.cache("nettyfoo.com.", null, dnsServer2.localAddress().getAddress(), 10000, group.next());
nsCache.cache("nettyfoo.com.", dnsServer2.localAddress(), 10000, group.next());
resolver = new DnsNameResolver(
group.next(), new ReflectiveChannelFactory<DatagramChannel>(NioDatagramChannel.class),
NoopDnsCache.INSTANCE, nsCache, NoopDnsQueryLifecycleObserverFactory.INSTANCE, 3000,
@ -906,8 +914,10 @@ public class DnsNameResolverTest {
new SequentialDnsServerAddressStreamProvider(dnsServer2.localAddress(), dnsServer3.localAddress()),
DnsNameResolver.DEFAULT_SEARCH_DOMAINS, 0, true) {
@Override
int dnsRedirectPort(InetAddress server) {
return hitServer2.get() ? dnsServer3.localAddress().getPort() : dnsServer2.localAddress().getPort();
InetSocketAddress newRedirectServerAddress(InetAddress server) {
int port = hitServer2.get() ? dnsServer3.localAddress().getPort() :
dnsServer2.localAddress().getPort();
return new InetSocketAddress(server, port);
}
};
InetAddress resolvedAddress = resolver.resolve(firstName).syncUninterruptibly().getNow();
@ -957,7 +967,7 @@ public class DnsNameResolverTest {
assertThat(resolver.isRecursionDesired(), is(true));
final Map<String, Future<List<DnsRecord>>> futures = new LinkedHashMap<String, Future<List<DnsRecord>>>();
for (String name: DOMAINS) {
for (String name : DOMAINS) {
if (EXCLUSIONS_QUERY_MX.contains(name)) {
continue;
}
@ -965,14 +975,14 @@ public class DnsNameResolverTest {
futures.put(name, resolver.resolveAll(new DefaultDnsQuestion(name, DnsRecordType.MX)));
}
for (Entry<String, Future<List<DnsRecord>>> e: futures.entrySet()) {
for (Entry<String, Future<List<DnsRecord>>> e : futures.entrySet()) {
String hostname = e.getKey();
Future<List<DnsRecord>> f = e.getValue().awaitUninterruptibly();
final List<DnsRecord> mxList = f.getNow();
assertThat(mxList.size(), is(greaterThan(0)));
StringBuilder buf = new StringBuilder();
for (DnsRecord r: mxList) {
for (DnsRecord r : mxList) {
ByteBuf recordContent = ((ByteBufHolder) r).content();
buf.append(StringUtil.NEWLINE);
@ -1014,7 +1024,7 @@ public class DnsNameResolverTest {
}).build();
final List<DnsRecord> records = resolver.resolveAll(new DefaultDnsQuestion("foo.com.", A))
.syncUninterruptibly().getNow();
.syncUninterruptibly().getNow();
assertThat(records, Matchers.<DnsRecord>hasSize(1));
assertThat(records.get(0), Matchers.<DnsRecord>instanceOf(DnsRawRecord.class));
@ -1198,11 +1208,11 @@ public class DnsNameResolverTest {
final String ipv6Address = "0:0:0:0:0:0:1:1";
final String ipv4Address = "1.1.1.1";
if (types == ResolvedAddressTypes.IPV4_PREFERRED) {
records.add(newAddressRecord(name, RecordType.AAAA, ipv6Address));
records.add(newAddressRecord(name, RecordType.A, ipv4Address));
records.add(Collections.singleton(TestDnsServer.newAddressRecord(name, RecordType.AAAA, ipv6Address)));
records.add(Collections.singleton(TestDnsServer.newAddressRecord(name, RecordType.A, ipv4Address)));
} else {
records.add(newAddressRecord(name, RecordType.A, ipv4Address));
records.add(newAddressRecord(name, RecordType.AAAA, ipv6Address));
records.add(Collections.singleton(TestDnsServer.newAddressRecord(name, RecordType.A, ipv4Address)));
records.add(Collections.singleton(TestDnsServer.newAddressRecord(name, RecordType.AAAA, ipv6Address)));
}
final Iterator<Set<ResourceRecord>> recordsIterator = records.iterator();
RecordStore arbitrarilyOrderedStore = new RecordStore() {
@ -1230,16 +1240,6 @@ public class DnsNameResolverTest {
}
}
private static Set<ResourceRecord> newAddressRecord(String name, RecordType type, String address) {
ResourceRecordModifier rm = new ResourceRecordModifier();
rm.setDnsClass(RecordClass.IN);
rm.setDnsName(name);
rm.setDnsTtl(100);
rm.setDnsType(type);
rm.put(DnsAttribute.IP_ADDRESS, address);
return Collections.singleton(rm.getEntry());
}
private static void testRecursiveResolveCache(boolean cache)
throws Exception {
final String hostname = "some.record.netty.io";
@ -1253,20 +1253,23 @@ public class DnsNameResolverTest {
dnsServerAuthority.localAddress().getAddress().getHostAddress());
dnsServer.start();
TestDnsCache nsCache = new TestDnsCache(cache ? new DefaultDnsCache() : NoopDnsCache.INSTANCE);
TestAuthoritativeDnsServerCache nsCache = new TestAuthoritativeDnsServerCache(
cache ? new DefaultAuthoritativeDnsServerCache() : NoopAuthoritativeDnsServerCache.INSTANCE);
TestRecursiveCacheDnsQueryLifecycleObserverFactory lifecycleObserverFactory =
new TestRecursiveCacheDnsQueryLifecycleObserverFactory();
EventLoopGroup group = new NioEventLoopGroup(1);
DnsNameResolver resolver = new DnsNameResolver(
final DnsNameResolver resolver = new DnsNameResolver(
group.next(), new ReflectiveChannelFactory<DatagramChannel>(NioDatagramChannel.class),
NoopDnsCache.INSTANCE, nsCache, lifecycleObserverFactory, 3000, ResolvedAddressTypes.IPV4_ONLY, true,
10, true, 4096, false, HostsFileEntriesResolver.DEFAULT,
new SingletonDnsServerAddressStreamProvider(dnsServer.localAddress()),
DnsNameResolver.DEFAULT_SEARCH_DOMAINS, 0, true) {
@Override
int dnsRedirectPort(InetAddress server) {
return server.equals(dnsServerAuthority.localAddress().getAddress()) ?
dnsServerAuthority.localAddress().getPort() : DNS_PORT;
InetSocketAddress newRedirectServerAddress(InetAddress server) {
if (server.equals(dnsServerAuthority.localAddress().getAddress())) {
return new InetSocketAddress(server, dnsServerAuthority.localAddress().getPort());
}
return super.newRedirectServerAddress(server);
}
};
@ -1293,12 +1296,16 @@ public class DnsNameResolverTest {
QuerySucceededEvent succeededEvent = (QuerySucceededEvent) observer.events.poll();
if (cache) {
assertNull(nsCache.cache.get("io.", null));
assertNull(nsCache.cache.get("netty.io.", null));
List<? extends DnsCacheEntry> entries = nsCache.cache.get("record.netty.io.", null);
assertEquals(1, entries.size());
assertNull(nsCache.cache.get("io."));
assertNull(nsCache.cache.get("netty.io."));
DnsServerAddressStream entries = nsCache.cache.get("record.netty.io.");
assertNull(nsCache.cache.get(hostname, null));
// First address should be resolved (as we received a matching additional record), second is unresolved.
assertEquals(2, entries.size());
assertFalse(entries.next().isUnresolved());
assertTrue(entries.next().isUnresolved());
assertNull(nsCache.cache.get(hostname));
// Test again via cache.
resolver.resolveAll(hostname).syncUninterruptibly();
@ -1337,6 +1344,431 @@ public class DnsNameResolverTest {
}
}
@Test
public void testFollowNsRedirectsNoopCaches() throws Exception {
testFollowNsRedirects(NoopDnsCache.INSTANCE, NoopAuthoritativeDnsServerCache.INSTANCE, false);
}
@Test
public void testFollowNsRedirectsNoopDnsCache() throws Exception {
testFollowNsRedirects(NoopDnsCache.INSTANCE, new DefaultAuthoritativeDnsServerCache(), false);
}
@Test
public void testFollowNsRedirectsNoopAuthoritativeDnsServerCache() throws Exception {
testFollowNsRedirects(new DefaultDnsCache(), NoopAuthoritativeDnsServerCache.INSTANCE, false);
}
@Test
public void testFollowNsRedirectsDefaultCaches() throws Exception {
testFollowNsRedirects(new DefaultDnsCache(), new DefaultAuthoritativeDnsServerCache(), false);
}
@Test
public void testFollowNsRedirectAndTrySecondNsOnTimeout() throws Exception {
testFollowNsRedirects(NoopDnsCache.INSTANCE, NoopAuthoritativeDnsServerCache.INSTANCE, true);
}
@Test
public void testFollowNsRedirectAndTrySecondNsOnTimeoutDefaultCaches() throws Exception {
testFollowNsRedirects(new DefaultDnsCache(), new DefaultAuthoritativeDnsServerCache(), true);
}
private void testFollowNsRedirects(DnsCache cache, AuthoritativeDnsServerCache authoritativeDnsServerCache,
final boolean invalidNsFirst) throws Exception {
final String domain = "netty.io";
final String ns1Name = "ns1." + domain;
final String ns2Name = "ns2." + domain;
final InetAddress expected = InetAddress.getByAddress("some.record." + domain, new byte[] { 10, 10, 10, 10 });
// This is used to simulate a query timeout...
final DatagramSocket socket = new DatagramSocket(new InetSocketAddress(0));
final TestDnsServer dnsServerAuthority = new TestDnsServer(new RecordStore() {
@Override
public Set<ResourceRecord> getRecords(QuestionRecord question) {
if (question.getDomainName().equals(expected.getHostName())) {
return Collections.singleton(TestDnsServer.newARecord(
expected.getHostName(), expected.getHostAddress()));
}
return Collections.emptySet();
}
});
dnsServerAuthority.start();
TestDnsServer redirectServer = new TestDnsServer(new HashSet<String>(
Arrays.asList(expected.getHostName(), ns1Name, ns2Name))) {
@Override
protected DnsMessage filterMessage(DnsMessage message) {
for (QuestionRecord record: message.getQuestionRecords()) {
if (record.getDomainName().equals(expected.getHostName())) {
message.getAdditionalRecords().clear();
message.getAnswerRecords().clear();
if (invalidNsFirst) {
message.getAuthorityRecords().add(TestDnsServer.newNsRecord(domain, ns2Name));
message.getAuthorityRecords().add(TestDnsServer.newNsRecord(domain, ns1Name));
} else {
message.getAuthorityRecords().add(TestDnsServer.newNsRecord(domain, ns1Name));
message.getAuthorityRecords().add(TestDnsServer.newNsRecord(domain, ns2Name));
}
return message;
}
}
return message;
}
};
redirectServer.start();
EventLoopGroup group = new NioEventLoopGroup(1);
final DnsNameResolver resolver = new DnsNameResolver(
group.next(), new ReflectiveChannelFactory<DatagramChannel>(NioDatagramChannel.class),
cache, authoritativeDnsServerCache, NoopDnsQueryLifecycleObserverFactory.INSTANCE, 2000,
ResolvedAddressTypes.IPV4_ONLY, true, 10, true, 4096,
false, HostsFileEntriesResolver.DEFAULT,
new SingletonDnsServerAddressStreamProvider(redirectServer.localAddress()),
DnsNameResolver.DEFAULT_SEARCH_DOMAINS, 0, true) {
@Override
InetSocketAddress newRedirectServerAddress(InetAddress server) {
try {
if (server.getHostName().startsWith(ns1Name)) {
return new InetSocketAddress(InetAddress.getByAddress(ns1Name,
dnsServerAuthority.localAddress().getAddress().getAddress()),
dnsServerAuthority.localAddress().getPort());
}
if (server.getHostName().startsWith(ns2Name)) {
return new InetSocketAddress(InetAddress.getByAddress(ns2Name,
NetUtil.LOCALHOST.getAddress()), socket.getLocalPort());
}
} catch (UnknownHostException e) {
throw new IllegalStateException(e);
}
return super.newRedirectServerAddress(server);
}
};
try {
List<InetAddress> resolved = resolver.resolveAll(expected.getHostName()).syncUninterruptibly().getNow();
assertEquals(1, resolved.size());
assertEquals(expected, resolved.get(0));
List<InetAddress> resolved2 = resolver.resolveAll(expected.getHostName()).syncUninterruptibly().getNow();
assertEquals(1, resolved2.size());
assertEquals(expected, resolved2.get(0));
if (authoritativeDnsServerCache != NoopAuthoritativeDnsServerCache.INSTANCE) {
DnsServerAddressStream cached = authoritativeDnsServerCache.get(domain + '.');
assertEquals(2, cached.size());
InetSocketAddress ns1Address = InetSocketAddress.createUnresolved(
ns1Name + '.', DefaultDnsServerAddressStreamProvider.DNS_PORT);
InetSocketAddress ns2Address = InetSocketAddress.createUnresolved(
ns2Name + '.', DefaultDnsServerAddressStreamProvider.DNS_PORT);
if (invalidNsFirst) {
assertEquals(ns2Address, cached.next());
assertEquals(ns1Address, cached.next());
} else {
assertEquals(ns1Address, cached.next());
assertEquals(ns2Address, cached.next());
}
}
if (cache != NoopDnsCache.INSTANCE) {
List<? extends DnsCacheEntry> ns1Cached = cache.get(ns1Name + '.', null);
assertEquals(1, ns1Cached.size());
DnsCacheEntry nsEntry = ns1Cached.get(0);
assertNotNull(nsEntry.address());
assertNull(nsEntry.cause());
List<? extends DnsCacheEntry> ns2Cached = cache.get(ns2Name + '.', null);
if (invalidNsFirst) {
assertEquals(1, ns2Cached.size());
DnsCacheEntry ns2Entry = ns2Cached.get(0);
assertNotNull(ns2Entry.address());
assertNull(ns2Entry.cause());
} else {
// We should not even have tried to resolve the DNS name so this should be null.
assertNull(ns2Cached);
}
List<? extends DnsCacheEntry> expectedCached = cache.get(expected.getHostName(), null);
assertEquals(1, expectedCached.size());
DnsCacheEntry expectedEntry = expectedCached.get(0);
assertEquals(expected, expectedEntry.address());
assertNull(expectedEntry.cause());
}
} finally {
resolver.close();
group.shutdownGracefully(0, 0, TimeUnit.SECONDS);
redirectServer.stop();
dnsServerAuthority.stop();
socket.close();
}
}
@Test
public void testMultipleAdditionalRecordsForSameNSRecord() throws Exception {
testMultipleAdditionalRecordsForSameNSRecord(false);
}
@Test
public void testMultipleAdditionalRecordsForSameNSRecordReordered() throws Exception {
testMultipleAdditionalRecordsForSameNSRecord(true);
}
private static void testMultipleAdditionalRecordsForSameNSRecord(final boolean reversed) throws Exception {
final String domain = "netty.io";
final String hostname = "test.netty.io";
final String ns1Name = "ns1." + domain;
final InetSocketAddress ns1Address = new InetSocketAddress(
InetAddress.getByAddress(ns1Name, new byte[] { 10, 0, 0, 1 }),
DefaultDnsServerAddressStreamProvider.DNS_PORT);
final InetSocketAddress ns2Address = new InetSocketAddress(
InetAddress.getByAddress(ns1Name, new byte[] { 10, 0, 0, 2 }),
DefaultDnsServerAddressStreamProvider.DNS_PORT);
final InetSocketAddress ns3Address = new InetSocketAddress(
InetAddress.getByAddress(ns1Name, new byte[] { 10, 0, 0, 3 }),
DefaultDnsServerAddressStreamProvider.DNS_PORT);
final InetSocketAddress ns4Address = new InetSocketAddress(
InetAddress.getByAddress(ns1Name, new byte[] { 10, 0, 0, 4 }),
DefaultDnsServerAddressStreamProvider.DNS_PORT);
TestDnsServer redirectServer = new TestDnsServer(new HashSet<String>(Arrays.asList(hostname, ns1Name))) {
@Override
protected DnsMessage filterMessage(DnsMessage message) {
for (QuestionRecord record: message.getQuestionRecords()) {
if (record.getDomainName().equals(hostname)) {
message.getAdditionalRecords().clear();
message.getAnswerRecords().clear();
message.getAuthorityRecords().add(TestDnsServer.newNsRecord(domain, ns1Name));
message.getAdditionalRecords().add(newARecord(ns1Address));
message.getAdditionalRecords().add(newARecord(ns2Address));
message.getAdditionalRecords().add(newARecord(ns3Address));
message.getAdditionalRecords().add(newARecord(ns4Address));
return message;
}
}
return message;
}
private ResourceRecord newARecord(InetSocketAddress address) {
return TestDnsServer.newARecord(address.getHostName(), address.getAddress().getHostAddress());
}
};
redirectServer.start();
EventLoopGroup group = new NioEventLoopGroup(1);
final List<InetSocketAddress> cached = new CopyOnWriteArrayList<InetSocketAddress>();
final AuthoritativeDnsServerCache authoritativeDnsServerCache = new AuthoritativeDnsServerCache() {
@Override
public DnsServerAddressStream get(String hostname) {
return null;
}
@Override
public void cache(String hostname, InetSocketAddress address, long originalTtl, EventLoop loop) {
cached.add(address);
}
@Override
public void clear() {
// NOOP
}
@Override
public boolean clear(String hostname) {
return false;
}
};
final AtomicReference<DnsServerAddressStream> redirectedRef = new AtomicReference<DnsServerAddressStream>();
final DnsNameResolver resolver = new DnsNameResolver(
group.next(), new ReflectiveChannelFactory<DatagramChannel>(NioDatagramChannel.class),
NoopDnsCache.INSTANCE, authoritativeDnsServerCache,
NoopDnsQueryLifecycleObserverFactory.INSTANCE, 2000, ResolvedAddressTypes.IPV4_ONLY,
true, 10, true, 4096,
false, HostsFileEntriesResolver.DEFAULT,
new SingletonDnsServerAddressStreamProvider(redirectServer.localAddress()),
DnsNameResolver.DEFAULT_SEARCH_DOMAINS, 0, true) {
@Override
protected DnsServerAddressStream newRedirectDnsServerStream(
String hostname, List<InetSocketAddress> nameservers) {
if (reversed) {
Collections.reverse(nameservers);
}
DnsServerAddressStream stream = new SequentialDnsServerAddressStream(nameservers, 0);
redirectedRef.set(stream);
return stream;
}
};
try {
Throwable cause = resolver.resolveAll(hostname).await().cause();
assertTrue(cause instanceof UnknownHostException);
DnsServerAddressStream redirected = redirectedRef.get();
assertNotNull(redirected);
assertEquals(4, redirected.size());
assertEquals(4, cached.size());
if (reversed) {
assertEquals(ns4Address, redirected.next());
assertEquals(ns3Address, redirected.next());
assertEquals(ns2Address, redirected.next());
assertEquals(ns1Address, redirected.next());
} else {
assertEquals(ns1Address, redirected.next());
assertEquals(ns2Address, redirected.next());
assertEquals(ns3Address, redirected.next());
assertEquals(ns4Address, redirected.next());
}
// We should always have the same order in the cache.
assertEquals(ns1Address, cached.get(0));
assertEquals(ns2Address, cached.get(1));
assertEquals(ns3Address, cached.get(2));
assertEquals(ns4Address, cached.get(3));
} finally {
resolver.close();
group.shutdownGracefully(0, 0, TimeUnit.SECONDS);
redirectServer.stop();
}
}
@Test
public void testNSRecordsFromCache() throws Exception {
final String domain = "netty.io";
final String hostname = "test.netty.io";
final String ns0Name = "ns0." + domain + '.';
final String ns1Name = "ns1." + domain + '.';
final String ns2Name = "ns2." + domain + '.';
final InetSocketAddress ns0Address = new InetSocketAddress(
InetAddress.getByAddress(ns0Name, new byte[] { 10, 1, 0, 1 }),
DefaultDnsServerAddressStreamProvider.DNS_PORT);
final InetSocketAddress ns1Address = new InetSocketAddress(
InetAddress.getByAddress(ns1Name, new byte[] { 10, 0, 0, 1 }),
DefaultDnsServerAddressStreamProvider.DNS_PORT);
final InetSocketAddress ns2Address = new InetSocketAddress(
InetAddress.getByAddress(ns1Name, new byte[] { 10, 0, 0, 2 }),
DefaultDnsServerAddressStreamProvider.DNS_PORT);
final InetSocketAddress ns3Address = new InetSocketAddress(
InetAddress.getByAddress(ns1Name, new byte[] { 10, 0, 0, 3 }),
DefaultDnsServerAddressStreamProvider.DNS_PORT);
final InetSocketAddress ns4Address = new InetSocketAddress(
InetAddress.getByAddress(ns1Name, new byte[] { 10, 0, 0, 4 }),
DefaultDnsServerAddressStreamProvider.DNS_PORT);
final InetSocketAddress ns5Address = new InetSocketAddress(
InetAddress.getByAddress(ns2Name, new byte[] { 10, 0, 0, 5 }),
DefaultDnsServerAddressStreamProvider.DNS_PORT);
TestDnsServer redirectServer = new TestDnsServer(new HashSet<String>(Arrays.asList(hostname, ns1Name))) {
@Override
protected DnsMessage filterMessage(DnsMessage message) {
for (QuestionRecord record: message.getQuestionRecords()) {
if (record.getDomainName().equals(hostname)) {
message.getAdditionalRecords().clear();
message.getAnswerRecords().clear();
message.getAuthorityRecords().add(TestDnsServer.newNsRecord(domain, ns0Name));
message.getAuthorityRecords().add(TestDnsServer.newNsRecord(domain, ns1Name));
message.getAuthorityRecords().add(TestDnsServer.newNsRecord(domain, ns2Name));
message.getAdditionalRecords().add(newARecord(ns0Address));
message.getAdditionalRecords().add(newARecord(ns5Address));
return message;
}
}
return message;
}
private ResourceRecord newARecord(InetSocketAddress address) {
return TestDnsServer.newARecord(address.getHostName(), address.getAddress().getHostAddress());
}
};
redirectServer.start();
EventLoopGroup group = new NioEventLoopGroup(1);
final List<InetSocketAddress> cached = new CopyOnWriteArrayList<InetSocketAddress>();
final AuthoritativeDnsServerCache authoritativeDnsServerCache = new AuthoritativeDnsServerCache() {
@Override
public DnsServerAddressStream get(String hostname) {
return null;
}
@Override
public void cache(String hostname, InetSocketAddress address, long originalTtl, EventLoop loop) {
cached.add(address);
}
@Override
public void clear() {
// NOOP
}
@Override
public boolean clear(String hostname) {
return false;
}
};
EventLoop loop = group.next();
DefaultDnsCache cache = new DefaultDnsCache();
cache.cache(ns1Name, null, ns1Address.getAddress(), 10000, loop);
cache.cache(ns1Name, null, ns2Address.getAddress(), 10000, loop);
cache.cache(ns1Name, null, ns3Address.getAddress(), 10000, loop);
cache.cache(ns1Name, null, ns4Address.getAddress(), 10000, loop);
final AtomicReference<DnsServerAddressStream> redirectedRef = new AtomicReference<DnsServerAddressStream>();
final DnsNameResolver resolver = new DnsNameResolver(
loop, new ReflectiveChannelFactory<DatagramChannel>(NioDatagramChannel.class),
cache, authoritativeDnsServerCache,
NoopDnsQueryLifecycleObserverFactory.INSTANCE, 2000, ResolvedAddressTypes.IPV4_ONLY,
true, 10, true, 4096,
false, HostsFileEntriesResolver.DEFAULT,
new SingletonDnsServerAddressStreamProvider(redirectServer.localAddress()),
DnsNameResolver.DEFAULT_SEARCH_DOMAINS, 0, true) {
@Override
protected DnsServerAddressStream newRedirectDnsServerStream(
String hostname, List<InetSocketAddress> nameservers) {
DnsServerAddressStream stream = new SequentialDnsServerAddressStream(nameservers, 0);
redirectedRef.set(stream);
return stream;
}
};
try {
Throwable cause = resolver.resolveAll(hostname).await().cause();
assertTrue(cause instanceof UnknownHostException);
DnsServerAddressStream redirected = redirectedRef.get();
assertNotNull(redirected);
assertEquals(6, redirected.size());
assertEquals(3, cached.size());
// The redirected addresses should have been retrieven from the DnsCache if not resolved, so these are
// fully resolved.
assertEquals(ns0Address, redirected.next());
assertEquals(ns1Address, redirected.next());
assertEquals(ns2Address, redirected.next());
assertEquals(ns3Address, redirected.next());
assertEquals(ns4Address, redirected.next());
assertEquals(ns5Address, redirected.next());
// As this address was supplied as ADDITIONAL we should put it resolved into the cache.
assertEquals(ns0Address, cached.get(0));
assertEquals(ns5Address, cached.get(1));
// We should have put the unresolved address in the AuthoritativeDnsServerCache (but only 1 time)
assertEquals(unresolved(ns1Address), cached.get(2));
} finally {
resolver.close();
group.shutdownGracefully(0, 0, TimeUnit.SECONDS);
redirectServer.stop();
}
}
private static InetSocketAddress unresolved(InetSocketAddress address) {
return InetSocketAddress.createUnresolved(address.getHostString(), address.getPort());
}
private static void resolve(DnsNameResolver resolver, Map<String, Future<InetAddress>> futures, String hostname) {
futures.put(hostname, resolver.resolve(hostname));
}
@ -1481,10 +1913,43 @@ public class DnsNameResolverTest {
}
}
private static final class TestAuthoritativeDnsServerCache implements AuthoritativeDnsServerCache {
final AuthoritativeDnsServerCache cache;
final Map<String, DnsServerAddressStream> cacheHits = new HashMap<String, DnsServerAddressStream>();
TestAuthoritativeDnsServerCache(AuthoritativeDnsServerCache cache) {
this.cache = cache;
}
@Override
public void clear() {
cache.clear();
}
@Override
public boolean clear(String hostname) {
return cache.clear(hostname);
}
@Override
public DnsServerAddressStream get(String hostname) {
DnsServerAddressStream cached = cache.get(hostname);
if (cached != null) {
cacheHits.put(hostname, cached.duplicate());
}
return cached;
}
@Override
public void cache(String hostname, InetSocketAddress address, long originalTtl, EventLoop loop) {
cache.cache(hostname, address, originalTtl, loop);
}
}
private static final class TestDnsCache implements DnsCache {
private final DnsCache cache;
final Map<String, List<? extends DnsCacheEntry>> cacheHits = new HashMap<String,
List<? extends DnsCacheEntry>>();
final DnsCache cache;
final Map<String, List<? extends DnsCacheEntry>> cacheHits =
new HashMap<String, List<? extends DnsCacheEntry>>();
TestDnsCache(DnsCache cache) {
this.cache = cache;
@ -1502,20 +1967,19 @@ public class DnsNameResolverTest {
@Override
public List<? extends DnsCacheEntry> get(String hostname, DnsRecord[] additionals) {
List<? extends DnsCacheEntry> cacheEntries = cache.get(hostname, additionals);
cacheHits.put(hostname, cacheEntries);
return cacheEntries;
List<? extends DnsCacheEntry> cached = cache.get(hostname, additionals);
cacheHits.put(hostname, cached);
return cached;
}
@Override
public DnsCacheEntry cache(
String hostname, DnsRecord[] additionals, InetAddress address, long originalTtl, EventLoop loop) {
public DnsCacheEntry cache(String hostname, DnsRecord[] additionals, InetAddress address,
long originalTtl, EventLoop loop) {
return cache.cache(hostname, additionals, address, originalTtl, loop);
}
@Override
public DnsCacheEntry cache(
String hostname, DnsRecord[] additionals, Throwable cause, EventLoop loop) {
public DnsCacheEntry cache(String hostname, DnsRecord[] additionals, Throwable cause, EventLoop loop) {
return cache.cache(hostname, additionals, cause, loop);
}
}
@ -1535,6 +1999,8 @@ public class DnsNameResolverTest {
protected DnsMessage filterMessage(DnsMessage message) {
// Clear the answers as we want to add our own stuff to test dns redirects.
message.getAnswerRecords().clear();
message.getAuthorityRecords().clear();
message.getAdditionalRecords().clear();
String name = domain;
for (int i = 0 ;; i++) {
@ -1546,30 +2012,13 @@ public class DnsNameResolverTest {
String dnsName = "dns" + idx + '.' + domain;
message.getAuthorityRecords().add(newNsRecord(name, dnsName));
message.getAdditionalRecords().add(newARecord(dnsName, i == 0 ? dnsAddress : "1.2.3." + idx));
// Add an unresolved NS record (with no additionals as well)
message.getAuthorityRecords().add(newNsRecord(name, "unresolved." + dnsName));
}
return message;
}
private static ResourceRecord newARecord(String dnsname, String ipAddress) {
ResourceRecordModifier rm = new ResourceRecordModifier();
rm.setDnsClass(RecordClass.IN);
rm.setDnsName(dnsname);
rm.setDnsTtl(100);
rm.setDnsType(RecordType.A);
rm.put(DnsAttribute.IP_ADDRESS, ipAddress);
return rm.getEntry();
}
private static ResourceRecord newNsRecord(String dnsname, String domainName) {
ResourceRecordModifier rm = new ResourceRecordModifier();
rm.setDnsClass(RecordClass.IN);
rm.setDnsName(dnsname);
rm.setDnsTtl(100);
rm.setDnsType(RecordType.NS);
rm.put(DnsAttribute.DOMAIN_NAME, domainName);
return rm.getEntry();
}
}
@Test(timeout = 3000)

View File

@ -0,0 +1,172 @@
/*
* Copyright 2018 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.resolver.dns;
import org.junit.BeforeClass;
import org.junit.Test;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static org.junit.Assert.*;
public class NameServerComparatorTest {
private static InetSocketAddress IPV4ADDRESS1;
private static InetSocketAddress IPV4ADDRESS2;
private static InetSocketAddress IPV4ADDRESS3;
private static InetSocketAddress IPV6ADDRESS1;
private static InetSocketAddress IPV6ADDRESS2;
private static InetSocketAddress UNRESOLVED1;
private static InetSocketAddress UNRESOLVED2;
private static InetSocketAddress UNRESOLVED3;
@BeforeClass
public static void before() throws UnknownHostException {
IPV4ADDRESS1 = new InetSocketAddress(InetAddress.getByAddress("ns1", new byte[] { 10, 0, 0, 1 }), 53);
IPV4ADDRESS2 = new InetSocketAddress(InetAddress.getByAddress("ns2", new byte[] { 10, 0, 0, 2 }), 53);
IPV4ADDRESS3 = new InetSocketAddress(InetAddress.getByAddress("ns3", new byte[] { 10, 0, 0, 3 }), 53);
IPV6ADDRESS1 = new InetSocketAddress(InetAddress.getByAddress(
"ns1", new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }), 53);
IPV6ADDRESS2 = new InetSocketAddress(InetAddress.getByAddress(
"ns2", new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 }), 53);
UNRESOLVED1 = InetSocketAddress.createUnresolved("ns3", 53);
UNRESOLVED2 = InetSocketAddress.createUnresolved("ns4", 53);
UNRESOLVED3 = InetSocketAddress.createUnresolved("ns5", 53);
}
@Test
public void testCompareResolvedOnly() {
NameServerComparator comparator = new NameServerComparator(Inet4Address.class);
int x = comparator.compare(IPV4ADDRESS1, IPV6ADDRESS1);
int y = comparator.compare(IPV6ADDRESS1, IPV4ADDRESS1);
assertEquals(-1, x);
assertEquals(x, -y);
assertEquals(0, comparator.compare(IPV4ADDRESS1, IPV4ADDRESS1));
assertEquals(0, comparator.compare(IPV6ADDRESS1, IPV6ADDRESS1));
}
@Test
public void testCompareUnresolvedSimple() {
NameServerComparator comparator = new NameServerComparator(Inet4Address.class);
int x = comparator.compare(IPV4ADDRESS1, UNRESOLVED1);
int y = comparator.compare(UNRESOLVED1, IPV4ADDRESS1);
assertEquals(-1, x);
assertEquals(x, -y);
assertEquals(0, comparator.compare(IPV4ADDRESS1, IPV4ADDRESS1));
assertEquals(0, comparator.compare(UNRESOLVED1, UNRESOLVED1));
}
@Test
public void testCompareUnresolvedOnly() {
NameServerComparator comparator = new NameServerComparator(Inet4Address.class);
int x = comparator.compare(UNRESOLVED1, UNRESOLVED2);
int y = comparator.compare(UNRESOLVED2, UNRESOLVED1);
assertEquals(0, x);
assertEquals(x, -y);
assertEquals(0, comparator.compare(UNRESOLVED1, UNRESOLVED1));
assertEquals(0, comparator.compare(UNRESOLVED2, UNRESOLVED2));
}
@Test
public void testSortAlreadySortedPreferred() {
List<InetSocketAddress> expected = Arrays.asList(IPV4ADDRESS1, IPV4ADDRESS2, IPV4ADDRESS3);
List<InetSocketAddress> addresses = new ArrayList<InetSocketAddress>(expected);
NameServerComparator comparator = new NameServerComparator(Inet4Address.class);
Collections.sort(addresses, comparator);
assertEquals(expected, addresses);
}
@Test
public void testSortAlreadySortedNotPreferred() {
List<InetSocketAddress> expected = Arrays.asList(IPV4ADDRESS1, IPV4ADDRESS2, IPV4ADDRESS3);
List<InetSocketAddress> addresses = new ArrayList<InetSocketAddress>(expected);
NameServerComparator comparator = new NameServerComparator(Inet6Address.class);
Collections.sort(addresses, comparator);
assertEquals(expected, addresses);
}
@Test
public void testSortAlreadySortedUnresolved() {
List<InetSocketAddress> expected = Arrays.asList(UNRESOLVED1, UNRESOLVED2, UNRESOLVED3);
List<InetSocketAddress> addresses = new ArrayList<InetSocketAddress>(expected);
NameServerComparator comparator = new NameServerComparator(Inet6Address.class);
Collections.sort(addresses, comparator);
assertEquals(expected, addresses);
}
@Test
public void testSortAlreadySortedMixed() {
List<InetSocketAddress> expected = Arrays.asList(
IPV4ADDRESS1, IPV4ADDRESS2, IPV6ADDRESS1, IPV6ADDRESS2, UNRESOLVED1, UNRESOLVED2);
List<InetSocketAddress> addresses = new ArrayList<InetSocketAddress>(expected);
NameServerComparator comparator = new NameServerComparator(Inet4Address.class);
Collections.sort(addresses, comparator);
assertEquals(expected, addresses);
}
@Test
public void testSort1() {
List<InetSocketAddress> expected = Arrays.asList(
IPV4ADDRESS1, IPV4ADDRESS2, IPV6ADDRESS1, IPV6ADDRESS2, UNRESOLVED1, UNRESOLVED2);
List<InetSocketAddress> addresses = new ArrayList<InetSocketAddress>(
Arrays.asList(IPV6ADDRESS1, IPV4ADDRESS1, IPV6ADDRESS2, UNRESOLVED1, UNRESOLVED2, IPV4ADDRESS2));
NameServerComparator comparator = new NameServerComparator(Inet4Address.class);
Collections.sort(addresses, comparator);
assertEquals(expected, addresses);
}
@Test
public void testSort2() {
List<InetSocketAddress> expected = Arrays.asList(
IPV4ADDRESS1, IPV4ADDRESS2, IPV6ADDRESS1, IPV6ADDRESS2, UNRESOLVED1, UNRESOLVED2);
List<InetSocketAddress> addresses = new ArrayList<InetSocketAddress>(
Arrays.asList(IPV4ADDRESS1, IPV6ADDRESS1, IPV6ADDRESS2, UNRESOLVED1, IPV4ADDRESS2, UNRESOLVED2));
NameServerComparator comparator = new NameServerComparator(Inet4Address.class);
Collections.sort(addresses, comparator);
assertEquals(expected, addresses);
}
}

View File

@ -26,6 +26,7 @@ import org.apache.directory.server.dns.messages.RecordClass;
import org.apache.directory.server.dns.messages.RecordType;
import org.apache.directory.server.dns.messages.ResourceRecord;
import org.apache.directory.server.dns.messages.ResourceRecordImpl;
import org.apache.directory.server.dns.messages.ResourceRecordModifier;
import org.apache.directory.server.dns.protocol.DnsProtocolHandler;
import org.apache.directory.server.dns.protocol.DnsUdpDecoder;
import org.apache.directory.server.dns.protocol.DnsUdpEncoder;
@ -111,6 +112,30 @@ class TestDnsServer extends DnsServer {
return message;
}
protected static ResourceRecord newARecord(String name, String ipAddress) {
return newAddressRecord(name, RecordType.A, ipAddress);
}
protected static ResourceRecord newNsRecord(String dnsname, String domainName) {
ResourceRecordModifier rm = new ResourceRecordModifier();
rm.setDnsClass(RecordClass.IN);
rm.setDnsName(dnsname);
rm.setDnsTtl(100);
rm.setDnsType(RecordType.NS);
rm.put(DnsAttribute.DOMAIN_NAME, domainName);
return rm.getEntry();
}
protected static ResourceRecord newAddressRecord(String name, RecordType type, String address) {
ResourceRecordModifier rm = new ResourceRecordModifier();
rm.setDnsClass(RecordClass.IN);
rm.setDnsName(name);
rm.setDnsTtl(100);
rm.setDnsType(type);
rm.put(DnsAttribute.IP_ADDRESS, address);
return rm.getEntry();
}
/**
* {@link ProtocolCodecFactory} which allows to test AAAA resolution.
*/