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