From a0e4104e81f4fa32ed61c592297a92d7e57157cc Mon Sep 17 00:00:00 2001 From: Luke Huang Date: Thu, 11 Apr 2019 17:57:03 -0700 Subject: [PATCH] DnsResolver cts changes to match API council requests Bug: 129261432 Test: atest DnsResolverTest Merged-In: I42df921cc3bb01ea25a671d5a1af678a6d3f5872 (cherry picked from commit 89996844304a2919f8cd000f82a4d1af9de3df01) Change-Id: Ibcb92ac23cf413322234bba9100293ab794cf50e --- .../src/android/net/cts/DnsResolverTest.java | 384 +++++++++--------- 1 file changed, 181 insertions(+), 203 deletions(-) diff --git a/tests/cts/net/src/android/net/cts/DnsResolverTest.java b/tests/cts/net/src/android/net/cts/DnsResolverTest.java index 0ff6cd8bac..40d64cf49b 100644 --- a/tests/cts/net/src/android/net/cts/DnsResolverTest.java +++ b/tests/cts/net/src/android/net/cts/DnsResolverTest.java @@ -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> answers = new AtomicReference<>(); - final DnsResolver.InetAddressAnswerCallback callback = - new DnsResolver.InetAddressAnswerCallback() { - @Override - public void onAnswer(@NonNull List 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 { 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 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> { private final CountDownLatch mLatch = new CountDownLatch(1); private final String mMsg; private final List 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 answerList) { + public void onAnswer(@NonNull List 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"); + } + } + } }