diff --git a/pom.xml b/pom.xml
index 2737bf3fc6..8eb07f5257 100644
--- a/pom.xml
+++ b/pom.xml
@@ -893,6 +893,14 @@
xz
1.5
+
+
+
+ org.apache.directory.server
+ apacheds-protocol-dns
+ 1.5.7
+ test
+
diff --git a/resolver-dns/pom.xml b/resolver-dns/pom.xml
index 72b5789be3..8137a87505 100644
--- a/resolver-dns/pom.xml
+++ b/resolver-dns/pom.xml
@@ -44,6 +44,11 @@
netty-transport
${project.version}
+
+ org.apache.directory.server
+ apacheds-protocol-dns
+ test
+
diff --git a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java
index 49ce9663ba..9c3b7a1d2b 100644
--- a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java
+++ b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java
@@ -129,6 +129,7 @@ public class DnsNameResolver extends SimpleNameResolver {
private volatile boolean recursionDesired = true;
private volatile int maxPayloadSize;
+ private volatile boolean optResourceEnabled = true;
/**
* Creates a new DNS-based name resolver that communicates with the specified list of DNS servers.
@@ -520,6 +521,24 @@ public class DnsNameResolver extends SimpleNameResolver {
return this;
}
+ /**
+ * Enable the automatic inclusion of a optional records that tries to give the remote DNS server a hint about how
+ * much data the resolver can read per response. Some DNSServer may not support this and so fail to answer
+ * queries. If you find problems you may want to disable this.
+ */
+ public DnsNameResolver setOptResourceEnabled(boolean optResourceEnabled) {
+ this.optResourceEnabled = optResourceEnabled;
+ return this;
+ }
+
+ /**
+ * Returns the automatic inclusion of a optional records that tries to give the remote DNS server a hint about how
+ * much data the resolver can read per response is enabled.
+ */
+ public boolean isOptResourceEnabled() {
+ return optResourceEnabled;
+ }
+
/**
* Clears all the resolved addresses cached by this resolver.
*
diff --git a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsQueryContext.java b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsQueryContext.java
index f7ab7696ff..1d1d96c35c 100644
--- a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsQueryContext.java
+++ b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsQueryContext.java
@@ -63,8 +63,12 @@ final class DnsQueryContext {
id = allocateId();
recursionDesired = parent.isRecursionDesired();
- optResource = new DefaultDnsRawRecord(
- StringUtil.EMPTY_STRING, DnsRecordType.OPT, parent.maxPayloadSize(), 0, Unpooled.EMPTY_BUFFER);
+ if (parent.isOptResourceEnabled()) {
+ optResource = new DefaultDnsRawRecord(
+ StringUtil.EMPTY_STRING, DnsRecordType.OPT, parent.maxPayloadSize(), 0, Unpooled.EMPTY_BUFFER);
+ } else {
+ optResource = null;
+ }
}
private int allocateId() {
@@ -89,7 +93,9 @@ final class DnsQueryContext {
final DatagramDnsQuery query = new DatagramDnsQuery(null, nameServerAddr, id);
query.setRecursionDesired(recursionDesired);
query.setRecord(DnsSection.QUESTION, question);
- query.setRecord(DnsSection.ADDITIONAL, optResource);
+ if (optResource != null) {
+ query.setRecord(DnsSection.ADDITIONAL, optResource);
+ }
if (logger.isDebugEnabled()) {
logger.debug("{} WRITE: [{}: {}], {}", parent.ch, id, nameServerAddr, question);
diff --git a/resolver-dns/src/test/java/io/netty/resolver/dns/DnsNameResolverTest.java b/resolver-dns/src/test/java/io/netty/resolver/dns/DnsNameResolverTest.java
index 8967150cb1..a1ca634386 100644
--- a/resolver-dns/src/test/java/io/netty/resolver/dns/DnsNameResolverTest.java
+++ b/resolver-dns/src/test/java/io/netty/resolver/dns/DnsNameResolverTest.java
@@ -28,15 +28,42 @@ 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.NetUtil;
import io.netty.util.concurrent.Future;
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.DnsServer;
+import org.apache.directory.server.dns.io.encoder.DnsMessageEncoder;
+import org.apache.directory.server.dns.io.encoder.ResourceRecordEncoder;
+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.protocol.DnsProtocolHandler;
+import org.apache.directory.server.dns.protocol.DnsUdpDecoder;
+import org.apache.directory.server.dns.protocol.DnsUdpEncoder;
+import org.apache.directory.server.dns.store.DnsAttribute;
+import org.apache.directory.server.dns.store.RecordStore;
+import org.apache.directory.server.protocol.shared.transport.UdpTransport;
+import org.apache.mina.core.buffer.IoBuffer;
+import org.apache.mina.core.session.IoSession;
+import org.apache.mina.filter.codec.ProtocolCodecFactory;
+import org.apache.mina.filter.codec.ProtocolCodecFilter;
+import org.apache.mina.filter.codec.ProtocolDecoder;
+import org.apache.mina.filter.codec.ProtocolEncoder;
+import org.apache.mina.filter.codec.ProtocolEncoderOutput;
+import org.apache.mina.transport.socket.DatagramAcceptor;
+import org.apache.mina.transport.socket.DatagramSessionConfig;
import org.junit.After;
import org.junit.AfterClass;
+import org.junit.BeforeClass;
import org.junit.Test;
+import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
@@ -63,86 +90,12 @@ public class DnsNameResolverTest {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(DnsNameResolver.class);
- private static final List SERVERS = Arrays.asList(
- new InetSocketAddress("8.8.8.8", 53), // Google Public DNS
- new InetSocketAddress("8.8.4.4", 53),
- new InetSocketAddress("208.67.222.222", 53), // OpenDNS
- new InetSocketAddress("208.67.220.220", 53),
- new InetSocketAddress("208.67.222.220", 53),
- new InetSocketAddress("208.67.220.222", 53),
- new InetSocketAddress("37.235.1.174", 53), // FreeDNS
- new InetSocketAddress("37.235.1.177", 53),
- //
- // OpenNIC - Fusl's Tier 2 DNS servers
- //
- // curl http://meo.ws/dnsrec.php | \
- // perl -p0 -e 's#(^(.|\r|\n)*(.|\r|\n)*)##g' | \
- // awk -F ',' '{ print $14 }' | \
- // grep -E '^[0-9]+.[0-9]+.[0-9]+.[0-9]+$' | \
- // perl -p -e 's/^/new InetSocketAddress("/' | \
- // perl -p -e 's/$/", 53),/'
- //
- new InetSocketAddress("79.133.43.124", 53),
- new InetSocketAddress("151.236.10.36", 53),
- new InetSocketAddress("163.47.20.30", 53),
- new InetSocketAddress("103.25.56.238", 53),
- new InetSocketAddress("111.223.227.125", 53),
- new InetSocketAddress("103.241.0.207", 53),
- new InetSocketAddress("192.71.249.83", 53),
- new InetSocketAddress("69.28.67.83", 53),
- new InetSocketAddress("192.121.170.22", 53),
- new InetSocketAddress("62.141.38.230", 53),
- new InetSocketAddress("185.97.7.7", 53),
- new InetSocketAddress("84.200.83.161", 53),
- new InetSocketAddress("78.47.34.12", 53),
- new InetSocketAddress("41.215.240.141", 53),
- new InetSocketAddress("5.134.117.239", 53),
- new InetSocketAddress("95.175.99.231", 53),
- new InetSocketAddress("92.222.80.28", 53),
- new InetSocketAddress("178.79.174.162", 53),
- new InetSocketAddress("95.129.41.126", 53),
- new InetSocketAddress("103.53.199.71", 53),
- new InetSocketAddress("176.62.0.26", 53),
- new InetSocketAddress("185.112.156.159", 53),
- new InetSocketAddress("217.78.6.191", 53),
- new InetSocketAddress("193.182.144.83", 53),
- new InetSocketAddress("37.235.55.46", 53),
- new InetSocketAddress("103.250.184.85", 53),
- new InetSocketAddress("151.236.24.245", 53),
- new InetSocketAddress("192.121.47.47", 53),
- new InetSocketAddress("106.185.41.36", 53),
- new InetSocketAddress("88.82.109.119", 53),
- new InetSocketAddress("212.117.180.145", 53),
- new InetSocketAddress("185.61.149.228", 53),
- new InetSocketAddress("93.158.205.94", 53),
- new InetSocketAddress("31.220.43.191", 53),
- new InetSocketAddress("91.247.228.155", 53),
- new InetSocketAddress("163.47.21.44", 53),
- new InetSocketAddress("94.46.12.224", 53),
- new InetSocketAddress("46.108.39.139", 53),
- new InetSocketAddress("94.242.57.130", 53),
- new InetSocketAddress("46.151.215.199", 53),
- new InetSocketAddress("31.220.5.106", 53),
- new InetSocketAddress("103.25.202.192", 53),
- new InetSocketAddress("185.65.206.121", 53),
- new InetSocketAddress("91.229.79.104", 53),
- new InetSocketAddress("74.207.241.202", 53),
- new InetSocketAddress("104.245.33.185", 53),
- new InetSocketAddress("104.245.39.112", 53),
- new InetSocketAddress("74.207.232.103", 53),
- new InetSocketAddress("104.237.144.172", 53),
- new InetSocketAddress("104.237.136.225", 53),
- new InetSocketAddress("104.219.55.89", 53),
- new InetSocketAddress("23.226.230.72", 53),
- new InetSocketAddress("41.185.78.25", 53)
- );
-
// Using the top-100 web sites ranked in Alexa.com (Oct 2014)
// Please use the following series of shell commands to get this up-to-date:
// $ curl -O http://s3.amazonaws.com/alexa-static/top-1m.csv.zip
// $ unzip -o top-1m.csv.zip top-1m.csv
// $ head -100 top-1m.csv | cut -d, -f2 | cut -d/ -f1 | while read L; do echo '"'"$L"'",'; done > topsites.txt
- private static final String[] DOMAINS = {
+ private static final Set DOMAINS = Collections.unmodifiableSet(new HashSet(Arrays.asList(
"google.com",
"facebook.com",
"youtube.com",
@@ -241,8 +194,7 @@ public class DnsNameResolverTest {
"cnet.com",
"vimeo.com",
"redtube.com",
- "blogspot.in",
- };
+ "blogspot.in")));
/**
* The list of the domain names to exclude from {@link #testResolveAorAAAA()}.
@@ -263,7 +215,7 @@ public class DnsNameResolverTest {
private static final Set EXCLUSIONS_RESOLVE_AAAA = new HashSet();
static {
EXCLUSIONS_RESOLVE_AAAA.addAll(EXCLUSIONS_RESOLVE_A);
- Collections.addAll(EXCLUSIONS_RESOLVE_AAAA, DOMAINS);
+ EXCLUSIONS_RESOLVE_AAAA.addAll(DOMAINS);
EXCLUSIONS_RESOLVE_AAAA.removeAll(Arrays.asList(
"google.com",
"facebook.com",
@@ -311,16 +263,21 @@ public class DnsNameResolverTest {
StringUtil.EMPTY_STRING);
}
+ private static final TestDnsServer dnsServer = new TestDnsServer();
private static final EventLoopGroup group = new NioEventLoopGroup(1);
- private static final DnsNameResolver resolver = new DnsNameResolver(
- group.next(), NioDatagramChannel.class, DnsServerAddresses.shuffled(SERVERS));
+ private static DnsNameResolver resolver;
- static {
- resolver.setMaxQueriesPerResolve(SERVERS.size());
+ @BeforeClass
+ public static void init() throws Exception {
+ dnsServer.start();
+ resolver = new DnsNameResolver(group.next(), NioDatagramChannel.class,
+ DnsServerAddresses.singleton(dnsServer.localAddress()));
+ resolver.setMaxQueriesPerResolve(1);
+ resolver.setOptResourceEnabled(false);
}
-
@AfterClass
public static void destroy() {
+ dnsServer.stop();
group.shutdownGracefully();
}
@@ -340,8 +297,7 @@ public class DnsNameResolverTest {
}
@Test
- public void testResolveA() throws Exception {
-
+ public void testResolveA() throws Exception {
final int oldMinTtl = resolver.minTtl();
final int oldMaxTtl = resolver.maxTtl();
@@ -448,15 +404,6 @@ public class DnsNameResolverTest {
for (Entry>> e: futures.entrySet()) {
String hostname = e.getKey();
Future> f = e.getValue().awaitUninterruptibly();
- if (!f.isSuccess()) {
- // Try again because the DNS servers might be throttling us down.
- for (int i = 0; i < SERVERS.size(); i++) {
- f = queryMx(hostname).awaitUninterruptibly();
- if (f.isSuccess() && !DnsResponseCode.SERVFAIL.equals(f.getNow().content().code())) {
- break;
- }
- }
- }
DnsResponse response = f.getNow().content();
assertThat(response.code(), is(DnsResponseCode.NOERROR));
@@ -563,7 +510,174 @@ public class DnsNameResolverTest {
futures.put(hostname, resolver.query(new DefaultDnsQuestion(hostname, DnsRecordType.MX)));
}
- private static Future> queryMx(String hostname) throws Exception {
- return resolver.query(new DefaultDnsQuestion(hostname, DnsRecordType.MX));
+ private static final class TestDnsServer extends DnsServer {
+ private static final Map BYTES = new HashMap();
+ private static final String[] IPV6_ADDRESSES;
+ static {
+ BYTES.put("::1", new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1});
+ BYTES.put("0:0:0:0:0:0:1:1", new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1});
+ BYTES.put("0:0:0:0:0:1:1:1", new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1});
+ BYTES.put("0:0:0:0:1:1:1:1", new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1});
+ BYTES.put("0:0:0:1:1:1:1:1", new byte[] {0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1});
+ BYTES.put("0:0:1:1:1:1:1:1", new byte[] {0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1});
+ BYTES.put("0:1:1:1:1:1:1:1", new byte[] {0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1});
+ BYTES.put("1:1:1:1:1:1:1:1", new byte[] {0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1});
+
+ IPV6_ADDRESSES = BYTES.keySet().toArray(new String[BYTES.size()]);
+ }
+
+ @Override
+ public void start() throws IOException {
+ InetSocketAddress address = new InetSocketAddress(NetUtil.LOCALHOST4, 0);
+ UdpTransport transport = new UdpTransport(address.getHostName(), address.getPort());
+ setTransports(transport);
+
+ DatagramAcceptor acceptor = transport.getAcceptor();
+
+ acceptor.setHandler(new DnsProtocolHandler(this, new TestRecordStore()) {
+ @Override
+ public void sessionCreated(IoSession session) throws Exception {
+ // USe our own codec to support AAAA testing
+ session.getFilterChain()
+ .addFirst("codec", new ProtocolCodecFilter(new TestDnsProtocolUdpCodecFactory()));
+ }
+ });
+
+ ((DatagramSessionConfig) acceptor.getSessionConfig()).setReuseAddress(true);
+
+ // Start the listener
+ acceptor.bind();
+ }
+
+ public InetSocketAddress localAddress() {
+ return (InetSocketAddress) getTransports()[0].getAcceptor().getLocalAddress();
+ }
+
+ /**
+ * {@link ProtocolCodecFactory} which allows to test AAAA resolution.
+ */
+ private static final class TestDnsProtocolUdpCodecFactory implements ProtocolCodecFactory {
+ private final DnsMessageEncoder encoder = new DnsMessageEncoder();
+ private final TestAAAARecordEncoder recordEncoder = new TestAAAARecordEncoder();
+
+ @Override
+ public ProtocolEncoder getEncoder(IoSession session) throws Exception {
+ return new DnsUdpEncoder() {
+
+ @Override
+ public void encode(IoSession session, Object message, ProtocolEncoderOutput out) {
+ IoBuffer buf = IoBuffer.allocate(1024);
+ DnsMessage dnsMessage = (DnsMessage) message;
+ encoder.encode(buf, dnsMessage);
+ for (ResourceRecord record: dnsMessage.getAnswerRecords()) {
+ // This is a hack to allow to also test for AAAA resolution as DnsMessageEncoder
+ // does not support it and it is hard to extend, because the interesting methods
+ // are private...
+ // In case of RecordType.AAAA we need to encode the RecordType by ourself.
+ if (record.getRecordType() == RecordType.AAAA) {
+ try {
+ recordEncoder.put(buf, record);
+ } catch (IOException e) {
+ // Should never happen
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+ buf.flip();
+
+ out.write(buf);
+ }
+ };
+ }
+
+ @Override
+ public ProtocolDecoder getDecoder(IoSession session) throws Exception {
+ return new DnsUdpDecoder();
+ }
+
+ private static final class TestAAAARecordEncoder extends ResourceRecordEncoder {
+
+ @Override
+ protected void putResourceRecordData(IoBuffer ioBuffer, ResourceRecord resourceRecord) {
+ byte[] bytes = BYTES.get(resourceRecord.get(DnsAttribute.IP_ADDRESS));
+ if (bytes == null) {
+ throw new IllegalStateException();
+ }
+ // encode the ::1
+ ioBuffer.put(bytes);
+ }
+ }
+ }
+
+ private static final class TestRecordStore implements RecordStore {
+ private static final int[] NUMBERS = new int[254];
+ private static final char[] CHARS = new char[26];
+
+ static {
+ for (int i = 0; i < NUMBERS.length; i++) {
+ NUMBERS[i] = i + 1;
+ }
+
+ for (int i = 0; i < CHARS.length; i++) {
+ CHARS[i] = (char) ('a' + i);
+ }
+ }
+
+ private static int index(int arrayLength) {
+ return Math.abs(ThreadLocalRandom.current().nextInt()) % arrayLength;
+ }
+
+ private static String nextDomain() {
+ return CHARS[index(CHARS.length)] + ".netty.io";
+ }
+
+ private static String nextIp() {
+ return ippart() + "." + ippart() + '.' + ippart() + '.' + ippart();
+ }
+
+ private static int ippart() {
+ return NUMBERS[index(NUMBERS.length)];
+ }
+
+ private static String nextIp6() {
+ return IPV6_ADDRESSES[index(IPV6_ADDRESSES.length)];
+ }
+
+ @Override
+ public Set getRecords(QuestionRecord questionRecord) {
+ String name = questionRecord.getDomainName();
+ if (DOMAINS.contains(name)) {
+ ResourceRecordModifier rm = new ResourceRecordModifier();
+ rm.setDnsClass(RecordClass.IN);
+ rm.setDnsName(name);
+ rm.setDnsTtl(100);
+ rm.setDnsType(questionRecord.getRecordType());
+
+ switch (questionRecord.getRecordType()) {
+ case A:
+ do {
+ rm.put(DnsAttribute.IP_ADDRESS, nextIp());
+ } while (ThreadLocalRandom.current().nextBoolean());
+ break;
+ case AAAA:
+ do {
+ rm.put(DnsAttribute.IP_ADDRESS, nextIp6());
+ } while (ThreadLocalRandom.current().nextBoolean());
+ break;
+ case MX:
+ int prioritity = 0;
+ do {
+ rm.put(DnsAttribute.DOMAIN_NAME, nextDomain());
+ rm.put(DnsAttribute.MX_PREFERENCE, String.valueOf(++prioritity));
+ } while (ThreadLocalRandom.current().nextBoolean());
+ break;
+ default:
+ return null;
+ }
+ return Collections.singleton(rm.getEntry());
+ }
+ return null;
+ }
+ }
}
}
diff --git a/resolver-dns/src/test/resources/logback-test.xml b/resolver-dns/src/test/resources/logback-test.xml
new file mode 100644
index 0000000000..86ce779632
--- /dev/null
+++ b/resolver-dns/src/test/resources/logback-test.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+
+
+ // Disable logging for apacheds to reduce noise.
+
+