• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.net.cts;
18 
19 import static android.net.DnsResolver.CLASS_IN;
20 import static android.net.DnsResolver.FLAG_EMPTY;
21 import static android.net.DnsResolver.FLAG_NO_CACHE_LOOKUP;
22 import static android.net.DnsResolver.TYPE_A;
23 import static android.net.DnsResolver.TYPE_AAAA;
24 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
25 import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
26 import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
27 import static android.system.OsConstants.ETIMEDOUT;
28 
29 import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
30 
31 import static org.junit.Assert.assertEquals;
32 import static org.junit.Assert.assertNotNull;
33 import static org.junit.Assert.assertNull;
34 import static org.junit.Assert.assertTrue;
35 import static org.junit.Assert.fail;
36 
37 import android.annotation.NonNull;
38 import android.annotation.Nullable;
39 import android.content.ContentResolver;
40 import android.content.Context;
41 import android.content.pm.PackageManager;
42 import android.net.ConnectivityManager;
43 import android.net.DnsResolver;
44 import android.net.Network;
45 import android.net.NetworkCapabilities;
46 import android.net.NetworkRequest;
47 import android.net.ParseException;
48 import android.net.cts.util.CtsNetUtils;
49 import android.os.CancellationSignal;
50 import android.os.Handler;
51 import android.os.Looper;
52 import android.platform.test.annotations.AppModeFull;
53 import android.provider.Settings;
54 import android.system.ErrnoException;
55 import android.util.Log;
56 
57 import androidx.test.InstrumentationRegistry;
58 import androidx.test.runner.AndroidJUnit4;
59 
60 import com.android.net.module.util.DnsPacket;
61 import com.android.testutils.ConnectivityDiagnosticsCollector;
62 import com.android.testutils.DevSdkIgnoreRule;
63 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
64 import com.android.testutils.DeviceConfigRule;
65 import com.android.testutils.DnsResolverModuleTest;
66 import com.android.testutils.SkipPresubmit;
67 
68 import org.junit.After;
69 import org.junit.Before;
70 import org.junit.BeforeClass;
71 import org.junit.ClassRule;
72 import org.junit.Rule;
73 import org.junit.Test;
74 import org.junit.runner.RunWith;
75 
76 import java.net.Inet4Address;
77 import java.net.Inet6Address;
78 import java.net.InetAddress;
79 import java.util.ArrayList;
80 import java.util.List;
81 import java.util.concurrent.CountDownLatch;
82 import java.util.concurrent.Executor;
83 import java.util.concurrent.TimeUnit;
84 
85 @AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
86 @RunWith(AndroidJUnit4.class)
87 public class DnsResolverTest {
88     @ClassRule
89     public static final DeviceConfigRule DEVICE_CONFIG_CLASS_RULE = new DeviceConfigRule();
90     @Rule
91     public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
92 
93     private static final String TAG = "DnsResolverTest";
94     private static final char[] HEX_CHARS = {
95             '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
96     };
97 
98     static final String TEST_DOMAIN = "www.google.com";
99     static final String TEST_NX_DOMAIN = "test1-nx.metric.gstatic.com";
100     static final String INVALID_PRIVATE_DNS_SERVER = "invalid.google";
101     static final String GOOGLE_PRIVATE_DNS_SERVER = "dns.google";
102     static final byte[] TEST_BLOB = new byte[]{
103             /* Header */
104             0x55, 0x66, /* Transaction ID */
105             0x01, 0x00, /* Flags */
106             0x00, 0x01, /* Questions */
107             0x00, 0x00, /* Answer RRs */
108             0x00, 0x00, /* Authority RRs */
109             0x00, 0x00, /* Additional RRs */
110             /* Queries */
111             0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
112             0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
113             0x00, 0x01, /* Type */
114             0x00, 0x01  /* Class */
115     };
116     static final int TIMEOUT_MS = 12_000;
117     static final int CANCEL_TIMEOUT_MS = 3_000;
118     static final int CANCEL_RETRY_TIMES = 5;
119     static final int QUERY_TIMES = 10;
120     static final int NXDOMAIN = 3;
121 
122     private Context mContext;
123     private ContentResolver mCR;
124     private ConnectivityManager mCM;
125     private PackageManager mPackageManager;
126     private CtsNetUtils mCtsNetUtils;
127     private Executor mExecutor;
128     private Executor mExecutorInline;
129     private DnsResolver mDns;
130 
131     private TestNetworkCallback mWifiRequestCallback = null;
132 
133     /**
134      * @see BeforeClass
135      */
136     @BeforeClass
beforeClass()137     public static void beforeClass() throws Exception {
138         // Use async private DNS resolution to avoid flakes due to races applying the setting
139         DEVICE_CONFIG_CLASS_RULE.setConfig(NAMESPACE_CONNECTIVITY,
140                 "networkmonitor_async_privdns_resolution", "1");
141         // Make sure NetworkMonitor is restarted before and after the test so the flag is applied
142         // and cleaned up.
143         maybeToggleWifiAndCell();
144         DEVICE_CONFIG_CLASS_RULE.runAfterNextCleanup(DnsResolverTest::maybeToggleWifiAndCell);
145     }
146 
147     @Before
setUp()148     public void setUp() throws Exception {
149         mContext = InstrumentationRegistry.getContext();
150         mCM = mContext.getSystemService(ConnectivityManager.class);
151         mDns = DnsResolver.getInstance();
152         mExecutor = new Handler(Looper.getMainLooper())::post;
153         mExecutorInline = (Runnable r) -> r.run();
154         mCR = mContext.getContentResolver();
155         mCtsNetUtils = new CtsNetUtils(mContext);
156         mCtsNetUtils.storePrivateDnsSetting();
157         mPackageManager = mContext.getPackageManager();
158     }
159 
160     @After
tearDown()161     public void tearDown() throws Exception {
162         mCtsNetUtils.restorePrivateDnsSetting();
163         if (mWifiRequestCallback != null) {
164             mCM.unregisterNetworkCallback(mWifiRequestCallback);
165         }
166     }
167 
maybeToggleWifiAndCell()168     private static void maybeToggleWifiAndCell() throws Exception {
169         final CtsNetUtils utils = new CtsNetUtils(InstrumentationRegistry.getContext());
170         utils.reconnectWifiIfSupported();
171         utils.reconnectCellIfSupported();
172     }
173 
byteArrayToHexString(byte[] bytes)174     private static String byteArrayToHexString(byte[] bytes) {
175         char[] hexChars = new char[bytes.length * 2];
176         for (int i = 0; i < bytes.length; ++i) {
177             int b = bytes[i] & 0xFF;
178             hexChars[i * 2] = HEX_CHARS[b >>> 4];
179             hexChars[i * 2 + 1] = HEX_CHARS[b & 0x0F];
180         }
181         return new String(hexChars);
182     }
183 
getTestableNetworks()184     private Network[] getTestableNetworks() {
185         if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
186             // File a NetworkRequest for Wi-Fi, so it connects even if a higher-scoring
187             // network, such as Ethernet, is already connected.
188             final NetworkRequest request = new NetworkRequest.Builder()
189                     .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
190                     .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
191                     .build();
192             mWifiRequestCallback = new TestNetworkCallback();
193             mCM.requestNetwork(request, mWifiRequestCallback);
194             mCtsNetUtils.ensureWifiConnected();
195         }
196         final ArrayList<Network> testableNetworks = new ArrayList<Network>();
197         for (Network network : mCM.getAllNetworks()) {
198             final NetworkCapabilities nc = mCM.getNetworkCapabilities(network);
199             if (nc != null
200                     && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
201                     && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
202                 testableNetworks.add(network);
203             }
204         }
205 
206         assertTrue(
207                 "This test requires that at least one network be connected. " +
208                         "Please ensure that the device is connected to a network.",
209                 testableNetworks.size() >= 1);
210         // In order to test query with null network, add null as an element.
211         // Test cases which query with null network will go on default network.
212         testableNetworks.add(null);
213         return testableNetworks.toArray(new Network[0]);
214     }
215 
assertGreaterThan(String msg, int first, int second)216     static private void assertGreaterThan(String msg, int first, int second) {
217         assertTrue(msg + " Excepted " + first + " to be greater than " + second, first > second);
218     }
219 
220     private static class DnsParseException extends Exception {
DnsParseException(String msg)221         public DnsParseException(String msg) {
222             super(msg);
223         }
224     }
225 
226     private static class DnsAnswer extends DnsPacket {
DnsAnswer(@onNull byte[] data)227         DnsAnswer(@NonNull byte[] data) throws DnsParseException {
228             super(data);
229 
230             // Check QR field.(query (0), or a response (1)).
231             if ((mHeader.getFlags() & (1 << 15)) == 0) {
232                 throw new DnsParseException("Not an answer packet");
233             }
234         }
235 
getRcode()236         int getRcode() {
237             return mHeader.getFlags() & 0x0F;
238         }
239 
getANCount()240         int getANCount() {
241             return mHeader.getRecordCount(ANSECTION);
242         }
243 
getQDCount()244         int getQDCount() {
245             return mHeader.getRecordCount(QDSECTION);
246         }
247     }
248 
249     /**
250      * A query callback that ensures that the query is cancelled and that onAnswer is never
251      * called. If the query succeeds before it is cancelled, needRetry will return true so the
252      * test can retry.
253      */
254     class VerifyCancelCallback implements DnsResolver.Callback<byte[]> {
255         private final CountDownLatch mLatch = new CountDownLatch(1);
256         private final String mMsg;
257         private final CancellationSignal mCancelSignal;
258         private int mRcode;
259         private DnsAnswer mDnsAnswer;
260         private String mErrorMsg = null;
261 
VerifyCancelCallback(@onNull String msg, @Nullable CancellationSignal cancel)262         VerifyCancelCallback(@NonNull String msg, @Nullable CancellationSignal cancel) {
263             mMsg = msg;
264             mCancelSignal = cancel;
265         }
266 
VerifyCancelCallback(@onNull String msg)267         VerifyCancelCallback(@NonNull String msg) {
268             this(msg, null);
269         }
270 
waitForAnswer(int timeout)271         public boolean waitForAnswer(int timeout) throws InterruptedException {
272             return mLatch.await(timeout, TimeUnit.MILLISECONDS);
273         }
274 
waitForAnswer()275         public boolean waitForAnswer() throws InterruptedException {
276             return waitForAnswer(TIMEOUT_MS);
277         }
278 
needRetry()279         public boolean needRetry() throws InterruptedException {
280             return mLatch.await(CANCEL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
281         }
282 
283         @Override
onAnswer(@onNull byte[] answer, int rcode)284         public void onAnswer(@NonNull byte[] answer, int rcode) {
285             if (mCancelSignal != null && mCancelSignal.isCanceled()) {
286                 mErrorMsg = mMsg + " should not have returned any answers";
287                 mLatch.countDown();
288                 return;
289             }
290 
291             mRcode = rcode;
292             try {
293                 mDnsAnswer = new DnsAnswer(answer);
294             } catch (ParseException | DnsParseException e) {
295                 mErrorMsg = mMsg + e.getMessage();
296                 mLatch.countDown();
297                 return;
298             }
299             Log.d(TAG, "Reported blob: " + byteArrayToHexString(answer));
300             mLatch.countDown();
301         }
302 
303         @Override
onError(@onNull DnsResolver.DnsException error)304         public void onError(@NonNull DnsResolver.DnsException error) {
305             mErrorMsg = mMsg + error.getMessage();
306             mLatch.countDown();
307         }
308 
assertValidAnswer()309         private void assertValidAnswer() {
310             assertNull(mErrorMsg);
311             assertNotNull(mMsg + " No valid answer", mDnsAnswer);
312             assertEquals(mMsg + " Unexpected error: reported rcode" + mRcode +
313                     " blob's rcode " + mDnsAnswer.getRcode(), mRcode, mDnsAnswer.getRcode());
314         }
315 
assertHasAnswer()316         public void assertHasAnswer() {
317             assertValidAnswer();
318             // Check rcode field.(0, No error condition).
319             assertEquals(mMsg + " Response error, rcode: " + mRcode, mRcode, 0);
320             // Check answer counts.
321             assertGreaterThan(mMsg + " No answer found", mDnsAnswer.getANCount(), 0);
322             // Check question counts.
323             assertGreaterThan(mMsg + " No question found", mDnsAnswer.getQDCount(), 0);
324         }
325 
assertNXDomain()326         public void assertNXDomain() {
327             assertValidAnswer();
328             // Check rcode field.(3, NXDomain).
329             assertEquals(mMsg + " Unexpected rcode: " + mRcode, mRcode, NXDOMAIN);
330             // Check answer counts. Expect 0 answer.
331             assertEquals(mMsg + " Not an empty answer", mDnsAnswer.getANCount(), 0);
332             // Check question counts.
333             assertGreaterThan(mMsg + " No question found", mDnsAnswer.getQDCount(), 0);
334         }
335 
assertEmptyAnswer()336         public void assertEmptyAnswer() {
337             assertValidAnswer();
338             // Check rcode field.(0, No error condition).
339             assertEquals(mMsg + " Response error, rcode: " + mRcode, mRcode, 0);
340             // Check answer counts. Expect 0 answer.
341             assertEquals(mMsg + " Not an empty answer", mDnsAnswer.getANCount(), 0);
342             // Check question counts.
343             assertGreaterThan(mMsg + " No question found", mDnsAnswer.getQDCount(), 0);
344         }
345     }
346 
347     @Test
348     @DnsResolverModuleTest
testRawQuery()349     public void testRawQuery() throws Exception {
350         doTestRawQuery(mExecutor);
351     }
352 
353     @Test
354     @DnsResolverModuleTest
testRawQueryInline()355     public void testRawQueryInline() throws Exception {
356         doTestRawQuery(mExecutorInline);
357     }
358 
359     @Test
360     @DnsResolverModuleTest
testRawQueryBlob()361     public void testRawQueryBlob() throws Exception {
362         doTestRawQueryBlob(mExecutor);
363     }
364 
365     @Test
366     @DnsResolverModuleTest
testRawQueryBlobInline()367     public void testRawQueryBlobInline() throws Exception {
368         doTestRawQueryBlob(mExecutorInline);
369     }
370 
371     @Test
372     @DnsResolverModuleTest
testRawQueryRoot()373     public void testRawQueryRoot() throws Exception {
374         doTestRawQueryRoot(mExecutor);
375     }
376 
377     @Test
378     @DnsResolverModuleTest
testRawQueryRootInline()379     public void testRawQueryRootInline() throws Exception {
380         doTestRawQueryRoot(mExecutorInline);
381     }
382 
383     @Test
384     @DnsResolverModuleTest
testRawQueryNXDomain()385     public void testRawQueryNXDomain() throws Exception {
386         doTestRawQueryNXDomain(mExecutor);
387     }
388 
389     @Test
390     @DnsResolverModuleTest
testRawQueryNXDomainInline()391     public void testRawQueryNXDomainInline() throws Exception {
392         doTestRawQueryNXDomain(mExecutorInline);
393     }
394 
395     @Test
396     @DnsResolverModuleTest
testRawQueryNXDomainWithPrivateDns()397     public void testRawQueryNXDomainWithPrivateDns() throws Exception {
398         try {
399             doTestRawQueryNXDomainWithPrivateDns(mExecutor);
400         } catch (Throwable e) {
401             final ConnectivityDiagnosticsCollector collector =
402                     ConnectivityDiagnosticsCollector.getInstance();
403             if (collector != null) {
404                 // IWLAN on U QPR3 release may cause failures in this test, see
405                 // CarrierConfigSetupTest which is supposed to avoid the issue. Collect IWLAN
406                 // related dumpsys if the test still fails.
407                 collector.collectDumpsys("carrier_config", e);
408                 collector.collectDumpsys("telecom", e);
409                 collector.collectDumpsys("telephony_ims", e);
410                 collector.collectDumpsys("telephony.registry", e);
411             }
412             throw e;
413         }
414     }
415 
416     @Test
417     @DnsResolverModuleTest
testRawQueryNXDomainInlineWithPrivateDns()418     public void testRawQueryNXDomainInlineWithPrivateDns() throws Exception {
419         doTestRawQueryNXDomainWithPrivateDns(mExecutorInline);
420     }
421 
doTestRawQuery(Executor executor)422     public void doTestRawQuery(Executor executor) throws InterruptedException {
423         final String msg = "RawQuery " + TEST_DOMAIN;
424         for (Network network : getTestableNetworks()) {
425             final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
426             mDns.rawQuery(network, TEST_DOMAIN, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
427                     executor, null, callback);
428 
429             assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
430                     callback.waitForAnswer());
431             callback.assertHasAnswer();
432         }
433     }
434 
doTestRawQueryBlob(Executor executor)435     public void doTestRawQueryBlob(Executor executor) throws InterruptedException {
436         final byte[] blob = new byte[]{
437                 /* Header */
438                 0x55, 0x66, /* Transaction ID */
439                 0x01, 0x00, /* Flags */
440                 0x00, 0x01, /* Questions */
441                 0x00, 0x00, /* Answer RRs */
442                 0x00, 0x00, /* Authority RRs */
443                 0x00, 0x00, /* Additional RRs */
444                 /* Queries */
445                 0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
446                 0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
447                 0x00, 0x01, /* Type */
448                 0x00, 0x01  /* Class */
449         };
450         final String msg = "RawQuery blob " + byteArrayToHexString(blob);
451         for (Network network : getTestableNetworks()) {
452             final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
453             mDns.rawQuery(network, blob, FLAG_NO_CACHE_LOOKUP, executor, null, callback);
454 
455             assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
456                     callback.waitForAnswer());
457             callback.assertHasAnswer();
458         }
459     }
460 
doTestRawQueryRoot(Executor executor)461     public void doTestRawQueryRoot(Executor executor) throws InterruptedException {
462         final String dname = "";
463         final String msg = "RawQuery empty dname(ROOT) ";
464         for (Network network : getTestableNetworks()) {
465             final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
466             mDns.rawQuery(network, dname, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
467                     executor, null, callback);
468 
469             assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
470                     callback.waitForAnswer());
471             // Except no answer record because the root does not have AAAA records.
472             callback.assertEmptyAnswer();
473         }
474     }
475 
doTestRawQueryNXDomain(Executor executor)476     public void doTestRawQueryNXDomain(Executor executor) throws InterruptedException {
477         final String msg = "RawQuery " + TEST_NX_DOMAIN;
478 
479         for (Network network : getTestableNetworks()) {
480             final NetworkCapabilities nc = (network != null)
481                     ? mCM.getNetworkCapabilities(network)
482                     : mCM.getNetworkCapabilities(mCM.getActiveNetwork());
483             assertNotNull("Couldn't determine NetworkCapabilities for " + network, nc);
484             // Some cellular networks configure their DNS servers never to return NXDOMAIN, so don't
485             // test NXDOMAIN on these DNS servers.
486             // b/144521720
487             if (nc.hasTransport(TRANSPORT_CELLULAR)) continue;
488             final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
489             mDns.rawQuery(network, TEST_NX_DOMAIN, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
490                     executor, null, callback);
491 
492             assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
493                     callback.waitForAnswer());
494             callback.assertNXDomain();
495         }
496     }
497 
doTestRawQueryNXDomainWithPrivateDns(Executor executor)498     public void doTestRawQueryNXDomainWithPrivateDns(Executor executor)
499             throws InterruptedException {
500         final String msg = "RawQuery " + TEST_NX_DOMAIN + " with private DNS";
501         // Enable private DNS strict mode and set server to dns.google before doing NxDomain test.
502         // b/144521720
503         mCtsNetUtils.setPrivateDnsStrictMode(GOOGLE_PRIVATE_DNS_SERVER);
504         for (Network network :  getTestableNetworks()) {
505             final Network networkForPrivateDns =
506                     (network != null) ? network : mCM.getActiveNetwork();
507             assertNotNull("Can't find network to await private DNS on", networkForPrivateDns);
508             mCtsNetUtils.awaitPrivateDnsSetting(msg + " wait private DNS setting timeout",
509                     networkForPrivateDns, GOOGLE_PRIVATE_DNS_SERVER, true);
510             final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
511             mDns.rawQuery(network, TEST_NX_DOMAIN, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
512                     executor, null, callback);
513 
514             assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
515                     callback.waitForAnswer());
516             callback.assertNXDomain();
517         }
518     }
519 
520     @Test
testRawQueryCancel()521     public void testRawQueryCancel() throws InterruptedException {
522         final String msg = "Test cancel RawQuery " + TEST_DOMAIN;
523         // Start a DNS query and the cancel it immediately. Use VerifyCancelCallback to expect
524         // that the query is cancelled before it succeeds. If it is not cancelled before it
525         // succeeds, retry the test until it is.
526         for (Network network : getTestableNetworks()) {
527             boolean retry = false;
528             int round = 0;
529             do {
530                 if (++round > CANCEL_RETRY_TIMES) {
531                     fail(msg + " cancel failed " + CANCEL_RETRY_TIMES + " times");
532                 }
533                 final CountDownLatch latch = new CountDownLatch(1);
534                 final CancellationSignal cancelSignal = new CancellationSignal();
535                 final VerifyCancelCallback callback = new VerifyCancelCallback(msg, cancelSignal);
536                 mDns.rawQuery(network, TEST_DOMAIN, CLASS_IN, TYPE_AAAA, FLAG_EMPTY,
537                         mExecutor, cancelSignal, callback);
538                 mExecutor.execute(() -> {
539                     cancelSignal.cancel();
540                     latch.countDown();
541                 });
542 
543                 retry = callback.needRetry();
544                 assertTrue(msg + " query was not cancelled",
545                         latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
546             } while (retry);
547         }
548     }
549 
550     @Test
testRawQueryBlobCancel()551     public void testRawQueryBlobCancel() throws InterruptedException {
552         final String msg = "Test cancel RawQuery blob " + byteArrayToHexString(TEST_BLOB);
553         // Start a DNS query and the cancel it immediately. Use VerifyCancelCallback to expect
554         // that the query is cancelled before it succeeds. If it is not cancelled before it
555         // succeeds, retry the test until it is.
556         for (Network network : getTestableNetworks()) {
557             boolean retry = false;
558             int round = 0;
559             do {
560                 if (++round > CANCEL_RETRY_TIMES) {
561                     fail(msg + " cancel failed " + CANCEL_RETRY_TIMES + " times");
562                 }
563                 final CountDownLatch latch = new CountDownLatch(1);
564                 final CancellationSignal cancelSignal = new CancellationSignal();
565                 final VerifyCancelCallback callback = new VerifyCancelCallback(msg, cancelSignal);
566                 mDns.rawQuery(network, TEST_BLOB, FLAG_EMPTY, mExecutor, cancelSignal, callback);
567                 mExecutor.execute(() -> {
568                     cancelSignal.cancel();
569                     latch.countDown();
570                 });
571 
572                 retry = callback.needRetry();
573                 assertTrue(msg + " cancel is not done",
574                         latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
575             } while (retry);
576         }
577     }
578 
579     @Test
testCancelBeforeQuery()580     public void testCancelBeforeQuery() throws InterruptedException {
581         final String msg = "Test cancelled RawQuery " + TEST_DOMAIN;
582         for (Network network : getTestableNetworks()) {
583             final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
584             final CancellationSignal cancelSignal = new CancellationSignal();
585             cancelSignal.cancel();
586             mDns.rawQuery(network, TEST_DOMAIN, CLASS_IN, TYPE_AAAA, FLAG_EMPTY,
587                     mExecutor, cancelSignal, callback);
588 
589             assertTrue(msg + " should not return any answers",
590                     !callback.waitForAnswer(CANCEL_TIMEOUT_MS));
591         }
592     }
593 
594     /**
595      * A query callback for InetAddress that ensures that the query is
596      * cancelled and that onAnswer is never called. If the query succeeds
597      * before it is cancelled, needRetry will return true so the
598      * test can retry.
599      */
600     class VerifyCancelInetAddressCallback implements DnsResolver.Callback<List<InetAddress>> {
601         private final CountDownLatch mLatch = new CountDownLatch(1);
602         private final String mMsg;
603         private final List<InetAddress> mAnswers;
604         private final CancellationSignal mCancelSignal;
605         private String mErrorMsg = null;
606 
VerifyCancelInetAddressCallback(@onNull String msg, @Nullable CancellationSignal cancel)607         VerifyCancelInetAddressCallback(@NonNull String msg, @Nullable CancellationSignal cancel) {
608             this.mMsg = msg;
609             this.mCancelSignal = cancel;
610             mAnswers = new ArrayList<>();
611         }
612 
waitForAnswer()613         public boolean waitForAnswer() throws InterruptedException {
614             return mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
615         }
616 
needRetry()617         public boolean needRetry() throws InterruptedException {
618             return mLatch.await(CANCEL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
619         }
620 
isAnswerEmpty()621         public boolean isAnswerEmpty() {
622             return mAnswers.isEmpty();
623         }
624 
hasIpv6Answer()625         public boolean hasIpv6Answer() {
626             for (InetAddress answer : mAnswers) {
627                 if (answer instanceof Inet6Address) return true;
628             }
629             return false;
630         }
631 
hasIpv4Answer()632         public boolean hasIpv4Answer() {
633             for (InetAddress answer : mAnswers) {
634                 if (answer instanceof Inet4Address) return true;
635             }
636             return false;
637         }
638 
assertNoError()639         public void assertNoError() {
640             assertNull(mErrorMsg);
641         }
642 
643         @Override
onAnswer(@onNull List<InetAddress> answerList, int rcode)644         public void onAnswer(@NonNull List<InetAddress> answerList, int rcode) {
645             if (mCancelSignal != null && mCancelSignal.isCanceled()) {
646                 mErrorMsg = mMsg + " should not have returned any answers";
647                 mLatch.countDown();
648                 return;
649             }
650             for (InetAddress addr : answerList) {
651                 Log.d(TAG, "Reported addr: " + addr.toString());
652             }
653             mAnswers.clear();
654             mAnswers.addAll(answerList);
655             mLatch.countDown();
656         }
657 
658         @Override
onError(@onNull DnsResolver.DnsException error)659         public void onError(@NonNull DnsResolver.DnsException error) {
660             mErrorMsg = mMsg + error.getMessage();
661             mLatch.countDown();
662         }
663     }
664 
665     @Test
666     @DnsResolverModuleTest
testQueryForInetAddress()667     public void testQueryForInetAddress() throws Exception {
668         doTestQueryForInetAddress(mExecutor);
669     }
670 
671     @Test
672     @DnsResolverModuleTest
testQueryForInetAddressInline()673     public void testQueryForInetAddressInline() throws Exception {
674         doTestQueryForInetAddress(mExecutorInline);
675     }
676 
677     @Test
678     @DnsResolverModuleTest
testQueryForInetAddressIpv4()679     public void testQueryForInetAddressIpv4() throws Exception {
680         doTestQueryForInetAddressIpv4(mExecutor);
681     }
682 
683     @Test
684     @DnsResolverModuleTest
testQueryForInetAddressIpv4Inline()685     public void testQueryForInetAddressIpv4Inline() throws Exception {
686         doTestQueryForInetAddressIpv4(mExecutorInline);
687     }
688 
689     @Test
690     @DnsResolverModuleTest
testQueryForInetAddressIpv6()691     public void testQueryForInetAddressIpv6() throws Exception {
692         doTestQueryForInetAddressIpv6(mExecutor);
693     }
694 
695     @Test
696     @DnsResolverModuleTest
testQueryForInetAddressIpv6Inline()697     public void testQueryForInetAddressIpv6Inline() throws Exception {
698         doTestQueryForInetAddressIpv6(mExecutorInline);
699     }
700 
701     @Test
702     @DnsResolverModuleTest
testContinuousQueries()703     public void testContinuousQueries() throws Exception {
704         doTestContinuousQueries(mExecutor);
705     }
706 
707     @Test
708     @DnsResolverModuleTest
709     @SkipPresubmit(reason = "Flaky: b/159762682; add to presubmit after fixing")
testContinuousQueriesInline()710     public void testContinuousQueriesInline() throws Exception {
711         doTestContinuousQueries(mExecutorInline);
712     }
713 
doTestQueryForInetAddress(Executor executor)714     public void doTestQueryForInetAddress(Executor executor) throws InterruptedException {
715         final String msg = "Test query for InetAddress " + TEST_DOMAIN;
716         for (Network network : getTestableNetworks()) {
717             final VerifyCancelInetAddressCallback callback =
718                     new VerifyCancelInetAddressCallback(msg, null);
719             mDns.query(network, TEST_DOMAIN, FLAG_NO_CACHE_LOOKUP, executor, null, callback);
720 
721             assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
722                     callback.waitForAnswer());
723             callback.assertNoError();
724             assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
725         }
726     }
727 
728     @Test
testQueryCancelForInetAddress()729     public void testQueryCancelForInetAddress() throws InterruptedException {
730         final String msg = "Test cancel query for InetAddress " + TEST_DOMAIN;
731         // Start a DNS query and the cancel it immediately. Use VerifyCancelInetAddressCallback to
732         // expect that the query is cancelled before it succeeds. If it is not cancelled before it
733         // succeeds, retry the test until it is.
734         for (Network network : getTestableNetworks()) {
735             boolean retry = false;
736             int round = 0;
737             do {
738                 if (++round > CANCEL_RETRY_TIMES) {
739                     fail(msg + " cancel failed " + CANCEL_RETRY_TIMES + " times");
740                 }
741                 final CountDownLatch latch = new CountDownLatch(1);
742                 final CancellationSignal cancelSignal = new CancellationSignal();
743                 final VerifyCancelInetAddressCallback callback =
744                         new VerifyCancelInetAddressCallback(msg, cancelSignal);
745                 mDns.query(network, TEST_DOMAIN, FLAG_EMPTY, mExecutor, cancelSignal, callback);
746                 mExecutor.execute(() -> {
747                     cancelSignal.cancel();
748                     latch.countDown();
749                 });
750 
751                 retry = callback.needRetry();
752                 assertTrue(msg + " query was not cancelled",
753                         latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
754             } while (retry);
755         }
756     }
757 
doTestQueryForInetAddressIpv4(Executor executor)758     public void doTestQueryForInetAddressIpv4(Executor executor) throws InterruptedException {
759         final String msg = "Test query for IPv4 InetAddress " + TEST_DOMAIN;
760         for (Network network : getTestableNetworks()) {
761             final VerifyCancelInetAddressCallback callback =
762                     new VerifyCancelInetAddressCallback(msg, null);
763             mDns.query(network, TEST_DOMAIN, TYPE_A, FLAG_NO_CACHE_LOOKUP,
764                     executor, null, callback);
765 
766             assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
767                     callback.waitForAnswer());
768             callback.assertNoError();
769             assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
770             assertTrue(msg + " returned Ipv6 results", !callback.hasIpv6Answer());
771         }
772     }
773 
doTestQueryForInetAddressIpv6(Executor executor)774     public void doTestQueryForInetAddressIpv6(Executor executor) throws InterruptedException {
775         final String msg = "Test query for IPv6 InetAddress " + TEST_DOMAIN;
776         for (Network network : getTestableNetworks()) {
777             final VerifyCancelInetAddressCallback callback =
778                     new VerifyCancelInetAddressCallback(msg, null);
779             mDns.query(network, TEST_DOMAIN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
780                     executor, null, callback);
781 
782             assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
783                     callback.waitForAnswer());
784             callback.assertNoError();
785             assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
786             assertTrue(msg + " returned Ipv4 results", !callback.hasIpv4Answer());
787         }
788     }
789 
790     @Test
testPrivateDnsBypass()791     public void testPrivateDnsBypass() throws InterruptedException {
792         final String dataStallSetting = Settings.Global.getString(mCR,
793                 Settings.Global.DATA_STALL_RECOVERY_ON_BAD_NETWORK);
794         Settings.Global.putInt(mCR, Settings.Global.DATA_STALL_RECOVERY_ON_BAD_NETWORK, 0);
795         try {
796             doTestPrivateDnsBypass();
797         } finally {
798             Settings.Global.putString(mCR, Settings.Global.DATA_STALL_RECOVERY_ON_BAD_NETWORK,
799                     dataStallSetting);
800         }
801     }
802 
doTestPrivateDnsBypass()803     private void doTestPrivateDnsBypass() throws InterruptedException {
804         final Network[] testNetworks = getTestableNetworks();
805 
806         // Set an invalid private DNS server
807         mCtsNetUtils.setPrivateDnsStrictMode(INVALID_PRIVATE_DNS_SERVER);
808         final String msg = "Test PrivateDnsBypass " + TEST_DOMAIN;
809         for (Network network : testNetworks) {
810             // This test cannot be ran with null network because we need to explicitly pass a
811             // private DNS bypassable network or bind one.
812             if (network == null) continue;
813 
814             // wait for private DNS setting propagating
815             mCtsNetUtils.awaitPrivateDnsSetting(msg + " wait private DNS setting timeout",
816                     network, INVALID_PRIVATE_DNS_SERVER, false);
817 
818             final CountDownLatch latch = new CountDownLatch(1);
819             final DnsResolver.Callback<List<InetAddress>> errorCallback =
820                     new DnsResolver.Callback<List<InetAddress>>() {
821                         @Override
822                         public void onAnswer(@NonNull List<InetAddress> answerList, int rcode) {
823                             fail(msg + " should not get valid answer");
824                         }
825 
826                         @Override
827                         public void onError(@NonNull DnsResolver.DnsException error) {
828                             assertEquals(DnsResolver.ERROR_SYSTEM, error.code);
829                             assertEquals(ETIMEDOUT, ((ErrnoException) error.getCause()).errno);
830                             latch.countDown();
831                         }
832                     };
833             // Private DNS strict mode with invalid DNS server is set
834             // Expect no valid answer returned but ErrnoException with ETIMEDOUT
835             mDns.query(network, TEST_DOMAIN, FLAG_NO_CACHE_LOOKUP, mExecutor, null, errorCallback);
836 
837             assertTrue(msg + " invalid server round. No response after " + TIMEOUT_MS + "ms.",
838                     latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
839 
840             final VerifyCancelInetAddressCallback callback =
841                     new VerifyCancelInetAddressCallback(msg, null);
842             // Bypass privateDns, expect query works fine
843             mDns.query(network.getPrivateDnsBypassingCopy(),
844                     TEST_DOMAIN, FLAG_NO_CACHE_LOOKUP, mExecutor, null, callback);
845 
846             assertTrue(msg + " bypass private DNS round. No answer after " + TIMEOUT_MS + "ms.",
847                     callback.waitForAnswer());
848             callback.assertNoError();
849             assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
850 
851             // To ensure private DNS bypass still work even if passing null network.
852             // Bind process network with a private DNS bypassable network.
853             mCM.bindProcessToNetwork(network.getPrivateDnsBypassingCopy());
854             final VerifyCancelInetAddressCallback callbackWithNullNetwork =
855                     new VerifyCancelInetAddressCallback(msg + " with null network ", null);
856             mDns.query(null,
857                     TEST_DOMAIN, FLAG_NO_CACHE_LOOKUP, mExecutor, null, callbackWithNullNetwork);
858 
859             assertTrue(msg + " with null network bypass private DNS round. No answer after " +
860                     TIMEOUT_MS + "ms.", callbackWithNullNetwork.waitForAnswer());
861             callbackWithNullNetwork.assertNoError();
862             assertTrue(msg + " with null network returned 0 results",
863                     !callbackWithNullNetwork.isAnswerEmpty());
864 
865             // Reset process network to default.
866             mCM.bindProcessToNetwork(null);
867         }
868     }
869 
doTestContinuousQueries(Executor executor)870     public void doTestContinuousQueries(Executor executor) throws InterruptedException {
871         for (Network network : getTestableNetworks()) {
872             for (int i = 0; i < QUERY_TIMES ; ++i) {
873                 // query v6/v4 in turn
874                 boolean queryV6 = (i % 2 == 0);
875                 final String msg = "Test continuous " + QUERY_TIMES + " queries " + TEST_DOMAIN
876                         + " on " + network + ", queryV6=" + queryV6;
877                 final VerifyCancelInetAddressCallback callback =
878                         new VerifyCancelInetAddressCallback(msg, null);
879                 mDns.query(network, TEST_DOMAIN, queryV6 ? TYPE_AAAA : TYPE_A,
880                         FLAG_NO_CACHE_LOOKUP, executor, null, callback);
881 
882                 assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
883                         callback.waitForAnswer());
884                 callback.assertNoError();
885                 assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
886                 assertTrue(msg + " returned " + (queryV6 ? "Ipv4" : "Ipv6") + " results",
887                         queryV6 ? !callback.hasIpv4Answer() : !callback.hasIpv6Answer());
888             }
889         }
890     }
891 
892     /** Verifies that DnsResolver.DnsException can be subclassed and its constructor re-used. */
893     @Test @IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
testDnsExceptionConstructor()894     public void testDnsExceptionConstructor() throws InterruptedException {
895         class TestDnsException extends DnsResolver.DnsException {
896             TestDnsException(int code, @Nullable Throwable cause) {
897                 super(code, cause);
898             }
899         }
900         try {
901             throw new TestDnsException(DnsResolver.ERROR_SYSTEM, null);
902         } catch (DnsResolver.DnsException e) {
903             assertEquals(DnsResolver.ERROR_SYSTEM, e.code);
904         }
905     }
906 
907     @Test
testNoRawBinderAccess()908     public void testNoRawBinderAccess() {
909         assertNull(mContext.getSystemService("dnsresolver"));
910     }
911 }
912