From 0d5b665fba20035d805377f668b021d37984d4df Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Wed, 18 Jan 2017 10:00:01 +0100 Subject: [PATCH] Automatically decode DNS domain name to unicode Motivation: DnsNameResolver will return the domain / host name as ascii code using punycode (https://tools.ietf.org/html/rfc3492). This is different to what the JDK does which always convert it to unicode. We should do the same by default but allow to also not do it. Modifications: - Add new builder method on DnsNameResolverBuilder which allow to disable / enable converting. Default is to convert just like the JDK does. - Add unit tests for it. Result: DnsNameResolver and JDK impl behave the same way. --- .../netty/resolver/dns/DnsNameResolver.java | 53 +++++++++++++++++++ .../resolver/dns/DnsNameResolverBuilder.java | 16 +++++- .../resolver/dns/DnsNameResolverContext.java | 4 +- .../resolver/dns/DnsNameResolverTest.java | 46 ++++++++++++++-- 4 files changed, 114 insertions(+), 5 deletions(-) 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 fdbf449cf5..10c0065e54 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 @@ -155,6 +155,7 @@ public class DnsNameResolver extends InetNameResolver { private final boolean cnameFollowAAAARecords; private final InternetProtocolFamily preferredAddressType; private final DnsRecordType[] resolveRecordTypes; + private final boolean decodeIdn; /** * Creates a new DNS-based name resolver that communicates with the specified list of DNS servers. @@ -175,7 +176,11 @@ public class DnsNameResolver extends InetNameResolver { * @param hostsFileEntriesResolver the {@link HostsFileEntriesResolver} used to check for local aliases * @param searchDomains the list of search domain * @param ndots the ndots value + * @deprecated use {@link #DnsNameResolver(EventLoop, ChannelFactory, DnsServerAddresses, DnsCache, long, + * InternetProtocolFamily[], boolean, int, boolean, int, + * boolean, HostsFileEntriesResolver, String[], int, boolean)} */ + @Deprecated public DnsNameResolver( EventLoop eventLoop, ChannelFactory channelFactory, @@ -191,6 +196,49 @@ public class DnsNameResolver extends InetNameResolver { HostsFileEntriesResolver hostsFileEntriesResolver, String[] searchDomains, int ndots) { + this(eventLoop, channelFactory, nameServerAddresses, resolveCache, queryTimeoutMillis, resolvedAddressTypes, + recursionDesired, maxQueriesPerResolve, traceEnabled, maxPayloadSize, optResourceEnabled, + hostsFileEntriesResolver, searchDomains, ndots, true); + } + + /** + * Creates a new DNS-based name resolver that communicates with the specified list of DNS servers. + * + * @param eventLoop the {@link EventLoop} which will perform the communication with the DNS servers + * @param channelFactory the {@link ChannelFactory} that will create a {@link DatagramChannel} + * @param nameServerAddresses the addresses of the DNS server. For each DNS query, a new stream is created from + * this to determine which DNS server should be contacted for the next retry in case + * of failure. + * @param resolveCache the DNS resolved entries cache + * @param queryTimeoutMillis timeout of each DNS query in millis + * @param resolvedAddressTypes list of the protocol families + * @param recursionDesired if recursion desired flag must be set + * @param maxQueriesPerResolve the maximum allowed number of DNS queries for a given name resolution + * @param traceEnabled if trace is enabled + * @param maxPayloadSize the capacity of the datagram packet buffer + * @param optResourceEnabled if automatic inclusion of a optional records is enabled + * @param hostsFileEntriesResolver the {@link HostsFileEntriesResolver} used to check for local aliases + * @param searchDomains the list of search domain + * @param ndots the ndots value + * @param decodeIdn {@code true} if domain / host names should be decoded to unicode when received. + * See rfc3492. + */ + public DnsNameResolver( + EventLoop eventLoop, + ChannelFactory channelFactory, + DnsServerAddresses nameServerAddresses, + final DnsCache resolveCache, + long queryTimeoutMillis, + InternetProtocolFamily[] resolvedAddressTypes, + boolean recursionDesired, + int maxQueriesPerResolve, + boolean traceEnabled, + int maxPayloadSize, + boolean optResourceEnabled, + HostsFileEntriesResolver hostsFileEntriesResolver, + String[] searchDomains, + int ndots, + boolean decodeIdn) { super(eventLoop); checkNotNull(channelFactory, "channelFactory"); @@ -206,6 +254,7 @@ public class DnsNameResolver extends InetNameResolver { this.resolveCache = checkNotNull(resolveCache, "resolveCache"); this.searchDomains = checkNotNull(searchDomains, "searchDomains").clone(); this.ndots = checkPositiveOrZero(ndots, "ndots"); + this.decodeIdn = decodeIdn; boolean cnameFollowARecords = false; boolean cnameFollowAAAARecords = false; @@ -309,6 +358,10 @@ public class DnsNameResolver extends InetNameResolver { return resolveRecordTypes; } + final boolean isDecodeIdn() { + return decodeIdn; + } + /** * Returns {@code true} if and only if this resolver sends a DNS query with the RD (recursion desired) flag set. * The default value is {@code true}. diff --git a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolverBuilder.java b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolverBuilder.java index dd6fed0e34..1b4561d904 100644 --- a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolverBuilder.java +++ b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolverBuilder.java @@ -53,6 +53,7 @@ public final class DnsNameResolverBuilder { private HostsFileEntriesResolver hostsFileEntriesResolver = HostsFileEntriesResolver.DEFAULT; private String[] searchDomains = DnsNameResolver.DEFAULT_SEACH_DOMAINS; private int ndots = 1; + private boolean decodeIdn = true; /** * Creates a new builder. @@ -330,6 +331,18 @@ public final class DnsNameResolverBuilder { return this; } + /** + * Set if domain / host names should be decoded to unicode when received. + * See rfc3492. + * + * @param decodeIdn if should get decoded + * @return {@code this} + */ + public DnsNameResolverBuilder decodeIdn(boolean decodeIdn) { + this.decodeIdn = decodeIdn; + return this; + } + /** * Returns a new {@link DnsNameResolver} instance. * @@ -358,6 +371,7 @@ public final class DnsNameResolverBuilder { optResourceEnabled, hostsFileEntriesResolver, searchDomains, - ndots); + ndots, + decodeIdn); } } diff --git a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolverContext.java b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolverContext.java index 41d7b72c63..5884513d84 100644 --- a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolverContext.java +++ b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolverContext.java @@ -36,6 +36,7 @@ import io.netty.util.concurrent.FutureListener; import io.netty.util.concurrent.Promise; import io.netty.util.internal.StringUtil; +import java.net.IDN; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; @@ -272,7 +273,8 @@ abstract class DnsNameResolverContext { final InetAddress resolved; try { - resolved = InetAddress.getByAddress(hostname, addrBytes); + resolved = InetAddress.getByAddress( + parent.isDecodeIdn() ? IDN.toUnicode(hostname) : hostname, addrBytes); } catch (UnknownHostException e) { // Should never reach here. throw new Error(e); 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 b27c6aa36e..d5fd45d79c 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 @@ -170,6 +170,20 @@ public class DnsNameResolverTest { "blogspot.in", "localhost"))); + private static final Map DOMAINS_PUNYCODE = new HashMap(); + 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 DOMAINS_ALL; + static { + Set all = new HashSet(DOMAINS.size() + DOMAINS_PUNYCODE.size()); + all.addAll(DOMAINS); + all.addAll(DOMAINS_PUNYCODE.values()); + DOMAINS_ALL = Collections.unmodifiableSet(all); + } + /** * The list of the domain names to exclude from {@link #testResolveAorAAAA()}. */ @@ -238,17 +252,22 @@ public class DnsNameResolverTest { StringUtil.EMPTY_STRING); } - private static final TestDnsServer dnsServer = new TestDnsServer(DOMAINS); + private static final TestDnsServer dnsServer = new TestDnsServer(DOMAINS_ALL); private static final EventLoopGroup group = new NioEventLoopGroup(1); - private static DnsNameResolverBuilder newResolver() { + private static DnsNameResolverBuilder newResolver(boolean decodeToUnicode) { return new DnsNameResolverBuilder(group.next()) .channelType(NioDatagramChannel.class) .nameServerAddresses(DnsServerAddresses.singleton(dnsServer.localAddress())) .maxQueriesPerResolve(1) + .decodeIdn(decodeToUnicode) .optResourceEnabled(false); } + private static DnsNameResolverBuilder newResolver() { + return newResolver(true); + } + private static DnsNameResolverBuilder newResolver(InternetProtocolFamily... resolvedAddressTypes) { return newResolver() .resolvedAddressTypes(resolvedAddressTypes); @@ -558,6 +577,28 @@ public class DnsNameResolverTest { } } + @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 entries : DOMAINS_PUNYCODE.entrySet()) { + InetAddress address = resolver.resolve(entries.getKey()).syncUninterruptibly().getNow(); + assertEquals(decode ? entries.getKey() : entries.getValue(), address.getHostName()); + } + } finally { + resolver.close(); + } + } + private static void resolve(DnsNameResolver resolver, Map> futures, String hostname) { futures.put(hostname, resolver.resolve(hostname)); } @@ -568,5 +609,4 @@ public class DnsNameResolverTest { String hostname) throws Exception { futures.put(hostname, resolver.query(new DefaultDnsQuestion(hostname, DnsRecordType.MX))); } - }