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 6e2d799e0e..260dde67c6 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 @@ -64,9 +64,9 @@ import java.util.Iterator; import java.util.List; import static io.netty.resolver.dns.DefaultDnsServerAddressStreamProvider.DNS_PORT; +import static io.netty.resolver.dns.UnixResolverDnsServerAddressStreamProvider.parseEtcResolverFirstNdots; import static io.netty.util.internal.ObjectUtil.checkNotNull; import static io.netty.util.internal.ObjectUtil.checkPositive; -import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero; /** * A DNS-based {@link InetNameResolver}. @@ -97,6 +97,7 @@ public class DnsNameResolver extends InetNameResolver { static final ResolvedAddressTypes DEFAULT_RESOLVE_ADDRESS_TYPES; static final String[] DEFAULT_SEARCH_DOMAINS; + private static final int DEFAULT_NDOTS; static { if (NetUtil.isIpV4StackPreferred()) { @@ -129,6 +130,14 @@ public class DnsNameResolver extends InetNameResolver { searchDomains = EmptyArrays.EMPTY_STRINGS; } DEFAULT_SEARCH_DOMAINS = searchDomains; + + int ndots; + try { + ndots = parseEtcResolverFirstNdots(); + } catch (Exception ignore) { + ndots = UnixResolverDnsServerAddressStreamProvider.DEFAULT_NDOTS; + } + DEFAULT_NDOTS = ndots; } private static final DatagramDnsResponseDecoder DECODER = new DatagramDnsResponseDecoder(); @@ -234,7 +243,7 @@ public class DnsNameResolver extends InetNameResolver { this.dnsQueryLifecycleObserverFactory = checkNotNull(dnsQueryLifecycleObserverFactory, "dnsQueryLifecycleObserverFactory"); this.searchDomains = searchDomains != null ? searchDomains.clone() : DEFAULT_SEARCH_DOMAINS; - this.ndots = checkPositiveOrZero(ndots, "ndots"); + this.ndots = ndots >= 0 ? ndots : DEFAULT_NDOTS; this.decodeIdn = decodeIdn; switch (this.resolvedAddressTypes) { 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 497ba5b39b..8ccf7c308f 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 @@ -55,7 +55,7 @@ public final class DnsNameResolverBuilder { private DnsQueryLifecycleObserverFactory dnsQueryLifecycleObserverFactory = NoopDnsQueryLifecycleObserverFactory.INSTANCE; private String[] searchDomains; - private int ndots = 1; + private int ndots = -1; private boolean decodeIdn = true; /** 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 2edb088c36..df34e4aa5a 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 @@ -91,7 +91,6 @@ abstract class DnsNameResolverContext { private final DnsNameResolver parent; private final DnsServerAddressStream nameServerAddrs; private final String hostname; - protected String pristineHostname; private final DnsCache resolveCache; private final boolean traceEnabled; private final int maxAllowedQueries; @@ -102,6 +101,7 @@ abstract class DnsNameResolverContext { Collections.newSetFromMap( new IdentityHashMap>, Boolean>()); + private String pristineHostname; private List resolvedEntries; private StringBuilder trace; private int allowedQueries; @@ -124,48 +124,46 @@ abstract class DnsNameResolverContext { allowedQueries = maxAllowedQueries; } - void resolve(Promise promise) { - boolean directSearch = parent.searchDomains().length == 0 || StringUtil.endsWith(hostname, '.'); - if (directSearch) { + void resolve(final Promise promise) { + if (parent.searchDomains().length == 0 || parent.ndots() == 0 || StringUtil.endsWith(hostname, '.')) { internalResolve(promise); } else { - final Promise original = promise; - promise = parent.executor().newPromise(); - promise.addListener(new FutureListener() { - int count; + int dots = 0; + for (int idx = hostname.length() - 1; idx >= 0; idx--) { + if (hostname.charAt(idx) == '.' && ++dots >= parent.ndots()) { + internalResolve(promise); + return; + } + } + + doSearchDomainQuery(0, new FutureListener() { + private int count = 1; @Override public void operationComplete(Future future) throws Exception { if (future.isSuccess()) { - original.trySuccess(future.getNow()); + promise.trySuccess(future.getNow()); } else if (count < parent.searchDomains().length) { - String searchDomain = parent.searchDomains()[count++]; - Promise nextPromise = parent.executor().newPromise(); - String nextHostname = hostname + '.' + searchDomain; - DnsNameResolverContext nextContext = newResolverContext(parent, - nextHostname, additionals, resolveCache, nameServerAddrs); - nextContext.pristineHostname = hostname; - nextContext.internalResolve(nextPromise); - nextPromise.addListener(this); + doSearchDomainQuery(count++, this); } else { - original.tryFailure(future.cause()); + promise.tryFailure(future.cause()); } } }); - if (parent.ndots() == 0) { - internalResolve(promise); - } else { - int dots = 0; - for (int idx = hostname.length() - 1; idx >= 0; idx--) { - if (hostname.charAt(idx) == '.' && ++dots >= parent.ndots()) { - internalResolve(promise); - return; - } - } - promise.tryFailure(new UnknownHostException(hostname)); - } } } + private void doSearchDomainQuery(int count, FutureListener listener) { + DnsNameResolverContext nextContext = newResolverContext(parent, + hostname + '.' + parent.searchDomains()[count], + additionals, + resolveCache, + nameServerAddrs); + nextContext.pristineHostname = hostname; + Promise nextPromise = parent.executor().newPromise(); + nextContext.internalResolve(nextPromise); + nextPromise.addListener(listener); + } + private void internalResolve(Promise promise) { DnsServerAddressStream nameServerAddressStream = getNameServers(hostname); diff --git a/resolver-dns/src/main/java/io/netty/resolver/dns/UnixResolverDnsServerAddressStreamProvider.java b/resolver-dns/src/main/java/io/netty/resolver/dns/UnixResolverDnsServerAddressStreamProvider.java index c22f3ae6c4..a72b0c16d1 100644 --- a/resolver-dns/src/main/java/io/netty/resolver/dns/UnixResolverDnsServerAddressStreamProvider.java +++ b/resolver-dns/src/main/java/io/netty/resolver/dns/UnixResolverDnsServerAddressStreamProvider.java @@ -45,10 +45,15 @@ import static io.netty.util.internal.StringUtil.indexOfNonWhiteSpace; public final class UnixResolverDnsServerAddressStreamProvider implements DnsServerAddressStreamProvider { private static final InternalLogger logger = InternalLoggerFactory.getInstance(UnixResolverDnsServerAddressStreamProvider.class); + private static final String ETC_RESOLV_CONF_FILE = "/etc/resolv.conf"; + private static final String ETC_RESOLVER_DIR = "/etc/resolver"; private static final String NAMESERVER_ROW_LABEL = "nameserver"; private static final String SORTLIST_ROW_LABEL = "sortlist"; + private static final String OPTIONS_ROW_LABEL = "options"; private static final String DOMAIN_ROW_LABEL = "domain"; private static final String PORT_ROW_LABEL = "port"; + private static final String NDOTS_LABEL = "ndots:"; + static final int DEFAULT_NDOTS = 1; private final DnsServerAddresses defaultNameServerAddresses; private final Map domainToNameServerStreamMap; @@ -59,11 +64,11 @@ public final class UnixResolverDnsServerAddressStreamProvider implements DnsServ static DnsServerAddressStreamProvider parseSilently() { try { UnixResolverDnsServerAddressStreamProvider nameServerCache = - new UnixResolverDnsServerAddressStreamProvider("/etc/resolv.conf", "/etc/resolver"); + new UnixResolverDnsServerAddressStreamProvider(ETC_RESOLV_CONF_FILE, ETC_RESOLVER_DIR); return nameServerCache.mayOverrideNameServers() ? nameServerCache : DefaultDnsServerAddressStreamProvider.INSTANCE; } catch (Exception e) { - logger.debug("failed to parse /etc/resolv.conf and/or /etc/resolver", e); + logger.debug("failed to parse {} and/or {}", ETC_RESOLV_CONF_FILE, ETC_RESOLVER_DIR, e); return DefaultDnsServerAddressStreamProvider.INSTANCE; } } @@ -235,4 +240,50 @@ public final class UnixResolverDnsServerAddressStreamProvider implements DnsServ domainName, existingAddresses, addresses); } } + + /** + * Parse a file of the format /etc/resolv.conf and return the + * value corresponding to the first ndots in an options configuration. + * @return the value corresponding to the first ndots in an options configuration, or {@link #DEFAULT_NDOTS} if not + * found. + * @throws IOException If a failure occurs parsing the file. + */ + static int parseEtcResolverFirstNdots() throws IOException { + return parseEtcResolverFirstNdots(new File(ETC_RESOLV_CONF_FILE)); + } + + /** + * Parse a file of the format /etc/resolv.conf and return the + * value corresponding to the first ndots in an options configuration. + * @param etcResolvConf a file of the format /etc/resolv.conf. + * @return the value corresponding to the first ndots in an options configuration, or {@link #DEFAULT_NDOTS} if not + * found. + * @throws IOException If a failure occurs parsing the file. + */ + static int parseEtcResolverFirstNdots(File etcResolvConf) throws IOException { + FileReader fr = new FileReader(etcResolvConf); + BufferedReader br = null; + try { + br = new BufferedReader(fr); + String line; + while ((line = br.readLine()) != null) { + if (line.startsWith(OPTIONS_ROW_LABEL)) { + int i = line.indexOf(NDOTS_LABEL); + if (i >= 0) { + i += NDOTS_LABEL.length(); + final int j = line.indexOf(' ', i); + return Integer.parseInt(line.substring(i, j < 0 ? line.length() : j)); + } + break; + } + } + } finally { + if (br == null) { + fr.close(); + } else { + br.close(); + } + } + return DEFAULT_NDOTS; + } } diff --git a/resolver-dns/src/test/java/io/netty/resolver/dns/SearchDomainTest.java b/resolver-dns/src/test/java/io/netty/resolver/dns/SearchDomainTest.java index 906aac64a4..1caac54718 100644 --- a/resolver-dns/src/test/java/io/netty/resolver/dns/SearchDomainTest.java +++ b/resolver-dns/src/test/java/io/netty/resolver/dns/SearchDomainTest.java @@ -85,7 +85,7 @@ public class SearchDomainTest { dnsServer = new TestDnsServer(store); dnsServer.start(); - resolver = newResolver().searchDomains(Collections.singletonList("foo.com")).build(); + resolver = newResolver().searchDomains(Collections.singletonList("foo.com")).ndots(2).build(); String a = "host1.foo.com"; String resolved = assertResolve(resolver, a); @@ -113,9 +113,10 @@ public class SearchDomainTest { resolved = assertResolve(resolver, "host4.sub"); assertEquals(store.getAddress("host4.sub.foo.com"), resolved); - // "host5.sub" contains a dot and is resolved + // "host5.sub" would have been directly resolved but since it has less than ndots the "foo.com" search domain + // is used. resolved = assertResolve(resolver, "host5.sub"); - assertEquals(store.getAddress("host5.sub"), resolved); + assertEquals(store.getAddress("host5.sub.foo.com"), resolved); } @Test @@ -132,7 +133,7 @@ public class SearchDomainTest { dnsServer = new TestDnsServer(store); dnsServer.start(); - resolver = newResolver().searchDomains(Collections.singletonList("foo.com")).build(); + resolver = newResolver().searchDomains(Collections.singletonList("foo.com")).ndots(2).build(); String a = "host1.foo.com"; List resolved = assertResolveAll(resolver, a); @@ -160,9 +161,10 @@ public class SearchDomainTest { resolved = assertResolveAll(resolver, "host4.sub"); assertEquals(store.getAddresses("host4.sub.foo.com"), resolved); - // "host5.sub" contains a dot and is resolved + // "host5.sub" would have been directly resolved but since it has less than ndots the "foo.com" search domain + // is used. resolved = assertResolveAll(resolver, "host5.sub"); - assertEquals(store.getAddresses("host5.sub"), resolved); + assertEquals(store.getAddresses("host5.sub.foo.com"), resolved); } @Test @@ -237,9 +239,9 @@ public class SearchDomainTest { resolved = assertResolve(resolver, "host1.foo.com"); assertEquals(store.getAddress("host1.foo.com"), resolved); - // "host2" resolves to host2.foo.com with the foo.com search domain - resolved = assertResolve(resolver, "host2"); - assertEquals(store.getAddress("host2.foo.com"), resolved); + // "host2" shouldn't resolve because it is not in the known domain names, and "host2" has 0 dots which is not + // less ndots (which is also 0). + assertNotResolve(resolver, "host2"); } private void assertNotResolve(DnsNameResolver resolver, String inetHost) throws InterruptedException { @@ -278,7 +280,7 @@ public class SearchDomainTest { dnsServer = new TestDnsServer(store); dnsServer.start(); - resolver = newResolver().searchDomains(Collections.singletonList("foo.com")).build(); + resolver = newResolver().searchDomains(Collections.singletonList("foo.com")).ndots(2).build(); Future fut = resolver.resolve("unknown.hostname"); assertTrue(fut.await(10, TimeUnit.SECONDS)); diff --git a/resolver-dns/src/test/java/io/netty/resolver/dns/UnixResolverDnsServerAddressStreamProviderTest.java b/resolver-dns/src/test/java/io/netty/resolver/dns/UnixResolverDnsServerAddressStreamProviderTest.java index bcb61e78d1..cf8b1d6521 100644 --- a/resolver-dns/src/test/java/io/netty/resolver/dns/UnixResolverDnsServerAddressStreamProviderTest.java +++ b/resolver-dns/src/test/java/io/netty/resolver/dns/UnixResolverDnsServerAddressStreamProviderTest.java @@ -26,6 +26,8 @@ import java.io.IOException; import java.io.OutputStream; import java.net.InetSocketAddress; +import static io.netty.resolver.dns.UnixResolverDnsServerAddressStreamProvider.DEFAULT_NDOTS; +import static io.netty.resolver.dns.UnixResolverDnsServerAddressStreamProvider.parseEtcResolverFirstNdots; import static org.junit.Assert.assertEquals; public class UnixResolverDnsServerAddressStreamProviderTest { @@ -77,6 +79,26 @@ public class UnixResolverDnsServerAddressStreamProviderTest { assertHostNameEquals("127.0.0.5", stream.next()); } + @Test + public void ndotsIsParsedIfPresent() throws IOException { + File f = buildFile("search localdomain\n" + + "nameserver 127.0.0.11\n" + + "options ndots:0\n"); + assertEquals(0, parseEtcResolverFirstNdots(f)); + + f = buildFile("search localdomain\n" + + "nameserver 127.0.0.11\n" + + "options ndots:123 foo:goo\n"); + assertEquals(123, parseEtcResolverFirstNdots(f)); + } + + @Test + public void defaultValueReturnedIfNdotsNotPresent() throws IOException { + File f = buildFile("search localdomain\n" + + "nameserver 127.0.0.11\n"); + assertEquals(DEFAULT_NDOTS, parseEtcResolverFirstNdots(f)); + } + private File buildFile(String contents) throws IOException { File f = folder.newFile(); OutputStream out = new FileOutputStream(f);