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.
This commit is contained in:
Norman Maurer 2017-01-18 10:00:01 +01:00
parent ead9938980
commit 0d5b665fba
4 changed files with 114 additions and 5 deletions

View File

@ -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<? extends DatagramChannel> 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 <a href="https://tools.ietf.org/html/rfc3492">rfc3492</a>.
*/
public DnsNameResolver(
EventLoop eventLoop,
ChannelFactory<? extends DatagramChannel> 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}.

View File

@ -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 <a href="https://tools.ietf.org/html/rfc3492">rfc3492</a>.
*
* @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);
}
}

View File

@ -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<T> {
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);

View File

@ -170,6 +170,20 @@ public class DnsNameResolverTest {
"blogspot.in",
"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);
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<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();
}
}
private static void resolve(DnsNameResolver resolver, Map<String, Future<InetAddress>> 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)));
}
}