Select correct nameserver for CNAME (#10272)

Motivation:

The nameserver that should / must be used to resolve a CNAME may be different then the nameserver that was selected for the hostname to resolve. Failing to select the correct nameserver may result in problems during resolution.

Modifications:

Use the correct DnsServerAddressStream for CNAMEs

Result:

Always use the correct DnsServerAddressStream for CNAMEs and so fix resolution failures which could accour when CNAMEs are in the mix that use a different domain then the original hostname that we try to resolve
This commit is contained in:
Norman Maurer 2020-05-12 08:46:45 +02:00
parent f3a97f4f7b
commit b40ae95044
2 changed files with 79 additions and 1 deletions

View File

@ -968,7 +968,9 @@ abstract class DnsResolveContext<T> {
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,

View File

@ -2932,4 +2932,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<ResourceRecord> 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<ResourceRecord> 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());
}
}