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