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