Merge "DnsResolver cts changes to match API council requests"

This commit is contained in:
Luke Huang
2019-04-12 00:45:18 +00:00
committed by Gerrit Code Review

View File

@@ -39,13 +39,14 @@ import android.system.ErrnoException;
import android.test.AndroidTestCase;
import android.util.Log;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
public class DnsResolverTest extends AndroidTestCase {
private static final String TAG = "DnsResolverTest";
@@ -53,7 +54,9 @@ public class DnsResolverTest extends AndroidTestCase {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};
static final int TIMEOUT_MS = 12_000;
static final int CANCEL_TIMEOUT_MS = 3_000;
static final int CANCEL_RETRY_TIMES = 5;
static final int NXDOMAIN = 3;
private ConnectivityManager mCM;
private Executor mExecutor;
@@ -94,73 +97,26 @@ public class DnsResolverTest extends AndroidTestCase {
return testableNetworks.toArray(new Network[0]);
}
public void testQueryWithInetAddressCallback() {
final String dname = "www.google.com";
final String msg = "Query with InetAddressAnswerCallback " + dname;
for (Network network : getTestableNetworks()) {
final CountDownLatch latch = new CountDownLatch(1);
final AtomicReference<List<InetAddress>> answers = new AtomicReference<>();
final DnsResolver.InetAddressAnswerCallback callback =
new DnsResolver.InetAddressAnswerCallback() {
@Override
public void onAnswer(@NonNull List<InetAddress> answerList) {
answers.set(answerList);
for (InetAddress addr : answerList) {
Log.d(TAG, "Reported addr: " + addr.toString());
}
latch.countDown();
}
@Override
public void onParseException(@NonNull ParseException e) {
fail(msg + e.getMessage());
}
@Override
public void onQueryException(@NonNull ErrnoException e) {
fail(msg + e.getMessage());
}
};
mDns.query(network, dname, CLASS_IN, TYPE_A, FLAG_NO_CACHE_LOOKUP,
mExecutor, null, callback);
try {
assertTrue(msg + " but no valid answer after " + TIMEOUT_MS + "ms.",
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertGreaterThan(msg + " returned 0 result", answers.get().size(), 0);
} catch (InterruptedException e) {
fail(msg + " Waiting for DNS lookup was interrupted");
}
}
}
static private void assertGreaterThan(String msg, int first, int second) {
assertTrue(msg + " Excepted " + first + " to be greater than " + second, first > second);
}
static private void assertValidAnswer(String msg, @NonNull DnsAnswer ans) {
// Check rcode field.(0, No error condition).
assertTrue(msg + " Response error, rcode: " + ans.getRcode(), ans.getRcode() == 0);
// Check answer counts.
assertGreaterThan(msg + " No answer found", ans.getANCount(), 0);
// Check question counts.
assertGreaterThan(msg + " No question found", ans.getQDCount(), 0);
}
private static class DnsParseException extends Exception {
public DnsParseException(String msg) {
super(msg);
}
static private void assertValidEmptyAnswer(String msg, @NonNull DnsAnswer ans) {
// Check rcode field.(0, No error condition).
assertTrue(msg + " Response error, rcode: " + ans.getRcode(), ans.getRcode() == 0);
// Check answer counts. Expect 0 answer.
assertTrue(msg + " Not an empty answer", ans.getANCount() == 0);
// Check question counts.
assertGreaterThan(msg + " No question found", ans.getQDCount(), 0);
public DnsParseException(String msg, Throwable cause) {
super(msg, cause);
}
}
private static class DnsAnswer extends DnsPacket {
DnsAnswer(@NonNull byte[] data) throws ParseException {
DnsAnswer(@NonNull byte[] data) throws DnsParseException {
super(data);
// Check QR field.(query (0), or a response (1)).
if ((mHeader.flags & (1 << 15)) == 0) {
throw new ParseException("Not an answer packet");
throw new DnsParseException("Not an answer packet");
}
}
@@ -175,63 +131,116 @@ public class DnsResolverTest extends AndroidTestCase {
}
}
class RawAnswerCallbackImpl extends DnsResolver.RawAnswerCallback {
/**
* A query callback that ensures that the query is cancelled and that onAnswer is never
* called. If the query succeeds before it is cancelled, needRetry will return true so the
* test can retry.
*/
class VerifyCancelCallback implements DnsResolver.Callback<byte[]> {
private final CountDownLatch mLatch = new CountDownLatch(1);
private final String mMsg;
private final int mTimeout;
private final CancellationSignal mCancelSignal;
private int mRcode;
private DnsAnswer mDnsAnswer;
RawAnswerCallbackImpl(@NonNull String msg, int timeout) {
VerifyCancelCallback(@NonNull String msg, @Nullable CancellationSignal cancel) {
this.mMsg = msg;
this.mTimeout = timeout;
this.mCancelSignal = cancel;
this.mDnsAnswer = null;
}
RawAnswerCallbackImpl(@NonNull String msg) {
this(msg, TIMEOUT_MS);
VerifyCancelCallback(@NonNull String msg) {
this(msg, null);
}
public boolean waitForAnswer(int timeout) throws InterruptedException {
return mLatch.await(timeout, TimeUnit.MILLISECONDS);
}
public boolean waitForAnswer() throws InterruptedException {
return mLatch.await(mTimeout, TimeUnit.MILLISECONDS);
return waitForAnswer(TIMEOUT_MS);
}
public boolean needRetry() throws InterruptedException {
return mLatch.await(CANCEL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
@Override
public void onAnswer(@NonNull byte[] answer) {
public void onAnswer(@NonNull byte[] answer, int rcode) {
if (mCancelSignal != null && mCancelSignal.isCanceled()) {
fail(mMsg + " should not have returned any answers");
}
mRcode = rcode;
try {
assertValidAnswer(mMsg, new DnsAnswer(answer));
Log.d(TAG, "Reported blob: " + byteArrayToHexString(answer));
mLatch.countDown();
} catch (ParseException e) {
mDnsAnswer = new DnsAnswer(answer);
} catch (DnsParseException e) {
fail(mMsg + e.getMessage());
}
Log.d(TAG, "Reported blob: " + byteArrayToHexString(answer));
mLatch.countDown();
}
@Override
public void onParseException(@NonNull ParseException e) {
fail(mMsg + e.getMessage());
public void onError(@NonNull DnsResolver.DnsException error) {
fail(mMsg + error.getMessage());
}
@Override
public void onQueryException(@NonNull ErrnoException e) {
fail(mMsg + e.getMessage());
private void assertValidAnswer() {
assertTrue(mMsg + "No valid answer", mDnsAnswer != null);
assertTrue(mMsg + " Unexpected error: reported rcode" + mRcode +
" blob's rcode " + mDnsAnswer.getRcode(), mRcode == mDnsAnswer.getRcode());
}
public void assertHasAnswer() {
assertValidAnswer();
// Check rcode field.(0, No error condition).
assertTrue(mMsg + " Response error, rcode: " + mRcode, mRcode == 0);
// Check answer counts.
assertGreaterThan(mMsg + " No answer found", mDnsAnswer.getANCount(), 0);
// Check question counts.
assertGreaterThan(mMsg + " No question found", mDnsAnswer.getQDCount(), 0);
}
public void assertNXDomain() {
assertValidAnswer();
// Check rcode field.(3, NXDomain).
assertTrue(mMsg + " Unexpected rcode: " + mRcode, mRcode == NXDOMAIN);
// Check answer counts. Expect 0 answer.
assertTrue(mMsg + " Not an empty answer", mDnsAnswer.getANCount() == 0);
// Check question counts.
assertGreaterThan(mMsg + " No question found", mDnsAnswer.getQDCount(), 0);
}
public void assertEmptyAnswer() {
assertValidAnswer();
// Check rcode field.(0, No error condition).
assertTrue(mMsg + " Response error, rcode: " + mRcode, mRcode == 0);
// Check answer counts. Expect 0 answer.
assertTrue(mMsg + " Not an empty answer", mDnsAnswer.getANCount() == 0);
// Check question counts.
assertGreaterThan(mMsg + " No question found", mDnsAnswer.getQDCount(), 0);
}
}
public void testQueryWithRawAnswerCallback() {
public void testRawQuery() {
final String dname = "www.google.com";
final String msg = "Query with RawAnswerCallback " + dname;
final String msg = "RawQuery " + dname;
for (Network network : getTestableNetworks()) {
final RawAnswerCallbackImpl callback = new RawAnswerCallbackImpl(msg);
mDns.query(network, dname, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
mDns.rawQuery(network, dname, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
mExecutor, null, callback);
try {
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
callback.waitForAnswer());
callback.assertHasAnswer();
} catch (InterruptedException e) {
fail(msg + " Waiting for DNS lookup was interrupted");
}
}
}
public void testQueryBlobWithRawAnswerCallback() {
public void testRawQueryBlob() {
final byte[] blob = new byte[]{
/* Header */
0x55, 0x66, /* Transaction ID */
@@ -246,137 +255,58 @@ public class DnsResolverTest extends AndroidTestCase {
0x00, 0x01, /* Type */
0x00, 0x01 /* Class */
};
final String msg = "Query with RawAnswerCallback " + byteArrayToHexString(blob);
final String msg = "RawQuery blob " + byteArrayToHexString(blob);
for (Network network : getTestableNetworks()) {
final RawAnswerCallbackImpl callback = new RawAnswerCallbackImpl(msg);
mDns.query(network, blob, FLAG_NO_CACHE_LOOKUP, mExecutor, null, callback);
final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
mDns.rawQuery(network, blob, FLAG_NO_CACHE_LOOKUP, mExecutor, null, callback);
try {
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
callback.waitForAnswer());
callback.assertHasAnswer();
} catch (InterruptedException e) {
fail(msg + " Waiting for DNS lookup was interrupted");
}
}
}
public void testQueryRoot() {
public void testRawQueryRoot() {
final String dname = "";
final String msg = "Query with RawAnswerCallback empty dname(ROOT) ";
final String msg = "RawQuery empty dname(ROOT) ";
for (Network network : getTestableNetworks()) {
final CountDownLatch latch = new CountDownLatch(1);
final DnsResolver.RawAnswerCallback callback = new DnsResolver.RawAnswerCallback() {
@Override
public void onAnswer(@NonNull byte[] answer) {
try {
// Except no answer record because of querying with empty dname(ROOT)
assertValidEmptyAnswer(msg, new DnsAnswer(answer));
latch.countDown();
} catch (ParseException e) {
fail(msg + e.getMessage());
}
}
@Override
public void onParseException(@NonNull ParseException e) {
fail(msg + e.getMessage());
}
@Override
public void onQueryException(@NonNull ErrnoException e) {
fail(msg + e.getMessage());
}
};
mDns.query(network, dname, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
mDns.rawQuery(network, dname, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
mExecutor, null, callback);
try {
assertTrue(msg + "but no answer after " + TIMEOUT_MS + "ms.",
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
callback.waitForAnswer());
// Except no answer record because of querying with empty dname(ROOT)
callback.assertEmptyAnswer();
} catch (InterruptedException e) {
fail(msg + "Waiting for DNS lookup was interrupted");
}
}
}
public void testQueryNXDomain() {
public void testRawQueryNXDomain() {
final String dname = "test1-nx.metric.gstatic.com";
final String msg = "Query with InetAddressAnswerCallback " + dname;
final String msg = "RawQuery " + dname;
for (Network network : getTestableNetworks()) {
final CountDownLatch latch = new CountDownLatch(1);
final DnsResolver.InetAddressAnswerCallback callback =
new DnsResolver.InetAddressAnswerCallback() {
@Override
public void onAnswer(@NonNull List<InetAddress> answerList) {
if (answerList.size() == 0) {
latch.countDown();
return;
}
fail(msg + " but get valid answers");
}
@Override
public void onParseException(@NonNull ParseException e) {
fail(msg + e.getMessage());
}
@Override
public void onQueryException(@NonNull ErrnoException e) {
fail(msg + e.getMessage());
}
};
mDns.query(network, dname, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
mDns.rawQuery(network, dname, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
mExecutor, null, callback);
try {
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
callback.waitForAnswer());
callback.assertNXDomain();
} catch (InterruptedException e) {
fail(msg + " Waiting for DNS lookup was interrupted");
}
}
}
/**
* A query callback that ensures that the query is cancelled and that onAnswer is never
* called. If the query succeeds before it is cancelled, needRetry will return true so the
* test can retry.
*/
class VerifyCancelCallback extends DnsResolver.RawAnswerCallback {
private static final int CANCEL_TIMEOUT = 3_000;
private final CountDownLatch mLatch = new CountDownLatch(1);
private final String mMsg;
private final CancellationSignal mCancelSignal;
VerifyCancelCallback(@NonNull String msg, @NonNull CancellationSignal cancelSignal) {
this.mMsg = msg;
this.mCancelSignal = cancelSignal;
}
public boolean needRetry() throws InterruptedException {
return mLatch.await(CANCEL_TIMEOUT, TimeUnit.MILLISECONDS);
}
@Override
public void onAnswer(@NonNull byte[] answer) {
if (mCancelSignal.isCanceled()) {
fail(mMsg + " should not have returned any answers");
}
mLatch.countDown();
}
@Override
public void onParseException(@NonNull ParseException e) {
fail(mMsg + e.getMessage());
}
@Override
public void onQueryException(@NonNull ErrnoException e) {
fail(mMsg + e.getMessage());
}
}
public void testQueryCancel() throws ErrnoException {
public void testRawQueryCancel() throws ErrnoException {
final String dname = "www.google.com";
final String msg = "Test cancel query " + dname;
final String msg = "Test cancel RawQuery " + dname;
// Start a DNS query and the cancel it immediately. Use VerifyCancelCallback to expect
// that the query is cancelled before it succeeds. If it is not cancelled before it
// succeeds, retry the test until it is.
@@ -390,7 +320,7 @@ public class DnsResolverTest extends AndroidTestCase {
final CountDownLatch latch = new CountDownLatch(1);
final CancellationSignal cancelSignal = new CancellationSignal();
final VerifyCancelCallback callback = new VerifyCancelCallback(msg, cancelSignal);
mDns.query(network, dname, CLASS_IN, TYPE_AAAA, FLAG_EMPTY,
mDns.rawQuery(network, dname, CLASS_IN, TYPE_AAAA, FLAG_EMPTY,
mExecutor, cancelSignal, callback);
mExecutor.execute(() -> {
cancelSignal.cancel();
@@ -399,7 +329,7 @@ public class DnsResolverTest extends AndroidTestCase {
try {
retry = callback.needRetry();
assertTrue(msg + " query was not cancelled",
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
} catch (InterruptedException e) {
fail(msg + "Waiting for DNS lookup was interrupted");
}
@@ -407,7 +337,7 @@ public class DnsResolverTest extends AndroidTestCase {
}
}
public void testQueryBlobCancel() throws ErrnoException {
public void testRawQueryBlobCancel() throws ErrnoException {
final byte[] blob = new byte[]{
/* Header */
0x55, 0x66, /* Transaction ID */
@@ -422,7 +352,7 @@ public class DnsResolverTest extends AndroidTestCase {
0x00, 0x01, /* Type */
0x00, 0x01 /* Class */
};
final String msg = "Test cancel raw Query " + byteArrayToHexString(blob);
final String msg = "Test cancel RawQuery blob " + byteArrayToHexString(blob);
// Start a DNS query and the cancel it immediately. Use VerifyCancelCallback to expect
// that the query is cancelled before it succeeds. If it is not cancelled before it
// succeeds, retry the test until it is.
@@ -436,7 +366,7 @@ public class DnsResolverTest extends AndroidTestCase {
final CountDownLatch latch = new CountDownLatch(1);
final CancellationSignal cancelSignal = new CancellationSignal();
final VerifyCancelCallback callback = new VerifyCancelCallback(msg, cancelSignal);
mDns.query(network, blob, FLAG_EMPTY, mExecutor, cancelSignal, callback);
mDns.rawQuery(network, blob, FLAG_EMPTY, mExecutor, cancelSignal, callback);
mExecutor.execute(() -> {
cancelSignal.cancel();
latch.countDown();
@@ -444,7 +374,7 @@ public class DnsResolverTest extends AndroidTestCase {
try {
retry = callback.needRetry();
assertTrue(msg + " cancel is not done",
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
} catch (InterruptedException e) {
fail(msg + " Waiting for DNS lookup was interrupted");
}
@@ -454,16 +384,16 @@ public class DnsResolverTest extends AndroidTestCase {
public void testCancelBeforeQuery() throws ErrnoException {
final String dname = "www.google.com";
final String msg = "Test cancelled query " + dname;
final String msg = "Test cancelled RawQuery " + dname;
for (Network network : getTestableNetworks()) {
final RawAnswerCallbackImpl callback = new RawAnswerCallbackImpl(msg, 3_000);
final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
final CancellationSignal cancelSignal = new CancellationSignal();
cancelSignal.cancel();
mDns.query(network, dname, CLASS_IN, TYPE_AAAA, FLAG_EMPTY,
mDns.rawQuery(network, dname, CLASS_IN, TYPE_AAAA, FLAG_EMPTY,
mExecutor, cancelSignal, callback);
try {
assertTrue(msg + " should not return any answers",
!callback.waitForAnswer());
!callback.waitForAnswer(CANCEL_TIMEOUT_MS));
} catch (InterruptedException e) {
fail(msg + " Waiting for DNS lookup was interrupted");
}
@@ -476,9 +406,7 @@ public class DnsResolverTest extends AndroidTestCase {
* before it is cancelled, needRetry will return true so the
* test can retry.
*/
class VerifyCancelInetAddressCallback extends DnsResolver.InetAddressAnswerCallback {
private static final int CANCEL_TIMEOUT = 3_000;
class VerifyCancelInetAddressCallback implements DnsResolver.Callback<List<InetAddress>> {
private final CountDownLatch mLatch = new CountDownLatch(1);
private final String mMsg;
private final List<InetAddress> mAnswers;
@@ -495,31 +423,43 @@ public class DnsResolverTest extends AndroidTestCase {
}
public boolean needRetry() throws InterruptedException {
return mLatch.await(CANCEL_TIMEOUT, TimeUnit.MILLISECONDS);
return mLatch.await(CANCEL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
public boolean isAnswerEmpty() {
return mAnswers.isEmpty();
}
public boolean hasIpv6Answer() {
for (InetAddress answer : mAnswers) {
if (answer instanceof Inet6Address) return true;
}
return false;
}
public boolean hasIpv4Answer() {
for (InetAddress answer : mAnswers) {
if (answer instanceof Inet4Address) return true;
}
return false;
}
@Override
public void onAnswer(@NonNull List<InetAddress> answerList) {
public void onAnswer(@NonNull List<InetAddress> answerList, int rcode) {
if (mCancelSignal != null && mCancelSignal.isCanceled()) {
fail(mMsg + " should not have returned any answers");
}
for (InetAddress addr : answerList) {
Log.d(TAG, "Reported addr: " + addr.toString());
}
mAnswers.clear();
mAnswers.addAll(answerList);
mLatch.countDown();
}
@Override
public void onParseException(@NonNull ParseException e) {
fail(mMsg + e.getMessage());
}
@Override
public void onQueryException(@NonNull ErrnoException e) {
fail(mMsg + e.getMessage());
public void onError(@NonNull DnsResolver.DnsException error) {
fail(mMsg + error.getMessage());
}
}
@@ -544,8 +484,8 @@ public class DnsResolverTest extends AndroidTestCase {
public void testQueryCancelForInetAddress() throws ErrnoException {
final String dname = "www.google.com";
final String msg = "Test cancel query for InetAddress " + dname;
// Start a DNS query and the cancel it immediately. Use VerifyCancelCallback to expect
// that the query is cancelled before it succeeds. If it is not cancelled before it
// Start a DNS query and the cancel it immediately. Use VerifyCancelInetAddressCallback to
// expect that the query is cancelled before it succeeds. If it is not cancelled before it
// succeeds, retry the test until it is.
for (Network network : getTestableNetworks()) {
boolean retry = false;
@@ -566,11 +506,49 @@ public class DnsResolverTest extends AndroidTestCase {
try {
retry = callback.needRetry();
assertTrue(msg + " query was not cancelled",
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
} catch (InterruptedException e) {
fail(msg + "Waiting for DNS lookup was interrupted");
}
} while (retry);
}
}
public void testQueryForInetAddressIpv4() {
final String dname = "www.google.com";
final String msg = "Test query for IPv4 InetAddress " + dname;
for (Network network : getTestableNetworks()) {
final VerifyCancelInetAddressCallback callback =
new VerifyCancelInetAddressCallback(msg, null);
mDns.query(network, dname, TYPE_A, FLAG_NO_CACHE_LOOKUP,
mExecutor, null, callback);
try {
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
callback.waitForAnswer());
assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
assertTrue(msg + " returned Ipv6 results", !callback.hasIpv6Answer());
} catch (InterruptedException e) {
fail(msg + " Waiting for DNS lookup was interrupted");
}
}
}
public void testQueryForInetAddressIpv6() {
final String dname = "www.google.com";
final String msg = "Test query for IPv6 InetAddress " + dname;
for (Network network : getTestableNetworks()) {
final VerifyCancelInetAddressCallback callback =
new VerifyCancelInetAddressCallback(msg, null);
mDns.query(network, dname, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
mExecutor, null, callback);
try {
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
callback.waitForAnswer());
assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
assertTrue(msg + " returned Ipv4 results", !callback.hasIpv4Answer());
} catch (InterruptedException e) {
fail(msg + " Waiting for DNS lookup was interrupted");
}
}
}
}