From 844d804aba4b459215f05c9ade59157f6c2cfb4b Mon Sep 17 00:00:00 2001 From: Stanley Shyiko Date: Thu, 16 Nov 2017 23:15:30 -0800 Subject: [PATCH] Fix DN resolution when ndots is greater than 1 Motivation: DN resolution does not fall back to the "original name" lookup after search list is checked. This results in a failure to resolve any name (outside of search list) that has number of dots less than resolv.conf's ndots value (which, for example, is often the case in the context of Kubernetes where kubelet passes on resolv.conf containing "options ndots:5"). It also does not go through the search list in a situation described in resolv.conf man: "The default for n[dots] is 1, meaning that if there are any dots in a name, the name will be tried first as an absolute name before any search list elements are appended to it." Modifications: DnsNameResolverContext::resolve was updated to match Go's https://github.com/golang/go/blob/release-branch.go1.9/src/net/dnsclient_unix.go#L338 logic. Result: DnsNameResolverContext::resolve will now try to resolve "original name" if search list yields no results when number of dots in the original name is less than resolv.conf's ndots value. It will also go through the search list in case "origin name" resolution fails and number of dots is equal or larger than resolv.conf's ndots value. --- .../resolver/dns/DnsNameResolverContext.java | 45 +++++++++++-------- .../netty/resolver/dns/SearchDomainTest.java | 18 +++++--- 2 files changed, 37 insertions(+), 26 deletions(-) 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 c38a91fdd8..b8590fc750 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 @@ -125,33 +125,43 @@ abstract class DnsNameResolverContext { } void resolve(final Promise promise) { - if (parent.searchDomains().length == 0 || parent.ndots() == 0 || StringUtil.endsWith(hostname, '.')) { + final String[] searchDomains = parent.searchDomains(); + if (searchDomains.length == 0 || parent.ndots() == 0 || StringUtil.endsWith(hostname, '.')) { 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; - } - } + final boolean startWithoutSearchDomain = hasNDots(); + final String initialHostname = startWithoutSearchDomain ? hostname : hostname + '.' + searchDomains[0]; + final int initialSearchDomainIdx = startWithoutSearchDomain ? 0 : 1; - doSearchDomainQuery(0, new FutureListener() { - private int count = 1; + doSearchDomainQuery(initialHostname, new FutureListener() { + private int searchDomainIdx = initialSearchDomainIdx; @Override public void operationComplete(Future future) throws Exception { if (future.isSuccess()) { promise.trySuccess(future.getNow()); - } else if (count < parent.searchDomains().length) { - doSearchDomainQuery(count++, this); + } else if (searchDomainIdx < searchDomains.length) { + doSearchDomainQuery(hostname + '.' + searchDomains[searchDomainIdx++], this); } else { - promise.tryFailure(new SearchDomainUnknownHostException(future.cause(), hostname)); + if (!startWithoutSearchDomain) { + internalResolve(promise); + } else { + promise.tryFailure(new SearchDomainUnknownHostException(future.cause(), hostname)); + } } } }); } } + private boolean hasNDots() { + for (int idx = hostname.length() - 1, dots = 0; idx >= 0; idx--) { + if (hostname.charAt(idx) == '.' && ++dots >= parent.ndots()) { + return true; + } + } + return false; + } + private static final class SearchDomainUnknownHostException extends UnknownHostException { private static final long serialVersionUID = -8573510133644997085L; @@ -166,12 +176,9 @@ abstract class DnsNameResolverContext { } } - private void doSearchDomainQuery(int count, FutureListener listener) { - DnsNameResolverContext nextContext = newResolverContext(parent, - hostname + '.' + parent.searchDomains()[count], - additionals, - resolveCache, - nameServerAddrs); + private void doSearchDomainQuery(String hostname, FutureListener listener) { + DnsNameResolverContext nextContext = newResolverContext(parent, hostname, additionals, resolveCache, + nameServerAddrs); Promise nextPromise = parent.executor().newPromise(); nextContext.internalResolve(nextPromise); nextPromise.addListener(listener); 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 67fc79cf57..2e33110034 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 @@ -104,8 +104,10 @@ public class SearchDomainTest { // "host2" not resolved assertNotResolve(resolver, "host2"); - // "host3" does not contain a dot or is not absolute - assertNotResolve(resolver, "host3"); + // "host3" does not contain a dot nor it's absolute but it should still be resolved after search list have + // been checked + resolved = assertResolve(resolver, "host3"); + assertEquals(store.getAddress("host3"), resolved); // "host3." does not contain a dot but is absolute resolved = assertResolve(resolver, "host3."); @@ -152,8 +154,10 @@ public class SearchDomainTest { // "host2" not resolved assertNotResolveAll(resolver, "host2"); - // "host3" does not contain a dot or is not absolute - assertNotResolveAll(resolver, "host3"); + // "host3" does not contain a dot nor it's absolute but it should still be resolved after search list have + // been checked + resolved = assertResolveAll(resolver, "host3"); + assertEquals(store.getAddresses("host3"), resolved); // "host3." does not contain a dot but is absolute resolved = assertResolveAll(resolver, "host3."); @@ -281,7 +285,7 @@ public class SearchDomainTest { dnsServer = new TestDnsServer(store); dnsServer.start(); - resolver = newResolver().searchDomains(Collections.singletonList("foo.com")).ndots(2).build(); + resolver = newResolver().searchDomains(Collections.singletonList("foo.com")).ndots(1).build(); Future fut = resolver.resolve("unknown.hostname"); assertTrue(fut.await(10, TimeUnit.SECONDS)); @@ -293,12 +297,12 @@ public class SearchDomainTest { } @Test - public void testExceptionMsgDoesNotContainSearchDomainIfNdotsNotHighEnough() throws Exception { + public void testExceptionMsgDoesNotContainSearchDomainIfNdotsIsNotReached() throws Exception { TestDnsServer.MapRecordStoreA store = new TestDnsServer.MapRecordStoreA(Collections.emptySet()); dnsServer = new TestDnsServer(store); dnsServer.start(); - resolver = newResolver().searchDomains(Collections.singletonList("foo.com")).ndots(1).build(); + resolver = newResolver().searchDomains(Collections.singletonList("foo.com")).ndots(2).build(); Future fut = resolver.resolve("unknown.hostname"); assertTrue(fut.await(10, TimeUnit.SECONDS));