Detect CNAME loops in the CNAME cache while trying to resolve (#10221)
Motivation: We need to detect CNAME loops during lookup the DnsCnameCache as otherwise we may try to follow cnames forever. Modifications: - Correctly detect CNAME loops in the cache - Add unit test Result: Fixes https://github.com/netty/netty/issues/10220
This commit is contained in:
parent
ae95e9c3d6
commit
fe20550a22
@ -265,15 +265,52 @@ abstract class DnsResolveContext<T> {
|
|||||||
return name + '.';
|
return name + '.';
|
||||||
}
|
}
|
||||||
|
|
||||||
private void internalResolve(String name, Promise<List<T>> promise) {
|
// Resolve the final name from the CNAME cache until there is nothing to follow anymore. This also
|
||||||
for (;;) {
|
// guards against loops in the cache but early return once a loop is detected.
|
||||||
|
private String cnameResolveFromCache(String name) {
|
||||||
|
DnsCnameCache cnameCache = cnameCache();
|
||||||
|
String first = cnameCache.get(hostnameWithDot(name));
|
||||||
|
if (first == null) {
|
||||||
|
// Nothing in the cache at all
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
String second = cnameCache.get(hostnameWithDot(first));
|
||||||
|
if (second == null) {
|
||||||
|
// Nothing else to follow, return first match.
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (first.equals(second)) {
|
||||||
|
// Loop detected.... early return.
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cnameResolveFromCacheLoop(cnameCache, first, second);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String cnameResolveFromCacheLoop(DnsCnameCache cnameCache, String first, String mapping) {
|
||||||
|
// Detect loops using a HashSet. We use this as last resort implementation to reduce allocations in the most
|
||||||
|
// common cases.
|
||||||
|
Set<String> cnames = new HashSet<>(4);
|
||||||
|
cnames.add(first);
|
||||||
|
cnames.add(mapping);
|
||||||
|
|
||||||
|
String name = mapping;
|
||||||
// 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(name));
|
while ((mapping = cnameCache.get(hostnameWithDot(name))) != null) {
|
||||||
if (mapping == null) {
|
if (!cnames.add(mapping)) {
|
||||||
|
// Follow CNAME from cache would loop. Lets break here.
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
name = mapping;
|
name = mapping;
|
||||||
}
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void internalResolve(String name, Promise<List<T>> promise) {
|
||||||
|
// Resolve from cnameCache() until there is no more cname entry cached.
|
||||||
|
name = cnameResolveFromCache(name);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
DnsServerAddressStream nameServerAddressStream = getNameServers(name);
|
DnsServerAddressStream nameServerAddressStream = getNameServers(name);
|
||||||
@ -936,24 +973,7 @@ 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;
|
cname = cnameResolveFromCache(cname);
|
||||||
for (;;) {
|
|
||||||
// Resolve from cnameCache() until there is no more cname entry cached.
|
|
||||||
String mapping = cnameCache().get(hostnameWithDot(cname));
|
|
||||||
if (mapping == null) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (cnames == null) {
|
|
||||||
// Detect loops.
|
|
||||||
cnames = new HashSet<>(2);
|
|
||||||
}
|
|
||||||
if (!cnames.add(cname)) {
|
|
||||||
// Follow CNAME from cache would loop. Lets break here.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
cname = mapping;
|
|
||||||
}
|
|
||||||
|
|
||||||
DnsServerAddressStream stream = getNameServers(cname);
|
DnsServerAddressStream stream = getNameServers(cname);
|
||||||
|
|
||||||
final DnsQuestion cnameQuestion;
|
final DnsQuestion cnameQuestion;
|
||||||
|
@ -2176,6 +2176,34 @@ public class DnsNameResolverTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCNAMELoopInCache() throws Throwable {
|
||||||
|
expectedException.expect(UnknownHostException.class);
|
||||||
|
DnsNameResolver resolver = null;
|
||||||
|
try {
|
||||||
|
DnsNameResolverBuilder builder = newResolver()
|
||||||
|
.recursionDesired(false)
|
||||||
|
.resolvedAddressTypes(ResolvedAddressTypes.IPV4_ONLY)
|
||||||
|
.maxQueriesPerResolve(16)
|
||||||
|
.nameServerProvider(new SingletonDnsServerAddressStreamProvider(dnsServer.localAddress()));
|
||||||
|
|
||||||
|
resolver = builder.build();
|
||||||
|
// Add a CNAME loop into the cache
|
||||||
|
String name = "somehost.netty.io.";
|
||||||
|
String name2 = "cname.netty.io.";
|
||||||
|
|
||||||
|
resolver.cnameCache().cache(name, name2, Long.MAX_VALUE, resolver.executor());
|
||||||
|
resolver.cnameCache().cache(name2, name, Long.MAX_VALUE, resolver.executor());
|
||||||
|
resolver.resolve(name).syncUninterruptibly().getNow();
|
||||||
|
} catch (CompletionException e) {
|
||||||
|
throw e.getCause();
|
||||||
|
} finally {
|
||||||
|
if (resolver != null) {
|
||||||
|
resolver.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSearchDomainQueryFailureForSingleAddressTypeCompletes() throws Throwable {
|
public void testSearchDomainQueryFailureForSingleAddressTypeCompletes() throws Throwable {
|
||||||
expectedException.expect(UnknownHostException.class);
|
expectedException.expect(UnknownHostException.class);
|
||||||
|
Loading…
Reference in New Issue
Block a user