Add DnsNameResolver.resolveAll(DnsQuestion) (#7803)
* Add DnsNameResolver.resolveAll(DnsQuestion) Motivation: A user is currently expected to use DnsNameResolver.query() when he or she wants to look up the full DNS records rather than just InetAddres. However, query() only performs a single query. It does not handle /etc/hosts file, redirection, CNAMEs or multiple name servers. As a result, such a user has to duplicate all the logic in DnsNameResolverContext. Modifications: - Refactor DnsNameResolverContext so that it can send queries for arbitrary record types. - Rename DnsNameResolverContext to DnsResolveContext - Add DnsAddressResolveContext which extends DnsResolveContext for A/AAAA lookup - Add DnsRecordResolveContext which extends DnsResolveContext for arbitrary lookup - Add DnsNameResolverContext.resolveAll(DnsQuestion) and its variants - Change DnsNameResolverContext.resolve() delegates the resolve request to resolveAll() for simplicity - Move the code that decodes A/AAAA record content to DnsAddressDecoder Result: - Fixes #7795 - A user does not have to duplicate DnsNameResolverContext in his or her own code to implement the usual DNS resolver behavior.
This commit is contained in:
parent
965734a1eb
commit
cd4594d292
@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.resolver.dns;
|
||||||
|
|
||||||
|
import java.net.IDN;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.ByteBufHolder;
|
||||||
|
import io.netty.handler.codec.dns.DnsRawRecord;
|
||||||
|
import io.netty.handler.codec.dns.DnsRecord;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes an {@link InetAddress} from an A or AAAA {@link DnsRawRecord}.
|
||||||
|
*/
|
||||||
|
final class DnsAddressDecoder {
|
||||||
|
|
||||||
|
private static final int INADDRSZ4 = 4;
|
||||||
|
private static final int INADDRSZ6 = 16;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes an {@link InetAddress} from an A or AAAA {@link DnsRawRecord}.
|
||||||
|
*
|
||||||
|
* @param record the {@link DnsRecord}, most likely a {@link DnsRawRecord}
|
||||||
|
* @param name the host name of the decoded address
|
||||||
|
* @param decodeIdn whether to convert {@code name} to a unicode host name
|
||||||
|
*
|
||||||
|
* @return the {@link InetAddress}, or {@code null} if {@code record} is not a {@link DnsRawRecord} or
|
||||||
|
* its content is malformed
|
||||||
|
*/
|
||||||
|
static InetAddress decodeAddress(DnsRecord record, String name, boolean decodeIdn) {
|
||||||
|
if (!(record instanceof DnsRawRecord)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final ByteBuf content = ((ByteBufHolder) record).content();
|
||||||
|
final int contentLen = content.readableBytes();
|
||||||
|
if (contentLen != INADDRSZ4 && contentLen != INADDRSZ6) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final byte[] addrBytes = new byte[contentLen];
|
||||||
|
content.getBytes(content.readerIndex(), addrBytes);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return InetAddress.getByAddress(decodeIdn ? IDN.toUnicode(name) : name, addrBytes);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
// Should never reach here.
|
||||||
|
throw new Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DnsAddressDecoder() { }
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.resolver.dns;
|
||||||
|
|
||||||
|
import static io.netty.resolver.dns.DnsAddressDecoder.decodeAddress;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.netty.channel.EventLoop;
|
||||||
|
import io.netty.handler.codec.dns.DnsRecord;
|
||||||
|
import io.netty.handler.codec.dns.DnsRecordType;
|
||||||
|
|
||||||
|
final class DnsAddressResolveContext extends DnsResolveContext<InetAddress> {
|
||||||
|
|
||||||
|
private final DnsCache resolveCache;
|
||||||
|
|
||||||
|
DnsAddressResolveContext(DnsNameResolver parent, String hostname, DnsRecord[] additionals,
|
||||||
|
DnsServerAddressStream nameServerAddrs, DnsCache resolveCache) {
|
||||||
|
super(parent, hostname, DnsRecord.CLASS_IN, parent.resolveRecordTypes(), additionals, nameServerAddrs);
|
||||||
|
this.resolveCache = resolveCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
DnsResolveContext<InetAddress> newResolverContext(DnsNameResolver parent, String hostname,
|
||||||
|
int dnsClass, DnsRecordType[] expectedTypes,
|
||||||
|
DnsRecord[] additionals,
|
||||||
|
DnsServerAddressStream nameServerAddrs) {
|
||||||
|
return new DnsAddressResolveContext(parent, hostname, additionals, nameServerAddrs, resolveCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
InetAddress convertRecord(DnsRecord record, String hostname, DnsRecord[] additionals, EventLoop eventLoop) {
|
||||||
|
return decodeAddress(record, hostname, parent.isDecodeIdn());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean containsExpectedResult(List<InetAddress> finalResult) {
|
||||||
|
final int size = finalResult.size();
|
||||||
|
final Class<? extends InetAddress> inetAddressType = parent.preferredAddressType().addressType();
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
InetAddress address = finalResult.get(i);
|
||||||
|
if (inetAddressType.isInstance(address)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void cache(String hostname, DnsRecord[] additionals,
|
||||||
|
DnsRecord result, InetAddress convertedResult) {
|
||||||
|
resolveCache.cache(hostname, additionals, convertedResult, result.timeToLive(), parent.ch.eventLoop());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void cache(String hostname, DnsRecord[] additionals, UnknownHostException cause) {
|
||||||
|
resolveCache.cache(hostname, additionals, cause, parent.ch.eventLoop());
|
||||||
|
}
|
||||||
|
}
|
@ -16,6 +16,8 @@
|
|||||||
package io.netty.resolver.dns;
|
package io.netty.resolver.dns;
|
||||||
|
|
||||||
import io.netty.bootstrap.Bootstrap;
|
import io.netty.bootstrap.Bootstrap;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
import io.netty.channel.AddressedEnvelope;
|
import io.netty.channel.AddressedEnvelope;
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
import io.netty.channel.ChannelFactory;
|
import io.netty.channel.ChannelFactory;
|
||||||
@ -33,11 +35,13 @@ import io.netty.channel.socket.InternetProtocolFamily;
|
|||||||
import io.netty.handler.codec.dns.DatagramDnsQueryEncoder;
|
import io.netty.handler.codec.dns.DatagramDnsQueryEncoder;
|
||||||
import io.netty.handler.codec.dns.DatagramDnsResponse;
|
import io.netty.handler.codec.dns.DatagramDnsResponse;
|
||||||
import io.netty.handler.codec.dns.DatagramDnsResponseDecoder;
|
import io.netty.handler.codec.dns.DatagramDnsResponseDecoder;
|
||||||
|
import io.netty.handler.codec.dns.DefaultDnsRawRecord;
|
||||||
import io.netty.handler.codec.dns.DnsQuestion;
|
import io.netty.handler.codec.dns.DnsQuestion;
|
||||||
import io.netty.handler.codec.dns.DnsRawRecord;
|
import io.netty.handler.codec.dns.DnsRawRecord;
|
||||||
import io.netty.handler.codec.dns.DnsRecord;
|
import io.netty.handler.codec.dns.DnsRecord;
|
||||||
import io.netty.handler.codec.dns.DnsRecordType;
|
import io.netty.handler.codec.dns.DnsRecordType;
|
||||||
import io.netty.handler.codec.dns.DnsResponse;
|
import io.netty.handler.codec.dns.DnsResponse;
|
||||||
|
import io.netty.resolver.HostsFileEntries;
|
||||||
import io.netty.resolver.HostsFileEntriesResolver;
|
import io.netty.resolver.HostsFileEntriesResolver;
|
||||||
import io.netty.resolver.InetNameResolver;
|
import io.netty.resolver.InetNameResolver;
|
||||||
import io.netty.resolver.ResolvedAddressTypes;
|
import io.netty.resolver.ResolvedAddressTypes;
|
||||||
@ -45,6 +49,7 @@ import io.netty.util.NetUtil;
|
|||||||
import io.netty.util.ReferenceCountUtil;
|
import io.netty.util.ReferenceCountUtil;
|
||||||
import io.netty.util.concurrent.FastThreadLocal;
|
import io.netty.util.concurrent.FastThreadLocal;
|
||||||
import io.netty.util.concurrent.Future;
|
import io.netty.util.concurrent.Future;
|
||||||
|
import io.netty.util.concurrent.FutureListener;
|
||||||
import io.netty.util.concurrent.Promise;
|
import io.netty.util.concurrent.Promise;
|
||||||
import io.netty.util.internal.EmptyArrays;
|
import io.netty.util.internal.EmptyArrays;
|
||||||
import io.netty.util.internal.PlatformDependent;
|
import io.netty.util.internal.PlatformDependent;
|
||||||
@ -55,6 +60,8 @@ import io.netty.util.internal.logging.InternalLoggerFactory;
|
|||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.net.IDN;
|
import java.net.IDN;
|
||||||
|
import java.net.Inet4Address;
|
||||||
|
import java.net.Inet6Address;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -527,6 +534,96 @@ public class DnsNameResolver extends InetNameResolver {
|
|||||||
doResolve(inetHost, EMPTY_ADDITIONALS, promise, resolveCache);
|
doResolve(inetHost, EMPTY_ADDITIONALS, promise, resolveCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the {@link DnsRecord}s that are matched by the specified {@link DnsQuestion}. Unlike
|
||||||
|
* {@link #query(DnsQuestion)}, this method handles redirection, CNAMEs and multiple name servers.
|
||||||
|
* If the specified {@link DnsQuestion} is {@code A} or {@code AAAA}, this method looks up the configured
|
||||||
|
* {@link HostsFileEntries} before sending a query to the name servers. If a match is found in the
|
||||||
|
* {@link HostsFileEntries}, a synthetic {@code A} or {@code AAAA} record will be returned.
|
||||||
|
*
|
||||||
|
* @param question the question
|
||||||
|
*
|
||||||
|
* @return the list of the {@link DnsRecord}s as the result of the resolution
|
||||||
|
*/
|
||||||
|
public final Future<List<DnsRecord>> resolveAll(DnsQuestion question) {
|
||||||
|
return resolveAll(question, EMPTY_ADDITIONALS, executor().<List<DnsRecord>>newPromise());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the {@link DnsRecord}s that are matched by the specified {@link DnsQuestion}. Unlike
|
||||||
|
* {@link #query(DnsQuestion)}, this method handles redirection, CNAMEs and multiple name servers.
|
||||||
|
* If the specified {@link DnsQuestion} is {@code A} or {@code AAAA}, this method looks up the configured
|
||||||
|
* {@link HostsFileEntries} before sending a query to the name servers. If a match is found in the
|
||||||
|
* {@link HostsFileEntries}, a synthetic {@code A} or {@code AAAA} record will be returned.
|
||||||
|
*
|
||||||
|
* @param question the question
|
||||||
|
* @param additionals additional records ({@code OPT})
|
||||||
|
*
|
||||||
|
* @return the list of the {@link DnsRecord}s as the result of the resolution
|
||||||
|
*/
|
||||||
|
public final Future<List<DnsRecord>> resolveAll(DnsQuestion question, Iterable<DnsRecord> additionals) {
|
||||||
|
return resolveAll(question, additionals, executor().<List<DnsRecord>>newPromise());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the {@link DnsRecord}s that are matched by the specified {@link DnsQuestion}. Unlike
|
||||||
|
* {@link #query(DnsQuestion)}, this method handles redirection, CNAMEs and multiple name servers.
|
||||||
|
* If the specified {@link DnsQuestion} is {@code A} or {@code AAAA}, this method looks up the configured
|
||||||
|
* {@link HostsFileEntries} before sending a query to the name servers. If a match is found in the
|
||||||
|
* {@link HostsFileEntries}, a synthetic {@code A} or {@code AAAA} record will be returned.
|
||||||
|
*
|
||||||
|
* @param question the question
|
||||||
|
* @param additionals additional records ({@code OPT})
|
||||||
|
* @param promise the {@link Promise} which will be fulfilled when the resolution is finished
|
||||||
|
*
|
||||||
|
* @return the list of the {@link DnsRecord}s as the result of the resolution
|
||||||
|
*/
|
||||||
|
public final Future<List<DnsRecord>> resolveAll(DnsQuestion question, Iterable<DnsRecord> additionals,
|
||||||
|
Promise<List<DnsRecord>> promise) {
|
||||||
|
final DnsRecord[] additionalsArray = toArray(additionals, true);
|
||||||
|
return resolveAll(question, additionalsArray, promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Future<List<DnsRecord>> resolveAll(DnsQuestion question, DnsRecord[] additionals,
|
||||||
|
Promise<List<DnsRecord>> promise) {
|
||||||
|
checkNotNull(question, "question");
|
||||||
|
checkNotNull(promise, "promise");
|
||||||
|
|
||||||
|
// Respect /etc/hosts as well if the record type is A or AAAA.
|
||||||
|
final DnsRecordType type = question.type();
|
||||||
|
final String hostname = question.name();
|
||||||
|
|
||||||
|
if (type == DnsRecordType.A || type == DnsRecordType.AAAA) {
|
||||||
|
final InetAddress hostsFileEntry = resolveHostsFileEntry(hostname);
|
||||||
|
if (hostsFileEntry != null) {
|
||||||
|
ByteBuf content = null;
|
||||||
|
if (hostsFileEntry instanceof Inet4Address) {
|
||||||
|
if (type == DnsRecordType.A) {
|
||||||
|
content = Unpooled.wrappedBuffer(hostsFileEntry.getAddress());
|
||||||
|
}
|
||||||
|
} else if (hostsFileEntry instanceof Inet6Address) {
|
||||||
|
if (type == DnsRecordType.AAAA) {
|
||||||
|
content = Unpooled.wrappedBuffer(hostsFileEntry.getAddress());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content != null) {
|
||||||
|
// Our current implementation does not support reloading the hosts file,
|
||||||
|
// so use a fairly large TTL (1 day, i.e. 86400 seconds).
|
||||||
|
trySuccess(promise, Collections.<DnsRecord>singletonList(
|
||||||
|
new DefaultDnsRawRecord(hostname, type, 86400, content)));
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// It was not A/AAAA question or there was no entry in /etc/hosts.
|
||||||
|
final DnsServerAddressStream nameServerAddrs =
|
||||||
|
dnsServerAddressStreamProvider.nameServerAddressStream(hostname);
|
||||||
|
new DnsRecordResolveContext(this, question, additionals, nameServerAddrs).resolve(promise);
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
private static DnsRecord[] toArray(Iterable<DnsRecord> additionals, boolean validateType) {
|
private static DnsRecord[] toArray(Iterable<DnsRecord> additionals, boolean validateType) {
|
||||||
checkNotNull(additionals, "additionals");
|
checkNotNull(additionals, "additionals");
|
||||||
if (additionals instanceof Collection) {
|
if (additionals instanceof Collection) {
|
||||||
@ -624,7 +721,7 @@ public class DnsNameResolver extends InetNameResolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> void trySuccess(Promise<T> promise, T result) {
|
static <T> void trySuccess(Promise<T> promise, T result) {
|
||||||
if (!promise.trySuccess(result)) {
|
if (!promise.trySuccess(result)) {
|
||||||
logger.warn("Failed to notify success ({}) to a promise: {}", result, promise);
|
logger.warn("Failed to notify success ({}) to a promise: {}", result, promise);
|
||||||
}
|
}
|
||||||
@ -638,40 +735,20 @@ public class DnsNameResolver extends InetNameResolver {
|
|||||||
|
|
||||||
private void doResolveUncached(String hostname,
|
private void doResolveUncached(String hostname,
|
||||||
DnsRecord[] additionals,
|
DnsRecord[] additionals,
|
||||||
Promise<InetAddress> promise,
|
final Promise<InetAddress> promise,
|
||||||
DnsCache resolveCache) {
|
DnsCache resolveCache) {
|
||||||
new SingleResolverContext(this, hostname, additionals, resolveCache,
|
final Promise<List<InetAddress>> allPromise = executor().newPromise();
|
||||||
dnsServerAddressStreamProvider.nameServerAddressStream(hostname)).resolve(promise);
|
doResolveAllUncached(hostname, additionals, allPromise, resolveCache);
|
||||||
}
|
allPromise.addListener(new FutureListener<List<InetAddress>>() {
|
||||||
|
@Override
|
||||||
static final class SingleResolverContext extends DnsNameResolverContext<InetAddress> {
|
public void operationComplete(Future<List<InetAddress>> future) {
|
||||||
SingleResolverContext(DnsNameResolver parent, String hostname,
|
if (future.isSuccess()) {
|
||||||
DnsRecord[] additionals, DnsCache resolveCache, DnsServerAddressStream nameServerAddrs) {
|
trySuccess(promise, future.getNow().get(0));
|
||||||
super(parent, hostname, additionals, resolveCache, nameServerAddrs);
|
} else {
|
||||||
}
|
tryFailure(promise, future.cause());
|
||||||
|
|
||||||
@Override
|
|
||||||
DnsNameResolverContext<InetAddress> newResolverContext(DnsNameResolver parent, String hostname,
|
|
||||||
DnsRecord[] additionals, DnsCache resolveCache,
|
|
||||||
DnsServerAddressStream nameServerAddrs) {
|
|
||||||
return new SingleResolverContext(parent, hostname, additionals, resolveCache, nameServerAddrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
boolean finishResolve(
|
|
||||||
Class<? extends InetAddress> addressType, List<DnsCacheEntry> resolvedEntries,
|
|
||||||
Promise<InetAddress> promise) {
|
|
||||||
|
|
||||||
final int numEntries = resolvedEntries.size();
|
|
||||||
for (int i = 0; i < numEntries; i++) {
|
|
||||||
final InetAddress a = resolvedEntries.get(i).address();
|
|
||||||
if (addressType.isInstance(a)) {
|
|
||||||
trySuccess(promise, a);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -747,50 +824,13 @@ public class DnsNameResolver extends InetNameResolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static final class ListResolverContext extends DnsNameResolverContext<List<InetAddress>> {
|
|
||||||
ListResolverContext(DnsNameResolver parent, String hostname,
|
|
||||||
DnsRecord[] additionals, DnsCache resolveCache, DnsServerAddressStream nameServerAddrs) {
|
|
||||||
super(parent, hostname, additionals, resolveCache, nameServerAddrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
DnsNameResolverContext<List<InetAddress>> newResolverContext(
|
|
||||||
DnsNameResolver parent, String hostname, DnsRecord[] additionals, DnsCache resolveCache,
|
|
||||||
DnsServerAddressStream nameServerAddrs) {
|
|
||||||
return new ListResolverContext(parent, hostname, additionals, resolveCache, nameServerAddrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
boolean finishResolve(
|
|
||||||
Class<? extends InetAddress> addressType, List<DnsCacheEntry> resolvedEntries,
|
|
||||||
Promise<List<InetAddress>> promise) {
|
|
||||||
|
|
||||||
List<InetAddress> result = null;
|
|
||||||
final int numEntries = resolvedEntries.size();
|
|
||||||
for (int i = 0; i < numEntries; i++) {
|
|
||||||
final InetAddress a = resolvedEntries.get(i).address();
|
|
||||||
if (addressType.isInstance(a)) {
|
|
||||||
if (result == null) {
|
|
||||||
result = new ArrayList<InetAddress>(numEntries);
|
|
||||||
}
|
|
||||||
result.add(a);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result != null) {
|
|
||||||
promise.trySuccess(result);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doResolveAllUncached(String hostname,
|
private void doResolveAllUncached(String hostname,
|
||||||
DnsRecord[] additionals,
|
DnsRecord[] additionals,
|
||||||
Promise<List<InetAddress>> promise,
|
Promise<List<InetAddress>> promise,
|
||||||
DnsCache resolveCache) {
|
DnsCache resolveCache) {
|
||||||
new ListResolverContext(this, hostname, additionals, resolveCache,
|
final DnsServerAddressStream nameServerAddrs =
|
||||||
dnsServerAddressStreamProvider.nameServerAddressStream(hostname)).resolve(promise);
|
dnsServerAddressStreamProvider.nameServerAddressStream(hostname);
|
||||||
|
new DnsAddressResolveContext(this, hostname, additionals, nameServerAddrs, resolveCache).resolve(promise);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String hostname(String inetHost) {
|
private static String hostname(String inetHost) {
|
||||||
|
@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.resolver.dns;
|
||||||
|
|
||||||
|
import static io.netty.resolver.dns.DnsAddressDecoder.decodeAddress;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.netty.channel.EventLoop;
|
||||||
|
import io.netty.handler.codec.dns.DnsQuestion;
|
||||||
|
import io.netty.handler.codec.dns.DnsRecord;
|
||||||
|
import io.netty.handler.codec.dns.DnsRecordType;
|
||||||
|
import io.netty.util.ReferenceCountUtil;
|
||||||
|
|
||||||
|
final class DnsRecordResolveContext extends DnsResolveContext<DnsRecord> {
|
||||||
|
|
||||||
|
DnsRecordResolveContext(DnsNameResolver parent, DnsQuestion question, DnsRecord[] additionals,
|
||||||
|
DnsServerAddressStream nameServerAddrs) {
|
||||||
|
this(parent, question.name(), question.dnsClass(),
|
||||||
|
new DnsRecordType[] { question.type() },
|
||||||
|
additionals, nameServerAddrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DnsRecordResolveContext(DnsNameResolver parent, String hostname,
|
||||||
|
int dnsClass, DnsRecordType[] expectedTypes,
|
||||||
|
DnsRecord[] additionals,
|
||||||
|
DnsServerAddressStream nameServerAddrs) {
|
||||||
|
super(parent, hostname, dnsClass, expectedTypes, additionals, nameServerAddrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
DnsResolveContext<DnsRecord> newResolverContext(DnsNameResolver parent, String hostname,
|
||||||
|
int dnsClass, DnsRecordType[] expectedTypes,
|
||||||
|
DnsRecord[] additionals,
|
||||||
|
DnsServerAddressStream nameServerAddrs) {
|
||||||
|
return new DnsRecordResolveContext(parent, hostname, dnsClass, expectedTypes, additionals, nameServerAddrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
DnsRecord convertRecord(DnsRecord record, String hostname, DnsRecord[] additionals, EventLoop eventLoop) {
|
||||||
|
return ReferenceCountUtil.retain(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean containsExpectedResult(List<DnsRecord> finalResult) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void cache(String hostname, DnsRecord[] additionals, DnsRecord result, DnsRecord convertedResult) {
|
||||||
|
// Do not cache.
|
||||||
|
// XXX: When we implement cache, we would need to retain the reference count of the result record.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void cache(String hostname, DnsRecord[] additionals, UnknownHostException cause) {
|
||||||
|
// Do not cache.
|
||||||
|
// XXX: When we implement cache, we would need to retain the reference count of the result record.
|
||||||
|
}
|
||||||
|
}
|
@ -20,7 +20,7 @@ import io.netty.buffer.ByteBuf;
|
|||||||
import io.netty.buffer.ByteBufHolder;
|
import io.netty.buffer.ByteBufHolder;
|
||||||
import io.netty.channel.AddressedEnvelope;
|
import io.netty.channel.AddressedEnvelope;
|
||||||
import io.netty.channel.ChannelPromise;
|
import io.netty.channel.ChannelPromise;
|
||||||
import io.netty.channel.socket.InternetProtocolFamily;
|
import io.netty.channel.EventLoop;
|
||||||
import io.netty.handler.codec.CorruptedFrameException;
|
import io.netty.handler.codec.CorruptedFrameException;
|
||||||
import io.netty.handler.codec.dns.DefaultDnsQuestion;
|
import io.netty.handler.codec.dns.DefaultDnsQuestion;
|
||||||
import io.netty.handler.codec.dns.DefaultDnsRecordDecoder;
|
import io.netty.handler.codec.dns.DefaultDnsRecordDecoder;
|
||||||
@ -40,7 +40,6 @@ import io.netty.util.internal.PlatformDependent;
|
|||||||
import io.netty.util.internal.StringUtil;
|
import io.netty.util.internal.StringUtil;
|
||||||
import io.netty.util.internal.ThrowableUtil;
|
import io.netty.util.internal.ThrowableUtil;
|
||||||
|
|
||||||
import java.net.IDN;
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
@ -54,13 +53,12 @@ import java.util.Locale;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static io.netty.resolver.dns.DnsAddressDecoder.decodeAddress;
|
||||||
|
import static io.netty.resolver.dns.DnsNameResolver.trySuccess;
|
||||||
import static java.lang.Math.min;
|
import static java.lang.Math.min;
|
||||||
import static java.util.Collections.unmodifiableList;
|
import static java.util.Collections.unmodifiableList;
|
||||||
|
|
||||||
abstract class DnsNameResolverContext<T> {
|
abstract class DnsResolveContext<T> {
|
||||||
|
|
||||||
private static final int INADDRSZ4 = 4;
|
|
||||||
private static final int INADDRSZ6 = 16;
|
|
||||||
|
|
||||||
private static final FutureListener<AddressedEnvelope<DnsResponse, InetSocketAddress>> RELEASE_RESPONSE =
|
private static final FutureListener<AddressedEnvelope<DnsResponse, InetSocketAddress>> RELEASE_RESPONSE =
|
||||||
new FutureListener<AddressedEnvelope<DnsResponse, InetSocketAddress>>() {
|
new FutureListener<AddressedEnvelope<DnsResponse, InetSocketAddress>>() {
|
||||||
@ -73,58 +71,90 @@ abstract class DnsNameResolverContext<T> {
|
|||||||
};
|
};
|
||||||
private static final RuntimeException NXDOMAIN_QUERY_FAILED_EXCEPTION = ThrowableUtil.unknownStackTrace(
|
private static final RuntimeException NXDOMAIN_QUERY_FAILED_EXCEPTION = ThrowableUtil.unknownStackTrace(
|
||||||
new RuntimeException("No answer found and NXDOMAIN response code returned"),
|
new RuntimeException("No answer found and NXDOMAIN response code returned"),
|
||||||
DnsNameResolverContext.class,
|
DnsResolveContext.class,
|
||||||
"onResponse(..)");
|
"onResponse(..)");
|
||||||
private static final RuntimeException CNAME_NOT_FOUND_QUERY_FAILED_EXCEPTION = ThrowableUtil.unknownStackTrace(
|
private static final RuntimeException CNAME_NOT_FOUND_QUERY_FAILED_EXCEPTION = ThrowableUtil.unknownStackTrace(
|
||||||
new RuntimeException("No matching CNAME record found"),
|
new RuntimeException("No matching CNAME record found"),
|
||||||
DnsNameResolverContext.class,
|
DnsResolveContext.class,
|
||||||
"onResponseCNAME(..)");
|
"onResponseCNAME(..)");
|
||||||
private static final RuntimeException NO_MATCHING_RECORD_QUERY_FAILED_EXCEPTION = ThrowableUtil.unknownStackTrace(
|
private static final RuntimeException NO_MATCHING_RECORD_QUERY_FAILED_EXCEPTION = ThrowableUtil.unknownStackTrace(
|
||||||
new RuntimeException("No matching record type found"),
|
new RuntimeException("No matching record type found"),
|
||||||
DnsNameResolverContext.class,
|
DnsResolveContext.class,
|
||||||
"onResponseAorAAAA(..)");
|
"onResponseAorAAAA(..)");
|
||||||
private static final RuntimeException UNRECOGNIZED_TYPE_QUERY_FAILED_EXCEPTION = ThrowableUtil.unknownStackTrace(
|
private static final RuntimeException UNRECOGNIZED_TYPE_QUERY_FAILED_EXCEPTION = ThrowableUtil.unknownStackTrace(
|
||||||
new RuntimeException("Response type was unrecognized"),
|
new RuntimeException("Response type was unrecognized"),
|
||||||
DnsNameResolverContext.class,
|
DnsResolveContext.class,
|
||||||
"onResponse(..)");
|
"onResponse(..)");
|
||||||
private static final RuntimeException NAME_SERVERS_EXHAUSTED_EXCEPTION = ThrowableUtil.unknownStackTrace(
|
private static final RuntimeException NAME_SERVERS_EXHAUSTED_EXCEPTION = ThrowableUtil.unknownStackTrace(
|
||||||
new RuntimeException("No name servers returned an answer"),
|
new RuntimeException("No name servers returned an answer"),
|
||||||
DnsNameResolverContext.class,
|
DnsResolveContext.class,
|
||||||
"tryToFinishResolve(..)");
|
"tryToFinishResolve(..)");
|
||||||
|
|
||||||
private final DnsNameResolver parent;
|
final DnsNameResolver parent;
|
||||||
private final DnsServerAddressStream nameServerAddrs;
|
private final DnsServerAddressStream nameServerAddrs;
|
||||||
private final String hostname;
|
private final String hostname;
|
||||||
private final DnsCache resolveCache;
|
private final int dnsClass;
|
||||||
|
private final DnsRecordType[] expectedTypes;
|
||||||
private final int maxAllowedQueries;
|
private final int maxAllowedQueries;
|
||||||
private final InternetProtocolFamily[] resolvedInternetProtocolFamilies;
|
|
||||||
private final DnsRecord[] additionals;
|
private final DnsRecord[] additionals;
|
||||||
|
|
||||||
private final Set<Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> queriesInProgress =
|
private final Set<Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> queriesInProgress =
|
||||||
Collections.newSetFromMap(
|
Collections.newSetFromMap(
|
||||||
new IdentityHashMap<Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>, Boolean>());
|
new IdentityHashMap<Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>, Boolean>());
|
||||||
|
|
||||||
private List<DnsCacheEntry> resolvedEntries;
|
private List<T> finalResult;
|
||||||
private int allowedQueries;
|
private int allowedQueries;
|
||||||
private boolean triedCNAME;
|
private boolean triedCNAME;
|
||||||
|
|
||||||
DnsNameResolverContext(DnsNameResolver parent,
|
DnsResolveContext(DnsNameResolver parent,
|
||||||
String hostname,
|
String hostname, int dnsClass, DnsRecordType[] expectedTypes,
|
||||||
DnsRecord[] additionals,
|
DnsRecord[] additionals, DnsServerAddressStream nameServerAddrs) {
|
||||||
DnsCache resolveCache,
|
|
||||||
DnsServerAddressStream nameServerAddrs) {
|
assert expectedTypes.length > 0;
|
||||||
|
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
this.hostname = hostname;
|
this.hostname = hostname;
|
||||||
|
this.dnsClass = dnsClass;
|
||||||
|
this.expectedTypes = expectedTypes;
|
||||||
this.additionals = additionals;
|
this.additionals = additionals;
|
||||||
this.resolveCache = resolveCache;
|
|
||||||
|
|
||||||
this.nameServerAddrs = ObjectUtil.checkNotNull(nameServerAddrs, "nameServerAddrs");
|
this.nameServerAddrs = ObjectUtil.checkNotNull(nameServerAddrs, "nameServerAddrs");
|
||||||
maxAllowedQueries = parent.maxQueriesPerResolve();
|
maxAllowedQueries = parent.maxQueriesPerResolve();
|
||||||
resolvedInternetProtocolFamilies = parent.resolvedInternetProtocolFamiliesUnsafe();
|
|
||||||
allowedQueries = maxAllowedQueries;
|
allowedQueries = maxAllowedQueries;
|
||||||
}
|
}
|
||||||
|
|
||||||
void resolve(final Promise<T> promise) {
|
/**
|
||||||
|
* Creates a new context with the given parameters.
|
||||||
|
*/
|
||||||
|
abstract DnsResolveContext<T> newResolverContext(DnsNameResolver parent, String hostname,
|
||||||
|
int dnsClass, DnsRecordType[] expectedTypes,
|
||||||
|
DnsRecord[] additionals,
|
||||||
|
DnsServerAddressStream nameServerAddrs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the given {@link DnsRecord} into {@code T}.
|
||||||
|
*/
|
||||||
|
abstract T convertRecord(DnsRecord record, String hostname, DnsRecord[] additionals, EventLoop eventLoop);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if the given list contains any expected records. {@code finalResult} always contains
|
||||||
|
* at least one element.
|
||||||
|
*/
|
||||||
|
abstract boolean containsExpectedResult(List<T> finalResult);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Caches a successful resolution.
|
||||||
|
*/
|
||||||
|
abstract void cache(String hostname, DnsRecord[] additionals,
|
||||||
|
DnsRecord result, T convertedResult);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Caches a failed resolution.
|
||||||
|
*/
|
||||||
|
abstract void cache(String hostname, DnsRecord[] additionals,
|
||||||
|
UnknownHostException cause);
|
||||||
|
|
||||||
|
void resolve(final Promise<List<T>> promise) {
|
||||||
final String[] searchDomains = parent.searchDomains();
|
final String[] searchDomains = parent.searchDomains();
|
||||||
if (searchDomains.length == 0 || parent.ndots() == 0 || StringUtil.endsWith(hostname, '.')) {
|
if (searchDomains.length == 0 || parent.ndots() == 0 || StringUtil.endsWith(hostname, '.')) {
|
||||||
internalResolve(promise);
|
internalResolve(promise);
|
||||||
@ -133,10 +163,10 @@ abstract class DnsNameResolverContext<T> {
|
|||||||
final String initialHostname = startWithoutSearchDomain ? hostname : hostname + '.' + searchDomains[0];
|
final String initialHostname = startWithoutSearchDomain ? hostname : hostname + '.' + searchDomains[0];
|
||||||
final int initialSearchDomainIdx = startWithoutSearchDomain ? 0 : 1;
|
final int initialSearchDomainIdx = startWithoutSearchDomain ? 0 : 1;
|
||||||
|
|
||||||
doSearchDomainQuery(initialHostname, new FutureListener<T>() {
|
doSearchDomainQuery(initialHostname, new FutureListener<List<T>>() {
|
||||||
private int searchDomainIdx = initialSearchDomainIdx;
|
private int searchDomainIdx = initialSearchDomainIdx;
|
||||||
@Override
|
@Override
|
||||||
public void operationComplete(Future<T> future) throws Exception {
|
public void operationComplete(Future<List<T>> future) throws Exception {
|
||||||
Throwable cause = future.cause();
|
Throwable cause = future.cause();
|
||||||
if (cause == null) {
|
if (cause == null) {
|
||||||
promise.trySuccess(future.getNow());
|
promise.trySuccess(future.getNow());
|
||||||
@ -182,26 +212,24 @@ abstract class DnsNameResolverContext<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doSearchDomainQuery(String hostname, FutureListener<T> listener) {
|
private void doSearchDomainQuery(String hostname, FutureListener<List<T>> listener) {
|
||||||
DnsNameResolverContext<T> nextContext = newResolverContext(parent, hostname, additionals, resolveCache,
|
DnsResolveContext<T> nextContext = newResolverContext(parent, hostname, dnsClass, expectedTypes,
|
||||||
nameServerAddrs);
|
additionals, nameServerAddrs);
|
||||||
Promise<T> nextPromise = parent.executor().newPromise();
|
Promise<List<T>> nextPromise = parent.executor().newPromise();
|
||||||
nextContext.internalResolve(nextPromise);
|
nextContext.internalResolve(nextPromise);
|
||||||
nextPromise.addListener(listener);
|
nextPromise.addListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void internalResolve(Promise<T> promise) {
|
private void internalResolve(Promise<List<T>> promise) {
|
||||||
DnsServerAddressStream nameServerAddressStream = getNameServers(hostname);
|
DnsServerAddressStream nameServerAddressStream = getNameServers(hostname);
|
||||||
|
|
||||||
DnsRecordType[] recordTypes = parent.resolveRecordTypes();
|
final int end = expectedTypes.length - 1;
|
||||||
assert recordTypes.length > 0;
|
|
||||||
final int end = recordTypes.length - 1;
|
|
||||||
for (int i = 0; i < end; ++i) {
|
for (int i = 0; i < end; ++i) {
|
||||||
if (!query(hostname, recordTypes[i], nameServerAddressStream.duplicate(), promise, null)) {
|
if (!query(hostname, expectedTypes[i], nameServerAddressStream.duplicate(), promise, null)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
query(hostname, recordTypes[end], nameServerAddressStream, promise, null);
|
query(hostname, expectedTypes[end], nameServerAddressStream, promise, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -291,7 +319,7 @@ abstract class DnsNameResolverContext<T> {
|
|||||||
|
|
||||||
private void query(final DnsServerAddressStream nameServerAddrStream, final int nameServerAddrStreamIndex,
|
private void query(final DnsServerAddressStream nameServerAddrStream, final int nameServerAddrStreamIndex,
|
||||||
final DnsQuestion question,
|
final DnsQuestion question,
|
||||||
final Promise<T> promise, Throwable cause) {
|
final Promise<List<T>> promise, Throwable cause) {
|
||||||
query(nameServerAddrStream, nameServerAddrStreamIndex, question,
|
query(nameServerAddrStream, nameServerAddrStreamIndex, question,
|
||||||
parent.dnsQueryLifecycleObserverFactory().newDnsQueryLifecycleObserver(question), promise, cause);
|
parent.dnsQueryLifecycleObserverFactory().newDnsQueryLifecycleObserver(question), promise, cause);
|
||||||
}
|
}
|
||||||
@ -300,7 +328,7 @@ abstract class DnsNameResolverContext<T> {
|
|||||||
final int nameServerAddrStreamIndex,
|
final int nameServerAddrStreamIndex,
|
||||||
final DnsQuestion question,
|
final DnsQuestion question,
|
||||||
final DnsQueryLifecycleObserver queryLifecycleObserver,
|
final DnsQueryLifecycleObserver queryLifecycleObserver,
|
||||||
final Promise<T> promise,
|
final Promise<List<T>> promise,
|
||||||
final Throwable cause) {
|
final Throwable cause) {
|
||||||
if (nameServerAddrStreamIndex >= nameServerAddrStream.size() || allowedQueries == 0 || promise.isCancelled()) {
|
if (nameServerAddrStreamIndex >= nameServerAddrStream.size() || allowedQueries == 0 || promise.isCancelled()) {
|
||||||
tryToFinishResolve(nameServerAddrStream, nameServerAddrStreamIndex, question, queryLifecycleObserver,
|
tryToFinishResolve(nameServerAddrStream, nameServerAddrStreamIndex, question, queryLifecycleObserver,
|
||||||
@ -359,7 +387,7 @@ abstract class DnsNameResolverContext<T> {
|
|||||||
void onResponse(final DnsServerAddressStream nameServerAddrStream, final int nameServerAddrStreamIndex,
|
void onResponse(final DnsServerAddressStream nameServerAddrStream, final int nameServerAddrStreamIndex,
|
||||||
final DnsQuestion question, AddressedEnvelope<DnsResponse, InetSocketAddress> envelope,
|
final DnsQuestion question, AddressedEnvelope<DnsResponse, InetSocketAddress> envelope,
|
||||||
final DnsQueryLifecycleObserver queryLifecycleObserver,
|
final DnsQueryLifecycleObserver queryLifecycleObserver,
|
||||||
Promise<T> promise) {
|
Promise<List<T>> promise) {
|
||||||
try {
|
try {
|
||||||
final DnsResponse res = envelope.content();
|
final DnsResponse res = envelope.content();
|
||||||
final DnsResponseCode code = res.code();
|
final DnsResponseCode code = res.code();
|
||||||
@ -370,13 +398,19 @@ abstract class DnsNameResolverContext<T> {
|
|||||||
}
|
}
|
||||||
final DnsRecordType type = question.type();
|
final DnsRecordType type = question.type();
|
||||||
|
|
||||||
if (type == DnsRecordType.A || type == DnsRecordType.AAAA) {
|
if (type == DnsRecordType.CNAME) {
|
||||||
onResponseAorAAAA(type, question, envelope, queryLifecycleObserver, promise);
|
|
||||||
} else if (type == DnsRecordType.CNAME) {
|
|
||||||
onResponseCNAME(question, envelope, queryLifecycleObserver, promise);
|
onResponseCNAME(question, envelope, queryLifecycleObserver, promise);
|
||||||
} else {
|
return;
|
||||||
queryLifecycleObserver.queryFailed(UNRECOGNIZED_TYPE_QUERY_FAILED_EXCEPTION);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (DnsRecordType expectedType : expectedTypes) {
|
||||||
|
if (type == expectedType) {
|
||||||
|
onExpectedResponse(question, envelope, queryLifecycleObserver, promise);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
queryLifecycleObserver.queryFailed(UNRECOGNIZED_TYPE_QUERY_FAILED_EXCEPTION);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -397,7 +431,7 @@ abstract class DnsNameResolverContext<T> {
|
|||||||
*/
|
*/
|
||||||
private boolean handleRedirect(
|
private boolean handleRedirect(
|
||||||
DnsQuestion question, AddressedEnvelope<DnsResponse, InetSocketAddress> envelope,
|
DnsQuestion question, AddressedEnvelope<DnsResponse, InetSocketAddress> envelope,
|
||||||
final DnsQueryLifecycleObserver queryLifecycleObserver, Promise<T> promise) {
|
final DnsQueryLifecycleObserver queryLifecycleObserver, Promise<List<T>> promise) {
|
||||||
final DnsResponse res = envelope.content();
|
final DnsResponse res = envelope.content();
|
||||||
|
|
||||||
// Check if we have answers, if not this may be an non authority NS and so redirects must be handled.
|
// Check if we have answers, if not this may be an non authority NS and so redirects must be handled.
|
||||||
@ -417,15 +451,14 @@ abstract class DnsNameResolverContext<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final String recordName = r.name();
|
final String recordName = r.name();
|
||||||
AuthoritativeNameServer authoritativeNameServer =
|
final AuthoritativeNameServer authoritativeNameServer = serverNames.remove(recordName);
|
||||||
serverNames.remove(recordName);
|
|
||||||
|
|
||||||
if (authoritativeNameServer == null) {
|
if (authoritativeNameServer == null) {
|
||||||
// Not a server we are interested in.
|
// Not a server we are interested in.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
InetAddress resolved = parseAddress(r, recordName);
|
InetAddress resolved = decodeAddress(r, recordName, parent.isDecodeIdn());
|
||||||
if (resolved == null) {
|
if (resolved == null) {
|
||||||
// Could not parse it, move to the next.
|
// Could not parse it, move to the next.
|
||||||
continue;
|
continue;
|
||||||
@ -462,10 +495,9 @@ abstract class DnsNameResolverContext<T> {
|
|||||||
return serverNames;
|
return serverNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onResponseAorAAAA(
|
private void onExpectedResponse(
|
||||||
DnsRecordType qType, DnsQuestion question, AddressedEnvelope<DnsResponse, InetSocketAddress> envelope,
|
DnsQuestion question, AddressedEnvelope<DnsResponse, InetSocketAddress> envelope,
|
||||||
final DnsQueryLifecycleObserver queryLifecycleObserver,
|
final DnsQueryLifecycleObserver queryLifecycleObserver, Promise<List<T>> promise) {
|
||||||
Promise<T> promise) {
|
|
||||||
|
|
||||||
// We often get a bunch of CNAMES as well when we asked for A/AAAA.
|
// We often get a bunch of CNAMES as well when we asked for A/AAAA.
|
||||||
final DnsResponse response = envelope.content();
|
final DnsResponse response = envelope.content();
|
||||||
@ -476,7 +508,15 @@ abstract class DnsNameResolverContext<T> {
|
|||||||
for (int i = 0; i < answerCount; i ++) {
|
for (int i = 0; i < answerCount; i ++) {
|
||||||
final DnsRecord r = response.recordAt(DnsSection.ANSWER, i);
|
final DnsRecord r = response.recordAt(DnsSection.ANSWER, i);
|
||||||
final DnsRecordType type = r.type();
|
final DnsRecordType type = r.type();
|
||||||
if (type != DnsRecordType.A && type != DnsRecordType.AAAA) {
|
boolean matches = false;
|
||||||
|
for (DnsRecordType expectedType : expectedTypes) {
|
||||||
|
if (type == expectedType) {
|
||||||
|
matches = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!matches) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -499,17 +539,17 @@ abstract class DnsNameResolverContext<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InetAddress resolved = parseAddress(r, hostname);
|
final T converted = convertRecord(r, hostname, additionals, parent.ch.eventLoop());
|
||||||
if (resolved == null) {
|
if (converted == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resolvedEntries == null) {
|
if (finalResult == null) {
|
||||||
resolvedEntries = new ArrayList<DnsCacheEntry>(8);
|
finalResult = new ArrayList<T>(8);
|
||||||
}
|
}
|
||||||
|
finalResult.add(converted);
|
||||||
|
|
||||||
resolvedEntries.add(
|
cache(hostname, additionals, r, converted);
|
||||||
resolveCache.cache(hostname, additionals, resolved, r.timeToLive(), parent.ch.eventLoop()));
|
|
||||||
found = true;
|
found = true;
|
||||||
|
|
||||||
// Note that we do not break from the loop here, so we decode/cache all A/AAAA records.
|
// Note that we do not break from the loop here, so we decode/cache all A/AAAA records.
|
||||||
@ -523,47 +563,23 @@ abstract class DnsNameResolverContext<T> {
|
|||||||
if (cnames.isEmpty()) {
|
if (cnames.isEmpty()) {
|
||||||
queryLifecycleObserver.queryFailed(NO_MATCHING_RECORD_QUERY_FAILED_EXCEPTION);
|
queryLifecycleObserver.queryFailed(NO_MATCHING_RECORD_QUERY_FAILED_EXCEPTION);
|
||||||
} else {
|
} else {
|
||||||
// We asked for A/AAAA but we got only CNAME.
|
// We got only CNAME, not one of expectedTypes.
|
||||||
onResponseCNAME(question, envelope, cnames, queryLifecycleObserver, promise);
|
onResponseCNAME(question, cnames, queryLifecycleObserver, promise);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private InetAddress parseAddress(DnsRecord r, String name) {
|
|
||||||
if (!(r instanceof DnsRawRecord)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
final ByteBuf content = ((ByteBufHolder) r).content();
|
|
||||||
final int contentLen = content.readableBytes();
|
|
||||||
if (contentLen != INADDRSZ4 && contentLen != INADDRSZ6) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final byte[] addrBytes = new byte[contentLen];
|
|
||||||
content.getBytes(content.readerIndex(), addrBytes);
|
|
||||||
|
|
||||||
try {
|
|
||||||
return InetAddress.getByAddress(
|
|
||||||
parent.isDecodeIdn() ? IDN.toUnicode(name) : name, addrBytes);
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
// Should never reach here.
|
|
||||||
throw new Error(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onResponseCNAME(DnsQuestion question, AddressedEnvelope<DnsResponse, InetSocketAddress> envelope,
|
private void onResponseCNAME(DnsQuestion question, AddressedEnvelope<DnsResponse, InetSocketAddress> envelope,
|
||||||
final DnsQueryLifecycleObserver queryLifecycleObserver,
|
final DnsQueryLifecycleObserver queryLifecycleObserver, Promise<List<T>> promise) {
|
||||||
Promise<T> promise) {
|
onResponseCNAME(question, buildAliasMap(envelope.content()), queryLifecycleObserver, promise);
|
||||||
onResponseCNAME(question, envelope, buildAliasMap(envelope.content()), queryLifecycleObserver, promise);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onResponseCNAME(
|
private void onResponseCNAME(
|
||||||
DnsQuestion question, AddressedEnvelope<DnsResponse, InetSocketAddress> response,
|
DnsQuestion question, Map<String, String> cnames,
|
||||||
Map<String, String> cnames, final DnsQueryLifecycleObserver queryLifecycleObserver,
|
final DnsQueryLifecycleObserver queryLifecycleObserver,
|
||||||
Promise<T> promise) {
|
Promise<List<T>> promise) {
|
||||||
|
|
||||||
// Resolve the host name in the question into the real host name.
|
// Resolve the host name in the question into the real host name.
|
||||||
final String name = question.name().toLowerCase(Locale.US);
|
String resolved = question.name().toLowerCase(Locale.US);
|
||||||
String resolved = name;
|
|
||||||
boolean found = false;
|
boolean found = false;
|
||||||
while (!cnames.isEmpty()) { // Do not attempt to call Map.remove() when the Map is empty
|
while (!cnames.isEmpty()) { // Do not attempt to call Map.remove() when the Map is empty
|
||||||
// because it can be Collections.emptyMap()
|
// because it can be Collections.emptyMap()
|
||||||
@ -618,24 +634,24 @@ abstract class DnsNameResolverContext<T> {
|
|||||||
final int nameServerAddrStreamIndex,
|
final int nameServerAddrStreamIndex,
|
||||||
final DnsQuestion question,
|
final DnsQuestion question,
|
||||||
final DnsQueryLifecycleObserver queryLifecycleObserver,
|
final DnsQueryLifecycleObserver queryLifecycleObserver,
|
||||||
final Promise<T> promise,
|
final Promise<List<T>> promise,
|
||||||
final Throwable cause) {
|
final Throwable cause) {
|
||||||
// There are no queries left to try.
|
// There are no queries left to try.
|
||||||
if (!queriesInProgress.isEmpty()) {
|
if (!queriesInProgress.isEmpty()) {
|
||||||
queryLifecycleObserver.queryCancelled(allowedQueries);
|
queryLifecycleObserver.queryCancelled(allowedQueries);
|
||||||
|
|
||||||
// There are still some queries we did not receive responses for.
|
// There are still some queries we did not receive responses for.
|
||||||
if (gotPreferredAddress()) {
|
if (finalResult != null && containsExpectedResult(finalResult)) {
|
||||||
// But it's OK to finish the resolution process if we got a resolved address of the preferred type.
|
// But it's OK to finish the resolution process if we got something expected.
|
||||||
finishResolve(promise, cause);
|
finishResolve(promise, cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We did not get any resolved address of the preferred type, so we can't finish the resolution process.
|
// We did not get an expected result yet, so we can't finish the resolution process.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// There are no queries left to try.
|
// There are no queries left to try.
|
||||||
if (resolvedEntries == null) {
|
if (finalResult == null) {
|
||||||
if (nameServerAddrStreamIndex < nameServerAddrStream.size()) {
|
if (nameServerAddrStreamIndex < nameServerAddrStream.size()) {
|
||||||
if (queryLifecycleObserver == NoopDnsQueryLifecycleObserver.INSTANCE) {
|
if (queryLifecycleObserver == NoopDnsQueryLifecycleObserver.INSTANCE) {
|
||||||
// If the queryLifecycleObserver has already been terminated we should create a new one for this
|
// If the queryLifecycleObserver has already been terminated we should create a new one for this
|
||||||
@ -650,11 +666,11 @@ abstract class DnsNameResolverContext<T> {
|
|||||||
|
|
||||||
queryLifecycleObserver.queryFailed(NAME_SERVERS_EXHAUSTED_EXCEPTION);
|
queryLifecycleObserver.queryFailed(NAME_SERVERS_EXHAUSTED_EXCEPTION);
|
||||||
|
|
||||||
// .. and we could not find any A/AAAA records.
|
// .. and we could not find any expected records.
|
||||||
|
|
||||||
// If cause != null we know this was caused by a timeout / cancel / transport exception. In this case we
|
// If cause != null we know this was caused by a timeout / cancel / transport exception. In this case we
|
||||||
// won't try to resolve the CNAME as we only should do this if we could not get the A/AAAA records because
|
// won't try to resolve the CNAME as we only should do this if we could not get the expected records
|
||||||
// these not exists and the DNS server did probably signal it.
|
// because they do not exist and the DNS server did probably signal it.
|
||||||
if (cause == null && !triedCNAME) {
|
if (cause == null && !triedCNAME) {
|
||||||
// As the last resort, try to query CNAME, just in case the name server has it.
|
// As the last resort, try to query CNAME, just in case the name server has it.
|
||||||
triedCNAME = true;
|
triedCNAME = true;
|
||||||
@ -666,27 +682,11 @@ abstract class DnsNameResolverContext<T> {
|
|||||||
queryLifecycleObserver.queryCancelled(allowedQueries);
|
queryLifecycleObserver.queryCancelled(allowedQueries);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We have at least one resolved address or tried CNAME as the last resort..
|
// We have at least one resolved record or tried CNAME as the last resort..
|
||||||
finishResolve(promise, cause);
|
finishResolve(promise, cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean gotPreferredAddress() {
|
private void finishResolve(Promise<List<T>> promise, Throwable cause) {
|
||||||
if (resolvedEntries == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int size = resolvedEntries.size();
|
|
||||||
final Class<? extends InetAddress> inetAddressType = parent.preferredAddressType().addressType();
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
InetAddress address = resolvedEntries.get(i).address();
|
|
||||||
if (inetAddressType.isInstance(address)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void finishResolve(Promise<T> promise, Throwable cause) {
|
|
||||||
if (!queriesInProgress.isEmpty()) {
|
if (!queriesInProgress.isEmpty()) {
|
||||||
// If there are queries in progress, we should cancel it because we already finished the resolution.
|
// If there are queries in progress, we should cancel it because we already finished the resolution.
|
||||||
for (Iterator<Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> i = queriesInProgress.iterator();
|
for (Iterator<Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> i = queriesInProgress.iterator();
|
||||||
@ -700,13 +700,10 @@ abstract class DnsNameResolverContext<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resolvedEntries != null) {
|
if (finalResult != null) {
|
||||||
// Found at least one resolved address.
|
// Found at least one resolved record.
|
||||||
for (InternetProtocolFamily f: resolvedInternetProtocolFamilies) {
|
trySuccess(promise, finalResult);
|
||||||
if (finishResolve(f.addressType(), resolvedEntries, promise)) {
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// No resolved address found.
|
// No resolved address found.
|
||||||
@ -729,20 +726,13 @@ abstract class DnsNameResolverContext<T> {
|
|||||||
if (cause == null) {
|
if (cause == null) {
|
||||||
// Only cache if the failure was not because of an IO error / timeout that was caused by the query
|
// Only cache if the failure was not because of an IO error / timeout that was caused by the query
|
||||||
// itself.
|
// itself.
|
||||||
resolveCache.cache(hostname, additionals, unknownHostException, parent.ch.eventLoop());
|
cache(hostname, additionals, unknownHostException);
|
||||||
} else {
|
} else {
|
||||||
unknownHostException.initCause(cause);
|
unknownHostException.initCause(cause);
|
||||||
}
|
}
|
||||||
promise.tryFailure(unknownHostException);
|
promise.tryFailure(unknownHostException);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract boolean finishResolve(Class<? extends InetAddress> addressType, List<DnsCacheEntry> resolvedEntries,
|
|
||||||
Promise<T> promise);
|
|
||||||
|
|
||||||
abstract DnsNameResolverContext<T> newResolverContext(DnsNameResolver parent, String hostname,
|
|
||||||
DnsRecord[] additionals, DnsCache resolveCache,
|
|
||||||
DnsServerAddressStream nameServerAddrs);
|
|
||||||
|
|
||||||
static String decodeDomainName(ByteBuf in) {
|
static String decodeDomainName(ByteBuf in) {
|
||||||
in.markReaderIndex();
|
in.markReaderIndex();
|
||||||
try {
|
try {
|
||||||
@ -760,8 +750,8 @@ abstract class DnsNameResolverContext<T> {
|
|||||||
return stream == null ? nameServerAddrs.duplicate() : stream;
|
return stream == null ? nameServerAddrs.duplicate() : stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void followCname(
|
private void followCname(DnsQuestion question, String cname, DnsQueryLifecycleObserver queryLifecycleObserver,
|
||||||
DnsQuestion question, String cname, DnsQueryLifecycleObserver queryLifecycleObserver, Promise<T> promise) {
|
Promise<List<T>> promise) {
|
||||||
DnsServerAddressStream stream = getNameServers(cname);
|
DnsServerAddressStream stream = getNameServers(cname);
|
||||||
|
|
||||||
final DnsQuestion cnameQuestion;
|
final DnsQuestion cnameQuestion;
|
||||||
@ -776,7 +766,7 @@ abstract class DnsNameResolverContext<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean query(String hostname, DnsRecordType type, DnsServerAddressStream dnsServerAddressStream,
|
private boolean query(String hostname, DnsRecordType type, DnsServerAddressStream dnsServerAddressStream,
|
||||||
Promise<T> promise, Throwable cause) {
|
Promise<List<T>> promise, Throwable cause) {
|
||||||
final DnsQuestion question = newQuestion(hostname, type);
|
final DnsQuestion question = newQuestion(hostname, type);
|
||||||
if (question == null) {
|
if (question == null) {
|
||||||
return false;
|
return false;
|
||||||
@ -785,9 +775,9 @@ abstract class DnsNameResolverContext<T> {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DnsQuestion newQuestion(String hostname, DnsRecordType type) {
|
private DnsQuestion newQuestion(String hostname, DnsRecordType type) {
|
||||||
try {
|
try {
|
||||||
return new DefaultDnsQuestion(hostname, type);
|
return new DefaultDnsQuestion(hostname, type, dnsClass);
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
// java.net.IDN.toASCII(...) may throw an IllegalArgumentException if it fails to parse the hostname
|
// java.net.IDN.toASCII(...) may throw an IllegalArgumentException if it fails to parse the hostname
|
||||||
return null;
|
return null;
|
@ -28,6 +28,7 @@ import io.netty.channel.socket.InternetProtocolFamily;
|
|||||||
import io.netty.channel.socket.nio.NioDatagramChannel;
|
import io.netty.channel.socket.nio.NioDatagramChannel;
|
||||||
import io.netty.handler.codec.dns.DefaultDnsQuestion;
|
import io.netty.handler.codec.dns.DefaultDnsQuestion;
|
||||||
import io.netty.handler.codec.dns.DnsQuestion;
|
import io.netty.handler.codec.dns.DnsQuestion;
|
||||||
|
import io.netty.handler.codec.dns.DnsRawRecord;
|
||||||
import io.netty.handler.codec.dns.DnsRecord;
|
import io.netty.handler.codec.dns.DnsRecord;
|
||||||
import io.netty.handler.codec.dns.DnsRecordType;
|
import io.netty.handler.codec.dns.DnsRecordType;
|
||||||
import io.netty.handler.codec.dns.DnsResponse;
|
import io.netty.handler.codec.dns.DnsResponse;
|
||||||
@ -36,6 +37,7 @@ import io.netty.handler.codec.dns.DnsSection;
|
|||||||
import io.netty.resolver.HostsFileEntriesResolver;
|
import io.netty.resolver.HostsFileEntriesResolver;
|
||||||
import io.netty.resolver.ResolvedAddressTypes;
|
import io.netty.resolver.ResolvedAddressTypes;
|
||||||
import io.netty.util.NetUtil;
|
import io.netty.util.NetUtil;
|
||||||
|
import io.netty.util.ReferenceCountUtil;
|
||||||
import io.netty.util.concurrent.Future;
|
import io.netty.util.concurrent.Future;
|
||||||
import io.netty.util.internal.PlatformDependent;
|
import io.netty.util.internal.PlatformDependent;
|
||||||
import io.netty.util.internal.SocketUtils;
|
import io.netty.util.internal.SocketUtils;
|
||||||
@ -52,6 +54,7 @@ import org.apache.directory.server.dns.messages.ResourceRecordModifier;
|
|||||||
import org.apache.directory.server.dns.messages.ResponseCode;
|
import org.apache.directory.server.dns.messages.ResponseCode;
|
||||||
import org.apache.directory.server.dns.store.DnsAttribute;
|
import org.apache.directory.server.dns.store.DnsAttribute;
|
||||||
import org.apache.directory.server.dns.store.RecordStore;
|
import org.apache.directory.server.dns.store.RecordStore;
|
||||||
|
import org.hamcrest.Matchers;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -608,7 +611,7 @@ public class DnsNameResolverTest {
|
|||||||
buf.append(' ');
|
buf.append(' ');
|
||||||
buf.append(recordContent.readUnsignedShort());
|
buf.append(recordContent.readUnsignedShort());
|
||||||
buf.append(' ');
|
buf.append(' ');
|
||||||
buf.append(DnsNameResolverContext.decodeDomainName(recordContent));
|
buf.append(DnsResolveContext.decodeDomainName(recordContent));
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("{} has the following MX records:{}", hostname, buf);
|
logger.info("{} has the following MX records:{}", hostname, buf);
|
||||||
@ -945,6 +948,84 @@ public class DnsNameResolverTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveAllMx() {
|
||||||
|
final DnsNameResolver resolver = newResolver().build();
|
||||||
|
try {
|
||||||
|
assertThat(resolver.isRecursionDesired(), is(true));
|
||||||
|
|
||||||
|
final Map<String, Future<List<DnsRecord>>> futures = new LinkedHashMap<String, Future<List<DnsRecord>>>();
|
||||||
|
for (String name: DOMAINS) {
|
||||||
|
if (EXCLUSIONS_QUERY_MX.contains(name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
futures.put(name, resolver.resolveAll(new DefaultDnsQuestion(name, DnsRecordType.MX)));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Entry<String, Future<List<DnsRecord>>> e: futures.entrySet()) {
|
||||||
|
String hostname = e.getKey();
|
||||||
|
Future<List<DnsRecord>> f = e.getValue().awaitUninterruptibly();
|
||||||
|
|
||||||
|
final List<DnsRecord> mxList = f.getNow();
|
||||||
|
assertThat(mxList.size(), is(greaterThan(0)));
|
||||||
|
StringBuilder buf = new StringBuilder();
|
||||||
|
for (DnsRecord r: mxList) {
|
||||||
|
ByteBuf recordContent = ((ByteBufHolder) r).content();
|
||||||
|
|
||||||
|
buf.append(StringUtil.NEWLINE);
|
||||||
|
buf.append('\t');
|
||||||
|
buf.append(r.name());
|
||||||
|
buf.append(' ');
|
||||||
|
buf.append(r.type().name());
|
||||||
|
buf.append(' ');
|
||||||
|
buf.append(recordContent.readUnsignedShort());
|
||||||
|
buf.append(' ');
|
||||||
|
buf.append(DnsResolveContext.decodeDomainName(recordContent));
|
||||||
|
|
||||||
|
ReferenceCountUtil.release(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("{} has the following MX records:{}", hostname, buf);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
resolver.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveAllHostsFile() {
|
||||||
|
final DnsNameResolver resolver = new DnsNameResolverBuilder(group.next())
|
||||||
|
.channelType(NioDatagramChannel.class)
|
||||||
|
.hostsFileEntriesResolver(new HostsFileEntriesResolver() {
|
||||||
|
@Override
|
||||||
|
public InetAddress address(String inetHost, ResolvedAddressTypes resolvedAddressTypes) {
|
||||||
|
if ("foo.com.".equals(inetHost)) {
|
||||||
|
try {
|
||||||
|
return InetAddress.getByAddress("foo.com", new byte[] { 1, 2, 3, 4 });
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
throw new Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}).build();
|
||||||
|
|
||||||
|
final List<DnsRecord> records = resolver.resolveAll(new DefaultDnsQuestion("foo.com.", A))
|
||||||
|
.syncUninterruptibly().getNow();
|
||||||
|
assertThat(records, Matchers.<DnsRecord>hasSize(1));
|
||||||
|
assertThat(records.get(0), Matchers.<DnsRecord>instanceOf(DnsRawRecord.class));
|
||||||
|
|
||||||
|
final DnsRawRecord record = (DnsRawRecord) records.get(0);
|
||||||
|
final ByteBuf content = record.content();
|
||||||
|
assertThat(record.name(), is("foo.com."));
|
||||||
|
assertThat(record.dnsClass(), is(DnsRecord.CLASS_IN));
|
||||||
|
assertThat(record.type(), is(A));
|
||||||
|
assertThat(content.readableBytes(), is(4));
|
||||||
|
assertThat(content.readInt(), is(0x01020304));
|
||||||
|
record.release();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testResolveDecodeUnicode() {
|
public void testResolveDecodeUnicode() {
|
||||||
testResolveUnicode(true);
|
testResolveUnicode(true);
|
||||||
|
Loading…
Reference in New Issue
Block a user