Correctly detect and handle CNAME loops. (#8691)
Motivation: We do not correctly detect loops when follow CNAMEs and so may try to follow it without any success. Modifications: - Correctly detect CNAME loops - Do not cache CNAME entries which point to itself - Add unit test. Result: Fixes https://github.com/netty/netty/issues/8687.
This commit is contained in:
parent
6fdd7fcddb
commit
82ec6ba815
@ -48,6 +48,7 @@ import java.util.AbstractList;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.IdentityHashMap;
|
import java.util.IdentityHashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -651,10 +652,11 @@ abstract class DnsResolveContext<T> {
|
|||||||
|
|
||||||
// Make sure the record is for the questioned domain.
|
// Make sure the record is for the questioned domain.
|
||||||
if (!recordName.equals(questionName)) {
|
if (!recordName.equals(questionName)) {
|
||||||
|
Map<String, String> cnamesCopy = new HashMap<String, String>(cnames);
|
||||||
// Even if the record's name is not exactly same, it might be an alias defined in the CNAME records.
|
// Even if the record's name is not exactly same, it might be an alias defined in the CNAME records.
|
||||||
String resolved = questionName;
|
String resolved = questionName;
|
||||||
do {
|
do {
|
||||||
resolved = cnames.get(resolved);
|
resolved = cnamesCopy.remove(resolved);
|
||||||
if (recordName.equals(resolved)) {
|
if (recordName.equals(resolved)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -749,9 +751,13 @@ abstract class DnsResolveContext<T> {
|
|||||||
String mapping = domainName.toLowerCase(Locale.US);
|
String mapping = domainName.toLowerCase(Locale.US);
|
||||||
|
|
||||||
// Cache the CNAME as well.
|
// Cache the CNAME as well.
|
||||||
cache.cache(hostnameWithDot(name), hostnameWithDot(mapping), r.timeToLive(), loop);
|
String nameWithDot = hostnameWithDot(name);
|
||||||
|
String mappingWithDot = hostnameWithDot(mapping);
|
||||||
|
if (!nameWithDot.equalsIgnoreCase(mappingWithDot)) {
|
||||||
|
cache.cache(nameWithDot, mappingWithDot, r.timeToLive(), loop);
|
||||||
cnames.put(name, mapping);
|
cnames.put(name, mapping);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return cnames != null? cnames : Collections.<String, String>emptyMap();
|
return cnames != null? cnames : Collections.<String, String>emptyMap();
|
||||||
}
|
}
|
||||||
@ -875,12 +881,21 @@ abstract class DnsResolveContext<T> {
|
|||||||
|
|
||||||
private void followCname(DnsQuestion question, String cname, DnsQueryLifecycleObserver queryLifecycleObserver,
|
private void followCname(DnsQuestion question, String cname, DnsQueryLifecycleObserver queryLifecycleObserver,
|
||||||
Promise<List<T>> promise) {
|
Promise<List<T>> promise) {
|
||||||
|
Set<String> cnames = null;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
// Resolve from cnameCache() until there is no more cname entry cached.
|
// Resolve from cnameCache() until there is no more cname entry cached.
|
||||||
String mapping = cnameCache().get(hostnameWithDot(cname));
|
String mapping = cnameCache().get(hostnameWithDot(cname));
|
||||||
if (mapping == null) {
|
if (mapping == null) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (cnames == null) {
|
||||||
|
// Detect loops.
|
||||||
|
cnames = new HashSet<String>(2);
|
||||||
|
}
|
||||||
|
if (!cnames.add(cname)) {
|
||||||
|
// Follow CNAME from cache would loop. Lets break here.
|
||||||
|
break;
|
||||||
|
}
|
||||||
cname = mapping;
|
cname = mapping;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,6 +96,7 @@ import static io.netty.handler.codec.dns.DnsRecordType.AAAA;
|
|||||||
import static io.netty.handler.codec.dns.DnsRecordType.CNAME;
|
import static io.netty.handler.codec.dns.DnsRecordType.CNAME;
|
||||||
import static io.netty.resolver.dns.DnsServerAddresses.sequential;
|
import static io.netty.resolver.dns.DnsServerAddresses.sequential;
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
|
import static java.util.Collections.singletonMap;
|
||||||
import static org.hamcrest.Matchers.greaterThan;
|
import static org.hamcrest.Matchers.greaterThan;
|
||||||
import static org.hamcrest.Matchers.hasSize;
|
import static org.hamcrest.Matchers.hasSize;
|
||||||
import static org.hamcrest.Matchers.instanceOf;
|
import static org.hamcrest.Matchers.instanceOf;
|
||||||
@ -2135,6 +2136,52 @@ public class DnsNameResolverTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFollowCNAMELoop() throws IOException {
|
||||||
|
expectedException.expect(UnknownHostException.class);
|
||||||
|
TestDnsServer dnsServer2 = new TestDnsServer(new RecordStore() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<ResourceRecord> getRecords(QuestionRecord question) {
|
||||||
|
Set<ResourceRecord> records = new LinkedHashSet<ResourceRecord>(4);
|
||||||
|
|
||||||
|
records.add(new TestDnsServer.TestResourceRecord("x." + question.getDomainName(),
|
||||||
|
RecordType.A, Collections.<String, Object>singletonMap(
|
||||||
|
DnsAttribute.IP_ADDRESS.toLowerCase(), "10.0.0.99")));
|
||||||
|
records.add(new TestDnsServer.TestResourceRecord(
|
||||||
|
"cname2.netty.io", RecordType.CNAME,
|
||||||
|
Collections.<String, Object>singletonMap(
|
||||||
|
DnsAttribute.DOMAIN_NAME.toLowerCase(), "cname.netty.io")));
|
||||||
|
records.add(new TestDnsServer.TestResourceRecord(
|
||||||
|
"cname.netty.io", RecordType.CNAME,
|
||||||
|
Collections.<String, Object>singletonMap(
|
||||||
|
DnsAttribute.DOMAIN_NAME.toLowerCase(), "cname2.netty.io")));
|
||||||
|
records.add(new TestDnsServer.TestResourceRecord(
|
||||||
|
question.getDomainName(), RecordType.CNAME,
|
||||||
|
Collections.<String, Object>singletonMap(
|
||||||
|
DnsAttribute.DOMAIN_NAME.toLowerCase(), "cname.netty.io")));
|
||||||
|
return records;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dnsServer2.start();
|
||||||
|
DnsNameResolver resolver = null;
|
||||||
|
try {
|
||||||
|
DnsNameResolverBuilder builder = newResolver()
|
||||||
|
.recursionDesired(false)
|
||||||
|
.resolvedAddressTypes(ResolvedAddressTypes.IPV4_ONLY)
|
||||||
|
.maxQueriesPerResolve(16)
|
||||||
|
.nameServerProvider(new SingletonDnsServerAddressStreamProvider(dnsServer2.localAddress()));
|
||||||
|
|
||||||
|
resolver = builder.build();
|
||||||
|
resolver.resolveAll("somehost.netty.io").syncUninterruptibly().getNow();
|
||||||
|
} finally {
|
||||||
|
dnsServer2.stop();
|
||||||
|
if (resolver != null) {
|
||||||
|
resolver.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSearchDomainQueryFailureForSingleAddressTypeCompletes() {
|
public void testSearchDomainQueryFailureForSingleAddressTypeCompletes() {
|
||||||
expectedException.expect(UnknownHostException.class);
|
expectedException.expect(UnknownHostException.class);
|
||||||
|
Loading…
Reference in New Issue
Block a user