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 df3b38a93e..c71946fab0 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 @@ -755,7 +755,7 @@ abstract class DnsNameResolverContext { private void followCname(String cname, final DnsQueryLifecycleObserver queryLifecycleObserver, Promise promise) { // Use the same server for both CNAME queries - DnsServerAddressStream stream = DnsServerAddresses.singleton(getNameServers(cname).next()).stream(); + DnsServerAddressStream stream = getNameServers(cname); DnsQuestion cnameQuestion = null; if (parent.supportsARecords()) { 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 95c32be0a3..805121c730 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 @@ -49,6 +49,7 @@ import org.apache.directory.server.dns.messages.RecordClass; import org.apache.directory.server.dns.messages.RecordType; import org.apache.directory.server.dns.messages.ResourceRecord; import org.apache.directory.server.dns.messages.ResourceRecordModifier; +import org.apache.directory.server.dns.messages.ResponseCode; import org.apache.directory.server.dns.store.DnsAttribute; import org.apache.directory.server.dns.store.RecordStore; import org.junit.AfterClass; @@ -73,6 +74,7 @@ import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import static io.netty.handler.codec.dns.DnsRecordType.A; import static io.netty.handler.codec.dns.DnsRecordType.AAAA; @@ -817,6 +819,108 @@ public class DnsNameResolverTest { } } + @Test + public void testCNAMERecursiveResolveMultipleNameServersIPv4() throws IOException { + testCNAMERecursiveResolveMultipleNameServers(true); + } + + @Test + public void testCNAMERecursiveResolveMultipleNameServersIPv6() throws IOException { + testCNAMERecursiveResolveMultipleNameServers(false); + } + + private static void testCNAMERecursiveResolveMultipleNameServers(boolean ipv4Preferred) throws IOException { + final String firstName = "firstname.nettyfoo.com"; + final String lastName = "lastname.nettybar.com"; + final String ipv4Addr = "1.2.3.4"; + final String ipv6Addr = "::1"; + final AtomicBoolean hitServer2 = new AtomicBoolean(); + final TestDnsServer dnsServer2 = new TestDnsServer(new RecordStore() { + @Override + public Set getRecords(QuestionRecord question) throws DnsException { + hitServer2.set(true); + if (question.getDomainName().equals(firstName)) { + 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()); + } else { + throw new DnsException(ResponseCode.REFUSED); + } + } + }); + final TestDnsServer dnsServer3 = new TestDnsServer(new RecordStore() { + @Override + public Set getRecords(QuestionRecord question) throws DnsException { + if (question.getDomainName().equals(lastName)) { + ResourceRecordModifier rm = new ResourceRecordModifier(); + rm.setDnsClass(RecordClass.IN); + rm.setDnsName(question.getDomainName()); + rm.setDnsTtl(100); + rm.setDnsType(question.getRecordType()); + switch (question.getRecordType()) { + case A: + rm.put(DnsAttribute.IP_ADDRESS, ipv4Addr); + break; + case AAAA: + rm.put(DnsAttribute.IP_ADDRESS, ipv6Addr); + break; + default: + return null; + } + + return Collections.singleton(rm.getEntry()); + } else { + throw new DnsException(ResponseCode.REFUSED); + } + } + }); + dnsServer2.start(); + dnsServer3.start(); + DnsNameResolver resolver = null; + try { + DnsCache nsCache = new DefaultDnsCache(); + // What we want to test is the following: + // 1. Do a DNS query. + // 2. CNAME is returned, we want to lookup that CNAME on multiple DNS servers + // 3. The first DNS server should fail + // 4. The second DNS server should succeed + // This verifies that we do in fact follow multiple DNS servers in the CNAME resolution. + // The DnsCache is used for the name server cache, but doesn't provide a InetSocketAddress (only InetAddress + // so no port), so we only specify the name server in the cache, and then specify both name servers in the + // fallback name server provider. + nsCache.cache("nettyfoo.com.", null, dnsServer2.localAddress().getAddress(), 10000, group.next()); + resolver = new DnsNameResolver( + group.next(), new ReflectiveChannelFactory(NioDatagramChannel.class), + NoopDnsCache.INSTANCE, nsCache, NoopDnsQueryLifecycleObserverFactory.INSTANCE, 3000, + ipv4Preferred ? ResolvedAddressTypes.IPV4_ONLY : ResolvedAddressTypes.IPV6_ONLY, true, + 10, true, 4096, false, HostsFileEntriesResolver.DEFAULT, + new SequentialDnsServerAddressStreamProvider(dnsServer2.localAddress(), dnsServer3.localAddress()), + DnsNameResolver.DEFAULT_SEARCH_DOMAINS, 0, true) { + @Override + int dnsRedirectPort(InetAddress server) { + return hitServer2.get() ? dnsServer3.localAddress().getPort() : dnsServer2.localAddress().getPort(); + } + }; + InetAddress resolvedAddress = resolver.resolve(firstName).syncUninterruptibly().getNow(); + if (ipv4Preferred) { + assertEquals(ipv4Addr, resolvedAddress.getHostAddress()); + } else { + assertEquals(ipv6Addr, NetUtil.toAddressString(resolvedAddress)); + } + assertEquals(firstName, resolvedAddress.getHostName()); + } finally { + dnsServer2.stop(); + dnsServer3.stop(); + if (resolver != null) { + resolver.close(); + } + } + } + @Test public void testResolveAllNullIpv4() { testResolveAll0(ResolvedAddressTypes.IPV4_ONLY, NetUtil.LOCALHOST4, null);