diff --git a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsResolveContext.java b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsResolveContext.java index 98d6591b49..091ec4d6da 100644 --- a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsResolveContext.java +++ b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsResolveContext.java @@ -986,7 +986,9 @@ abstract class DnsResolveContext { private DnsServerAddressStream getNameServers(String hostname) { DnsServerAddressStream stream = getNameServersFromCache(hostname); - return stream == null ? nameServerAddrs.duplicate() : stream; + // We need to obtain a new stream from the parent DnsNameResolver as the hostname may not be the same as the + // one used for the original query (for example we may follow CNAMEs). + return stream == null ? parent.newNameServerAddressStream(hostname) : stream; } private void followCname(DnsQuestion question, String cname, DnsQueryLifecycleObserver queryLifecycleObserver, 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 0def8cf61c..fa87f37fa5 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 @@ -2974,4 +2974,80 @@ public class DnsNameResolverTest { } assertThat(isQuerySentToSecondServer.get(), is(false)); } + + @Test + public void testCNAMERecursiveResolveDifferentNameServersForDomains() throws IOException { + final String firstName = "firstname.com"; + final String secondName = "secondname.com"; + final String lastName = "lastname.com"; + final String ipv4Addr = "1.2.3.4"; + final TestDnsServer dnsServer2 = new TestDnsServer(new RecordStore() { + @Override + public Set getRecords(QuestionRecord question) { + ResourceRecordModifier rm = new ResourceRecordModifier(); + rm.setDnsClass(RecordClass.IN); + rm.setDnsName(question.getDomainName()); + rm.setDnsTtl(100); + + if (question.getDomainName().equals(firstName)) { + rm.setDnsType(RecordType.CNAME); + rm.put(DnsAttribute.DOMAIN_NAME, secondName); + } else if (question.getDomainName().equals(lastName)) { + rm.setDnsType(question.getRecordType()); + rm.put(DnsAttribute.IP_ADDRESS, ipv4Addr); + } else { + return null; + } + return Collections.singleton(rm.getEntry()); + } + }); + dnsServer2.start(); + final TestDnsServer dnsServer3 = new TestDnsServer(new RecordStore() { + @Override + public Set getRecords(QuestionRecord question) { + if (question.getDomainName().equals(secondName)) { + ResourceRecordModifier rm = new ResourceRecordModifier(); + rm.setDnsClass(RecordClass.IN); + rm.setDnsName(question.getDomainName()); + rm.setDnsTtl(100); + rm.setDnsType(RecordType.CNAME); + rm.put(DnsAttribute.DOMAIN_NAME, lastName); + return Collections.singleton(rm.getEntry()); + } + return null; + } + }); + dnsServer3.start(); + DnsNameResolver resolver = null; + try { + resolver = newResolver() + .resolveCache(NoopDnsCache.INSTANCE) + .cnameCache(NoopDnsCnameCache.INSTANCE) + .recursionDesired(true) + .maxQueriesPerResolve(16) + .nameServerProvider(new DnsServerAddressStreamProvider() { + @Override + public DnsServerAddressStream nameServerAddressStream(String hostname) { + if (hostname.equals(secondName + '.')) { + return DnsServerAddresses.singleton(dnsServer3.localAddress()).stream(); + } + return DnsServerAddresses.singleton(dnsServer2.localAddress()).stream(); + } + }) + .resolvedAddressTypes(ResolvedAddressTypes.IPV4_PREFERRED).build(); + + assertResolvedAddress(resolver.resolve(firstName).syncUninterruptibly().getNow(), ipv4Addr, firstName); + } finally { + dnsServer2.stop(); + dnsServer3.stop(); + if (resolver != null) { + resolver.close(); + } + } + } + + private static void assertResolvedAddress(InetAddress resolvedAddress, String ipAddr, String hostname) { + assertEquals(ipAddr, resolvedAddress.getHostAddress()); + assertEquals(hostname, resolvedAddress.getHostName()); + } }