DnsNameResolver should respect /etc/resolv.conf and /etc/resolver

Motivation:
The JDK uses gethostbyname for blocking hostname resoltuion. gethostbyname can be configured on Unix systems according to [1][2]. This may impact the name server that is used to resolve particular domains or just override the default fall-back resolver. DnsNameResolver currently ignores these configuration files which means the default resolution behavior is different than the JDK. This may lead to unexpected resolution failures which succeed when using the JDK's resolver.

Modifications:
- Add an interface which can override what DnsServerAddressStream to use for a given hostname
- Provide a Unix specific implementation of this interface and implement [1][2]. Some elements may be ignored sortlist, timeout, etc...

Result:
DnsNameResolver behaves more like the JDK resolver by default.

[1] https://linux.die.net/man/5/resolver
[2] https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man5/resolver.5.html
This commit is contained in:
Scott Mitchell 2017-02-02 01:32:33 -08:00
parent fe522fb18e
commit 54c9ecf682
10 changed files with 451 additions and 23 deletions

View File

@ -428,6 +428,21 @@ public final class StringUtil {
return s == null || s.isEmpty();
}
/**
* Find the index of the first non-white space character in {@code s} starting at {@code offset}.
* @param seq The string to search.
* @param offset The offset to start searching at.
* @return the index of the first non-white space character or <{@code 0} if none was found.
*/
public static int indexOfNonWhiteSpace(CharSequence seq, int offset) {
for (; offset < seq.length(); ++offset) {
if (!Character.isWhitespace(seq.charAt(offset))) {
return offset;
}
}
return -1;
}
/**
* Determine if {@code c} lies within the range of values defined for
* <a href="http://unicode.org/glossary/#surrogate_code_point">Surrogate Code Point</a>.

View File

@ -62,7 +62,6 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import static io.netty.util.internal.ObjectUtil.checkNonEmpty;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import static io.netty.util.internal.ObjectUtil.checkPositive;
import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
@ -165,6 +164,7 @@ public class DnsNameResolver extends InetNameResolver {
private final int maxPayloadSize;
private final boolean optResourceEnabled;
private final HostsFileEntriesResolver hostsFileEntriesResolver;
private final DnsServerAddressStreamProvider dnsServerAddressStreamProvider;
private final String[] searchDomains;
private final int ndots;
private final boolean supportsAAAARecords;
@ -191,6 +191,8 @@ public class DnsNameResolver extends InetNameResolver {
* @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 override the name
* servers for each hostname lookup.
* @param searchDomains the list of search domain
* @param ndots the ndots value
* @param decodeIdn {@code true} if domain / host names should be decoded to unicode when received.
@ -210,6 +212,7 @@ public class DnsNameResolver extends InetNameResolver {
int maxPayloadSize,
boolean optResourceEnabled,
HostsFileEntriesResolver hostsFileEntriesResolver,
DnsServerAddressStreamProvider dnsServerAddressStreamProvider,
String[] searchDomains,
int ndots,
boolean decodeIdn) {
@ -224,6 +227,8 @@ public class DnsNameResolver extends InetNameResolver {
this.maxPayloadSize = checkPositive(maxPayloadSize, "maxPayloadSize");
this.optResourceEnabled = optResourceEnabled;
this.hostsFileEntriesResolver = checkNotNull(hostsFileEntriesResolver, "hostsFileEntriesResolver");
this.dnsServerAddressStreamProvider =
checkNotNull(dnsServerAddressStreamProvider, "dnsServerAddressStreamProvider");
this.resolveCache = checkNotNull(resolveCache, "resolveCache");
this.authoritativeDnsServerCache = checkNotNull(authoritativeDnsServerCache, "authoritativeDnsServerCache");
this.searchDomains = checkNotNull(searchDomains, "searchDomains").clone();
@ -631,21 +636,25 @@ public class DnsNameResolver extends InetNameResolver {
DnsRecord[] additionals,
Promise<InetAddress> promise,
DnsCache resolveCache) {
SingleResolverContext ctx = new SingleResolverContext(this, hostname, additionals, resolveCache);
DnsServerAddressStream dnsServerAddressStream =
dnsServerAddressStreamProvider.nameServerAddressStream(hostname);
SingleResolverContext ctx = dnsServerAddressStream == null ?
new SingleResolverContext(this, hostname, additionals, resolveCache, nameServerAddresses.stream()) :
new SingleResolverContext(this, hostname, additionals, resolveCache, dnsServerAddressStream);
ctx.resolve(promise);
}
static final class SingleResolverContext extends DnsNameResolverContext<InetAddress> {
SingleResolverContext(DnsNameResolver parent, String hostname,
DnsRecord[] additionals, DnsCache resolveCache) {
super(parent, hostname, additionals, resolveCache);
DnsRecord[] additionals, DnsCache resolveCache, DnsServerAddressStream nameServerAddrs) {
super(parent, hostname, additionals, resolveCache, nameServerAddrs);
}
@Override
DnsNameResolverContext<InetAddress> newResolverContext(DnsNameResolver parent, String hostname,
DnsRecord[] additionals, DnsCache resolveCache) {
return new SingleResolverContext(parent, hostname, additionals, resolveCache);
DnsRecord[] additionals, DnsCache resolveCache,
DnsServerAddressStream nameServerAddrs) {
return new SingleResolverContext(parent, hostname, additionals, resolveCache, nameServerAddrs);
}
@Override
@ -748,14 +757,15 @@ public class DnsNameResolver extends InetNameResolver {
static final class ListResolverContext extends DnsNameResolverContext<List<InetAddress>> {
ListResolverContext(DnsNameResolver parent, String hostname,
DnsRecord[] additionals, DnsCache resolveCache) {
super(parent, hostname, additionals, resolveCache);
DnsRecord[] additionals, DnsCache resolveCache, DnsServerAddressStream nameServerAddrs) {
super(parent, hostname, additionals, resolveCache, nameServerAddrs);
}
@Override
DnsNameResolverContext<List<InetAddress>> newResolverContext(
DnsNameResolver parent, String hostname, DnsRecord[] additionals, DnsCache resolveCache) {
return new ListResolverContext(parent, hostname, additionals, resolveCache);
DnsNameResolver parent, String hostname, DnsRecord[] additionals, DnsCache resolveCache,
DnsServerAddressStream nameServerAddrs) {
return new ListResolverContext(parent, hostname, additionals, resolveCache, nameServerAddrs);
}
@Override
@ -787,8 +797,11 @@ public class DnsNameResolver extends InetNameResolver {
DnsRecord[] additionals,
Promise<List<InetAddress>> promise,
DnsCache resolveCache) {
DnsNameResolverContext<List<InetAddress>> ctx = new ListResolverContext(
this, hostname, additionals, resolveCache);
DnsServerAddressStream dnsServerAddressStream =
dnsServerAddressStreamProvider.nameServerAddressStream(hostname);
ListResolverContext ctx = dnsServerAddressStream == null ?
new ListResolverContext(this, hostname, additionals, resolveCache, nameServerAddresses.stream()) :
new ListResolverContext(this, hostname, additionals, resolveCache, dnsServerAddressStream);
ctx.resolve(promise);
}

View File

@ -15,8 +15,6 @@
*/
package io.netty.resolver.dns;
import static io.netty.util.internal.ObjectUtil.intValue;
import io.netty.channel.ChannelFactory;
import io.netty.channel.EventLoop;
import io.netty.channel.ReflectiveChannelFactory;
@ -30,12 +28,17 @@ import java.util.ArrayList;
import java.util.List;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import static io.netty.util.internal.ObjectUtil.intValue;
/**
* A {@link DnsNameResolver} builder.
*/
@UnstableApi
public final class DnsNameResolverBuilder {
// TODO(scott): how is this done on Windows? This may require a JNI call to GetNetworkParams
// https://msdn.microsoft.com/en-us/library/aa365968(VS.85).aspx.
private static final DnsServerAddressStreamProvider DEFAULT_DNS_SERVER_ADDRESS_STREAM_PROVIDER =
UnixResolverDnsServerAddressStreamProvider.parseSilently();
private final EventLoop eventLoop;
private ChannelFactory<? extends DatagramChannel> channelFactory;
@ -53,6 +56,7 @@ public final class DnsNameResolverBuilder {
private int maxPayloadSize = 4096;
private boolean optResourceEnabled = true;
private HostsFileEntriesResolver hostsFileEntriesResolver = HostsFileEntriesResolver.DEFAULT;
private DnsServerAddressStreamProvider dnsServerAddressStreamProvider = DEFAULT_DNS_SERVER_ADDRESS_STREAM_PROVIDER;
private String[] searchDomains = DnsNameResolver.DEFAULT_SEARCH_DOMAINS;
private int ndots = 1;
private boolean decodeIdn = true;
@ -273,6 +277,17 @@ public final class DnsNameResolverBuilder {
return this;
}
/**
* Set the {@link DnsServerAddressStreamProvider} which is used to determine which DNS server is used to resolve
* each hostname.
* @return {@code this}.
*/
public DnsNameResolverBuilder nameServerCache(DnsServerAddressStreamProvider dnsServerAddressStreamProvider) {
this.dnsServerAddressStreamProvider =
checkNotNull(dnsServerAddressStreamProvider, "dnsServerAddressStreamProvider");
return this;
}
/**
* Set the list of search domains of the resolver.
*
@ -360,6 +375,7 @@ public final class DnsNameResolverBuilder {
maxPayloadSize,
optResourceEnabled,
hostsFileEntriesResolver,
dnsServerAddressStreamProvider,
searchDomains,
ndots,
decodeIdn);

View File

@ -23,13 +23,13 @@ import io.netty.channel.socket.InternetProtocolFamily;
import io.netty.handler.codec.CorruptedFrameException;
import io.netty.handler.codec.dns.DefaultDnsQuestion;
import io.netty.handler.codec.dns.DefaultDnsRecordDecoder;
import io.netty.handler.codec.dns.DnsResponseCode;
import io.netty.handler.codec.dns.DnsSection;
import io.netty.handler.codec.dns.DnsQuestion;
import io.netty.handler.codec.dns.DnsRawRecord;
import io.netty.handler.codec.dns.DnsRecord;
import io.netty.handler.codec.dns.DnsRecordType;
import io.netty.handler.codec.dns.DnsResponse;
import io.netty.handler.codec.dns.DnsResponseCode;
import io.netty.handler.codec.dns.DnsSection;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
@ -89,13 +89,14 @@ abstract class DnsNameResolverContext<T> {
protected DnsNameResolverContext(DnsNameResolver parent,
String hostname,
DnsRecord[] additionals,
DnsCache resolveCache) {
DnsCache resolveCache,
DnsServerAddressStream nameServerAddrs) {
this.parent = parent;
this.hostname = hostname;
this.additionals = additionals;
this.resolveCache = resolveCache;
nameServerAddrs = parent.nameServerAddresses.stream();
this.nameServerAddrs = nameServerAddrs;
maxAllowedQueries = parent.maxQueriesPerResolve();
resolvedInternetProtocolFamilies = parent.resolvedInternetProtocolFamiliesUnsafe();
traceEnabled = parent.isTraceEnabled();
@ -120,7 +121,7 @@ abstract class DnsNameResolverContext<T> {
Promise<T> nextPromise = parent.executor().newPromise();
String nextHostname = hostname + '.' + searchDomain;
DnsNameResolverContext<T> nextContext = newResolverContext(parent,
nextHostname, additionals, resolveCache);
nextHostname, additionals, resolveCache, nameServerAddrs);
nextContext.pristineHostname = hostname;
nextContext.internalResolve(nextPromise);
nextPromise.addListener(this);
@ -654,7 +655,8 @@ abstract class DnsNameResolverContext<T> {
Promise<T> promise);
abstract DnsNameResolverContext<T> newResolverContext(DnsNameResolver parent, String hostname,
DnsRecord[] additionals, DnsCache resolveCache);
DnsRecord[] additionals, DnsCache resolveCache,
DnsServerAddressStream nameServerAddrs);
static String decodeDomainName(ByteBuf in) {
in.markReaderIndex();

View File

@ -0,0 +1,36 @@
/*
* Copyright 2017 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.resolver.dns;
import io.netty.util.internal.UnstableApi;
/**
* Provides an opportunity to override which {@link DnsServerAddressStream} is used to resolve a specific hostname.
* <p>
* For example this can be used to represent <a href="https://linux.die.net/man/5/resolver">/etc/resolv.conf</a> and
* <a href="https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man5/resolver.5.html">
* /etc/resolver</a>.
*/
@UnstableApi
public interface DnsServerAddressStreamProvider {
/**
* Ask this provider for the name servers to query for {@code hostname}.
* @param hostname The hostname for which to lookup the DNS server addressed to use.
* @return The {@link DnsServerAddressStream} which should be used to resolve {@code hostname} or {@code null} to
* use the default resolvers.
*/
DnsServerAddressStream nameServerAddressStream(String hostname);
}

View File

@ -0,0 +1,31 @@
/*
* Copyright 2017 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.resolver.dns;
import io.netty.util.internal.UnstableApi;
@UnstableApi
public final class NoopDnsServerAddressStreamProvider implements DnsServerAddressStreamProvider {
public static final NoopDnsServerAddressStreamProvider INSTANCE = new NoopDnsServerAddressStreamProvider();
private NoopDnsServerAddressStreamProvider() {
}
@Override
public DnsServerAddressStream nameServerAddressStream(String hostname) {
return null;
}
}

View File

@ -0,0 +1,233 @@
/*
* Copyright 2017 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.resolver.dns;
import io.netty.util.NetUtil;
import io.netty.util.internal.SocketUtils;
import io.netty.util.internal.UnstableApi;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static io.netty.resolver.dns.DnsServerAddresses.DNS_PORT;
import static io.netty.util.internal.StringUtil.indexOfNonWhiteSpace;
/**
* Able to parse files such as <a href="https://linux.die.net/man/5/resolver">/etc/resolv.conf</a> and
* <a href="https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man5/resolver.5.html">
* /etc/resolver</a> to respect the system default domain servers.
*/
@UnstableApi
public final class UnixResolverDnsServerAddressStreamProvider implements DnsServerAddressStreamProvider {
private static final InternalLogger logger =
InternalLoggerFactory.getInstance(UnixResolverDnsServerAddressStreamProvider.class);
private static final String NAMESERVER_ROW_LABEL = "nameserver";
private static final String SORTLIST_ROW_LABEL = "sortlist";
private static final String DOMAIN_ROW_LABEL = "domain";
private static final String PORT_ROW_LABEL = "port";
private final DnsServerAddresses defaultNameServerAddresses;
private final Map<String, DnsServerAddresses> domainToNameServerStreamMap;
/**
* Attempt to parse {@code /etc/resolv.conf} and files in the {@code /etc/resolver} directory by default.
* A failure to parse will return {@link NoopDnsServerAddressStreamProvider}.
*/
public static DnsServerAddressStreamProvider parseSilently() {
try {
UnixResolverDnsServerAddressStreamProvider nameServerCache =
new UnixResolverDnsServerAddressStreamProvider("/etc/resolv.conf", "/etc/resolver");
return nameServerCache.mayOverrideNameServers() ? nameServerCache
: NoopDnsServerAddressStreamProvider.INSTANCE;
} catch (Exception e) {
logger.debug("failed to parse /etc/resolv.conf and/or /etc/resolver", e);
return NoopDnsServerAddressStreamProvider.INSTANCE;
}
}
/**
* Parse a file of the format <a href="https://linux.die.net/man/5/resolver">/etc/resolv.conf</a> which may contain
* the default DNS server to use, and also overrides for individual domains. Also parse list of files of the format
* <a href="
* https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man5/resolver.5.html">
* /etc/resolver</a> which may contain multiple files to override the name servers used for multimple domains.
* @param etcResolvConf <a href="https://linux.die.net/man/5/resolver">/etc/resolv.conf</a>.
* @param etcResolverFiles List of files of the format defined in
* <a href="
* https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man5/resolver.5.html">
* /etc/resolver</a>.
* @throws IOException If an error occurs while parsing the input files.
*/
public UnixResolverDnsServerAddressStreamProvider(File etcResolvConf, File... etcResolverFiles) throws IOException {
if (etcResolvConf == null && (etcResolverFiles == null || etcResolverFiles.length == 0)) {
throw new IllegalArgumentException("no files to parse");
}
if (etcResolverFiles != null) {
domainToNameServerStreamMap = parse(etcResolverFiles);
if (etcResolvConf != null) {
Map<String, DnsServerAddresses> etcResolvConfMap = parse(etcResolvConf);
defaultNameServerAddresses = etcResolvConfMap.remove(etcResolvConf.getName());
domainToNameServerStreamMap.putAll(etcResolvConfMap);
} else {
defaultNameServerAddresses = null;
}
} else {
domainToNameServerStreamMap = parse(etcResolvConf);
defaultNameServerAddresses = domainToNameServerStreamMap.remove(etcResolvConf.getName());
}
}
/**
* Parse a file of the format <a href="https://linux.die.net/man/5/resolver">/etc/resolv.conf</a> which may contain
* the default DNS server to use, and also overrides for individual domains. Also parse a directory of the format
* <a href="
* https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man5/resolver.5.html">
* /etc/resolver</a> which may contain multiple files to override the name servers used for multimple domains.
* @param etcResolvConf <a href="https://linux.die.net/man/5/resolver">/etc/resolv.conf</a>.
* @param etcResolverDir Directory containing files of the format defined in
* <a href="
* https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man5/resolver.5.html">
* /etc/resolver</a>.
* @throws IOException If an error occurs while parsing the input files.
*/
public UnixResolverDnsServerAddressStreamProvider(String etcResolvConf, String etcResolverDir) throws IOException {
this(etcResolvConf == null ? null : new File(etcResolvConf),
etcResolverDir == null ? null :new File(etcResolverDir).listFiles());
}
@Override
public DnsServerAddressStream nameServerAddressStream(String hostname) {
for (;;) {
int i = hostname.indexOf('.', 1);
if (i < 0 || i == hostname.length() - 1) {
return defaultNameServerAddresses != null ? defaultNameServerAddresses.stream() : null;
}
DnsServerAddresses addresses = domainToNameServerStreamMap.get(hostname);
if (addresses != null) {
return addresses.stream();
}
hostname = hostname.substring(i + 1);
}
}
boolean mayOverrideNameServers() {
return !domainToNameServerStreamMap.isEmpty() ||
defaultNameServerAddresses != null && defaultNameServerAddresses.stream().next() != null;
}
private static Map<String, DnsServerAddresses> parse(File... etcResolverFiles) throws IOException {
Map<String, DnsServerAddresses> domainToNameServerStreamMap =
new HashMap<String, DnsServerAddresses>(etcResolverFiles.length << 1);
for (File etcResolverFile : etcResolverFiles) {
if (!etcResolverFile.isFile()) {
continue;
}
FileReader fr = new FileReader(etcResolverFile);
BufferedReader br = null;
try {
br = new BufferedReader(fr);
List<InetSocketAddress> addresses = new ArrayList<InetSocketAddress>(2);
String domainName = etcResolverFile.getName();
int port = DNS_PORT;
String line;
while ((line = br.readLine()) != null) {
line = line.trim();
char c;
if (line.isEmpty() || (c = line.charAt(0)) == '#' || c == ';') {
continue;
}
if (line.startsWith(NAMESERVER_ROW_LABEL)) {
int i = indexOfNonWhiteSpace(line, NAMESERVER_ROW_LABEL.length());
if (i < 0) {
throw new IllegalArgumentException("error parsing label " + NAMESERVER_ROW_LABEL +
" in file " + etcResolverFile + ". value: " + line);
}
String maybeIP = line.substring(i);
// There may be a port appended onto the IP address so we attempt to extract it.
if (!NetUtil.isValidIpV4Address(maybeIP) && !NetUtil.isValidIpV6Address(maybeIP)) {
i = maybeIP.lastIndexOf('.');
if (i + 1 >= maybeIP.length()) {
throw new IllegalArgumentException("error parsing label " + NAMESERVER_ROW_LABEL +
" in file " + etcResolverFile + ". invalid IP value: " + line);
}
port = Integer.parseInt(maybeIP.substring(i + 1));
maybeIP = maybeIP.substring(0, i);
}
addresses.add(new InetSocketAddress(SocketUtils.addressByName(maybeIP), port));
} else if (line.startsWith(DOMAIN_ROW_LABEL)) {
int i = indexOfNonWhiteSpace(line, DOMAIN_ROW_LABEL.length());
if (i < 0) {
throw new IllegalArgumentException("error parsing label " + DOMAIN_ROW_LABEL +
" in file " + etcResolverFile + " value: " + line);
}
domainName = line.substring(i);
if (addresses != null && !addresses.isEmpty()) {
putIfAbsent(domainToNameServerStreamMap, domainName, addresses);
}
addresses = new ArrayList<InetSocketAddress>(2);
} else if (line.startsWith(PORT_ROW_LABEL)) {
int i = indexOfNonWhiteSpace(line, PORT_ROW_LABEL.length());
if (i < 0) {
throw new IllegalArgumentException("error parsing label " + PORT_ROW_LABEL +
" in file " + etcResolverFile + " value: " + line);
}
port = Integer.parseInt(line.substring(i));
} else if (line.startsWith(SORTLIST_ROW_LABEL)) {
logger.info("row type {} not supported. ignoring line: {}", SORTLIST_ROW_LABEL, line);
}
}
if (addresses != null && !addresses.isEmpty()) {
putIfAbsent(domainToNameServerStreamMap, domainName, addresses);
}
} finally {
if (br == null) {
fr.close();
} else {
br.close();
}
}
}
return domainToNameServerStreamMap;
}
private static void putIfAbsent(Map<String, DnsServerAddresses> domainToNameServerStreamMap,
String domainName,
List<InetSocketAddress> addresses) {
// TODO(scott): sortlist is being ignored.
putIfAbsent(domainToNameServerStreamMap, domainName, DnsServerAddresses.shuffled(addresses));
}
private static void putIfAbsent(Map<String, DnsServerAddresses> domainToNameServerStreamMap,
String domainName,
DnsServerAddresses addresses) {
DnsServerAddresses existingAddresses = domainToNameServerStreamMap.put(domainName, addresses);
if (existingAddresses != null) {
domainToNameServerStreamMap.put(domainName, existingAddresses);
logger.debug("Domain name {} already maps to addresses {} so new addresses {} will be discarded",
domainName, existingAddresses, addresses);
}
}
}

View File

@ -61,6 +61,7 @@ public class DnsNameResolverClientSubnetTest {
return new DnsNameResolverBuilder(group.next())
.channelType(NioDatagramChannel.class)
.nameServerAddresses(DnsServerAddresses.singleton(SocketUtils.socketAddress("8.8.8.8", 53)))
.nameServerCache(NoopDnsServerAddressStreamProvider.INSTANCE)
.maxQueriesPerResolve(1)
.optResourceEnabled(false);
}

View File

@ -31,24 +31,29 @@ import io.netty.handler.codec.dns.DnsRecordType;
import io.netty.handler.codec.dns.DnsResponse;
import io.netty.handler.codec.dns.DnsResponseCode;
import io.netty.handler.codec.dns.DnsSection;
import io.netty.resolver.HostsFileEntriesResolver;
import io.netty.resolver.ResolvedAddressTypes;
import io.netty.util.NetUtil;
import io.netty.resolver.HostsFileEntriesResolver;
import io.netty.util.concurrent.Future;
import io.netty.util.internal.SocketUtils;
import io.netty.util.internal.StringUtil;
import io.netty.util.internal.ThreadLocalRandom;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import org.apache.directory.server.dns.DnsException;
import org.apache.directory.server.dns.messages.DnsMessage;
import org.apache.directory.server.dns.messages.QuestionRecord;
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.ResourceRecordModifier;
import org.apache.directory.server.dns.store.DnsAttribute;
import org.apache.directory.server.dns.store.RecordStore;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
@ -64,11 +69,13 @@ import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import static io.netty.resolver.dns.DnsServerAddresses.sequential;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
@ -271,9 +278,15 @@ public class DnsNameResolverTest {
private static final EventLoopGroup group = new NioEventLoopGroup(1);
private static DnsNameResolverBuilder newResolver(boolean decodeToUnicode) {
return newResolver(decodeToUnicode, NoopDnsServerAddressStreamProvider.INSTANCE);
}
private static DnsNameResolverBuilder newResolver(boolean decodeToUnicode,
DnsServerAddressStreamProvider dnsServerAddressStreamProvider) {
return new DnsNameResolverBuilder(group.next())
.channelType(NioDatagramChannel.class)
.nameServerAddresses(DnsServerAddresses.singleton(dnsServer.localAddress()))
.nameServerCache(dnsServerAddressStreamProvider)
.maxQueriesPerResolve(1)
.decodeIdn(decodeToUnicode)
.optResourceEnabled(false);
@ -324,6 +337,73 @@ public class DnsNameResolverTest {
}
}
/**
* This test will start an second DNS test server which returns fixed results that can be easily verified as
* originating from the second DNS test server. The resolver will put {@link DnsServerAddressStreamProvider} under
* test to ensure that some hostnames can be directed toward both the primary and secondary DNS test servers
* simultaneously.
*/
@Test
public void testNameServerCache() throws IOException, InterruptedException {
final String overriddenIP = "12.34.12.34";
final TestDnsServer dnsServer2 = new TestDnsServer(new RecordStore() {
@Override
public Set<ResourceRecord> getRecords(QuestionRecord question) throws DnsException {
ResourceRecordModifier rm = new ResourceRecordModifier();
rm.setDnsClass(RecordClass.IN);
rm.setDnsName(question.getDomainName());
rm.setDnsTtl(100);
rm.setDnsType(question.getRecordType());
switch (question.getRecordType()) {
case A:
rm.put(DnsAttribute.IP_ADDRESS, overriddenIP);
break;
default:
return null;
}
return Collections.singleton(rm.getEntry());
}
});
dnsServer2.start();
try {
final Set<String> overridenHostnames = new HashSet<String>();
for (String name : DOMAINS) {
if (EXCLUSIONS_RESOLVE_A.contains(name)) {
continue;
}
if (ThreadLocalRandom.current().nextBoolean()) {
overridenHostnames.add(name);
}
}
DnsNameResolver resolver = newResolver(false, new DnsServerAddressStreamProvider() {
@Override
public DnsServerAddressStream nameServerAddressStream(String hostname) {
return overridenHostnames.contains(hostname) ? sequential(dnsServer2.localAddress()).stream() :
null;
}
}).build();
try {
final Map<String, InetAddress> resultA = testResolve0(resolver, EXCLUSIONS_RESOLVE_A);
for (Entry<String, InetAddress> resolvedEntry : resultA.entrySet()) {
if (resolvedEntry.getValue().getHostAddress().equalsIgnoreCase("localhost")) {
continue;
}
if (overridenHostnames.contains(resolvedEntry.getKey())) {
assertEquals("failed to resolve " + resolvedEntry.getKey(),
overriddenIP, resolvedEntry.getValue().getHostAddress());
} else {
assertNotEquals("failed to resolve " + resolvedEntry.getKey(),
overriddenIP, resolvedEntry.getValue().getHostAddress());
}
}
} finally {
resolver.close();
}
} finally {
dnsServer2.stop();
}
}
@Test
public void testResolveA() throws Exception {
DnsNameResolver resolver = newResolver(ResolvedAddressTypes.IPV4_ONLY)
@ -683,7 +763,7 @@ public class DnsNameResolverTest {
group.next(), new ReflectiveChannelFactory<DatagramChannel>(NioDatagramChannel.class),
DnsServerAddresses.singleton(dnsServer.localAddress()), NoopDnsCache.INSTANCE, nsCache,
3000, ResolvedAddressTypes.IPV4_ONLY, true, 10,
true, 4096, false, HostsFileEntriesResolver.DEFAULT,
true, 4096, false, HostsFileEntriesResolver.DEFAULT, NoopDnsServerAddressStreamProvider.INSTANCE,
DnsNameResolver.DEFAULT_SEARCH_DOMAINS, 0, true) {
@Override
int dnsRedirectPort(InetAddress server) {

View File

@ -46,6 +46,7 @@ public class SearchDomainTest {
return new DnsNameResolverBuilder(group.next())
.channelType(NioDatagramChannel.class)
.nameServerAddresses(DnsServerAddresses.singleton(dnsServer.localAddress()))
.nameServerCache(NoopDnsServerAddressStreamProvider.INSTANCE)
.maxQueriesPerResolve(1)
.optResourceEnabled(false);
}