blob: a24ed5a715f3c7f52b7324b5b5b07826e772a1c1 [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 VANcfff01e2019-02-13 20:58:59 +090019import static android.net.CaptivePortal.APP_RETURN_DISMISSED;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +090020import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
lucasline252a742019-03-12 13:08:03 +080021import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +090022import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID;
23import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
Chiachang Wang0b984412019-04-08 19:06:21 +080024import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD;
25import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_EVALUATION_TYPE;
26import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_MIN_EVALUATE_INTERVAL;
27import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_VALID_DNS_TIME_THRESHOLD;
28import static android.net.util.DataStallUtils.DATA_STALL_EVALUATION_TYPE_DNS;
Chiachang Wang98b02db2019-04-18 01:25:00 -070029import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS;
30import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS;
31import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_USE_HTTPS;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +090032
33import static junit.framework.Assert.assertEquals;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090034import static junit.framework.Assert.assertFalse;
35
Lorenzo Colitti28c966e2019-04-23 09:57:47 -070036import static org.junit.Assert.assertArrayEquals;
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +090037import static org.junit.Assert.assertNotNull;
38import static org.junit.Assert.assertNull;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090039import static org.junit.Assert.assertTrue;
Hugo Benichic894b122017-07-31 12:58:20 +090040import static org.junit.Assert.fail;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090041import static org.mockito.ArgumentMatchers.eq;
Hugo Benichic894b122017-07-31 12:58:20 +090042import static org.mockito.Mockito.any;
43import static org.mockito.Mockito.anyInt;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +090044import static org.mockito.Mockito.doAnswer;
45import static org.mockito.Mockito.doReturn;
46import static org.mockito.Mockito.doThrow;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090047import static org.mockito.Mockito.never;
Lorenzo Colittiab3611b2019-05-09 05:27:29 -070048import static org.mockito.Mockito.reset;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +090049import static org.mockito.Mockito.timeout;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090050import static org.mockito.Mockito.times;
Hugo Benichic894b122017-07-31 12:58:20 +090051import static org.mockito.Mockito.verify;
52import static org.mockito.Mockito.when;
53
Chiachang Wang95489ca2019-02-26 11:32:18 +080054import android.annotation.NonNull;
Lorenzo Colitti5beebde2019-04-10 02:57:17 -070055import android.content.BroadcastReceiver;
Hugo Benichic894b122017-07-31 12:58:20 +090056import android.content.Context;
Lorenzo Colitti5beebde2019-04-10 02:57:17 -070057import android.content.Intent;
Remi NGUYEN VAN9ca4c622019-04-09 02:04:54 -070058import android.content.res.Resources;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090059import android.net.ConnectivityManager;
Lorenzo Colitti28c966e2019-04-23 09:57:47 -070060import android.net.DnsResolver;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +090061import android.net.INetworkMonitorCallbacks;
62import android.net.InetAddresses;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090063import android.net.LinkProperties;
Hugo Benichic894b122017-07-31 12:58:20 +090064import android.net.Network;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090065import android.net.NetworkCapabilities;
66import android.net.NetworkInfo;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090067import android.net.captiveportal.CaptivePortalProbeResult;
Hugo Benichic894b122017-07-31 12:58:20 +090068import android.net.metrics.IpConnectivityLog;
Lorenzo Colittiab3611b2019-05-09 05:27:29 -070069import android.net.shared.PrivateDnsConfig;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +090070import android.net.util.SharedLog;
Chiachang Wang95489ca2019-02-26 11:32:18 +080071import android.net.wifi.WifiInfo;
Hugo Benichic894b122017-07-31 12:58:20 +090072import android.net.wifi.WifiManager;
Remi NGUYEN VANcfff01e2019-02-13 20:58:59 +090073import android.os.Bundle;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +090074import android.os.ConditionVariable;
Hugo Benichic894b122017-07-31 12:58:20 +090075import android.os.Handler;
Lorenzo Colitti28c966e2019-04-23 09:57:47 -070076import android.os.Looper;
Lorenzo Colittiab3611b2019-05-09 05:27:29 -070077import android.os.Process;
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +090078import android.os.RemoteException;
Chiachang Wang7a70a7e2018-11-27 18:00:05 +080079import android.os.SystemClock;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090080import android.provider.Settings;
Chiachang Wang95489ca2019-02-26 11:32:18 +080081import android.telephony.CellSignalStrength;
Hugo Benichic894b122017-07-31 12:58:20 +090082import android.telephony.TelephonyManager;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +090083import android.util.ArrayMap;
Hugo Benichic894b122017-07-31 12:58:20 +090084
Brett Chabot8091d9e2019-02-26 14:52:33 -080085import androidx.test.filters.SmallTest;
86import androidx.test.runner.AndroidJUnit4;
87
Lorenzo Colitti28c966e2019-04-23 09:57:47 -070088import com.android.networkstack.R;
Chiachang Wange512b262019-04-11 21:24:28 +080089import com.android.networkstack.metrics.DataStallDetectionStats;
90import com.android.networkstack.metrics.DataStallStatsUtils;
91
Lorenzo Colitti5beebde2019-04-10 02:57:17 -070092import org.junit.After;
Hugo Benichic894b122017-07-31 12:58:20 +090093import org.junit.Before;
94import org.junit.Test;
95import org.junit.runner.RunWith;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +090096import org.mockito.ArgumentCaptor;
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +090097import org.mockito.Captor;
Hugo Benichic894b122017-07-31 12:58:20 +090098import org.mockito.Mock;
99import org.mockito.MockitoAnnotations;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900100import org.mockito.Spy;
Hugo Benichic894b122017-07-31 12:58:20 +0900101
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900102import java.io.IOException;
103import java.net.HttpURLConnection;
104import java.net.InetAddress;
105import java.net.URL;
Lorenzo Colitti28c966e2019-04-23 09:57:47 -0700106import java.net.UnknownHostException;
107import java.util.ArrayList;
Lorenzo Colitti5beebde2019-04-10 02:57:17 -0700108import java.util.HashSet;
Lorenzo Colitti28c966e2019-04-23 09:57:47 -0700109import java.util.List;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900110import java.util.Random;
Lorenzo Colitti28c966e2019-04-23 09:57:47 -0700111import java.util.concurrent.Executor;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900112
113import javax.net.ssl.SSLHandshakeException;
114
Hugo Benichic894b122017-07-31 12:58:20 +0900115@RunWith(AndroidJUnit4.class)
116@SmallTest
117public class NetworkMonitorTest {
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900118 private static final String LOCATION_HEADER = "location";
Hugo Benichic894b122017-07-31 12:58:20 +0900119
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900120 private @Mock Context mContext;
Remi NGUYEN VAN9ca4c622019-04-09 02:04:54 -0700121 private @Mock Resources mResources;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900122 private @Mock IpConnectivityLog mLogger;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900123 private @Mock SharedLog mValidationLogger;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900124 private @Mock NetworkInfo mNetworkInfo;
Lorenzo Colitti28c966e2019-04-23 09:57:47 -0700125 private @Mock DnsResolver mDnsResolver;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900126 private @Mock ConnectivityManager mCm;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900127 private @Mock TelephonyManager mTelephony;
128 private @Mock WifiManager mWifi;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900129 private @Mock HttpURLConnection mHttpConnection;
130 private @Mock HttpURLConnection mHttpsConnection;
131 private @Mock HttpURLConnection mFallbackConnection;
132 private @Mock HttpURLConnection mOtherFallbackConnection;
133 private @Mock Random mRandom;
134 private @Mock NetworkMonitor.Dependencies mDependencies;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900135 private @Mock INetworkMonitorCallbacks mCallbacks;
136 private @Spy Network mNetwork = new Network(TEST_NETID);
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700137 private @Mock Network mNonPrivateDnsBypassNetwork;
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800138 private @Mock DataStallStatsUtils mDataStallStatsUtils;
Chiachang Wang95489ca2019-02-26 11:32:18 +0800139 private @Mock WifiInfo mWifiInfo;
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900140 private @Captor ArgumentCaptor<String> mNetworkTestedRedirectUrlCaptor;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900141
Lorenzo Colitti5beebde2019-04-10 02:57:17 -0700142 private HashSet<WrappedNetworkMonitor> mCreatedNetworkMonitors;
143 private HashSet<BroadcastReceiver> mRegisteredReceivers;
144
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900145 private static final int TEST_NETID = 4242;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900146 private static final String TEST_HTTP_URL = "http://www.google.com/gen_204";
147 private static final String TEST_HTTPS_URL = "https://www.google.com/gen_204";
148 private static final String TEST_FALLBACK_URL = "http://fallback.google.com/gen_204";
149 private static final String TEST_OTHER_FALLBACK_URL = "http://otherfallback.google.com/gen_204";
Chiachang Wang95489ca2019-02-26 11:32:18 +0800150 private static final String TEST_MCCMNC = "123456";
Hugo Benichic894b122017-07-31 12:58:20 +0900151
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800152 private static final int RETURN_CODE_DNS_SUCCESS = 0;
153 private static final int RETURN_CODE_DNS_TIMEOUT = 255;
Chiachang Wang95489ca2019-02-26 11:32:18 +0800154 private static final int DEFAULT_DNS_TIMEOUT_THRESHOLD = 5;
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800155
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900156 private static final int HANDLER_TIMEOUT_MS = 1000;
157
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900158 private static final LinkProperties TEST_LINK_PROPERTIES = new LinkProperties();
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900159
160 private static final NetworkCapabilities METERED_CAPABILITIES = new NetworkCapabilities()
161 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
162 .addCapability(NET_CAPABILITY_INTERNET);
163
164 private static final NetworkCapabilities NOT_METERED_CAPABILITIES = new NetworkCapabilities()
165 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
166 .addCapability(NET_CAPABILITY_INTERNET)
167 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
168
169 private static final NetworkCapabilities NO_INTERNET_CAPABILITIES = new NetworkCapabilities()
170 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
171
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700172 /**
173 * Fakes DNS responses.
174 *
175 * Allows test methods to configure the IP addresses that will be resolved by
176 * Network#getAllByName and by DnsResolver#query.
177 */
178 class FakeDns {
179 private final ArrayMap<String, List<InetAddress>> mAnswers = new ArrayMap<>();
180 private boolean mNonBypassPrivateDnsWorking = true;
181
182 /** Whether DNS queries on mNonBypassPrivateDnsWorking should succeed. */
183 private void setNonBypassPrivateDnsWorking(boolean working) {
184 mNonBypassPrivateDnsWorking = working;
Lorenzo Colitti28c966e2019-04-23 09:57:47 -0700185 }
186
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700187 /** Clears all DNS entries. */
188 private synchronized void clearAll() {
189 mAnswers.clear();
Lorenzo Colitti28c966e2019-04-23 09:57:47 -0700190 }
Lorenzo Colitti28c966e2019-04-23 09:57:47 -0700191
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700192 /** Returns the answer for a given name on the given mock network. */
193 private synchronized List<InetAddress> getAnswer(Object mock, String hostname) {
194 if (mock == mNonPrivateDnsBypassNetwork && !mNonBypassPrivateDnsWorking) {
195 return null;
196 }
197 if (mAnswers.containsKey(hostname)) {
198 return mAnswers.get(hostname);
199 }
200 return mAnswers.get("*");
201 }
Lorenzo Colitti28c966e2019-04-23 09:57:47 -0700202
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700203 /** Sets the answer for a given name. */
204 private synchronized void setAnswer(String hostname, String[] answer)
205 throws UnknownHostException {
206 if (answer == null) {
207 mAnswers.remove(hostname);
208 } else {
209 List<InetAddress> answerList = new ArrayList<>();
210 for (String addr : answer) {
211 answerList.add(InetAddresses.parseNumericAddress(addr));
212 }
213 mAnswers.put(hostname, answerList);
214 }
215 }
216
217 /** Simulates a getAllByName call for the specified name on the specified mock network. */
218 private InetAddress[] getAllByName(Object mock, String hostname)
219 throws UnknownHostException {
220 List<InetAddress> answer = getAnswer(mock, hostname);
221 if (answer == null || answer.size() == 0) {
222 throw new UnknownHostException(hostname);
223 }
224 return answer.toArray(new InetAddress[0]);
225 }
226
227 /** Starts mocking DNS queries. */
228 private void startMocking() throws UnknownHostException {
229 // Queries on mNetwork (i.e., bypassing private DNS) using getAllByName.
230 doAnswer(invocation -> {
231 return getAllByName(invocation.getMock(), invocation.getArgument(0));
232 }).when(mNetwork).getAllByName(any());
233
234 // Queries on mNonBypassPrivateDnsNetwork using getAllByName.
235 doAnswer(invocation -> {
236 return getAllByName(invocation.getMock(), invocation.getArgument(0));
237 }).when(mNonPrivateDnsBypassNetwork).getAllByName(any());
238
239 // Queries on mNetwork (i.e., bypassing private DNS) using DnsResolver#query.
240 doAnswer(invocation -> {
241 String hostname = (String) invocation.getArgument(1);
242 Executor executor = (Executor) invocation.getArgument(3);
243 DnsResolver.Callback<List<InetAddress>> callback = invocation.getArgument(5);
244
245 List<InetAddress> answer = getAnswer(invocation.getMock(), hostname);
246 if (answer != null && answer.size() > 0) {
247 new Handler(Looper.getMainLooper()).post(() -> {
248 executor.execute(() -> callback.onAnswer(answer, 0));
249 });
250 }
251 // If no answers, do nothing. sendDnsProbeWithTimeout will time out and throw UHE.
252 return null;
253 }).when(mDnsResolver).query(any(), any(), anyInt(), any(), any(), any());
254 }
Lorenzo Colitti28c966e2019-04-23 09:57:47 -0700255 }
256
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700257 private FakeDns mFakeDns;
258
Hugo Benichic894b122017-07-31 12:58:20 +0900259 @Before
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900260 public void setUp() throws IOException {
Hugo Benichic894b122017-07-31 12:58:20 +0900261 MockitoAnnotations.initMocks(this);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900262 when(mDependencies.getPrivateDnsBypassNetwork(any())).thenReturn(mNetwork);
Lorenzo Colitti28c966e2019-04-23 09:57:47 -0700263 when(mDependencies.getDnsResolver()).thenReturn(mDnsResolver);
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900264 when(mDependencies.getRandom()).thenReturn(mRandom);
265 when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt()))
266 .thenReturn(Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT);
Chiachang Wang98b02db2019-04-18 01:25:00 -0700267 when(mDependencies.getDeviceConfigPropertyInt(any(), eq(CAPTIVE_PORTAL_USE_HTTPS),
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900268 anyInt())).thenReturn(1);
Remi NGUYEN VAN9ca4c622019-04-09 02:04:54 -0700269 when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTP_URL), any()))
270 .thenReturn(TEST_HTTP_URL);
271 when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTPS_URL), any()))
272 .thenReturn(TEST_HTTPS_URL);
273
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700274 doReturn(mNetwork).when(mNonPrivateDnsBypassNetwork).getPrivateDnsBypassingCopy();
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900275
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900276 when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mCm);
Hugo Benichic894b122017-07-31 12:58:20 +0900277 when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephony);
278 when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mWifi);
Remi NGUYEN VAN9ca4c622019-04-09 02:04:54 -0700279 when(mContext.getResources()).thenReturn(mResources);
280
281 when(mResources.getString(anyInt())).thenReturn("");
282 when(mResources.getStringArray(anyInt())).thenReturn(new String[0]);
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900283
284 when(mNetworkInfo.getType()).thenReturn(ConnectivityManager.TYPE_WIFI);
285 setFallbackUrl(TEST_FALLBACK_URL);
286 setOtherFallbackUrls(TEST_OTHER_FALLBACK_URL);
287 setFallbackSpecs(null); // Test with no fallback spec by default
288 when(mRandom.nextInt()).thenReturn(0);
289
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700290 when(mResources.getInteger(eq(R.integer.config_captive_portal_dns_probe_timeout)))
291 .thenReturn(500);
292
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900293 doAnswer((invocation) -> {
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900294 URL url = invocation.getArgument(0);
295 switch(url.toString()) {
296 case TEST_HTTP_URL:
297 return mHttpConnection;
298 case TEST_HTTPS_URL:
299 return mHttpsConnection;
300 case TEST_FALLBACK_URL:
301 return mFallbackConnection;
302 case TEST_OTHER_FALLBACK_URL:
303 return mOtherFallbackConnection;
304 default:
305 fail("URL not mocked: " + url.toString());
306 return null;
307 }
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900308 }).when(mNetwork).openConnection(any());
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900309 when(mHttpConnection.getRequestProperties()).thenReturn(new ArrayMap<>());
310 when(mHttpsConnection.getRequestProperties()).thenReturn(new ArrayMap<>());
Lorenzo Colitti28c966e2019-04-23 09:57:47 -0700311
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700312 mFakeDns = new FakeDns();
313 mFakeDns.startMocking();
314 mFakeDns.setAnswer("*", new String[]{"2001:db8::1", "192.0.2.2"});
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900315
Lorenzo Colitti5beebde2019-04-10 02:57:17 -0700316 when(mContext.registerReceiver(any(BroadcastReceiver.class), any())).then((invocation) -> {
317 mRegisteredReceivers.add(invocation.getArgument(0));
318 return new Intent();
319 });
320
321 doAnswer((invocation) -> {
322 mRegisteredReceivers.remove(invocation.getArgument(0));
323 return null;
324 }).when(mContext).unregisterReceiver(any());
325
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800326 setMinDataStallEvaluateInterval(500);
Chiachang Wang4349dc02019-03-05 20:31:57 +0800327 setDataStallEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS);
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800328 setValidDataStallDnsTimeThreshold(500);
329 setConsecutiveDnsTimeoutThreshold(5);
Lorenzo Colitti5beebde2019-04-10 02:57:17 -0700330
331 mCreatedNetworkMonitors = new HashSet<>();
332 mRegisteredReceivers = new HashSet<>();
333 }
334
335 @After
336 public void tearDown() {
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700337 mFakeDns.clearAll();
Lorenzo Colitti5beebde2019-04-10 02:57:17 -0700338 assertTrue(mCreatedNetworkMonitors.size() > 0);
339 // Make a local copy of mCreatedNetworkMonitors because during the iteration below,
340 // WrappedNetworkMonitor#onQuitting will delete elements from it on the handler threads.
341 WrappedNetworkMonitor[] networkMonitors = mCreatedNetworkMonitors.toArray(
342 new WrappedNetworkMonitor[0]);
343 for (WrappedNetworkMonitor nm : networkMonitors) {
344 nm.notifyNetworkDisconnected();
345 nm.awaitQuit();
346 }
347 assertEquals("NetworkMonitor still running after disconnect",
348 0, mCreatedNetworkMonitors.size());
349 assertEquals("BroadcastReceiver still registered after disconnect",
350 0, mRegisteredReceivers.size());
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800351 }
352
353 private class WrappedNetworkMonitor extends NetworkMonitor {
354 private long mProbeTime = 0;
Lorenzo Colitti5beebde2019-04-10 02:57:17 -0700355 private final ConditionVariable mQuitCv = new ConditionVariable(false);
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800356
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900357 WrappedNetworkMonitor() {
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700358 super(mContext, mCallbacks, mNonPrivateDnsBypassNetwork, mLogger, mValidationLogger,
359 mDependencies, mDataStallStatsUtils);
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800360 }
361
362 @Override
363 protected long getLastProbeTime() {
364 return mProbeTime;
365 }
366
367 protected void setLastProbeTime(long time) {
368 mProbeTime = time;
369 }
Chiachang Wang95489ca2019-02-26 11:32:18 +0800370
371 @Override
372 protected void addDnsEvents(@NonNull final DataStallDetectionStats.Builder stats) {
373 generateTimeoutDnsEvent(stats, DEFAULT_DNS_TIMEOUT_THRESHOLD);
374 }
Lorenzo Colitti5beebde2019-04-10 02:57:17 -0700375
376 @Override
377 protected void onQuitting() {
378 assertTrue(mCreatedNetworkMonitors.remove(this));
379 mQuitCv.open();
380 }
381
382 protected void awaitQuit() {
383 assertTrue("NetworkMonitor did not quit after " + HANDLER_TIMEOUT_MS + "ms",
384 mQuitCv.block(HANDLER_TIMEOUT_MS));
385 }
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800386 }
387
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700388 private WrappedNetworkMonitor makeMonitor(NetworkCapabilities nc) {
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900389 final WrappedNetworkMonitor nm = new WrappedNetworkMonitor();
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900390 nm.start();
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700391 setNetworkCapabilities(nm, nc);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900392 waitForIdle(nm.getHandler());
Lorenzo Colitti5beebde2019-04-10 02:57:17 -0700393 mCreatedNetworkMonitors.add(nm);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900394 return nm;
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800395 }
396
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900397 private WrappedNetworkMonitor makeMeteredNetworkMonitor() {
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700398 final WrappedNetworkMonitor nm = makeMonitor(METERED_CAPABILITIES);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900399 return nm;
Hugo Benichic894b122017-07-31 12:58:20 +0900400 }
401
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900402 private WrappedNetworkMonitor makeNotMeteredNetworkMonitor() {
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700403 final WrappedNetworkMonitor nm = makeMonitor(NOT_METERED_CAPABILITIES);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900404 return nm;
405 }
406
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900407 private void setNetworkCapabilities(NetworkMonitor nm, NetworkCapabilities nc) {
408 nm.notifyNetworkCapabilitiesChanged(nc);
409 waitForIdle(nm.getHandler());
410 }
411
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900412 private void waitForIdle(Handler handler) {
413 final ConditionVariable cv = new ConditionVariable(false);
414 handler.post(cv::open);
415 if (!cv.block(HANDLER_TIMEOUT_MS)) {
416 fail("Timed out waiting for handler");
417 }
Hugo Benichic894b122017-07-31 12:58:20 +0900418 }
419
420 @Test
Lorenzo Colitti28c966e2019-04-23 09:57:47 -0700421 public void testGetIntSetting() throws Exception {
422 WrappedNetworkMonitor wnm = makeNotMeteredNetworkMonitor();
423
424 // No config resource, no device config. Expect to get default resource.
425 doThrow(new Resources.NotFoundException())
426 .when(mResources).getInteger(eq(R.integer.config_captive_portal_dns_probe_timeout));
427 doAnswer(invocation -> {
428 int defaultValue = invocation.getArgument(2);
429 return defaultValue;
430 }).when(mDependencies).getDeviceConfigPropertyInt(any(),
431 eq(NetworkMonitor.CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT),
432 anyInt());
433 when(mResources.getInteger(eq(R.integer.default_captive_portal_dns_probe_timeout)))
434 .thenReturn(42);
435 assertEquals(42, wnm.getIntSetting(mContext,
436 R.integer.config_captive_portal_dns_probe_timeout,
437 NetworkMonitor.CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT,
438 R.integer.default_captive_portal_dns_probe_timeout));
439
440 // Set device config. Expect to get device config.
441 when(mDependencies.getDeviceConfigPropertyInt(any(),
442 eq(NetworkMonitor.CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT), anyInt()))
443 .thenReturn(1234);
444 assertEquals(1234, wnm.getIntSetting(mContext,
445 R.integer.config_captive_portal_dns_probe_timeout,
446 NetworkMonitor.CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT,
447 R.integer.default_captive_portal_dns_probe_timeout));
448
449 // Set config resource. Expect to get config resource.
450 when(mResources.getInteger(eq(R.integer.config_captive_portal_dns_probe_timeout)))
451 .thenReturn(5678);
452 assertEquals(5678, wnm.getIntSetting(mContext,
453 R.integer.config_captive_portal_dns_probe_timeout,
454 NetworkMonitor.CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT,
455 R.integer.default_captive_portal_dns_probe_timeout));
456 }
457
458 @Test
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900459 public void testIsCaptivePortal_HttpProbeIsPortal() throws IOException {
460 setSslException(mHttpsConnection);
461 setPortal302(mHttpConnection);
462
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900463 runPortalNetworkTest();
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900464 }
465
466 @Test
467 public void testIsCaptivePortal_HttpsProbeIsNotPortal() throws IOException {
468 setStatus(mHttpsConnection, 204);
469 setStatus(mHttpConnection, 500);
470
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900471 runNotPortalNetworkTest();
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900472 }
473
474 @Test
475 public void testIsCaptivePortal_FallbackProbeIsPortal() throws IOException {
476 setSslException(mHttpsConnection);
477 setStatus(mHttpConnection, 500);
478 setPortal302(mFallbackConnection);
479
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900480 runPortalNetworkTest();
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900481 }
482
483 @Test
484 public void testIsCaptivePortal_FallbackProbeIsNotPortal() throws IOException {
485 setSslException(mHttpsConnection);
486 setStatus(mHttpConnection, 500);
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900487 setStatus(mFallbackConnection, 500);
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900488
489 // Fallback probe did not see portal, HTTPS failed -> inconclusive
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900490 runFailedNetworkTest();
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900491 }
492
493 @Test
494 public void testIsCaptivePortal_OtherFallbackProbeIsPortal() throws IOException {
495 // Set all fallback probes but one to invalid URLs to verify they are being skipped
496 setFallbackUrl(TEST_FALLBACK_URL);
497 setOtherFallbackUrls(TEST_FALLBACK_URL + "," + TEST_OTHER_FALLBACK_URL);
498
499 setSslException(mHttpsConnection);
500 setStatus(mHttpConnection, 500);
501 setStatus(mFallbackConnection, 500);
502 setPortal302(mOtherFallbackConnection);
503
504 // TEST_OTHER_FALLBACK_URL is third
505 when(mRandom.nextInt()).thenReturn(2);
506
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900507 // First check always uses the first fallback URL: inconclusive
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900508 final NetworkMonitor monitor = runNetworkTest(NETWORK_TEST_RESULT_INVALID);
509 assertNull(mNetworkTestedRedirectUrlCaptor.getValue());
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900510 verify(mFallbackConnection, times(1)).getResponseCode();
511 verify(mOtherFallbackConnection, never()).getResponseCode();
512
513 // Second check uses the URL chosen by Random
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900514 final CaptivePortalProbeResult result = monitor.isCaptivePortal();
515 assertTrue(result.isPortal());
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900516 verify(mOtherFallbackConnection, times(1)).getResponseCode();
517 }
518
519 @Test
520 public void testIsCaptivePortal_AllProbesFailed() throws IOException {
521 setSslException(mHttpsConnection);
522 setStatus(mHttpConnection, 500);
523 setStatus(mFallbackConnection, 404);
524
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900525 runFailedNetworkTest();
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900526 verify(mFallbackConnection, times(1)).getResponseCode();
527 verify(mOtherFallbackConnection, never()).getResponseCode();
528 }
529
530 @Test
531 public void testIsCaptivePortal_InvalidUrlSkipped() throws IOException {
532 setFallbackUrl("invalid");
533 setOtherFallbackUrls("otherinvalid," + TEST_OTHER_FALLBACK_URL + ",yetanotherinvalid");
534
535 setSslException(mHttpsConnection);
536 setStatus(mHttpConnection, 500);
537 setPortal302(mOtherFallbackConnection);
538
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900539 runPortalNetworkTest();
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900540 verify(mOtherFallbackConnection, times(1)).getResponseCode();
541 verify(mFallbackConnection, never()).getResponseCode();
542 }
543
544 private void setupFallbackSpec() throws IOException {
545 setFallbackSpecs("http://example.com@@/@@204@@/@@"
546 + "@@,@@"
547 + TEST_OTHER_FALLBACK_URL + "@@/@@30[12]@@/@@https://(www\\.)?google.com/?.*");
548
549 setSslException(mHttpsConnection);
550 setStatus(mHttpConnection, 500);
551
552 // Use the 2nd fallback spec
553 when(mRandom.nextInt()).thenReturn(1);
554 }
555
556 @Test
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900557 public void testIsCaptivePortal_FallbackSpecIsPartial() throws IOException {
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900558 setupFallbackSpec();
559 set302(mOtherFallbackConnection, "https://www.google.com/test?q=3");
560
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900561 // HTTPS failed, fallback spec went through -> partial connectivity
562 runPartialConnectivityNetworkTest();
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900563 verify(mOtherFallbackConnection, times(1)).getResponseCode();
564 verify(mFallbackConnection, never()).getResponseCode();
565 }
566
567 @Test
568 public void testIsCaptivePortal_FallbackSpecIsPortal() throws IOException {
569 setupFallbackSpec();
570 set302(mOtherFallbackConnection, "http://login.portal.example.com");
571
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900572 runPortalNetworkTest();
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900573 }
574
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800575 @Test
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900576 public void testIsCaptivePortal_IgnorePortals() throws IOException {
577 setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE);
578 setSslException(mHttpsConnection);
579 setPortal302(mHttpConnection);
580
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900581 runNotPortalNetworkTest();
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900582 }
583
584 @Test
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800585 public void testIsDataStall_EvaluationDisabled() {
586 setDataStallEvaluationType(0);
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900587 WrappedNetworkMonitor wrappedMonitor = makeMeteredNetworkMonitor();
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800588 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
589 assertFalse(wrappedMonitor.isDataStall());
590 }
591
592 @Test
593 public void testIsDataStall_EvaluationDnsOnNotMeteredNetwork() {
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900594 WrappedNetworkMonitor wrappedMonitor = makeNotMeteredNetworkMonitor();
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800595 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
Chiachang Wang95489ca2019-02-26 11:32:18 +0800596 makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD);
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800597 assertTrue(wrappedMonitor.isDataStall());
598 }
599
600 @Test
601 public void testIsDataStall_EvaluationDnsOnMeteredNetwork() {
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900602 WrappedNetworkMonitor wrappedMonitor = makeMeteredNetworkMonitor();
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800603 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
604 assertFalse(wrappedMonitor.isDataStall());
605
606 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
Chiachang Wang95489ca2019-02-26 11:32:18 +0800607 makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD);
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800608 assertTrue(wrappedMonitor.isDataStall());
609 }
610
611 @Test
612 public void testIsDataStall_EvaluationDnsWithDnsTimeoutCount() {
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900613 WrappedNetworkMonitor wrappedMonitor = makeMeteredNetworkMonitor();
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800614 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
615 makeDnsTimeoutEvent(wrappedMonitor, 3);
616 assertFalse(wrappedMonitor.isDataStall());
617 // Reset consecutive timeout counts.
618 makeDnsSuccessEvent(wrappedMonitor, 1);
619 makeDnsTimeoutEvent(wrappedMonitor, 2);
620 assertFalse(wrappedMonitor.isDataStall());
621
622 makeDnsTimeoutEvent(wrappedMonitor, 3);
623 assertTrue(wrappedMonitor.isDataStall());
624
625 // Set the value to larger than the default dns log size.
626 setConsecutiveDnsTimeoutThreshold(51);
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900627 wrappedMonitor = makeMeteredNetworkMonitor();
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800628 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
629 makeDnsTimeoutEvent(wrappedMonitor, 50);
630 assertFalse(wrappedMonitor.isDataStall());
631
632 makeDnsTimeoutEvent(wrappedMonitor, 1);
633 assertTrue(wrappedMonitor.isDataStall());
634 }
635
636 @Test
637 public void testIsDataStall_EvaluationDnsWithDnsTimeThreshold() {
638 // Test dns events happened in valid dns time threshold.
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900639 WrappedNetworkMonitor wrappedMonitor = makeMeteredNetworkMonitor();
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800640 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
Chiachang Wang95489ca2019-02-26 11:32:18 +0800641 makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD);
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800642 assertFalse(wrappedMonitor.isDataStall());
643 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
644 assertTrue(wrappedMonitor.isDataStall());
645
646 // Test dns events happened before valid dns time threshold.
647 setValidDataStallDnsTimeThreshold(0);
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900648 wrappedMonitor = makeMeteredNetworkMonitor();
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800649 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
Chiachang Wang95489ca2019-02-26 11:32:18 +0800650 makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD);
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800651 assertFalse(wrappedMonitor.isDataStall());
652 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
653 assertFalse(wrappedMonitor.isDataStall());
654 }
655
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900656 @Test
657 public void testBrokenNetworkNotValidated() throws Exception {
658 setSslException(mHttpsConnection);
659 setStatus(mHttpConnection, 500);
660 setStatus(mFallbackConnection, 404);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900661
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900662 runFailedNetworkTest();
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900663 }
664
665 @Test
666 public void testNoInternetCapabilityValidated() throws Exception {
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900667 runNetworkTest(NO_INTERNET_CAPABILITIES, NETWORK_TEST_RESULT_VALID);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900668 verify(mNetwork, never()).openConnection(any());
669 }
670
671 @Test
672 public void testLaunchCaptivePortalApp() throws Exception {
673 setSslException(mHttpsConnection);
674 setPortal302(mHttpConnection);
675
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700676 final NetworkMonitor nm = makeMonitor(METERED_CAPABILITIES);
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900677 nm.notifyNetworkConnected(TEST_LINK_PROPERTIES, METERED_CAPABILITIES);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900678
679 verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
Remi NGUYEN VAN9c5d9642019-02-07 21:29:57 +0900680 .showProvisioningNotification(any(), any());
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900681
Lorenzo Colitti5beebde2019-04-10 02:57:17 -0700682 assertEquals(1, mRegisteredReceivers.size());
683
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900684 // Check that startCaptivePortalApp sends the expected intent.
685 nm.launchCaptivePortalApp();
686
Remi NGUYEN VANcfff01e2019-02-13 20:58:59 +0900687 final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
688 final ArgumentCaptor<Network> networkCaptor = ArgumentCaptor.forClass(Network.class);
689 verify(mCm, timeout(HANDLER_TIMEOUT_MS).times(1))
690 .startCaptivePortalApp(networkCaptor.capture(), bundleCaptor.capture());
691 final Bundle bundle = bundleCaptor.getValue();
692 final Network bundleNetwork = bundle.getParcelable(ConnectivityManager.EXTRA_NETWORK);
693 assertEquals(TEST_NETID, bundleNetwork.netId);
694 // network is passed both in bundle and as parameter, as the bundle is opaque to the
695 // framework and only intended for the captive portal app, but the framework needs
696 // the network to identify the right NetworkMonitor.
697 assertEquals(TEST_NETID, networkCaptor.getValue().netId);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900698
699 // Have the app report that the captive portal is dismissed, and check that we revalidate.
700 setStatus(mHttpsConnection, 204);
701 setStatus(mHttpConnection, 204);
Remi NGUYEN VANcfff01e2019-02-13 20:58:59 +0900702
703 nm.notifyCaptivePortalAppFinished(APP_RETURN_DISMISSED);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900704 verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
705 .notifyNetworkTested(NETWORK_TEST_RESULT_VALID, null);
Lorenzo Colitti5beebde2019-04-10 02:57:17 -0700706
707 assertEquals(0, mRegisteredReceivers.size());
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900708 }
709
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800710 @Test
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700711 public void testPrivateDnsSuccess() throws Exception {
712 setStatus(mHttpsConnection, 204);
713 setStatus(mHttpConnection, 204);
714 mFakeDns.setAnswer("dns.google", new String[]{"2001:db8::53"});
715
716 WrappedNetworkMonitor wnm = makeNotMeteredNetworkMonitor();
717 wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
718 wnm.notifyNetworkConnected(TEST_LINK_PROPERTIES, NOT_METERED_CAPABILITIES);
719 verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
720 .notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), eq(null));
721 }
722
723 @Test
724 public void testPrivateDnsResolutionRetryUpdate() throws Exception {
725 // Set a private DNS hostname that doesn't resolve and expect validation to fail.
726 mFakeDns.setAnswer("dns.google", new String[0]);
727 setStatus(mHttpsConnection, 204);
728 setStatus(mHttpConnection, 204);
729
730 WrappedNetworkMonitor wnm = makeNotMeteredNetworkMonitor();
731 wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
732 wnm.notifyNetworkConnected(TEST_LINK_PROPERTIES, NOT_METERED_CAPABILITIES);
733 verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
734 .notifyNetworkTested(eq(NETWORK_TEST_RESULT_INVALID), eq(null));
735
736 // Fix DNS and retry, expect validation to succeed.
737 reset(mCallbacks);
738 mFakeDns.setAnswer("dns.google", new String[]{"2001:db8::1"});
739
740 wnm.forceReevaluation(Process.myUid());
741 verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
742 .notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), eq(null));
743
744 // Change configuration to an invalid DNS name, expect validation to fail.
745 reset(mCallbacks);
746 mFakeDns.setAnswer("dns.bad", new String[0]);
747 wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.bad", new InetAddress[0]));
748 verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
749 .notifyNetworkTested(eq(NETWORK_TEST_RESULT_INVALID), eq(null));
750
751 // Change configuration back to working again, but make private DNS not work.
752 // Expect validation to fail.
753 reset(mCallbacks);
754 mFakeDns.setNonBypassPrivateDnsWorking(false);
755 wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
756 verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
757 .notifyNetworkTested(eq(NETWORK_TEST_RESULT_INVALID), eq(null));
758
759 // Make private DNS work again. Expect validation to succeed.
760 reset(mCallbacks);
761 mFakeDns.setNonBypassPrivateDnsWorking(true);
762 wnm.forceReevaluation(Process.myUid());
763 verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
764 .notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), eq(null));
765 }
766
767 @Test
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800768 public void testDataStall_StallSuspectedAndSendMetrics() throws IOException {
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900769 WrappedNetworkMonitor wrappedMonitor = makeNotMeteredNetworkMonitor();
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800770 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
771 makeDnsTimeoutEvent(wrappedMonitor, 5);
772 assertTrue(wrappedMonitor.isDataStall());
Chiachang Wange43f1622019-03-08 14:34:19 +0800773 verify(mDataStallStatsUtils, times(1)).write(makeEmptyDataStallDetectionStats(), any());
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800774 }
775
776 @Test
777 public void testDataStall_NoStallSuspectedAndSendMetrics() throws IOException {
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900778 WrappedNetworkMonitor wrappedMonitor = makeNotMeteredNetworkMonitor();
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800779 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
780 makeDnsTimeoutEvent(wrappedMonitor, 3);
781 assertFalse(wrappedMonitor.isDataStall());
Chiachang Wange43f1622019-03-08 14:34:19 +0800782 verify(mDataStallStatsUtils, never()).write(makeEmptyDataStallDetectionStats(), any());
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800783 }
Chiachang Wang95489ca2019-02-26 11:32:18 +0800784
785 @Test
786 public void testCollectDataStallMetrics() {
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900787 WrappedNetworkMonitor wrappedMonitor = makeNotMeteredNetworkMonitor();
Chiachang Wang95489ca2019-02-26 11:32:18 +0800788
789 when(mTelephony.getDataNetworkType()).thenReturn(TelephonyManager.NETWORK_TYPE_LTE);
790 when(mTelephony.getNetworkOperator()).thenReturn(TEST_MCCMNC);
791 when(mTelephony.getSimOperator()).thenReturn(TEST_MCCMNC);
792
793 DataStallDetectionStats.Builder stats =
794 new DataStallDetectionStats.Builder()
795 .setEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS)
796 .setNetworkType(NetworkCapabilities.TRANSPORT_CELLULAR)
797 .setCellData(TelephonyManager.NETWORK_TYPE_LTE /* radioType */,
798 true /* roaming */,
799 TEST_MCCMNC /* networkMccmnc */,
800 TEST_MCCMNC /* simMccmnc */,
801 CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN /* signalStrength */);
802 generateTimeoutDnsEvent(stats, DEFAULT_DNS_TIMEOUT_THRESHOLD);
803
804 assertEquals(wrappedMonitor.buildDataStallDetectionStats(
805 NetworkCapabilities.TRANSPORT_CELLULAR), stats.build());
806
807 when(mWifi.getConnectionInfo()).thenReturn(mWifiInfo);
808
809 stats = new DataStallDetectionStats.Builder()
810 .setEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS)
811 .setNetworkType(NetworkCapabilities.TRANSPORT_WIFI)
812 .setWiFiData(mWifiInfo);
813 generateTimeoutDnsEvent(stats, DEFAULT_DNS_TIMEOUT_THRESHOLD);
814
815 assertEquals(
816 wrappedMonitor.buildDataStallDetectionStats(NetworkCapabilities.TRANSPORT_WIFI),
817 stats.build());
818 }
819
lucasline252a742019-03-12 13:08:03 +0800820 @Test
821 public void testIgnoreHttpsProbe() throws Exception {
822 setSslException(mHttpsConnection);
823 setStatus(mHttpConnection, 204);
824
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900825 final NetworkMonitor nm = runNetworkTest(NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY);
lucasline252a742019-03-12 13:08:03 +0800826
lucaslin43338992019-03-20 18:21:59 +0800827 nm.setAcceptPartialConnectivity();
lucasline252a742019-03-12 13:08:03 +0800828 verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900829 .notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), any());
lucasline252a742019-03-12 13:08:03 +0800830 }
831
832 @Test
833 public void testIsPartialConnectivity() throws IOException {
834 setStatus(mHttpsConnection, 500);
835 setStatus(mHttpConnection, 204);
836 setStatus(mFallbackConnection, 500);
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900837 runPartialConnectivityNetworkTest();
lucasline252a742019-03-12 13:08:03 +0800838
839 setStatus(mHttpsConnection, 500);
840 setStatus(mHttpConnection, 500);
841 setStatus(mFallbackConnection, 204);
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900842 runPartialConnectivityNetworkTest();
lucasline252a742019-03-12 13:08:03 +0800843 }
844
Lorenzo Colitti28c966e2019-04-23 09:57:47 -0700845 private void assertIpAddressArrayEquals(String[] expected, InetAddress[] actual) {
846 String[] actualStrings = new String[actual.length];
847 for (int i = 0; i < actual.length; i++) {
848 actualStrings[i] = actual[i].getHostAddress();
849 }
850 assertArrayEquals("Array of IP addresses differs", expected, actualStrings);
851 }
852
853 @Test
854 public void testSendDnsProbeWithTimeout() throws Exception {
855 WrappedNetworkMonitor wnm = makeNotMeteredNetworkMonitor();
856 final int shortTimeoutMs = 200;
857
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700858 // Clear the wildcard DNS response created in setUp.
859 mFakeDns.setAnswer("*", null);
860
Lorenzo Colitti28c966e2019-04-23 09:57:47 -0700861 String[] expected = new String[]{"2001:db8::"};
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700862 mFakeDns.setAnswer("www.google.com", expected);
Lorenzo Colitti28c966e2019-04-23 09:57:47 -0700863 InetAddress[] actual = wnm.sendDnsProbeWithTimeout("www.google.com", shortTimeoutMs);
864 assertIpAddressArrayEquals(expected, actual);
865
866 expected = new String[]{"2001:db8::", "192.0.2.1"};
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700867 mFakeDns.setAnswer("www.googleapis.com", expected);
868 actual = wnm.sendDnsProbeWithTimeout("www.googleapis.com", shortTimeoutMs);
Lorenzo Colitti28c966e2019-04-23 09:57:47 -0700869 assertIpAddressArrayEquals(expected, actual);
870
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700871 mFakeDns.setAnswer("www.google.com", new String[0]);
Lorenzo Colitti28c966e2019-04-23 09:57:47 -0700872 try {
873 wnm.sendDnsProbeWithTimeout("www.google.com", shortTimeoutMs);
874 fail("No DNS results, expected UnknownHostException");
875 } catch (UnknownHostException e) {
876 }
877
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700878 mFakeDns.setAnswer("www.google.com", null);
Lorenzo Colitti28c966e2019-04-23 09:57:47 -0700879 try {
880 wnm.sendDnsProbeWithTimeout("www.google.com", shortTimeoutMs);
881 fail("DNS query timed out, expected UnknownHostException");
882 } catch (UnknownHostException e) {
883 }
884 }
885
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800886 private void makeDnsTimeoutEvent(WrappedNetworkMonitor wrappedMonitor, int count) {
887 for (int i = 0; i < count; i++) {
888 wrappedMonitor.getDnsStallDetector().accumulateConsecutiveDnsTimeoutCount(
889 RETURN_CODE_DNS_TIMEOUT);
890 }
891 }
892
893 private void makeDnsSuccessEvent(WrappedNetworkMonitor wrappedMonitor, int count) {
894 for (int i = 0; i < count; i++) {
895 wrappedMonitor.getDnsStallDetector().accumulateConsecutiveDnsTimeoutCount(
896 RETURN_CODE_DNS_SUCCESS);
897 }
898 }
899
Chiachang Wange43f1622019-03-08 14:34:19 +0800900 private DataStallDetectionStats makeEmptyDataStallDetectionStats() {
901 return new DataStallDetectionStats.Builder().build();
902 }
903
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800904 private void setDataStallEvaluationType(int type) {
Chiachang Wang0b984412019-04-08 19:06:21 +0800905 when(mDependencies.getDeviceConfigPropertyInt(any(),
906 eq(CONFIG_DATA_STALL_EVALUATION_TYPE), anyInt())).thenReturn(type);
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800907 }
908
909 private void setMinDataStallEvaluateInterval(int time) {
Chiachang Wang0b984412019-04-08 19:06:21 +0800910 when(mDependencies.getDeviceConfigPropertyInt(any(),
911 eq(CONFIG_DATA_STALL_MIN_EVALUATE_INTERVAL), anyInt())).thenReturn(time);
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800912 }
913
914 private void setValidDataStallDnsTimeThreshold(int time) {
Chiachang Wang0b984412019-04-08 19:06:21 +0800915 when(mDependencies.getDeviceConfigPropertyInt(any(),
916 eq(CONFIG_DATA_STALL_VALID_DNS_TIME_THRESHOLD), anyInt())).thenReturn(time);
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800917 }
918
919 private void setConsecutiveDnsTimeoutThreshold(int num) {
Chiachang Wang0b984412019-04-08 19:06:21 +0800920 when(mDependencies.getDeviceConfigPropertyInt(any(),
921 eq(CONFIG_DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD), anyInt())).thenReturn(num);
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800922 }
923
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900924 private void setFallbackUrl(String url) {
925 when(mDependencies.getSetting(any(),
926 eq(Settings.Global.CAPTIVE_PORTAL_FALLBACK_URL), any())).thenReturn(url);
927 }
928
929 private void setOtherFallbackUrls(String urls) {
Chiachang Wang98b02db2019-04-18 01:25:00 -0700930 when(mDependencies.getDeviceConfigProperty(any(),
931 eq(CAPTIVE_PORTAL_OTHER_FALLBACK_URLS), any())).thenReturn(urls);
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900932 }
933
934 private void setFallbackSpecs(String specs) {
Chiachang Wang98b02db2019-04-18 01:25:00 -0700935 when(mDependencies.getDeviceConfigProperty(any(),
936 eq(CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS), any())).thenReturn(specs);
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900937 }
938
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900939 private void setCaptivePortalMode(int mode) {
940 when(mDependencies.getSetting(any(),
941 eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt())).thenReturn(mode);
942 }
943
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900944 private void runPortalNetworkTest() {
945 runNetworkTest(NETWORK_TEST_RESULT_INVALID);
Lorenzo Colitti5beebde2019-04-10 02:57:17 -0700946 assertEquals(1, mRegisteredReceivers.size());
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900947 assertNotNull(mNetworkTestedRedirectUrlCaptor.getValue());
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900948 }
949
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900950 private void runNotPortalNetworkTest() {
951 runNetworkTest(NETWORK_TEST_RESULT_VALID);
Lorenzo Colitti5beebde2019-04-10 02:57:17 -0700952 assertEquals(0, mRegisteredReceivers.size());
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900953 assertNull(mNetworkTestedRedirectUrlCaptor.getValue());
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900954 }
955
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900956 private void runFailedNetworkTest() {
957 runNetworkTest(NETWORK_TEST_RESULT_INVALID);
Lorenzo Colitti5beebde2019-04-10 02:57:17 -0700958 assertEquals(0, mRegisteredReceivers.size());
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900959 assertNull(mNetworkTestedRedirectUrlCaptor.getValue());
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900960 }
961
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900962 private void runPartialConnectivityNetworkTest() {
963 runNetworkTest(NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY);
Lorenzo Colitti5beebde2019-04-10 02:57:17 -0700964 assertEquals(0, mRegisteredReceivers.size());
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900965 assertNull(mNetworkTestedRedirectUrlCaptor.getValue());
966 }
967
968 private NetworkMonitor runNetworkTest(int testResult) {
969 return runNetworkTest(METERED_CAPABILITIES, testResult);
970 }
971
972 private NetworkMonitor runNetworkTest(NetworkCapabilities nc, int testResult) {
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700973 final NetworkMonitor monitor = makeMonitor(nc);
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900974 monitor.notifyNetworkConnected(TEST_LINK_PROPERTIES, nc);
975 try {
976 verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
977 .notifyNetworkTested(eq(testResult), mNetworkTestedRedirectUrlCaptor.capture());
978 } catch (RemoteException e) {
979 fail("Unexpected exception: " + e);
980 }
Lorenzo Colitti5beebde2019-04-10 02:57:17 -0700981 waitForIdle(monitor.getHandler());
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900982
983 return monitor;
lucasline252a742019-03-12 13:08:03 +0800984 }
985
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900986 private void setSslException(HttpURLConnection connection) throws IOException {
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900987 doThrow(new SSLHandshakeException("Invalid cert")).when(connection).getResponseCode();
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900988 }
989
990 private void set302(HttpURLConnection connection, String location) throws IOException {
991 setStatus(connection, 302);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900992 doReturn(location).when(connection).getHeaderField(LOCATION_HEADER);
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900993 }
994
995 private void setPortal302(HttpURLConnection connection) throws IOException {
996 set302(connection, "http://login.example.com");
997 }
998
999 private void setStatus(HttpURLConnection connection, int status) throws IOException {
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001000 doReturn(status).when(connection).getResponseCode();
Hugo Benichic894b122017-07-31 12:58:20 +09001001 }
Chiachang Wang95489ca2019-02-26 11:32:18 +08001002
1003 private void generateTimeoutDnsEvent(DataStallDetectionStats.Builder stats, int num) {
1004 for (int i = 0; i < num; i++) {
1005 stats.addDnsEvent(RETURN_CODE_DNS_TIMEOUT, 123456789 /* timeMs */);
1006 }
1007 }
Hugo Benichic894b122017-07-31 12:58:20 +09001008}
1009