Merge "Add tests for strict mode private DNS validation."

am: 6a4ae85e23

Change-Id: I55aba3a8537395d7dd20e10e96a20d097546f65b
This commit is contained in:
Lorenzo Colitti
2019-05-09 05:16:53 -07:00
committed by android-build-merger

View File

@@ -42,10 +42,10 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -66,6 +66,7 @@ import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.captiveportal.CaptivePortalProbeResult;
import android.net.metrics.IpConnectivityLog;
import android.net.shared.PrivateDnsConfig;
import android.net.util.SharedLog;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
@@ -73,6 +74,7 @@ import android.os.Bundle;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.Looper;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.provider.Settings;
@@ -132,6 +134,7 @@ public class NetworkMonitorTest {
private @Mock NetworkMonitor.Dependencies mDependencies;
private @Mock INetworkMonitorCallbacks mCallbacks;
private @Spy Network mNetwork = new Network(TEST_NETID);
private @Mock Network mNonPrivateDnsBypassNetwork;
private @Mock DataStallStatsUtils mDataStallStatsUtils;
private @Mock WifiInfo mWifiInfo;
private @Captor ArgumentCaptor<String> mNetworkTestedRedirectUrlCaptor;
@@ -166,31 +169,93 @@ public class NetworkMonitorTest {
private static final NetworkCapabilities NO_INTERNET_CAPABILITIES = new NetworkCapabilities()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
private void setDnsAnswers(String[] answers) throws UnknownHostException {
if (answers == null) {
doThrow(new UnknownHostException()).when(mNetwork).getAllByName(any());
doNothing().when(mDnsResolver).query(any(), any(), anyInt(), any(), any(), any());
return;
/**
* Fakes DNS responses.
*
* Allows test methods to configure the IP addresses that will be resolved by
* Network#getAllByName and by DnsResolver#query.
*/
class FakeDns {
private final ArrayMap<String, List<InetAddress>> mAnswers = new ArrayMap<>();
private boolean mNonBypassPrivateDnsWorking = true;
/** Whether DNS queries on mNonBypassPrivateDnsWorking should succeed. */
private void setNonBypassPrivateDnsWorking(boolean working) {
mNonBypassPrivateDnsWorking = working;
}
List<InetAddress> answerList = new ArrayList<>();
for (String answer : answers) {
answerList.add(InetAddresses.parseNumericAddress(answer));
/** Clears all DNS entries. */
private synchronized void clearAll() {
mAnswers.clear();
}
InetAddress[] answerArray = answerList.toArray(new InetAddress[0]);
doReturn(answerArray).when(mNetwork).getAllByName(any());
/** Returns the answer for a given name on the given mock network. */
private synchronized List<InetAddress> getAnswer(Object mock, String hostname) {
if (mock == mNonPrivateDnsBypassNetwork && !mNonBypassPrivateDnsWorking) {
return null;
}
if (mAnswers.containsKey(hostname)) {
return mAnswers.get(hostname);
}
return mAnswers.get("*");
}
doAnswer((invocation) -> {
Executor executor = (Executor) invocation.getArgument(3);
DnsResolver.Callback<List<InetAddress>> callback = invocation.getArgument(5);
new Handler(Looper.getMainLooper()).post(() -> {
executor.execute(() -> callback.onAnswer(answerList, 0));
});
return null;
}).when(mDnsResolver).query(eq(mNetwork), any(), anyInt(), any(), any(), any());
/** Sets the answer for a given name. */
private synchronized void setAnswer(String hostname, String[] answer)
throws UnknownHostException {
if (answer == null) {
mAnswers.remove(hostname);
} else {
List<InetAddress> answerList = new ArrayList<>();
for (String addr : answer) {
answerList.add(InetAddresses.parseNumericAddress(addr));
}
mAnswers.put(hostname, answerList);
}
}
/** Simulates a getAllByName call for the specified name on the specified mock network. */
private InetAddress[] getAllByName(Object mock, String hostname)
throws UnknownHostException {
List<InetAddress> answer = getAnswer(mock, hostname);
if (answer == null || answer.size() == 0) {
throw new UnknownHostException(hostname);
}
return answer.toArray(new InetAddress[0]);
}
/** Starts mocking DNS queries. */
private void startMocking() throws UnknownHostException {
// Queries on mNetwork (i.e., bypassing private DNS) using getAllByName.
doAnswer(invocation -> {
return getAllByName(invocation.getMock(), invocation.getArgument(0));
}).when(mNetwork).getAllByName(any());
// Queries on mNonBypassPrivateDnsNetwork using getAllByName.
doAnswer(invocation -> {
return getAllByName(invocation.getMock(), invocation.getArgument(0));
}).when(mNonPrivateDnsBypassNetwork).getAllByName(any());
// Queries on mNetwork (i.e., bypassing private DNS) using DnsResolver#query.
doAnswer(invocation -> {
String hostname = (String) invocation.getArgument(1);
Executor executor = (Executor) invocation.getArgument(3);
DnsResolver.Callback<List<InetAddress>> callback = invocation.getArgument(5);
List<InetAddress> answer = getAnswer(invocation.getMock(), hostname);
if (answer != null && answer.size() > 0) {
new Handler(Looper.getMainLooper()).post(() -> {
executor.execute(() -> callback.onAnswer(answer, 0));
});
}
// If no answers, do nothing. sendDnsProbeWithTimeout will time out and throw UHE.
return null;
}).when(mDnsResolver).query(any(), any(), anyInt(), any(), any(), any());
}
}
private FakeDns mFakeDns;
@Before
public void setUp() throws IOException {
MockitoAnnotations.initMocks(this);
@@ -206,7 +271,7 @@ public class NetworkMonitorTest {
when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTPS_URL), any()))
.thenReturn(TEST_HTTPS_URL);
doReturn(mNetwork).when(mNetwork).getPrivateDnsBypassingCopy();
doReturn(mNetwork).when(mNonPrivateDnsBypassNetwork).getPrivateDnsBypassingCopy();
when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mCm);
when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephony);
@@ -222,6 +287,9 @@ public class NetworkMonitorTest {
setFallbackSpecs(null); // Test with no fallback spec by default
when(mRandom.nextInt()).thenReturn(0);
when(mResources.getInteger(eq(R.integer.config_captive_portal_dns_probe_timeout)))
.thenReturn(500);
doAnswer((invocation) -> {
URL url = invocation.getArgument(0);
switch(url.toString()) {
@@ -241,7 +309,9 @@ public class NetworkMonitorTest {
when(mHttpConnection.getRequestProperties()).thenReturn(new ArrayMap<>());
when(mHttpsConnection.getRequestProperties()).thenReturn(new ArrayMap<>());
setDnsAnswers(new String[]{"2001:db8::1", "192.0.2.2"});
mFakeDns = new FakeDns();
mFakeDns.startMocking();
mFakeDns.setAnswer("*", new String[]{"2001:db8::1", "192.0.2.2"});
when(mContext.registerReceiver(any(BroadcastReceiver.class), any())).then((invocation) -> {
mRegisteredReceivers.add(invocation.getArgument(0));
@@ -264,6 +334,7 @@ public class NetworkMonitorTest {
@After
public void tearDown() {
mFakeDns.clearAll();
assertTrue(mCreatedNetworkMonitors.size() > 0);
// Make a local copy of mCreatedNetworkMonitors because during the iteration below,
// WrappedNetworkMonitor#onQuitting will delete elements from it on the handler threads.
@@ -284,8 +355,8 @@ public class NetworkMonitorTest {
private final ConditionVariable mQuitCv = new ConditionVariable(false);
WrappedNetworkMonitor() {
super(mContext, mCallbacks, mNetwork, mLogger, mValidationLogger, mDependencies,
mDataStallStatsUtils);
super(mContext, mCallbacks, mNonPrivateDnsBypassNetwork, mLogger, mValidationLogger,
mDependencies, mDataStallStatsUtils);
}
@Override
@@ -314,23 +385,22 @@ public class NetworkMonitorTest {
}
}
private WrappedNetworkMonitor makeMonitor() {
private WrappedNetworkMonitor makeMonitor(NetworkCapabilities nc) {
final WrappedNetworkMonitor nm = new WrappedNetworkMonitor();
nm.start();
setNetworkCapabilities(nm, nc);
waitForIdle(nm.getHandler());
mCreatedNetworkMonitors.add(nm);
return nm;
}
private WrappedNetworkMonitor makeMeteredNetworkMonitor() {
final WrappedNetworkMonitor nm = makeMonitor();
setNetworkCapabilities(nm, METERED_CAPABILITIES);
final WrappedNetworkMonitor nm = makeMonitor(METERED_CAPABILITIES);
return nm;
}
private WrappedNetworkMonitor makeNotMeteredNetworkMonitor() {
final WrappedNetworkMonitor nm = makeMonitor();
setNetworkCapabilities(nm, NOT_METERED_CAPABILITIES);
final WrappedNetworkMonitor nm = makeMonitor(NOT_METERED_CAPABILITIES);
return nm;
}
@@ -603,7 +673,7 @@ public class NetworkMonitorTest {
setSslException(mHttpsConnection);
setPortal302(mHttpConnection);
final NetworkMonitor nm = makeMonitor();
final NetworkMonitor nm = makeMonitor(METERED_CAPABILITIES);
nm.notifyNetworkConnected(TEST_LINK_PROPERTIES, METERED_CAPABILITIES);
verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
@@ -637,6 +707,63 @@ public class NetworkMonitorTest {
assertEquals(0, mRegisteredReceivers.size());
}
@Test
public void testPrivateDnsSuccess() throws Exception {
setStatus(mHttpsConnection, 204);
setStatus(mHttpConnection, 204);
mFakeDns.setAnswer("dns.google", new String[]{"2001:db8::53"});
WrappedNetworkMonitor wnm = makeNotMeteredNetworkMonitor();
wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
wnm.notifyNetworkConnected(TEST_LINK_PROPERTIES, NOT_METERED_CAPABILITIES);
verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
.notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), eq(null));
}
@Test
public void testPrivateDnsResolutionRetryUpdate() throws Exception {
// Set a private DNS hostname that doesn't resolve and expect validation to fail.
mFakeDns.setAnswer("dns.google", new String[0]);
setStatus(mHttpsConnection, 204);
setStatus(mHttpConnection, 204);
WrappedNetworkMonitor wnm = makeNotMeteredNetworkMonitor();
wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
wnm.notifyNetworkConnected(TEST_LINK_PROPERTIES, NOT_METERED_CAPABILITIES);
verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
.notifyNetworkTested(eq(NETWORK_TEST_RESULT_INVALID), eq(null));
// Fix DNS and retry, expect validation to succeed.
reset(mCallbacks);
mFakeDns.setAnswer("dns.google", new String[]{"2001:db8::1"});
wnm.forceReevaluation(Process.myUid());
verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
.notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), eq(null));
// Change configuration to an invalid DNS name, expect validation to fail.
reset(mCallbacks);
mFakeDns.setAnswer("dns.bad", new String[0]);
wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.bad", new InetAddress[0]));
verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
.notifyNetworkTested(eq(NETWORK_TEST_RESULT_INVALID), eq(null));
// Change configuration back to working again, but make private DNS not work.
// Expect validation to fail.
reset(mCallbacks);
mFakeDns.setNonBypassPrivateDnsWorking(false);
wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
.notifyNetworkTested(eq(NETWORK_TEST_RESULT_INVALID), eq(null));
// Make private DNS work again. Expect validation to succeed.
reset(mCallbacks);
mFakeDns.setNonBypassPrivateDnsWorking(true);
wnm.forceReevaluation(Process.myUid());
verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
.notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), eq(null));
}
@Test
public void testDataStall_StallSuspectedAndSendMetrics() throws IOException {
WrappedNetworkMonitor wrappedMonitor = makeNotMeteredNetworkMonitor();
@@ -728,25 +855,27 @@ public class NetworkMonitorTest {
WrappedNetworkMonitor wnm = makeNotMeteredNetworkMonitor();
final int shortTimeoutMs = 200;
// Clear the wildcard DNS response created in setUp.
mFakeDns.setAnswer("*", null);
String[] expected = new String[]{"2001:db8::"};
setDnsAnswers(expected);
mFakeDns.setAnswer("www.google.com", expected);
InetAddress[] actual = wnm.sendDnsProbeWithTimeout("www.google.com", shortTimeoutMs);
assertIpAddressArrayEquals(expected, actual);
expected = new String[]{"2001:db8::", "192.0.2.1"};
setDnsAnswers(expected);
actual = wnm.sendDnsProbeWithTimeout("www.google.com", shortTimeoutMs);
mFakeDns.setAnswer("www.googleapis.com", expected);
actual = wnm.sendDnsProbeWithTimeout("www.googleapis.com", shortTimeoutMs);
assertIpAddressArrayEquals(expected, actual);
expected = new String[0];
setDnsAnswers(expected);
mFakeDns.setAnswer("www.google.com", new String[0]);
try {
wnm.sendDnsProbeWithTimeout("www.google.com", shortTimeoutMs);
fail("No DNS results, expected UnknownHostException");
} catch (UnknownHostException e) {
}
setDnsAnswers(null);
mFakeDns.setAnswer("www.google.com", null);
try {
wnm.sendDnsProbeWithTimeout("www.google.com", shortTimeoutMs);
fail("DNS query timed out, expected UnknownHostException");
@@ -841,7 +970,7 @@ public class NetworkMonitorTest {
}
private NetworkMonitor runNetworkTest(NetworkCapabilities nc, int testResult) {
final NetworkMonitor monitor = makeMonitor();
final NetworkMonitor monitor = makeMonitor(nc);
monitor.notifyNetworkConnected(TEST_LINK_PROPERTIES, nc);
try {
verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))