Filter DNS results so they only contain the expected type when multiple types are present. (#7875)
Motivation: Currently, if a DNS server returns a non-preferred address type before the preferred one, then both will be returned as the result, and when only taking a single one, this usually ends up being the non-preferred type. However, the JDK requires lookups to only return the preferred type when possible to allow for backwards compatibility. To allow a client to be able to resolve the appropriate address when running on a machine that does not support IPv6 but the DNS server returns IPv6 addresses before IPv4 addresses when querying. Modification: Filter the returned records to the expected type when both types are present. Result: Allows a client to run on a machine with IPv6 disabled even when a server returns both IPv4 and IPv6 results. Netty-based code can be a drop-in replacement for JDK-based code in such circumstances. This PR filters results before returning them to respect JDK expectations.
This commit is contained in:
parent
3ec29455af
commit
4cd39cc4b3
@ -19,6 +19,7 @@ import static io.netty.resolver.dns.DnsAddressDecoder.decodeAddress;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import io.netty.channel.EventLoop;
|
||||
@ -61,6 +62,31 @@ final class DnsAddressResolveContext extends DnsResolveContext<InetAddress> {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
List<InetAddress> filterResults(List<InetAddress> unfiltered) {
|
||||
final Class<? extends InetAddress> inetAddressType = parent.preferredAddressType().addressType();
|
||||
final int size = unfiltered.size();
|
||||
int numExpected = 0;
|
||||
for (int i = 0; i < size; i++) {
|
||||
InetAddress address = unfiltered.get(i);
|
||||
if (inetAddressType.isInstance(address)) {
|
||||
numExpected++;
|
||||
}
|
||||
}
|
||||
if (numExpected == size || numExpected == 0) {
|
||||
// If all the results are the preferred type, or none of them are, then we don't need to do any filtering.
|
||||
return unfiltered;
|
||||
}
|
||||
List<InetAddress> filtered = new ArrayList<InetAddress>(numExpected);
|
||||
for (int i = 0; i < size; i++) {
|
||||
InetAddress address = unfiltered.get(i);
|
||||
if (inetAddressType.isInstance(address)) {
|
||||
filtered.add(address);
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
}
|
||||
|
||||
@Override
|
||||
void cache(String hostname, DnsRecord[] additionals,
|
||||
DnsRecord result, InetAddress convertedResult) {
|
||||
|
@ -61,6 +61,11 @@ final class DnsRecordResolveContext extends DnsResolveContext<DnsRecord> {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
List<DnsRecord> filterResults(List<DnsRecord> unfiltered) {
|
||||
return unfiltered;
|
||||
}
|
||||
|
||||
@Override
|
||||
void cache(String hostname, DnsRecord[] additionals, DnsRecord result, DnsRecord convertedResult) {
|
||||
// Do not cache.
|
||||
|
@ -31,6 +31,7 @@ import io.netty.handler.codec.dns.DnsRecordType;
|
||||
import io.netty.handler.codec.dns.DnsResponse;
|
||||
import io.netty.handler.codec.dns.DnsResponseCode;
|
||||
import io.netty.handler.codec.dns.DnsSection;
|
||||
import io.netty.util.NetUtil;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.FutureListener;
|
||||
@ -142,6 +143,12 @@ abstract class DnsResolveContext<T> {
|
||||
*/
|
||||
abstract boolean containsExpectedResult(List<T> finalResult);
|
||||
|
||||
/**
|
||||
* Returns a filtered list of results which should be the final result of DNS resolution. This must take into
|
||||
* account JDK semantics such as {@link NetUtil#isIpV6AddressesPreferred()}.
|
||||
*/
|
||||
abstract List<T> filterResults(List<T> unfiltered);
|
||||
|
||||
/**
|
||||
* Caches a successful resolution.
|
||||
*/
|
||||
@ -702,7 +709,7 @@ abstract class DnsResolveContext<T> {
|
||||
|
||||
if (finalResult != null) {
|
||||
// Found at least one resolved record.
|
||||
trySuccess(promise, finalResult);
|
||||
trySuccess(promise, filterResults(finalResult));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -66,9 +66,12 @@ import java.net.UnknownHostException;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -76,6 +79,7 @@ import java.util.Map.Entry;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@ -1177,6 +1181,67 @@ public class DnsNameResolverTest {
|
||||
testRecursiveResolveCache(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIpv4PreferredWhenIpv6First() throws Exception {
|
||||
testResolvesPreferredWhenNonPreferredFirst0(ResolvedAddressTypes.IPV4_PREFERRED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIpv6PreferredWhenIpv4First() throws Exception {
|
||||
testResolvesPreferredWhenNonPreferredFirst0(ResolvedAddressTypes.IPV6_PREFERRED);
|
||||
}
|
||||
|
||||
private static void testResolvesPreferredWhenNonPreferredFirst0(ResolvedAddressTypes types) throws Exception {
|
||||
final String name = "netty.com";
|
||||
// This store is non-compliant, returning records of the wrong type for a query.
|
||||
// It works since we don't verify the type of the result when resolving to deal with
|
||||
// non-compliant servers in the wild.
|
||||
List<Set<ResourceRecord>> records = new ArrayList<Set<ResourceRecord>>();
|
||||
final String ipv6Address = "0:0:0:0:0:0:1:1";
|
||||
final String ipv4Address = "1.1.1.1";
|
||||
if (types == ResolvedAddressTypes.IPV4_PREFERRED) {
|
||||
records.add(newAddressRecord(name, RecordType.AAAA, ipv6Address));
|
||||
records.add(newAddressRecord(name, RecordType.A, ipv4Address));
|
||||
} else {
|
||||
records.add(newAddressRecord(name, RecordType.A, ipv4Address));
|
||||
records.add(newAddressRecord(name, RecordType.AAAA, ipv6Address));
|
||||
}
|
||||
final Iterator<Set<ResourceRecord>> recordsIterator = records.iterator();
|
||||
RecordStore arbitrarilyOrderedStore = new RecordStore() {
|
||||
@Override
|
||||
public Set<ResourceRecord> getRecords(QuestionRecord questionRecord) {
|
||||
return recordsIterator.next();
|
||||
}
|
||||
};
|
||||
TestDnsServer nonCompliantDnsServer = new TestDnsServer(arbitrarilyOrderedStore);
|
||||
nonCompliantDnsServer.start();
|
||||
try {
|
||||
DnsNameResolver resolver = newResolver(types)
|
||||
.maxQueriesPerResolve(2)
|
||||
.nameServerProvider(new SingletonDnsServerAddressStreamProvider(
|
||||
nonCompliantDnsServer.localAddress()))
|
||||
.build();
|
||||
InetAddress resolved = resolver.resolve("netty.com").syncUninterruptibly().getNow();
|
||||
if (types == ResolvedAddressTypes.IPV4_PREFERRED) {
|
||||
assertEquals(ipv4Address, resolved.getHostAddress());
|
||||
} else {
|
||||
assertEquals(ipv6Address, resolved.getHostAddress());
|
||||
}
|
||||
} finally {
|
||||
nonCompliantDnsServer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private static Set<ResourceRecord> newAddressRecord(String name, RecordType type, String address) {
|
||||
ResourceRecordModifier rm = new ResourceRecordModifier();
|
||||
rm.setDnsClass(RecordClass.IN);
|
||||
rm.setDnsName(name);
|
||||
rm.setDnsTtl(100);
|
||||
rm.setDnsType(type);
|
||||
rm.put(DnsAttribute.IP_ADDRESS, address);
|
||||
return Collections.singleton(rm.getEntry());
|
||||
}
|
||||
|
||||
private static void testRecursiveResolveCache(boolean cache)
|
||||
throws Exception {
|
||||
final String hostname = "some.record.netty.io";
|
||||
|
@ -258,7 +258,7 @@ class TestDnsServer extends DnsServer {
|
||||
|
||||
private final Set<String> domains;
|
||||
|
||||
public TestRecordStore(Set<String> domains) {
|
||||
private TestRecordStore(Set<String> domains) {
|
||||
this.domains = domains;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user