blob: ef8badd32adf4c67f4c95001eed34cbbfa971f8c [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed 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 android.net.cts;
import static android.net.DnsResolver.CLASS_IN;
import static android.net.DnsResolver.FLAG_EMPTY;
import static android.net.DnsResolver.FLAG_NO_CACHE_LOOKUP;
import static android.net.DnsResolver.TYPE_A;
import static android.net.DnsResolver.TYPE_AAAA;
import static android.system.OsConstants.ETIMEDOUT;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.ContentResolver;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.DnsPacket;
import android.net.DnsResolver;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.ParseException;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Looper;
import android.platform.test.annotations.AppModeFull;
import android.provider.Settings;
import android.system.ErrnoException;
import android.test.AndroidTestCase;
import android.util.Log;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
@AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
public class DnsResolverTest extends AndroidTestCase {
private static final String TAG = "DnsResolverTest";
private static final char[] HEX_CHARS = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};
static final String TEST_DOMAIN = "www.google.com";
static final String INVALID_PRIVATE_DNS_SERVER = "invalid.google";
static final byte[] TEST_BLOB = new byte[]{
/* Header */
0x55, 0x66, /* Transaction ID */
0x01, 0x00, /* Flags */
0x00, 0x01, /* Questions */
0x00, 0x00, /* Answer RRs */
0x00, 0x00, /* Authority RRs */
0x00, 0x00, /* Additional RRs */
/* Queries */
0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
0x00, 0x01, /* Type */
0x00, 0x01 /* Class */
};
static final int TIMEOUT_MS = 12_000;
static final int CANCEL_TIMEOUT_MS = 3_000;
static final int CANCEL_RETRY_TIMES = 5;
static final int QUERY_TIMES = 10;
static final int NXDOMAIN = 3;
static final int PRIVATE_DNS_SETTING_TIMEOUT_MS = 2_000;
private ContentResolver mCR;
private ConnectivityManager mCM;
private Executor mExecutor;
private Executor mExecutorInline;
private DnsResolver mDns;
private String mOldMode;
private String mOldDnsSpecifier;
@Override
protected void setUp() throws Exception {
super.setUp();
mCM = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
mDns = DnsResolver.getInstance();
mExecutor = new Handler(Looper.getMainLooper())::post;
mExecutorInline = (Runnable r) -> r.run();
mCR = getContext().getContentResolver();
storePrivateDnsSetting();
}
@Override
protected void tearDown() throws Exception {
restorePrivateDnsSetting();
super.tearDown();
}
private void storePrivateDnsSetting() {
// Store private DNS setting
mOldMode = Settings.Global.getString(mCR, Settings.Global.PRIVATE_DNS_MODE);
mOldDnsSpecifier = Settings.Global.getString(mCR, Settings.Global.PRIVATE_DNS_SPECIFIER);
}
private void restorePrivateDnsSetting() {
// restore private DNS setting
Settings.Global.putString(mCR, Settings.Global.PRIVATE_DNS_MODE, mOldMode);
Settings.Global.putString(mCR, Settings.Global.PRIVATE_DNS_SPECIFIER, mOldDnsSpecifier);
}
private static String byteArrayToHexString(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int i = 0; i < bytes.length; ++i) {
int b = bytes[i] & 0xFF;
hexChars[i * 2] = HEX_CHARS[b >>> 4];
hexChars[i * 2 + 1] = HEX_CHARS[b & 0x0F];
}
return new String(hexChars);
}
private Network[] getTestableNetworks() {
final ArrayList<Network> testableNetworks = new ArrayList<Network>();
for (Network network : mCM.getAllNetworks()) {
final NetworkCapabilities nc = mCM.getNetworkCapabilities(network);
if (nc != null
&& nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
&& nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
testableNetworks.add(network);
}
}
assertTrue(
"This test requires that at least one network be connected. " +
"Please ensure that the device is connected to a network.",
testableNetworks.size() >= 1);
// In order to test query with null network, add null as an element.
// Test cases which query with null network will go on default network.
testableNetworks.add(null);
return testableNetworks.toArray(new Network[0]);
}
static private void assertGreaterThan(String msg, int first, int second) {
assertTrue(msg + " Excepted " + first + " to be greater than " + second, first > second);
}
private static class DnsParseException extends Exception {
public DnsParseException(String msg) {
super(msg);
}
}
private static class DnsAnswer extends DnsPacket {
DnsAnswer(@NonNull byte[] data) throws DnsParseException {
super(data);
// Check QR field.(query (0), or a response (1)).
if ((mHeader.flags & (1 << 15)) == 0) {
throw new DnsParseException("Not an answer packet");
}
}
int getRcode() {
return mHeader.rcode;
}
int getANCount() {
return mHeader.getRecordCount(ANSECTION);
}
int getQDCount() {
return mHeader.getRecordCount(QDSECTION);
}
}
/**
* A query callback that ensures that the query is cancelled and that onAnswer is never
* called. If the query succeeds before it is cancelled, needRetry will return true so the
* test can retry.
*/
class VerifyCancelCallback implements DnsResolver.Callback<byte[]> {
private final CountDownLatch mLatch = new CountDownLatch(1);
private final String mMsg;
private final CancellationSignal mCancelSignal;
private int mRcode;
private DnsAnswer mDnsAnswer;
VerifyCancelCallback(@NonNull String msg, @Nullable CancellationSignal cancel) {
mMsg = msg;
mCancelSignal = cancel;
}
VerifyCancelCallback(@NonNull String msg) {
this(msg, null);
}
public boolean waitForAnswer(int timeout) throws InterruptedException {
return mLatch.await(timeout, TimeUnit.MILLISECONDS);
}
public boolean waitForAnswer() throws InterruptedException {
return waitForAnswer(TIMEOUT_MS);
}
public boolean needRetry() throws InterruptedException {
return mLatch.await(CANCEL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
@Override
public void onAnswer(@NonNull byte[] answer, int rcode) {
if (mCancelSignal != null && mCancelSignal.isCanceled()) {
fail(mMsg + " should not have returned any answers");
}
mRcode = rcode;
try {
mDnsAnswer = new DnsAnswer(answer);
} catch (ParseException | DnsParseException e) {
fail(mMsg + e.getMessage());
}
Log.d(TAG, "Reported blob: " + byteArrayToHexString(answer));
mLatch.countDown();
}
@Override
public void onError(@NonNull DnsResolver.DnsException error) {
fail(mMsg + error.getMessage());
}
private void assertValidAnswer() {
assertNotNull(mMsg + " No valid answer", mDnsAnswer);
assertEquals(mMsg + " Unexpected error: reported rcode" + mRcode +
" blob's rcode " + mDnsAnswer.getRcode(), mRcode, mDnsAnswer.getRcode());
}
public void assertHasAnswer() {
assertValidAnswer();
// Check rcode field.(0, No error condition).
assertEquals(mMsg + " Response error, rcode: " + mRcode, mRcode, 0);
// Check answer counts.
assertGreaterThan(mMsg + " No answer found", mDnsAnswer.getANCount(), 0);
// Check question counts.
assertGreaterThan(mMsg + " No question found", mDnsAnswer.getQDCount(), 0);
}
public void assertNXDomain() {
assertValidAnswer();
// Check rcode field.(3, NXDomain).
assertEquals(mMsg + " Unexpected rcode: " + mRcode, mRcode, NXDOMAIN);
// Check answer counts. Expect 0 answer.
assertEquals(mMsg + " Not an empty answer", mDnsAnswer.getANCount(), 0);
// Check question counts.
assertGreaterThan(mMsg + " No question found", mDnsAnswer.getQDCount(), 0);
}
public void assertEmptyAnswer() {
assertValidAnswer();
// Check rcode field.(0, No error condition).
assertEquals(mMsg + " Response error, rcode: " + mRcode, mRcode, 0);
// Check answer counts. Expect 0 answer.
assertEquals(mMsg + " Not an empty answer", mDnsAnswer.getANCount(), 0);
// Check question counts.
assertGreaterThan(mMsg + " No question found", mDnsAnswer.getQDCount(), 0);
}
}
public void testRawQuery() throws Exception {
doTestRawQuery(mExecutor);
}
public void testRawQueryInline() throws Exception {
doTestRawQuery(mExecutorInline);
}
public void testRawQueryBlob() throws Exception {
doTestRawQueryBlob(mExecutor);
}
public void testRawQueryBlobInline() throws Exception {
doTestRawQueryBlob(mExecutorInline);
}
public void testRawQueryRoot() throws Exception {
doTestRawQueryRoot(mExecutor);
}
public void testRawQueryRootInline() throws Exception {
doTestRawQueryRoot(mExecutorInline);
}
public void testRawQueryNXDomain() throws Exception {
doTestRawQueryNXDomain(mExecutor);
}
public void testRawQueryNXDomainInline() throws Exception {
doTestRawQueryNXDomain(mExecutorInline);
}
public void doTestRawQuery(Executor executor) throws InterruptedException {
final String msg = "RawQuery " + TEST_DOMAIN;
for (Network network : getTestableNetworks()) {
final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
mDns.rawQuery(network, TEST_DOMAIN, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
executor, null, callback);
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
callback.waitForAnswer());
callback.assertHasAnswer();
}
}
public void doTestRawQueryBlob(Executor executor) throws InterruptedException {
final byte[] blob = new byte[]{
/* Header */
0x55, 0x66, /* Transaction ID */
0x01, 0x00, /* Flags */
0x00, 0x01, /* Questions */
0x00, 0x00, /* Answer RRs */
0x00, 0x00, /* Authority RRs */
0x00, 0x00, /* Additional RRs */
/* Queries */
0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
0x00, 0x01, /* Type */
0x00, 0x01 /* Class */
};
final String msg = "RawQuery blob " + byteArrayToHexString(blob);
for (Network network : getTestableNetworks()) {
final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
mDns.rawQuery(network, blob, FLAG_NO_CACHE_LOOKUP, executor, null, callback);
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
callback.waitForAnswer());
callback.assertHasAnswer();
}
}
public void doTestRawQueryRoot(Executor executor) throws InterruptedException {
final String dname = "";
final String msg = "RawQuery empty dname(ROOT) ";
for (Network network : getTestableNetworks()) {
final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
mDns.rawQuery(network, dname, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
executor, null, callback);
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
callback.waitForAnswer());
// Except no answer record because the root does not have AAAA records.
callback.assertEmptyAnswer();
}
}
public void doTestRawQueryNXDomain(Executor executor) throws InterruptedException {
final String dname = "test1-nx.metric.gstatic.com";
final String msg = "RawQuery " + dname;
for (Network network : getTestableNetworks()) {
final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
mDns.rawQuery(network, dname, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
executor, null, callback);
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
callback.waitForAnswer());
callback.assertNXDomain();
}
}
public void testRawQueryCancel() throws InterruptedException {
final String msg = "Test cancel RawQuery " + TEST_DOMAIN;
// Start a DNS query and the cancel it immediately. Use VerifyCancelCallback to expect
// that the query is cancelled before it succeeds. If it is not cancelled before it
// succeeds, retry the test until it is.
for (Network network : getTestableNetworks()) {
boolean retry = false;
int round = 0;
do {
if (++round > CANCEL_RETRY_TIMES) {
fail(msg + " cancel failed " + CANCEL_RETRY_TIMES + " times");
}
final CountDownLatch latch = new CountDownLatch(1);
final CancellationSignal cancelSignal = new CancellationSignal();
final VerifyCancelCallback callback = new VerifyCancelCallback(msg, cancelSignal);
mDns.rawQuery(network, TEST_DOMAIN, CLASS_IN, TYPE_AAAA, FLAG_EMPTY,
mExecutor, cancelSignal, callback);
mExecutor.execute(() -> {
cancelSignal.cancel();
latch.countDown();
});
retry = callback.needRetry();
assertTrue(msg + " query was not cancelled",
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
} while (retry);
}
}
public void testRawQueryBlobCancel() throws InterruptedException {
final String msg = "Test cancel RawQuery blob " + byteArrayToHexString(TEST_BLOB);
// Start a DNS query and the cancel it immediately. Use VerifyCancelCallback to expect
// that the query is cancelled before it succeeds. If it is not cancelled before it
// succeeds, retry the test until it is.
for (Network network : getTestableNetworks()) {
boolean retry = false;
int round = 0;
do {
if (++round > CANCEL_RETRY_TIMES) {
fail(msg + " cancel failed " + CANCEL_RETRY_TIMES + " times");
}
final CountDownLatch latch = new CountDownLatch(1);
final CancellationSignal cancelSignal = new CancellationSignal();
final VerifyCancelCallback callback = new VerifyCancelCallback(msg, cancelSignal);
mDns.rawQuery(network, TEST_BLOB, FLAG_EMPTY, mExecutor, cancelSignal, callback);
mExecutor.execute(() -> {
cancelSignal.cancel();
latch.countDown();
});
retry = callback.needRetry();
assertTrue(msg + " cancel is not done",
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
} while (retry);
}
}
public void testCancelBeforeQuery() throws InterruptedException {
final String msg = "Test cancelled RawQuery " + TEST_DOMAIN;
for (Network network : getTestableNetworks()) {
final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
final CancellationSignal cancelSignal = new CancellationSignal();
cancelSignal.cancel();
mDns.rawQuery(network, TEST_DOMAIN, CLASS_IN, TYPE_AAAA, FLAG_EMPTY,
mExecutor, cancelSignal, callback);
assertTrue(msg + " should not return any answers",
!callback.waitForAnswer(CANCEL_TIMEOUT_MS));
}
}
/**
* A query callback for InetAddress that ensures that the query is
* cancelled and that onAnswer is never called. If the query succeeds
* before it is cancelled, needRetry will return true so the
* test can retry.
*/
class VerifyCancelInetAddressCallback implements DnsResolver.Callback<List<InetAddress>> {
private final CountDownLatch mLatch = new CountDownLatch(1);
private final String mMsg;
private final List<InetAddress> mAnswers;
private final CancellationSignal mCancelSignal;
VerifyCancelInetAddressCallback(@NonNull String msg, @Nullable CancellationSignal cancel) {
this.mMsg = msg;
this.mCancelSignal = cancel;
mAnswers = new ArrayList<>();
}
public boolean waitForAnswer() throws InterruptedException {
return mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
public boolean needRetry() throws InterruptedException {
return mLatch.await(CANCEL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
public boolean isAnswerEmpty() {
return mAnswers.isEmpty();
}
public boolean hasIpv6Answer() {
for (InetAddress answer : mAnswers) {
if (answer instanceof Inet6Address) return true;
}
return false;
}
public boolean hasIpv4Answer() {
for (InetAddress answer : mAnswers) {
if (answer instanceof Inet4Address) return true;
}
return false;
}
@Override
public void onAnswer(@NonNull List<InetAddress> answerList, int rcode) {
if (mCancelSignal != null && mCancelSignal.isCanceled()) {
fail(mMsg + " should not have returned any answers");
}
for (InetAddress addr : answerList) {
Log.d(TAG, "Reported addr: " + addr.toString());
}
mAnswers.clear();
mAnswers.addAll(answerList);
mLatch.countDown();
}
@Override
public void onError(@NonNull DnsResolver.DnsException error) {
fail(mMsg + error.getMessage());
}
}
public void testQueryForInetAddress() throws Exception {
doTestQueryForInetAddress(mExecutor);
}
public void testQueryForInetAddressInline() throws Exception {
doTestQueryForInetAddress(mExecutorInline);
}
public void testQueryForInetAddressIpv4() throws Exception {
doTestQueryForInetAddressIpv4(mExecutor);
}
public void testQueryForInetAddressIpv4Inline() throws Exception {
doTestQueryForInetAddressIpv4(mExecutorInline);
}
public void testQueryForInetAddressIpv6() throws Exception {
doTestQueryForInetAddressIpv6(mExecutor);
}
public void testQueryForInetAddressIpv6Inline() throws Exception {
doTestQueryForInetAddressIpv6(mExecutorInline);
}
public void testContinuousQueries() throws Exception {
doTestContinuousQueries(mExecutor);
}
public void testContinuousQueriesInline() throws Exception {
doTestContinuousQueries(mExecutorInline);
}
public void doTestQueryForInetAddress(Executor executor) throws InterruptedException {
final String msg = "Test query for InetAddress " + TEST_DOMAIN;
for (Network network : getTestableNetworks()) {
final VerifyCancelInetAddressCallback callback =
new VerifyCancelInetAddressCallback(msg, null);
mDns.query(network, TEST_DOMAIN, FLAG_NO_CACHE_LOOKUP, executor, null, callback);
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
callback.waitForAnswer());
assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
}
}
public void testQueryCancelForInetAddress() throws InterruptedException {
final String msg = "Test cancel query for InetAddress " + TEST_DOMAIN;
// Start a DNS query and the cancel it immediately. Use VerifyCancelInetAddressCallback to
// expect that the query is cancelled before it succeeds. If it is not cancelled before it
// succeeds, retry the test until it is.
for (Network network : getTestableNetworks()) {
boolean retry = false;
int round = 0;
do {
if (++round > CANCEL_RETRY_TIMES) {
fail(msg + " cancel failed " + CANCEL_RETRY_TIMES + " times");
}
final CountDownLatch latch = new CountDownLatch(1);
final CancellationSignal cancelSignal = new CancellationSignal();
final VerifyCancelInetAddressCallback callback =
new VerifyCancelInetAddressCallback(msg, cancelSignal);
mDns.query(network, TEST_DOMAIN, FLAG_EMPTY, mExecutor, cancelSignal, callback);
mExecutor.execute(() -> {
cancelSignal.cancel();
latch.countDown();
});
retry = callback.needRetry();
assertTrue(msg + " query was not cancelled",
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
} while (retry);
}
}
public void doTestQueryForInetAddressIpv4(Executor executor) throws InterruptedException {
final String msg = "Test query for IPv4 InetAddress " + TEST_DOMAIN;
for (Network network : getTestableNetworks()) {
final VerifyCancelInetAddressCallback callback =
new VerifyCancelInetAddressCallback(msg, null);
mDns.query(network, TEST_DOMAIN, TYPE_A, FLAG_NO_CACHE_LOOKUP,
executor, null, callback);
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
callback.waitForAnswer());
assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
assertTrue(msg + " returned Ipv6 results", !callback.hasIpv6Answer());
}
}
public void doTestQueryForInetAddressIpv6(Executor executor) throws InterruptedException {
final String msg = "Test query for IPv6 InetAddress " + TEST_DOMAIN;
for (Network network : getTestableNetworks()) {
final VerifyCancelInetAddressCallback callback =
new VerifyCancelInetAddressCallback(msg, null);
mDns.query(network, TEST_DOMAIN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
executor, null, callback);
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
callback.waitForAnswer());
assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
assertTrue(msg + " returned Ipv4 results", !callback.hasIpv4Answer());
}
}
private void awaitPrivateDnsSetting(@NonNull String msg,
@NonNull Network network, @NonNull String server) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
NetworkCallback callback = new NetworkCallback() {
@Override
public void onLinkPropertiesChanged(Network n, LinkProperties lp) {
if (network.equals(n) && server.equals(lp.getPrivateDnsServerName())) {
latch.countDown();
}
}
};
mCM.registerNetworkCallback(request, callback);
assertTrue(msg, latch.await(PRIVATE_DNS_SETTING_TIMEOUT_MS, TimeUnit.MILLISECONDS));
mCM.unregisterNetworkCallback(callback);
}
public void testPrivateDnsBypass() throws InterruptedException {
final Network[] testNetworks = getTestableNetworks();
// Set an invalid private DNS server
Settings.Global.putString(mCR, Settings.Global.PRIVATE_DNS_MODE, "hostname");
Settings.Global.putString(mCR,
Settings.Global.PRIVATE_DNS_SPECIFIER, INVALID_PRIVATE_DNS_SERVER);
final String msg = "Test PrivateDnsBypass " + TEST_DOMAIN;
for (Network network : testNetworks) {
// This test cannot be ran with null network because we need to explicitly pass a
// private DNS bypassable network or bind one.
if (network == null) continue;
// wait for private DNS setting propagating
awaitPrivateDnsSetting(msg + " wait private DNS setting timeout",
network, INVALID_PRIVATE_DNS_SERVER);
final CountDownLatch latch = new CountDownLatch(1);
final DnsResolver.Callback<List<InetAddress>> errorCallback =
new DnsResolver.Callback<List<InetAddress>>() {
@Override
public void onAnswer(@NonNull List<InetAddress> answerList, int rcode) {
fail(msg + " should not get valid answer");
}
@Override
public void onError(@NonNull DnsResolver.DnsException error) {
assertEquals(DnsResolver.ERROR_SYSTEM, error.code);
assertEquals(ETIMEDOUT, ((ErrnoException) error.getCause()).errno);
latch.countDown();
}
};
// Private DNS strict mode with invalid DNS server is set
// Expect no valid answer returned but ErrnoException with ETIMEDOUT
mDns.query(network, TEST_DOMAIN, FLAG_NO_CACHE_LOOKUP, mExecutor, null, errorCallback);
assertTrue(msg + " invalid server round. No response after " + TIMEOUT_MS + "ms.",
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
final VerifyCancelInetAddressCallback callback =
new VerifyCancelInetAddressCallback(msg, null);
// Bypass privateDns, expect query works fine
mDns.query(network.getPrivateDnsBypassingCopy(),
TEST_DOMAIN, FLAG_NO_CACHE_LOOKUP, mExecutor, null, callback);
assertTrue(msg + " bypass private DNS round. No answer after " + TIMEOUT_MS + "ms.",
callback.waitForAnswer());
assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
// To ensure private DNS bypass still work even if passing null network.
// Bind process network with a private DNS bypassable network.
mCM.bindProcessToNetwork(network.getPrivateDnsBypassingCopy());
final VerifyCancelInetAddressCallback callbackWithNullNetwork =
new VerifyCancelInetAddressCallback(msg + " with null network ", null);
mDns.query(null,
TEST_DOMAIN, FLAG_NO_CACHE_LOOKUP, mExecutor, null, callbackWithNullNetwork);
assertTrue(msg + " with null network bypass private DNS round. No answer after " +
TIMEOUT_MS + "ms.", callbackWithNullNetwork.waitForAnswer());
assertTrue(msg + " with null network returned 0 results",
!callbackWithNullNetwork.isAnswerEmpty());
// Reset process network to default.
mCM.bindProcessToNetwork(null);
}
}
public void doTestContinuousQueries(Executor executor) throws InterruptedException {
final String msg = "Test continuous " + QUERY_TIMES + " queries " + TEST_DOMAIN;
for (Network network : getTestableNetworks()) {
for (int i = 0; i < QUERY_TIMES ; ++i) {
final VerifyCancelInetAddressCallback callback =
new VerifyCancelInetAddressCallback(msg, null);
// query v6/v4 in turn
boolean queryV6 = (i % 2 == 0);
mDns.query(network, TEST_DOMAIN, queryV6 ? TYPE_AAAA : TYPE_A,
FLAG_NO_CACHE_LOOKUP, executor, null, callback);
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
callback.waitForAnswer());
assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
assertTrue(msg + " returned " + (queryV6 ? "Ipv4" : "Ipv6") + " results",
queryV6 ? !callback.hasIpv4Answer() : !callback.hasIpv6Answer());
}
}
}
}