blob: b399b0d51577d5a581b78bcac91f93f93152185c [file] [log] [blame]
Hugo Benichic894b122017-07-31 12:58:20 +09001/*
2 * Copyright (C) 2017 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
17package com.android.server.connectivity;
18
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090019import static junit.framework.Assert.assertFalse;
20
21import static org.junit.Assert.assertTrue;
Hugo Benichic894b122017-07-31 12:58:20 +090022import static org.junit.Assert.fail;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090023import static org.mockito.ArgumentMatchers.anyString;
24import static org.mockito.ArgumentMatchers.eq;
Hugo Benichic894b122017-07-31 12:58:20 +090025import static org.mockito.Mockito.any;
26import static org.mockito.Mockito.anyInt;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090027import static org.mockito.Mockito.never;
28import static org.mockito.Mockito.times;
Hugo Benichic894b122017-07-31 12:58:20 +090029import static org.mockito.Mockito.verify;
30import static org.mockito.Mockito.when;
31
32import android.content.Context;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090033import android.net.ConnectivityManager;
34import android.net.LinkProperties;
Hugo Benichic894b122017-07-31 12:58:20 +090035import android.net.Network;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090036import android.net.NetworkCapabilities;
37import android.net.NetworkInfo;
Hugo Benichic894b122017-07-31 12:58:20 +090038import android.net.NetworkRequest;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090039import android.net.captiveportal.CaptivePortalProbeResult;
Hugo Benichic894b122017-07-31 12:58:20 +090040import android.net.metrics.IpConnectivityLog;
41import android.net.wifi.WifiManager;
42import android.os.Handler;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090043import android.provider.Settings;
Hugo Benichic894b122017-07-31 12:58:20 +090044import android.support.test.filters.SmallTest;
45import android.support.test.runner.AndroidJUnit4;
46import android.telephony.TelephonyManager;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090047import android.util.ArrayMap;
Hugo Benichic894b122017-07-31 12:58:20 +090048
49import org.junit.Before;
50import org.junit.Test;
51import org.junit.runner.RunWith;
52import org.mockito.Mock;
53import org.mockito.MockitoAnnotations;
54
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090055import java.io.IOException;
56import java.net.HttpURLConnection;
57import java.net.InetAddress;
58import java.net.URL;
59import java.util.Random;
60
61import javax.net.ssl.SSLHandshakeException;
62
Hugo Benichic894b122017-07-31 12:58:20 +090063
64@RunWith(AndroidJUnit4.class)
65@SmallTest
66public class NetworkMonitorTest {
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090067 private static final String LOCATION_HEADER = "location";
Hugo Benichic894b122017-07-31 12:58:20 +090068
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090069 private @Mock Context mContext;
70 private @Mock Handler mHandler;
71 private @Mock IpConnectivityLog mLogger;
72 private @Mock NetworkAgentInfo mAgent;
73 private @Mock NetworkInfo mNetworkInfo;
74 private @Mock NetworkRequest mRequest;
75 private @Mock TelephonyManager mTelephony;
76 private @Mock WifiManager mWifi;
77 private @Mock Network mNetwork;
78 private @Mock HttpURLConnection mHttpConnection;
79 private @Mock HttpURLConnection mHttpsConnection;
80 private @Mock HttpURLConnection mFallbackConnection;
81 private @Mock HttpURLConnection mOtherFallbackConnection;
82 private @Mock Random mRandom;
83 private @Mock NetworkMonitor.Dependencies mDependencies;
Hugo Benichic894b122017-07-31 12:58:20 +090084
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090085 private static final String TEST_HTTP_URL = "http://www.google.com/gen_204";
86 private static final String TEST_HTTPS_URL = "https://www.google.com/gen_204";
87 private static final String TEST_FALLBACK_URL = "http://fallback.google.com/gen_204";
88 private static final String TEST_OTHER_FALLBACK_URL = "http://otherfallback.google.com/gen_204";
Hugo Benichic894b122017-07-31 12:58:20 +090089
90 @Before
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090091 public void setUp() throws IOException {
Hugo Benichic894b122017-07-31 12:58:20 +090092 MockitoAnnotations.initMocks(this);
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090093 mAgent.linkProperties = new LinkProperties();
94 mAgent.networkCapabilities = new NetworkCapabilities()
95 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
96 mAgent.networkInfo = mNetworkInfo;
Hugo Benichic894b122017-07-31 12:58:20 +090097
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090098 when(mAgent.network()).thenReturn(mNetwork);
99 when(mDependencies.getNetwork(any())).thenReturn(mNetwork);
100 when(mDependencies.getRandom()).thenReturn(mRandom);
101 when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt()))
102 .thenReturn(Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT);
103 when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_USE_HTTPS),
104 anyInt())).thenReturn(1);
105 when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTP_URL),
106 anyString())).thenReturn(TEST_HTTP_URL);
107 when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTPS_URL),
108 anyString())).thenReturn(TEST_HTTPS_URL);
Erik Klinef4fa9822018-04-27 22:48:33 +0900109 when(mNetwork.getPrivateDnsBypassingCopy()).thenReturn(mNetwork);
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900110
Hugo Benichic894b122017-07-31 12:58:20 +0900111 when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephony);
112 when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mWifi);
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900113
114 when(mNetworkInfo.getType()).thenReturn(ConnectivityManager.TYPE_WIFI);
115 setFallbackUrl(TEST_FALLBACK_URL);
116 setOtherFallbackUrls(TEST_OTHER_FALLBACK_URL);
117 setFallbackSpecs(null); // Test with no fallback spec by default
118 when(mRandom.nextInt()).thenReturn(0);
119
120 when(mNetwork.openConnection(any())).then((invocation) -> {
121 URL url = invocation.getArgument(0);
122 switch(url.toString()) {
123 case TEST_HTTP_URL:
124 return mHttpConnection;
125 case TEST_HTTPS_URL:
126 return mHttpsConnection;
127 case TEST_FALLBACK_URL:
128 return mFallbackConnection;
129 case TEST_OTHER_FALLBACK_URL:
130 return mOtherFallbackConnection;
131 default:
132 fail("URL not mocked: " + url.toString());
133 return null;
134 }
135 });
136 when(mHttpConnection.getRequestProperties()).thenReturn(new ArrayMap<>());
137 when(mHttpsConnection.getRequestProperties()).thenReturn(new ArrayMap<>());
138 when(mNetwork.getAllByName(any())).thenReturn(new InetAddress[] {
139 InetAddress.parseNumericAddress("192.168.0.0")
140 });
Hugo Benichic894b122017-07-31 12:58:20 +0900141 }
142
143 NetworkMonitor makeMonitor() {
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900144 return new NetworkMonitor(
145 mContext, mHandler, mAgent, mRequest, mLogger, mDependencies);
Hugo Benichic894b122017-07-31 12:58:20 +0900146 }
147
148 @Test
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900149 public void testIsCaptivePortal_HttpProbeIsPortal() throws IOException {
150 setSslException(mHttpsConnection);
151 setPortal302(mHttpConnection);
152
153 assertPortal(makeMonitor().isCaptivePortal());
154 }
155
156 @Test
157 public void testIsCaptivePortal_HttpsProbeIsNotPortal() throws IOException {
158 setStatus(mHttpsConnection, 204);
159 setStatus(mHttpConnection, 500);
160
161 assertNotPortal(makeMonitor().isCaptivePortal());
162 }
163
164 @Test
165 public void testIsCaptivePortal_HttpsProbeFailedHttpSuccessNotUsed() throws IOException {
166 setSslException(mHttpsConnection);
167 // Even if HTTP returns a 204, do not use the result unless HTTPS succeeded
168 setStatus(mHttpConnection, 204);
169 setStatus(mFallbackConnection, 500);
170
171 assertFailed(makeMonitor().isCaptivePortal());
172 }
173
174 @Test
175 public void testIsCaptivePortal_FallbackProbeIsPortal() throws IOException {
176 setSslException(mHttpsConnection);
177 setStatus(mHttpConnection, 500);
178 setPortal302(mFallbackConnection);
179
180 assertPortal(makeMonitor().isCaptivePortal());
181 }
182
183 @Test
184 public void testIsCaptivePortal_FallbackProbeIsNotPortal() throws IOException {
185 setSslException(mHttpsConnection);
186 setStatus(mHttpConnection, 500);
187 setStatus(mFallbackConnection, 204);
188
189 // Fallback probe did not see portal, HTTPS failed -> inconclusive
190 assertFailed(makeMonitor().isCaptivePortal());
191 }
192
193 @Test
194 public void testIsCaptivePortal_OtherFallbackProbeIsPortal() throws IOException {
195 // Set all fallback probes but one to invalid URLs to verify they are being skipped
196 setFallbackUrl(TEST_FALLBACK_URL);
197 setOtherFallbackUrls(TEST_FALLBACK_URL + "," + TEST_OTHER_FALLBACK_URL);
198
199 setSslException(mHttpsConnection);
200 setStatus(mHttpConnection, 500);
201 setStatus(mFallbackConnection, 500);
202 setPortal302(mOtherFallbackConnection);
203
204 // TEST_OTHER_FALLBACK_URL is third
205 when(mRandom.nextInt()).thenReturn(2);
206
207 final NetworkMonitor monitor = makeMonitor();
208
209 // First check always uses the first fallback URL: inconclusive
210 assertFailed(monitor.isCaptivePortal());
211 verify(mFallbackConnection, times(1)).getResponseCode();
212 verify(mOtherFallbackConnection, never()).getResponseCode();
213
214 // Second check uses the URL chosen by Random
215 assertPortal(monitor.isCaptivePortal());
216 verify(mOtherFallbackConnection, times(1)).getResponseCode();
217 }
218
219 @Test
220 public void testIsCaptivePortal_AllProbesFailed() throws IOException {
221 setSslException(mHttpsConnection);
222 setStatus(mHttpConnection, 500);
223 setStatus(mFallbackConnection, 404);
224
225 assertFailed(makeMonitor().isCaptivePortal());
226 verify(mFallbackConnection, times(1)).getResponseCode();
227 verify(mOtherFallbackConnection, never()).getResponseCode();
228 }
229
230 @Test
231 public void testIsCaptivePortal_InvalidUrlSkipped() throws IOException {
232 setFallbackUrl("invalid");
233 setOtherFallbackUrls("otherinvalid," + TEST_OTHER_FALLBACK_URL + ",yetanotherinvalid");
234
235 setSslException(mHttpsConnection);
236 setStatus(mHttpConnection, 500);
237 setPortal302(mOtherFallbackConnection);
238
239 assertPortal(makeMonitor().isCaptivePortal());
240 verify(mOtherFallbackConnection, times(1)).getResponseCode();
241 verify(mFallbackConnection, never()).getResponseCode();
242 }
243
244 private void setupFallbackSpec() throws IOException {
245 setFallbackSpecs("http://example.com@@/@@204@@/@@"
246 + "@@,@@"
247 + TEST_OTHER_FALLBACK_URL + "@@/@@30[12]@@/@@https://(www\\.)?google.com/?.*");
248
249 setSslException(mHttpsConnection);
250 setStatus(mHttpConnection, 500);
251
252 // Use the 2nd fallback spec
253 when(mRandom.nextInt()).thenReturn(1);
254 }
255
256 @Test
257 public void testIsCaptivePortal_FallbackSpecIsNotPortal() throws IOException {
258 setupFallbackSpec();
259 set302(mOtherFallbackConnection, "https://www.google.com/test?q=3");
260
261 // HTTPS failed, fallback spec did not see a portal -> inconclusive
262 assertFailed(makeMonitor().isCaptivePortal());
263 verify(mOtherFallbackConnection, times(1)).getResponseCode();
264 verify(mFallbackConnection, never()).getResponseCode();
265 }
266
267 @Test
268 public void testIsCaptivePortal_FallbackSpecIsPortal() throws IOException {
269 setupFallbackSpec();
270 set302(mOtherFallbackConnection, "http://login.portal.example.com");
271
272 assertPortal(makeMonitor().isCaptivePortal());
273 }
274
275 private void setFallbackUrl(String url) {
276 when(mDependencies.getSetting(any(),
277 eq(Settings.Global.CAPTIVE_PORTAL_FALLBACK_URL), any())).thenReturn(url);
278 }
279
280 private void setOtherFallbackUrls(String urls) {
281 when(mDependencies.getSetting(any(),
282 eq(Settings.Global.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS), any())).thenReturn(urls);
283 }
284
285 private void setFallbackSpecs(String specs) {
286 when(mDependencies.getSetting(any(),
287 eq(Settings.Global.CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS), any())).thenReturn(specs);
288 }
289
290 private void assertPortal(CaptivePortalProbeResult result) {
291 assertTrue(result.isPortal());
292 assertFalse(result.isFailed());
293 assertFalse(result.isSuccessful());
294 }
295
296 private void assertNotPortal(CaptivePortalProbeResult result) {
297 assertFalse(result.isPortal());
298 assertFalse(result.isFailed());
299 assertTrue(result.isSuccessful());
300 }
301
302 private void assertFailed(CaptivePortalProbeResult result) {
303 assertFalse(result.isPortal());
304 assertTrue(result.isFailed());
305 assertFalse(result.isSuccessful());
306 }
307
308 private void setSslException(HttpURLConnection connection) throws IOException {
309 when(connection.getResponseCode()).thenThrow(new SSLHandshakeException("Invalid cert"));
310 }
311
312 private void set302(HttpURLConnection connection, String location) throws IOException {
313 setStatus(connection, 302);
314 when(connection.getHeaderField(LOCATION_HEADER)).thenReturn(location);
315 }
316
317 private void setPortal302(HttpURLConnection connection) throws IOException {
318 set302(connection, "http://login.example.com");
319 }
320
321 private void setStatus(HttpURLConnection connection, int status) throws IOException {
322 when(connection.getResponseCode()).thenReturn(status);
Hugo Benichic894b122017-07-31 12:58:20 +0900323 }
324}
325