blob: b399b0d51577d5a581b78bcac91f93f93152185c [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.connectivity;
import static junit.framework.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkRequest;
import android.net.captiveportal.CaptivePortalProbeResult;
import android.net.metrics.IpConnectivityLog;
import android.net.wifi.WifiManager;
import android.os.Handler;
import android.provider.Settings;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.URL;
import java.util.Random;
import javax.net.ssl.SSLHandshakeException;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class NetworkMonitorTest {
private static final String LOCATION_HEADER = "location";
private @Mock Context mContext;
private @Mock Handler mHandler;
private @Mock IpConnectivityLog mLogger;
private @Mock NetworkAgentInfo mAgent;
private @Mock NetworkInfo mNetworkInfo;
private @Mock NetworkRequest mRequest;
private @Mock TelephonyManager mTelephony;
private @Mock WifiManager mWifi;
private @Mock Network mNetwork;
private @Mock HttpURLConnection mHttpConnection;
private @Mock HttpURLConnection mHttpsConnection;
private @Mock HttpURLConnection mFallbackConnection;
private @Mock HttpURLConnection mOtherFallbackConnection;
private @Mock Random mRandom;
private @Mock NetworkMonitor.Dependencies mDependencies;
private static final String TEST_HTTP_URL = "http://www.google.com/gen_204";
private static final String TEST_HTTPS_URL = "https://www.google.com/gen_204";
private static final String TEST_FALLBACK_URL = "http://fallback.google.com/gen_204";
private static final String TEST_OTHER_FALLBACK_URL = "http://otherfallback.google.com/gen_204";
@Before
public void setUp() throws IOException {
MockitoAnnotations.initMocks(this);
mAgent.linkProperties = new LinkProperties();
mAgent.networkCapabilities = new NetworkCapabilities()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
mAgent.networkInfo = mNetworkInfo;
when(mAgent.network()).thenReturn(mNetwork);
when(mDependencies.getNetwork(any())).thenReturn(mNetwork);
when(mDependencies.getRandom()).thenReturn(mRandom);
when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt()))
.thenReturn(Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT);
when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_USE_HTTPS),
anyInt())).thenReturn(1);
when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTP_URL),
anyString())).thenReturn(TEST_HTTP_URL);
when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTPS_URL),
anyString())).thenReturn(TEST_HTTPS_URL);
when(mNetwork.getPrivateDnsBypassingCopy()).thenReturn(mNetwork);
when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephony);
when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mWifi);
when(mNetworkInfo.getType()).thenReturn(ConnectivityManager.TYPE_WIFI);
setFallbackUrl(TEST_FALLBACK_URL);
setOtherFallbackUrls(TEST_OTHER_FALLBACK_URL);
setFallbackSpecs(null); // Test with no fallback spec by default
when(mRandom.nextInt()).thenReturn(0);
when(mNetwork.openConnection(any())).then((invocation) -> {
URL url = invocation.getArgument(0);
switch(url.toString()) {
case TEST_HTTP_URL:
return mHttpConnection;
case TEST_HTTPS_URL:
return mHttpsConnection;
case TEST_FALLBACK_URL:
return mFallbackConnection;
case TEST_OTHER_FALLBACK_URL:
return mOtherFallbackConnection;
default:
fail("URL not mocked: " + url.toString());
return null;
}
});
when(mHttpConnection.getRequestProperties()).thenReturn(new ArrayMap<>());
when(mHttpsConnection.getRequestProperties()).thenReturn(new ArrayMap<>());
when(mNetwork.getAllByName(any())).thenReturn(new InetAddress[] {
InetAddress.parseNumericAddress("192.168.0.0")
});
}
NetworkMonitor makeMonitor() {
return new NetworkMonitor(
mContext, mHandler, mAgent, mRequest, mLogger, mDependencies);
}
@Test
public void testIsCaptivePortal_HttpProbeIsPortal() throws IOException {
setSslException(mHttpsConnection);
setPortal302(mHttpConnection);
assertPortal(makeMonitor().isCaptivePortal());
}
@Test
public void testIsCaptivePortal_HttpsProbeIsNotPortal() throws IOException {
setStatus(mHttpsConnection, 204);
setStatus(mHttpConnection, 500);
assertNotPortal(makeMonitor().isCaptivePortal());
}
@Test
public void testIsCaptivePortal_HttpsProbeFailedHttpSuccessNotUsed() throws IOException {
setSslException(mHttpsConnection);
// Even if HTTP returns a 204, do not use the result unless HTTPS succeeded
setStatus(mHttpConnection, 204);
setStatus(mFallbackConnection, 500);
assertFailed(makeMonitor().isCaptivePortal());
}
@Test
public void testIsCaptivePortal_FallbackProbeIsPortal() throws IOException {
setSslException(mHttpsConnection);
setStatus(mHttpConnection, 500);
setPortal302(mFallbackConnection);
assertPortal(makeMonitor().isCaptivePortal());
}
@Test
public void testIsCaptivePortal_FallbackProbeIsNotPortal() throws IOException {
setSslException(mHttpsConnection);
setStatus(mHttpConnection, 500);
setStatus(mFallbackConnection, 204);
// Fallback probe did not see portal, HTTPS failed -> inconclusive
assertFailed(makeMonitor().isCaptivePortal());
}
@Test
public void testIsCaptivePortal_OtherFallbackProbeIsPortal() throws IOException {
// Set all fallback probes but one to invalid URLs to verify they are being skipped
setFallbackUrl(TEST_FALLBACK_URL);
setOtherFallbackUrls(TEST_FALLBACK_URL + "," + TEST_OTHER_FALLBACK_URL);
setSslException(mHttpsConnection);
setStatus(mHttpConnection, 500);
setStatus(mFallbackConnection, 500);
setPortal302(mOtherFallbackConnection);
// TEST_OTHER_FALLBACK_URL is third
when(mRandom.nextInt()).thenReturn(2);
final NetworkMonitor monitor = makeMonitor();
// First check always uses the first fallback URL: inconclusive
assertFailed(monitor.isCaptivePortal());
verify(mFallbackConnection, times(1)).getResponseCode();
verify(mOtherFallbackConnection, never()).getResponseCode();
// Second check uses the URL chosen by Random
assertPortal(monitor.isCaptivePortal());
verify(mOtherFallbackConnection, times(1)).getResponseCode();
}
@Test
public void testIsCaptivePortal_AllProbesFailed() throws IOException {
setSslException(mHttpsConnection);
setStatus(mHttpConnection, 500);
setStatus(mFallbackConnection, 404);
assertFailed(makeMonitor().isCaptivePortal());
verify(mFallbackConnection, times(1)).getResponseCode();
verify(mOtherFallbackConnection, never()).getResponseCode();
}
@Test
public void testIsCaptivePortal_InvalidUrlSkipped() throws IOException {
setFallbackUrl("invalid");
setOtherFallbackUrls("otherinvalid," + TEST_OTHER_FALLBACK_URL + ",yetanotherinvalid");
setSslException(mHttpsConnection);
setStatus(mHttpConnection, 500);
setPortal302(mOtherFallbackConnection);
assertPortal(makeMonitor().isCaptivePortal());
verify(mOtherFallbackConnection, times(1)).getResponseCode();
verify(mFallbackConnection, never()).getResponseCode();
}
private void setupFallbackSpec() throws IOException {
setFallbackSpecs("http://example.com@@/@@204@@/@@"
+ "@@,@@"
+ TEST_OTHER_FALLBACK_URL + "@@/@@30[12]@@/@@https://(www\\.)?google.com/?.*");
setSslException(mHttpsConnection);
setStatus(mHttpConnection, 500);
// Use the 2nd fallback spec
when(mRandom.nextInt()).thenReturn(1);
}
@Test
public void testIsCaptivePortal_FallbackSpecIsNotPortal() throws IOException {
setupFallbackSpec();
set302(mOtherFallbackConnection, "https://www.google.com/test?q=3");
// HTTPS failed, fallback spec did not see a portal -> inconclusive
assertFailed(makeMonitor().isCaptivePortal());
verify(mOtherFallbackConnection, times(1)).getResponseCode();
verify(mFallbackConnection, never()).getResponseCode();
}
@Test
public void testIsCaptivePortal_FallbackSpecIsPortal() throws IOException {
setupFallbackSpec();
set302(mOtherFallbackConnection, "http://login.portal.example.com");
assertPortal(makeMonitor().isCaptivePortal());
}
private void setFallbackUrl(String url) {
when(mDependencies.getSetting(any(),
eq(Settings.Global.CAPTIVE_PORTAL_FALLBACK_URL), any())).thenReturn(url);
}
private void setOtherFallbackUrls(String urls) {
when(mDependencies.getSetting(any(),
eq(Settings.Global.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS), any())).thenReturn(urls);
}
private void setFallbackSpecs(String specs) {
when(mDependencies.getSetting(any(),
eq(Settings.Global.CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS), any())).thenReturn(specs);
}
private void assertPortal(CaptivePortalProbeResult result) {
assertTrue(result.isPortal());
assertFalse(result.isFailed());
assertFalse(result.isSuccessful());
}
private void assertNotPortal(CaptivePortalProbeResult result) {
assertFalse(result.isPortal());
assertFalse(result.isFailed());
assertTrue(result.isSuccessful());
}
private void assertFailed(CaptivePortalProbeResult result) {
assertFalse(result.isPortal());
assertTrue(result.isFailed());
assertFalse(result.isSuccessful());
}
private void setSslException(HttpURLConnection connection) throws IOException {
when(connection.getResponseCode()).thenThrow(new SSLHandshakeException("Invalid cert"));
}
private void set302(HttpURLConnection connection, String location) throws IOException {
setStatus(connection, 302);
when(connection.getHeaderField(LOCATION_HEADER)).thenReturn(location);
}
private void setPortal302(HttpURLConnection connection) throws IOException {
set302(connection, "http://login.example.com");
}
private void setStatus(HttpURLConnection connection, int status) throws IOException {
when(connection.getResponseCode()).thenReturn(status);
}
}