2014-09-19 15:36:32 +02:00
|
|
|
/*
|
2015-03-16 07:46:14 +01:00
|
|
|
* Copyright 2015 The Netty Project
|
2014-09-19 15:36:32 +02:00
|
|
|
*
|
|
|
|
* 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;
|
|
|
|
|
2015-03-16 07:46:14 +01:00
|
|
|
import io.netty.buffer.ByteBuf;
|
|
|
|
import io.netty.buffer.ByteBufHolder;
|
|
|
|
import io.netty.channel.AddressedEnvelope;
|
2017-01-25 09:28:22 +01:00
|
|
|
import io.netty.channel.EventLoop;
|
2014-09-19 15:36:32 +02:00
|
|
|
import io.netty.channel.EventLoopGroup;
|
2017-01-25 09:28:22 +01:00
|
|
|
import io.netty.channel.ReflectiveChannelFactory;
|
2014-09-19 15:36:32 +02:00
|
|
|
import io.netty.channel.nio.NioEventLoopGroup;
|
2017-01-25 09:28:22 +01:00
|
|
|
import io.netty.channel.socket.DatagramChannel;
|
2014-09-19 15:36:32 +02:00
|
|
|
import io.netty.channel.socket.InternetProtocolFamily;
|
|
|
|
import io.netty.channel.socket.nio.NioDatagramChannel;
|
2015-03-16 07:46:14 +01:00
|
|
|
import io.netty.handler.codec.dns.DefaultDnsQuestion;
|
|
|
|
import io.netty.handler.codec.dns.DnsRecord;
|
|
|
|
import io.netty.handler.codec.dns.DnsRecordType;
|
2014-09-19 15:36:32 +02:00
|
|
|
import io.netty.handler.codec.dns.DnsResponse;
|
|
|
|
import io.netty.handler.codec.dns.DnsResponseCode;
|
2015-08-18 11:23:59 +02:00
|
|
|
import io.netty.handler.codec.dns.DnsSection;
|
2017-01-20 15:46:46 +01:00
|
|
|
import io.netty.util.NetUtil;
|
2017-01-25 09:28:22 +01:00
|
|
|
import io.netty.resolver.HostsFileEntriesResolver;
|
2014-09-19 15:36:32 +02:00
|
|
|
import io.netty.util.concurrent.Future;
|
|
|
|
import io.netty.util.internal.StringUtil;
|
|
|
|
import io.netty.util.internal.logging.InternalLogger;
|
|
|
|
import io.netty.util.internal.logging.InternalLoggerFactory;
|
2017-01-25 09:28:22 +01:00
|
|
|
import org.apache.directory.server.dns.messages.DnsMessage;
|
|
|
|
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;
|
2014-09-19 15:36:32 +02:00
|
|
|
import org.junit.AfterClass;
|
2015-10-02 15:16:16 +02:00
|
|
|
import org.junit.BeforeClass;
|
2014-09-19 15:36:32 +02:00
|
|
|
import org.junit.Test;
|
|
|
|
|
|
|
|
import java.net.InetAddress;
|
|
|
|
import java.net.InetSocketAddress;
|
2015-08-18 11:44:51 +02:00
|
|
|
import java.net.UnknownHostException;
|
2014-09-19 15:36:32 +02:00
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.Arrays;
|
|
|
|
import java.util.Collections;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.HashSet;
|
|
|
|
import java.util.LinkedHashMap;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
|
|
|
import java.util.Map.Entry;
|
|
|
|
import java.util.Set;
|
2017-01-25 09:28:22 +01:00
|
|
|
import java.util.concurrent.TimeUnit;
|
2014-09-19 15:36:32 +02:00
|
|
|
|
2015-08-18 11:23:59 +02:00
|
|
|
import static org.hamcrest.Matchers.greaterThan;
|
2015-08-18 11:44:51 +02:00
|
|
|
import static org.hamcrest.Matchers.hasSize;
|
2017-01-25 09:28:22 +01:00
|
|
|
import static org.hamcrest.Matchers.hasToString;
|
2015-08-18 11:44:51 +02:00
|
|
|
import static org.hamcrest.Matchers.instanceOf;
|
2015-08-18 11:23:59 +02:00
|
|
|
import static org.hamcrest.Matchers.is;
|
|
|
|
import static org.junit.Assert.assertEquals;
|
2017-01-25 09:28:22 +01:00
|
|
|
import static org.junit.Assert.assertNotNull;
|
|
|
|
import static org.junit.Assert.assertNull;
|
2015-08-18 11:23:59 +02:00
|
|
|
import static org.junit.Assert.assertThat;
|
2015-08-18 11:44:51 +02:00
|
|
|
import static org.junit.Assert.fail;
|
2014-09-19 15:36:32 +02:00
|
|
|
|
|
|
|
public class DnsNameResolverTest {
|
|
|
|
|
|
|
|
private static final InternalLogger logger = InternalLoggerFactory.getInstance(DnsNameResolver.class);
|
|
|
|
|
|
|
|
// 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
|
2015-10-02 15:16:16 +02:00
|
|
|
private static final Set<String> DOMAINS = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
|
2014-09-19 15:36:32 +02:00
|
|
|
"google.com",
|
|
|
|
"facebook.com",
|
|
|
|
"youtube.com",
|
|
|
|
"yahoo.com",
|
|
|
|
"baidu.com",
|
|
|
|
"wikipedia.org",
|
|
|
|
"amazon.com",
|
|
|
|
"twitter.com",
|
|
|
|
"qq.com",
|
|
|
|
"taobao.com",
|
|
|
|
"linkedin.com",
|
|
|
|
"google.co.in",
|
|
|
|
"live.com",
|
|
|
|
"hao123.com",
|
|
|
|
"sina.com.cn",
|
|
|
|
"blogspot.com",
|
|
|
|
"weibo.com",
|
|
|
|
"yahoo.co.jp",
|
|
|
|
"tmall.com",
|
|
|
|
"yandex.ru",
|
|
|
|
"sohu.com",
|
|
|
|
"bing.com",
|
|
|
|
"ebay.com",
|
|
|
|
"pinterest.com",
|
|
|
|
"vk.com",
|
|
|
|
"google.de",
|
|
|
|
"wordpress.com",
|
|
|
|
"apple.com",
|
|
|
|
"google.co.jp",
|
|
|
|
"google.co.uk",
|
|
|
|
"360.cn",
|
|
|
|
"instagram.com",
|
|
|
|
"google.fr",
|
|
|
|
"msn.com",
|
|
|
|
"ask.com",
|
|
|
|
"soso.com",
|
|
|
|
"google.com.br",
|
|
|
|
"tumblr.com",
|
|
|
|
"paypal.com",
|
|
|
|
"mail.ru",
|
|
|
|
"xvideos.com",
|
|
|
|
"microsoft.com",
|
|
|
|
"google.ru",
|
|
|
|
"reddit.com",
|
|
|
|
"google.it",
|
|
|
|
"imgur.com",
|
|
|
|
"163.com",
|
|
|
|
"google.es",
|
|
|
|
"imdb.com",
|
|
|
|
"aliexpress.com",
|
|
|
|
"t.co",
|
|
|
|
"go.com",
|
|
|
|
"adcash.com",
|
|
|
|
"craigslist.org",
|
|
|
|
"amazon.co.jp",
|
|
|
|
"alibaba.com",
|
|
|
|
"google.com.mx",
|
|
|
|
"stackoverflow.com",
|
|
|
|
"xhamster.com",
|
|
|
|
"fc2.com",
|
|
|
|
"google.ca",
|
|
|
|
"bbc.co.uk",
|
|
|
|
"espn.go.com",
|
|
|
|
"cnn.com",
|
|
|
|
"google.co.id",
|
|
|
|
"people.com.cn",
|
|
|
|
"gmw.cn",
|
|
|
|
"pornhub.com",
|
|
|
|
"blogger.com",
|
|
|
|
"huffingtonpost.com",
|
|
|
|
"flipkart.com",
|
|
|
|
"akamaihd.net",
|
|
|
|
"google.com.tr",
|
|
|
|
"amazon.de",
|
|
|
|
"netflix.com",
|
|
|
|
"onclickads.net",
|
|
|
|
"googleusercontent.com",
|
|
|
|
"kickass.to",
|
|
|
|
"google.com.au",
|
|
|
|
"google.pl",
|
|
|
|
"xinhuanet.com",
|
|
|
|
"ebay.de",
|
|
|
|
"wordpress.org",
|
|
|
|
"odnoklassniki.ru",
|
|
|
|
"google.com.hk",
|
|
|
|
"adobe.com",
|
|
|
|
"dailymotion.com",
|
|
|
|
"dailymail.co.uk",
|
|
|
|
"indiatimes.com",
|
|
|
|
"amazon.co.uk",
|
|
|
|
"xnxx.com",
|
|
|
|
"rakuten.co.jp",
|
|
|
|
"dropbox.com",
|
|
|
|
"tudou.com",
|
|
|
|
"about.com",
|
|
|
|
"cnet.com",
|
|
|
|
"vimeo.com",
|
|
|
|
"redtube.com",
|
2016-07-20 00:01:38 +02:00
|
|
|
"blogspot.in",
|
|
|
|
"localhost")));
|
2014-09-19 15:36:32 +02:00
|
|
|
|
2017-01-18 10:00:01 +01:00
|
|
|
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);
|
|
|
|
all.addAll(DOMAINS_PUNYCODE.values());
|
|
|
|
DOMAINS_ALL = Collections.unmodifiableSet(all);
|
|
|
|
}
|
|
|
|
|
2014-09-19 15:36:32 +02:00
|
|
|
/**
|
|
|
|
* 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,
|
|
|
|
"akamaihd.net",
|
|
|
|
"googleusercontent.com",
|
2015-03-16 07:46:14 +01:00
|
|
|
StringUtil.EMPTY_STRING);
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The list of the domain names to exclude from {@link #testResolveAAAA()}.
|
|
|
|
* 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);
|
2015-10-02 15:16:16 +02:00
|
|
|
EXCLUSIONS_RESOLVE_AAAA.addAll(DOMAINS);
|
2014-09-19 15:36:32 +02:00
|
|
|
EXCLUSIONS_RESOLVE_AAAA.removeAll(Arrays.asList(
|
|
|
|
"google.com",
|
|
|
|
"facebook.com",
|
|
|
|
"youtube.com",
|
|
|
|
"wikipedia.org",
|
|
|
|
"google.co.in",
|
|
|
|
"blogspot.com",
|
|
|
|
"vk.com",
|
|
|
|
"google.de",
|
|
|
|
"google.co.jp",
|
|
|
|
"google.co.uk",
|
|
|
|
"google.fr",
|
|
|
|
"google.com.br",
|
|
|
|
"google.ru",
|
|
|
|
"google.it",
|
|
|
|
"google.es",
|
|
|
|
"google.com.mx",
|
|
|
|
"xhamster.com",
|
|
|
|
"google.ca",
|
|
|
|
"google.co.id",
|
|
|
|
"blogger.com",
|
|
|
|
"flipkart.com",
|
|
|
|
"google.com.tr",
|
|
|
|
"google.com.au",
|
|
|
|
"google.pl",
|
|
|
|
"google.com.hk",
|
|
|
|
"blogspot.in"
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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,
|
|
|
|
"hao123.com",
|
|
|
|
"blogspot.com",
|
|
|
|
"t.co",
|
|
|
|
"espn.go.com",
|
|
|
|
"people.com.cn",
|
|
|
|
"googleusercontent.com",
|
|
|
|
"blogspot.in",
|
2016-07-20 00:01:38 +02:00
|
|
|
"localhost",
|
2015-03-16 07:46:14 +01:00
|
|
|
StringUtil.EMPTY_STRING);
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
|
2017-01-18 10:00:01 +01:00
|
|
|
private static final TestDnsServer dnsServer = new TestDnsServer(DOMAINS_ALL);
|
2014-09-19 15:36:32 +02:00
|
|
|
private static final EventLoopGroup group = new NioEventLoopGroup(1);
|
2015-12-14 15:27:49 +01:00
|
|
|
|
2017-01-18 10:00:01 +01:00
|
|
|
private static DnsNameResolverBuilder newResolver(boolean decodeToUnicode) {
|
2015-12-14 15:27:49 +01:00
|
|
|
return new DnsNameResolverBuilder(group.next())
|
|
|
|
.channelType(NioDatagramChannel.class)
|
|
|
|
.nameServerAddresses(DnsServerAddresses.singleton(dnsServer.localAddress()))
|
|
|
|
.maxQueriesPerResolve(1)
|
2017-01-18 10:00:01 +01:00
|
|
|
.decodeIdn(decodeToUnicode)
|
2015-12-14 15:27:49 +01:00
|
|
|
.optResourceEnabled(false);
|
|
|
|
}
|
|
|
|
|
2017-01-18 10:00:01 +01:00
|
|
|
private static DnsNameResolverBuilder newResolver() {
|
|
|
|
return newResolver(true);
|
|
|
|
}
|
|
|
|
|
2016-03-07 03:12:33 +01:00
|
|
|
private static DnsNameResolverBuilder newResolver(InternetProtocolFamily... resolvedAddressTypes) {
|
2015-12-14 15:27:49 +01:00
|
|
|
return newResolver()
|
|
|
|
.resolvedAddressTypes(resolvedAddressTypes);
|
|
|
|
}
|
2015-10-02 15:16:16 +02:00
|
|
|
|
2016-06-26 11:16:12 +02:00
|
|
|
private static DnsNameResolverBuilder newNonCachedResolver(InternetProtocolFamily... resolvedAddressTypes) {
|
|
|
|
return newResolver()
|
|
|
|
.resolveCache(NoopDnsCache.INSTANCE)
|
|
|
|
.resolvedAddressTypes(resolvedAddressTypes);
|
|
|
|
}
|
|
|
|
|
2015-10-02 15:16:16 +02:00
|
|
|
@BeforeClass
|
|
|
|
public static void init() throws Exception {
|
|
|
|
dnsServer.start();
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
@AfterClass
|
|
|
|
public static void destroy() {
|
2015-10-02 15:16:16 +02:00
|
|
|
dnsServer.stop();
|
2014-09-19 15:36:32 +02:00
|
|
|
group.shutdownGracefully();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
public void testResolveAorAAAA() throws Exception {
|
2015-12-14 15:27:49 +01:00
|
|
|
DnsNameResolver resolver = newResolver(InternetProtocolFamily.IPv4, InternetProtocolFamily.IPv6).build();
|
|
|
|
try {
|
|
|
|
testResolve0(resolver, EXCLUSIONS_RESOLVE_A);
|
|
|
|
} finally {
|
|
|
|
resolver.close();
|
|
|
|
}
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
public void testResolveAAAAorA() throws Exception {
|
2015-12-14 15:27:49 +01:00
|
|
|
DnsNameResolver resolver = newResolver(InternetProtocolFamily.IPv6, InternetProtocolFamily.IPv4).build();
|
|
|
|
try {
|
|
|
|
testResolve0(resolver, EXCLUSIONS_RESOLVE_A);
|
|
|
|
} finally {
|
|
|
|
resolver.close();
|
|
|
|
}
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
2015-10-02 15:16:16 +02:00
|
|
|
public void testResolveA() throws Exception {
|
2015-12-14 15:27:49 +01:00
|
|
|
DnsNameResolver resolver = newResolver(InternetProtocolFamily.IPv4)
|
|
|
|
// Cache for eternity
|
|
|
|
.ttl(Integer.MAX_VALUE, Integer.MAX_VALUE)
|
|
|
|
.build();
|
2014-09-19 15:36:32 +02:00
|
|
|
try {
|
2015-12-14 15:27:49 +01:00
|
|
|
final Map<String, InetAddress> resultA = testResolve0(resolver, EXCLUSIONS_RESOLVE_A);
|
2014-09-19 15:36:32 +02:00
|
|
|
|
|
|
|
// Now, try to resolve again to see if it's cached.
|
|
|
|
// This test works because the DNS servers usually randomizes the order of the records in a response.
|
|
|
|
// If cached, the resolved addresses must be always same, because we reuse the same response.
|
|
|
|
|
2015-12-14 15:27:49 +01:00
|
|
|
final Map<String, InetAddress> resultB = testResolve0(resolver, EXCLUSIONS_RESOLVE_A);
|
2014-09-19 15:36:32 +02:00
|
|
|
|
2014-10-25 10:29:06 +02:00
|
|
|
// 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()) {
|
|
|
|
InetAddress expected = e.getValue();
|
|
|
|
InetAddress actual = resultB.get(e.getKey());
|
2015-07-12 12:34:05 +02:00
|
|
|
if (!actual.equals(expected)) {
|
|
|
|
// Print the content of the cache when test failure is expected.
|
2015-12-13 00:11:59 +01:00
|
|
|
System.err.println("Cache for " + e.getKey() + ": " + resolver.resolveAll(e.getKey()).getNow());
|
2015-07-12 12:34:05 +02:00
|
|
|
}
|
2014-10-25 10:29:06 +02:00
|
|
|
assertThat(actual, is(expected));
|
|
|
|
}
|
2014-09-19 15:36:32 +02:00
|
|
|
} finally {
|
2015-12-14 15:27:49 +01:00
|
|
|
resolver.close();
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
public void testResolveAAAA() throws Exception {
|
2015-12-14 15:27:49 +01:00
|
|
|
DnsNameResolver resolver = newResolver(InternetProtocolFamily.IPv6).build();
|
|
|
|
try {
|
|
|
|
testResolve0(resolver, EXCLUSIONS_RESOLVE_AAAA);
|
|
|
|
} finally {
|
|
|
|
resolver.close();
|
|
|
|
}
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
|
2016-06-26 11:16:12 +02:00
|
|
|
@Test
|
|
|
|
public void testNonCachedResolve() throws Exception {
|
|
|
|
DnsNameResolver resolver = newNonCachedResolver(InternetProtocolFamily.IPv4).build();
|
|
|
|
try {
|
|
|
|
testResolve0(resolver, EXCLUSIONS_RESOLVE_A);
|
|
|
|
} finally {
|
|
|
|
resolver.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-07 03:12:33 +01:00
|
|
|
private static Map<String, InetAddress> testResolve0(DnsNameResolver resolver, Set<String> excludedDomains)
|
2015-12-14 15:27:49 +01:00
|
|
|
throws InterruptedException {
|
2014-09-19 15:36:32 +02:00
|
|
|
|
|
|
|
assertThat(resolver.isRecursionDesired(), is(true));
|
|
|
|
|
|
|
|
final Map<String, InetAddress> results = new HashMap<String, InetAddress>();
|
2015-12-14 15:27:49 +01:00
|
|
|
final Map<String, Future<InetAddress>> futures =
|
|
|
|
new LinkedHashMap<String, Future<InetAddress>>();
|
2014-09-19 15:36:32 +02:00
|
|
|
|
2015-12-14 15:27:49 +01:00
|
|
|
for (String name : DOMAINS) {
|
|
|
|
if (excludedDomains.contains(name)) {
|
|
|
|
continue;
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
|
2015-12-14 15:27:49 +01:00
|
|
|
resolve(resolver, futures, name);
|
|
|
|
}
|
2014-09-19 15:36:32 +02:00
|
|
|
|
2015-12-14 15:27:49 +01:00
|
|
|
for (Entry<String, Future<InetAddress>> e : futures.entrySet()) {
|
|
|
|
String unresolved = e.getKey();
|
|
|
|
InetAddress resolved = e.getValue().sync().getNow();
|
2014-09-19 15:36:32 +02:00
|
|
|
|
2015-12-14 15:27:49 +01:00
|
|
|
logger.info("{}: {}", unresolved, resolved.getHostAddress());
|
2014-09-19 15:36:32 +02:00
|
|
|
|
2015-12-14 15:27:49 +01:00
|
|
|
assertThat(resolved.getHostName(), is(unresolved));
|
|
|
|
|
|
|
|
boolean typeMatches = false;
|
|
|
|
for (InternetProtocolFamily f: resolver.resolvedAddressTypes()) {
|
|
|
|
Class<?> resolvedType = resolved.getClass();
|
|
|
|
if (f.addressType().isAssignableFrom(resolvedType)) {
|
|
|
|
typeMatches = true;
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
2015-12-14 15:27:49 +01:00
|
|
|
}
|
2014-09-19 15:36:32 +02:00
|
|
|
|
2015-12-14 15:27:49 +01:00
|
|
|
assertThat(typeMatches, is(true));
|
2014-09-19 15:36:32 +02:00
|
|
|
|
2015-12-14 15:27:49 +01:00
|
|
|
results.put(resolved.getHostName(), resolved);
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
public void testQueryMx() throws Exception {
|
2015-12-14 15:27:49 +01:00
|
|
|
DnsNameResolver resolver = newResolver().build();
|
|
|
|
try {
|
|
|
|
assertThat(resolver.isRecursionDesired(), is(true));
|
2014-09-19 15:36:32 +02:00
|
|
|
|
2015-12-14 15:27:49 +01:00
|
|
|
Map<String, Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> futures =
|
|
|
|
new LinkedHashMap<String, Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>>();
|
|
|
|
for (String name: DOMAINS) {
|
|
|
|
if (EXCLUSIONS_QUERY_MX.contains(name)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
queryMx(resolver, futures, name);
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
|
2015-12-14 15:27:49 +01:00
|
|
|
for (Entry<String, Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> e: futures.entrySet()) {
|
|
|
|
String hostname = e.getKey();
|
|
|
|
Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> f = e.getValue().awaitUninterruptibly();
|
2014-09-19 15:36:32 +02:00
|
|
|
|
2015-12-14 15:27:49 +01:00
|
|
|
DnsResponse response = f.getNow().content();
|
|
|
|
assertThat(response.code(), is(DnsResponseCode.NOERROR));
|
2015-03-16 07:46:14 +01:00
|
|
|
|
2015-12-14 15:27:49 +01:00
|
|
|
final int answerCount = response.count(DnsSection.ANSWER);
|
|
|
|
final List<DnsRecord> mxList = new ArrayList<DnsRecord>(answerCount);
|
|
|
|
for (int i = 0; i < answerCount; i ++) {
|
|
|
|
final DnsRecord r = response.recordAt(DnsSection.ANSWER, i);
|
|
|
|
if (r.type() == DnsRecordType.MX) {
|
|
|
|
mxList.add(r);
|
|
|
|
}
|
|
|
|
}
|
2014-09-19 15:36:32 +02:00
|
|
|
|
2015-12-14 15:27:49 +01:00
|
|
|
assertThat(mxList.size(), is(greaterThan(0)));
|
|
|
|
StringBuilder buf = new StringBuilder();
|
|
|
|
for (DnsRecord r: mxList) {
|
|
|
|
ByteBuf recordContent = ((ByteBufHolder) r).content();
|
|
|
|
|
|
|
|
buf.append(StringUtil.NEWLINE);
|
|
|
|
buf.append('\t');
|
|
|
|
buf.append(r.name());
|
|
|
|
buf.append(' ');
|
|
|
|
buf.append(r.type().name());
|
|
|
|
buf.append(' ');
|
|
|
|
buf.append(recordContent.readUnsignedShort());
|
|
|
|
buf.append(' ');
|
|
|
|
buf.append(DnsNameResolverContext.decodeDomainName(recordContent));
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
|
2015-12-14 15:27:49 +01:00
|
|
|
logger.info("{} has the following MX records:{}", hostname, buf);
|
|
|
|
response.release();
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
2015-12-14 15:27:49 +01:00
|
|
|
} finally {
|
|
|
|
resolver.close();
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-29 04:34:52 +02:00
|
|
|
@Test
|
2015-08-18 11:44:51 +02:00
|
|
|
public void testNegativeTtl() throws Exception {
|
2015-12-14 15:27:49 +01:00
|
|
|
final DnsNameResolver resolver = newResolver().negativeTtl(10).build();
|
2015-08-18 11:44:51 +02:00
|
|
|
try {
|
2015-12-14 15:27:49 +01:00
|
|
|
resolveNonExistentDomain(resolver);
|
2015-08-18 11:44:51 +02:00
|
|
|
|
|
|
|
final int size = 10000;
|
2015-08-29 04:34:52 +02:00
|
|
|
final List<UnknownHostException> exceptions = new ArrayList<UnknownHostException>();
|
|
|
|
|
|
|
|
// If negative cache works, this thread should be done really quickly.
|
|
|
|
final Thread negativeLookupThread = new Thread() {
|
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
for (int i = 0; i < size; i++) {
|
2015-12-14 15:27:49 +01:00
|
|
|
exceptions.add(resolveNonExistentDomain(resolver));
|
2015-08-29 04:34:52 +02:00
|
|
|
if (isInterrupted()) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
negativeLookupThread.start();
|
|
|
|
negativeLookupThread.join(5000);
|
2015-08-18 11:44:51 +02:00
|
|
|
|
2015-08-29 04:34:52 +02:00
|
|
|
if (negativeLookupThread.isAlive()) {
|
|
|
|
negativeLookupThread.interrupt();
|
|
|
|
fail("Cached negative lookups did not finish quickly.");
|
2015-08-18 11:44:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
assertThat(exceptions, hasSize(size));
|
|
|
|
} finally {
|
2015-12-14 15:27:49 +01:00
|
|
|
resolver.close();
|
2015-08-18 11:44:51 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-07 03:12:33 +01:00
|
|
|
private static UnknownHostException resolveNonExistentDomain(DnsNameResolver resolver) {
|
2015-08-18 11:44:51 +02:00
|
|
|
try {
|
2015-12-13 00:11:59 +01:00
|
|
|
resolver.resolve("non-existent.netty.io").sync();
|
2015-08-18 11:44:51 +02:00
|
|
|
fail();
|
|
|
|
return null;
|
|
|
|
} catch (Exception e) {
|
|
|
|
assertThat(e, is(instanceOf(UnknownHostException.class)));
|
|
|
|
return (UnknownHostException) e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-10 22:00:30 +02:00
|
|
|
@Test
|
|
|
|
public void testResolveIp() {
|
2015-12-14 15:27:49 +01:00
|
|
|
DnsNameResolver resolver = newResolver().build();
|
|
|
|
try {
|
|
|
|
InetAddress address = resolver.resolve("10.0.0.1").syncUninterruptibly().getNow();
|
2015-07-10 22:00:30 +02:00
|
|
|
|
2016-01-18 08:07:02 +01:00
|
|
|
assertEquals("10.0.0.1", address.getHostAddress());
|
2015-12-14 15:27:49 +01:00
|
|
|
} finally {
|
|
|
|
resolver.close();
|
|
|
|
}
|
2015-07-10 22:00:30 +02:00
|
|
|
}
|
|
|
|
|
2017-01-20 15:46:46 +01:00
|
|
|
@Test
|
|
|
|
public void testResolveEmptyIpv4() {
|
|
|
|
testResolve0(InternetProtocolFamily.IPv4, NetUtil.LOCALHOST4, StringUtil.EMPTY_STRING);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
public void testResolveEmptyIpv6() {
|
|
|
|
testResolve0(InternetProtocolFamily.IPv6, NetUtil.LOCALHOST6, StringUtil.EMPTY_STRING);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
public void testResolveNullIpv4() {
|
|
|
|
testResolve0(InternetProtocolFamily.IPv4, NetUtil.LOCALHOST4, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
public void testResolveNullIpv6() {
|
|
|
|
testResolve0(InternetProtocolFamily.IPv6, NetUtil.LOCALHOST6, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void testResolve0(InternetProtocolFamily family, InetAddress expectedAddr, String name) {
|
|
|
|
DnsNameResolver resolver = newResolver(family).build();
|
|
|
|
try {
|
|
|
|
InetAddress address = resolver.resolve(name).syncUninterruptibly().getNow();
|
|
|
|
assertEquals(expectedAddr, address);
|
|
|
|
} finally {
|
|
|
|
resolver.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
public void testResolveAllEmptyIpv4() {
|
|
|
|
testResolveAll0(InternetProtocolFamily.IPv4, NetUtil.LOCALHOST4, StringUtil.EMPTY_STRING);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
public void testResolveAllEmptyIpv6() {
|
|
|
|
testResolveAll0(InternetProtocolFamily.IPv6, NetUtil.LOCALHOST6, StringUtil.EMPTY_STRING);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
public void testResolveAllNullIpv4() {
|
|
|
|
testResolveAll0(InternetProtocolFamily.IPv4, NetUtil.LOCALHOST4, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
public void testResolveAllNullIpv6() {
|
|
|
|
testResolveAll0(InternetProtocolFamily.IPv6, NetUtil.LOCALHOST6, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void testResolveAll0(InternetProtocolFamily family, InetAddress expectedAddr, String name) {
|
|
|
|
DnsNameResolver resolver = newResolver(family).build();
|
|
|
|
try {
|
|
|
|
List<InetAddress> addresses = resolver.resolveAll(name).syncUninterruptibly().getNow();
|
|
|
|
assertEquals(1, addresses.size());
|
|
|
|
assertEquals(expectedAddr, addresses.get(0));
|
|
|
|
} finally {
|
|
|
|
resolver.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-18 10:00:01 +01:00
|
|
|
@Test
|
|
|
|
public void testResolveDecodeUnicode() {
|
|
|
|
testResolveUnicode(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
public void testResolveNotDecodeUnicode() {
|
|
|
|
testResolveUnicode(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void testResolveUnicode(boolean decode) {
|
|
|
|
DnsNameResolver resolver = newResolver(decode).build();
|
|
|
|
try {
|
|
|
|
for (Entry<String, String> entries : DOMAINS_PUNYCODE.entrySet()) {
|
|
|
|
InetAddress address = resolver.resolve(entries.getKey()).syncUninterruptibly().getNow();
|
|
|
|
assertEquals(decode ? entries.getKey() : entries.getValue(), address.getHostName());
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
resolver.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-25 09:28:22 +01:00
|
|
|
@Test
|
|
|
|
public void testRecursiveResolveNoCache() throws Exception {
|
|
|
|
testRecursiveResolveCache(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
public void testRecursiveResolveCache() throws Exception {
|
|
|
|
testRecursiveResolveCache(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void testRecursiveResolveCache(boolean cache) throws Exception {
|
|
|
|
final String hostname = "some.record.netty.io";
|
|
|
|
final String hostname2 = "some2.record.netty.io";
|
|
|
|
|
|
|
|
final TestDnsServer dnsServerAuthority = new TestDnsServer(new HashSet<String>(
|
|
|
|
Arrays.asList(hostname, hostname2)));
|
|
|
|
dnsServerAuthority.start();
|
|
|
|
|
|
|
|
TestDnsServer dnsServer = new RedirectingTestDnsServer(hostname,
|
|
|
|
dnsServerAuthority.localAddress().getAddress().getHostAddress());
|
|
|
|
dnsServer.start();
|
|
|
|
|
|
|
|
TestDnsCache nsCache = new TestDnsCache(cache ? new DefaultDnsCache() : NoopDnsCache.INSTANCE);
|
|
|
|
EventLoopGroup group = new NioEventLoopGroup(1);
|
|
|
|
DnsNameResolver resolver = new DnsNameResolver(
|
|
|
|
group.next(), new ReflectiveChannelFactory<DatagramChannel>(NioDatagramChannel.class),
|
|
|
|
DnsServerAddresses.singleton(dnsServer.localAddress()), NoopDnsCache.INSTANCE, nsCache,
|
|
|
|
3000, new InternetProtocolFamily[] { InternetProtocolFamily.IPv4 }, true, 10,
|
|
|
|
true, 4096, false, HostsFileEntriesResolver.DEFAULT,
|
|
|
|
DnsNameResolver.DEFAULT_SEACH_DOMAINS, 0, true) {
|
|
|
|
@Override
|
|
|
|
int dnsRedirectPort(InetAddress server) {
|
|
|
|
return server.equals(dnsServerAuthority.localAddress().getAddress()) ?
|
|
|
|
dnsServerAuthority.localAddress().getPort() : DnsServerAddresses.DNS_PORT;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
try {
|
|
|
|
resolver.resolveAll(hostname).syncUninterruptibly();
|
|
|
|
|
|
|
|
if (cache) {
|
|
|
|
assertNull(nsCache.cache.get("io.", null));
|
|
|
|
assertNull(nsCache.cache.get("netty.io.", null));
|
|
|
|
List<DnsCacheEntry> entries = nsCache.cache.get("record.netty.io.", null);
|
|
|
|
assertEquals(1, entries.size());
|
|
|
|
|
|
|
|
assertNull(nsCache.cache.get(hostname, null));
|
|
|
|
|
|
|
|
// Test again via cache.
|
|
|
|
resolver.resolveAll(hostname).syncUninterruptibly();
|
|
|
|
resolver.resolveAll(hostname2).syncUninterruptibly();
|
|
|
|
|
|
|
|
// Check that it only queried the cache for record.netty.io.
|
|
|
|
assertNull(nsCache.cacheHits.get("io."));
|
|
|
|
assertNull(nsCache.cacheHits.get("netty.io."));
|
|
|
|
assertNotNull(nsCache.cacheHits.get("record.netty.io."));
|
|
|
|
assertNull(nsCache.cacheHits.get("some.record.netty.io."));
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
resolver.close();
|
|
|
|
group.shutdownGracefully(0, 0, TimeUnit.SECONDS);
|
|
|
|
dnsServer.stop();
|
|
|
|
dnsServerAuthority.stop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-07 03:12:33 +01:00
|
|
|
private static void resolve(DnsNameResolver resolver, Map<String, Future<InetAddress>> futures, String hostname) {
|
2015-12-13 00:11:59 +01:00
|
|
|
futures.put(hostname, resolver.resolve(hostname));
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
|
2015-03-16 07:46:14 +01:00
|
|
|
private static void queryMx(
|
2015-12-14 15:27:49 +01:00
|
|
|
DnsNameResolver resolver,
|
2015-03-16 07:46:14 +01:00
|
|
|
Map<String, Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> futures,
|
|
|
|
String hostname) throws Exception {
|
|
|
|
futures.put(hostname, resolver.query(new DefaultDnsQuestion(hostname, DnsRecordType.MX)));
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
2017-01-25 09:28:22 +01:00
|
|
|
|
|
|
|
private static final class TestDnsCache implements DnsCache {
|
|
|
|
private final DnsCache cache;
|
|
|
|
final Map<String, List<DnsCacheEntry>> cacheHits = new HashMap<String, List<DnsCacheEntry>>();
|
|
|
|
|
|
|
|
TestDnsCache(DnsCache cache) {
|
|
|
|
this.cache = cache;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void clear() {
|
|
|
|
cache.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean clear(String hostname) {
|
|
|
|
return cache.clear(hostname);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public List<DnsCacheEntry> get(String hostname, DnsRecord[] additionals) {
|
|
|
|
List<DnsCacheEntry> cacheEntries = cache.get(hostname, additionals);
|
|
|
|
cacheHits.put(hostname, cacheEntries);
|
|
|
|
return cacheEntries;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void cache(
|
|
|
|
String hostname, DnsRecord[] additionals, InetAddress address, long originalTtl, EventLoop loop) {
|
|
|
|
cache.cache(hostname, additionals, address, originalTtl, loop);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void cache(
|
|
|
|
String hostname, DnsRecord[] additionals, Throwable cause, EventLoop loop) {
|
|
|
|
cache.cache(hostname, additionals, cause, loop);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static class RedirectingTestDnsServer extends TestDnsServer {
|
|
|
|
|
|
|
|
private final String dnsAddress;
|
|
|
|
private final String domain;
|
|
|
|
|
|
|
|
RedirectingTestDnsServer(String domain, String dnsAddress) {
|
|
|
|
super(Collections.singleton(domain));
|
|
|
|
this.domain = domain;
|
|
|
|
this.dnsAddress = dnsAddress;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected DnsMessage filterMessage(DnsMessage message) {
|
|
|
|
// Clear the answers as we want to add our own stuff to test dns redirects.
|
|
|
|
message.getAnswerRecords().clear();
|
|
|
|
|
|
|
|
String name = domain;
|
|
|
|
for (int i = 0 ;; i++) {
|
|
|
|
int idx = name.indexOf('.');
|
|
|
|
if (idx <= 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
name = name.substring(idx + 1); // skip the '.' as well.
|
|
|
|
String dnsName = "dns" + idx + '.' + domain;
|
|
|
|
message.getAuthorityRecords().add(newNsRecord(name, dnsName));
|
|
|
|
message.getAdditionalRecords().add(newARecord(dnsName, i == 0 ? dnsAddress : "1.2.3." + idx));
|
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|