blob: 26186751c2822b5e6c6e780e0cab4aa7128fe828 [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;
Lorenzo Colittic52bc222019-05-09 05:33:27 -0700136 private @Spy Network mCleartextDnsNetwork = new Network(TEST_NETID);
137 private @Mock Network mNetwork;
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) {
Lorenzo Colittic52bc222019-05-09 05:33:27 -0700194 if (mock == mNetwork && !mNonBypassPrivateDnsWorking) {
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700195 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 {
Lorenzo Colittic52bc222019-05-09 05:33:27 -0700229 // Queries on mNetwork using getAllByName.
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700230 doAnswer(invocation -> {
231 return getAllByName(invocation.getMock(), invocation.getArgument(0));
232 }).when(mNetwork).getAllByName(any());
233
Lorenzo Colittic52bc222019-05-09 05:33:27 -0700234 // Queries on mCleartextDnsNetwork using DnsResolver#query.
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700235 doAnswer(invocation -> {
236 String hostname = (String) invocation.getArgument(1);
237 Executor executor = (Executor) invocation.getArgument(3);
238 DnsResolver.Callback<List<InetAddress>> callback = invocation.getArgument(5);
239
240 List<InetAddress> answer = getAnswer(invocation.getMock(), hostname);
241 if (answer != null && answer.size() > 0) {
242 new Handler(Looper.getMainLooper()).post(() -> {
243 executor.execute(() -> callback.onAnswer(answer, 0));
244 });
245 }
246 // If no answers, do nothing. sendDnsProbeWithTimeout will time out and throw UHE.
247 return null;
248 }).when(mDnsResolver).query(any(), any(), anyInt(), any(), any(), any());
Chiachang Wang2d5847b2019-05-10 02:26:23 -0700249
250 // Queries on mCleartextDnsNetwork using using DnsResolver#query with QueryType.
251 doAnswer(invocation -> {
252 String hostname = (String) invocation.getArgument(1);
253 Executor executor = (Executor) invocation.getArgument(4);
254 DnsResolver.Callback<List<InetAddress>> callback = invocation.getArgument(6);
255
256 List<InetAddress> answer = getAnswer(invocation.getMock(), hostname);
257 if (answer != null && answer.size() > 0) {
258 new Handler(Looper.getMainLooper()).post(() -> {
259 executor.execute(() -> callback.onAnswer(answer, 0));
260 });
261 }
262 // If no answers, do nothing. sendDnsProbeWithTimeout will time out and throw UHE.
263 return null;
264 }).when(mDnsResolver).query(any(), any(), anyInt(), anyInt(), any(), any(), any());
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700265 }
Lorenzo Colitti28c966e2019-04-23 09:57:47 -0700266 }
267
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700268 private FakeDns mFakeDns;
269
Hugo Benichic894b122017-07-31 12:58:20 +0900270 @Before
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900271 public void setUp() throws IOException {
Hugo Benichic894b122017-07-31 12:58:20 +0900272 MockitoAnnotations.initMocks(this);
Lorenzo Colittic52bc222019-05-09 05:33:27 -0700273 when(mDependencies.getPrivateDnsBypassNetwork(any())).thenReturn(mCleartextDnsNetwork);
Lorenzo Colitti28c966e2019-04-23 09:57:47 -0700274 when(mDependencies.getDnsResolver()).thenReturn(mDnsResolver);
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900275 when(mDependencies.getRandom()).thenReturn(mRandom);
276 when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt()))
277 .thenReturn(Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT);
Chiachang Wang98b02db2019-04-18 01:25:00 -0700278 when(mDependencies.getDeviceConfigPropertyInt(any(), eq(CAPTIVE_PORTAL_USE_HTTPS),
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900279 anyInt())).thenReturn(1);
Remi NGUYEN VAN9ca4c622019-04-09 02:04:54 -0700280 when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTP_URL), any()))
281 .thenReturn(TEST_HTTP_URL);
282 when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTPS_URL), any()))
283 .thenReturn(TEST_HTTPS_URL);
284
Lorenzo Colittic52bc222019-05-09 05:33:27 -0700285 doReturn(mCleartextDnsNetwork).when(mNetwork).getPrivateDnsBypassingCopy();
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900286
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900287 when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mCm);
Hugo Benichic894b122017-07-31 12:58:20 +0900288 when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephony);
289 when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mWifi);
Remi NGUYEN VAN9ca4c622019-04-09 02:04:54 -0700290 when(mContext.getResources()).thenReturn(mResources);
291
292 when(mResources.getString(anyInt())).thenReturn("");
293 when(mResources.getStringArray(anyInt())).thenReturn(new String[0]);
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900294
295 when(mNetworkInfo.getType()).thenReturn(ConnectivityManager.TYPE_WIFI);
296 setFallbackUrl(TEST_FALLBACK_URL);
297 setOtherFallbackUrls(TEST_OTHER_FALLBACK_URL);
298 setFallbackSpecs(null); // Test with no fallback spec by default
299 when(mRandom.nextInt()).thenReturn(0);
Chiachang Wang3134eb42019-05-15 15:33:46 +0800300 // DNS probe timeout should not be defined more than half of HANDLER_TIMEOUT_MS. Otherwise,
301 // it will fail the test because of timeout expired for querying AAAA and A sequentially.
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700302 when(mResources.getInteger(eq(R.integer.config_captive_portal_dns_probe_timeout)))
Chiachang Wang3134eb42019-05-15 15:33:46 +0800303 .thenReturn(200);
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700304
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900305 doAnswer((invocation) -> {
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900306 URL url = invocation.getArgument(0);
307 switch(url.toString()) {
308 case TEST_HTTP_URL:
309 return mHttpConnection;
310 case TEST_HTTPS_URL:
311 return mHttpsConnection;
312 case TEST_FALLBACK_URL:
313 return mFallbackConnection;
314 case TEST_OTHER_FALLBACK_URL:
315 return mOtherFallbackConnection;
316 default:
317 fail("URL not mocked: " + url.toString());
318 return null;
319 }
Lorenzo Colittic52bc222019-05-09 05:33:27 -0700320 }).when(mCleartextDnsNetwork).openConnection(any());
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900321 when(mHttpConnection.getRequestProperties()).thenReturn(new ArrayMap<>());
322 when(mHttpsConnection.getRequestProperties()).thenReturn(new ArrayMap<>());
Lorenzo Colitti28c966e2019-04-23 09:57:47 -0700323
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700324 mFakeDns = new FakeDns();
325 mFakeDns.startMocking();
326 mFakeDns.setAnswer("*", new String[]{"2001:db8::1", "192.0.2.2"});
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900327
Lorenzo Colitti5beebde2019-04-10 02:57:17 -0700328 when(mContext.registerReceiver(any(BroadcastReceiver.class), any())).then((invocation) -> {
329 mRegisteredReceivers.add(invocation.getArgument(0));
330 return new Intent();
331 });
332
333 doAnswer((invocation) -> {
334 mRegisteredReceivers.remove(invocation.getArgument(0));
335 return null;
336 }).when(mContext).unregisterReceiver(any());
337
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800338 setMinDataStallEvaluateInterval(500);
Chiachang Wang4349dc02019-03-05 20:31:57 +0800339 setDataStallEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS);
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800340 setValidDataStallDnsTimeThreshold(500);
341 setConsecutiveDnsTimeoutThreshold(5);
Lorenzo Colitti5beebde2019-04-10 02:57:17 -0700342
343 mCreatedNetworkMonitors = new HashSet<>();
344 mRegisteredReceivers = new HashSet<>();
345 }
346
347 @After
348 public void tearDown() {
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700349 mFakeDns.clearAll();
Lorenzo Colitti5beebde2019-04-10 02:57:17 -0700350 assertTrue(mCreatedNetworkMonitors.size() > 0);
351 // Make a local copy of mCreatedNetworkMonitors because during the iteration below,
352 // WrappedNetworkMonitor#onQuitting will delete elements from it on the handler threads.
353 WrappedNetworkMonitor[] networkMonitors = mCreatedNetworkMonitors.toArray(
354 new WrappedNetworkMonitor[0]);
355 for (WrappedNetworkMonitor nm : networkMonitors) {
356 nm.notifyNetworkDisconnected();
357 nm.awaitQuit();
358 }
359 assertEquals("NetworkMonitor still running after disconnect",
360 0, mCreatedNetworkMonitors.size());
361 assertEquals("BroadcastReceiver still registered after disconnect",
362 0, mRegisteredReceivers.size());
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800363 }
364
365 private class WrappedNetworkMonitor extends NetworkMonitor {
366 private long mProbeTime = 0;
Lorenzo Colitti5beebde2019-04-10 02:57:17 -0700367 private final ConditionVariable mQuitCv = new ConditionVariable(false);
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800368
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900369 WrappedNetworkMonitor() {
Lorenzo Colittic52bc222019-05-09 05:33:27 -0700370 super(mContext, mCallbacks, mNetwork, mLogger, mValidationLogger,
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700371 mDependencies, mDataStallStatsUtils);
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800372 }
373
374 @Override
375 protected long getLastProbeTime() {
376 return mProbeTime;
377 }
378
379 protected void setLastProbeTime(long time) {
380 mProbeTime = time;
381 }
Chiachang Wang95489ca2019-02-26 11:32:18 +0800382
383 @Override
384 protected void addDnsEvents(@NonNull final DataStallDetectionStats.Builder stats) {
385 generateTimeoutDnsEvent(stats, DEFAULT_DNS_TIMEOUT_THRESHOLD);
386 }
Lorenzo Colitti5beebde2019-04-10 02:57:17 -0700387
388 @Override
389 protected void onQuitting() {
390 assertTrue(mCreatedNetworkMonitors.remove(this));
391 mQuitCv.open();
392 }
393
394 protected void awaitQuit() {
395 assertTrue("NetworkMonitor did not quit after " + HANDLER_TIMEOUT_MS + "ms",
396 mQuitCv.block(HANDLER_TIMEOUT_MS));
397 }
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800398 }
399
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700400 private WrappedNetworkMonitor makeMonitor(NetworkCapabilities nc) {
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900401 final WrappedNetworkMonitor nm = new WrappedNetworkMonitor();
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900402 nm.start();
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700403 setNetworkCapabilities(nm, nc);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900404 waitForIdle(nm.getHandler());
Lorenzo Colitti5beebde2019-04-10 02:57:17 -0700405 mCreatedNetworkMonitors.add(nm);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900406 return nm;
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800407 }
408
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900409 private WrappedNetworkMonitor makeMeteredNetworkMonitor() {
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700410 final WrappedNetworkMonitor nm = makeMonitor(METERED_CAPABILITIES);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900411 return nm;
Hugo Benichic894b122017-07-31 12:58:20 +0900412 }
413
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900414 private WrappedNetworkMonitor makeNotMeteredNetworkMonitor() {
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700415 final WrappedNetworkMonitor nm = makeMonitor(NOT_METERED_CAPABILITIES);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900416 return nm;
417 }
418
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900419 private void setNetworkCapabilities(NetworkMonitor nm, NetworkCapabilities nc) {
420 nm.notifyNetworkCapabilitiesChanged(nc);
421 waitForIdle(nm.getHandler());
422 }
423
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900424 private void waitForIdle(Handler handler) {
425 final ConditionVariable cv = new ConditionVariable(false);
426 handler.post(cv::open);
427 if (!cv.block(HANDLER_TIMEOUT_MS)) {
428 fail("Timed out waiting for handler");
429 }
Hugo Benichic894b122017-07-31 12:58:20 +0900430 }
431
432 @Test
Lorenzo Colitti28c966e2019-04-23 09:57:47 -0700433 public void testGetIntSetting() throws Exception {
434 WrappedNetworkMonitor wnm = makeNotMeteredNetworkMonitor();
435
436 // No config resource, no device config. Expect to get default resource.
437 doThrow(new Resources.NotFoundException())
438 .when(mResources).getInteger(eq(R.integer.config_captive_portal_dns_probe_timeout));
439 doAnswer(invocation -> {
440 int defaultValue = invocation.getArgument(2);
441 return defaultValue;
442 }).when(mDependencies).getDeviceConfigPropertyInt(any(),
443 eq(NetworkMonitor.CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT),
444 anyInt());
445 when(mResources.getInteger(eq(R.integer.default_captive_portal_dns_probe_timeout)))
446 .thenReturn(42);
447 assertEquals(42, wnm.getIntSetting(mContext,
448 R.integer.config_captive_portal_dns_probe_timeout,
449 NetworkMonitor.CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT,
450 R.integer.default_captive_portal_dns_probe_timeout));
451
452 // Set device config. Expect to get device config.
453 when(mDependencies.getDeviceConfigPropertyInt(any(),
454 eq(NetworkMonitor.CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT), anyInt()))
455 .thenReturn(1234);
456 assertEquals(1234, wnm.getIntSetting(mContext,
457 R.integer.config_captive_portal_dns_probe_timeout,
458 NetworkMonitor.CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT,
459 R.integer.default_captive_portal_dns_probe_timeout));
460
461 // Set config resource. Expect to get config resource.
462 when(mResources.getInteger(eq(R.integer.config_captive_portal_dns_probe_timeout)))
463 .thenReturn(5678);
464 assertEquals(5678, wnm.getIntSetting(mContext,
465 R.integer.config_captive_portal_dns_probe_timeout,
466 NetworkMonitor.CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT,
467 R.integer.default_captive_portal_dns_probe_timeout));
468 }
469
470 @Test
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900471 public void testIsCaptivePortal_HttpProbeIsPortal() throws IOException {
472 setSslException(mHttpsConnection);
473 setPortal302(mHttpConnection);
474
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900475 runPortalNetworkTest();
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900476 }
477
478 @Test
479 public void testIsCaptivePortal_HttpsProbeIsNotPortal() throws IOException {
480 setStatus(mHttpsConnection, 204);
481 setStatus(mHttpConnection, 500);
482
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900483 runNotPortalNetworkTest();
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900484 }
485
486 @Test
487 public void testIsCaptivePortal_FallbackProbeIsPortal() throws IOException {
488 setSslException(mHttpsConnection);
489 setStatus(mHttpConnection, 500);
490 setPortal302(mFallbackConnection);
491
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900492 runPortalNetworkTest();
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900493 }
494
495 @Test
496 public void testIsCaptivePortal_FallbackProbeIsNotPortal() throws IOException {
497 setSslException(mHttpsConnection);
498 setStatus(mHttpConnection, 500);
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900499 setStatus(mFallbackConnection, 500);
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900500
501 // Fallback probe did not see portal, HTTPS failed -> inconclusive
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900502 runFailedNetworkTest();
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900503 }
504
505 @Test
506 public void testIsCaptivePortal_OtherFallbackProbeIsPortal() throws IOException {
507 // Set all fallback probes but one to invalid URLs to verify they are being skipped
508 setFallbackUrl(TEST_FALLBACK_URL);
509 setOtherFallbackUrls(TEST_FALLBACK_URL + "," + TEST_OTHER_FALLBACK_URL);
510
511 setSslException(mHttpsConnection);
512 setStatus(mHttpConnection, 500);
513 setStatus(mFallbackConnection, 500);
514 setPortal302(mOtherFallbackConnection);
515
516 // TEST_OTHER_FALLBACK_URL is third
517 when(mRandom.nextInt()).thenReturn(2);
518
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900519 // First check always uses the first fallback URL: inconclusive
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900520 final NetworkMonitor monitor = runNetworkTest(NETWORK_TEST_RESULT_INVALID);
521 assertNull(mNetworkTestedRedirectUrlCaptor.getValue());
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900522 verify(mFallbackConnection, times(1)).getResponseCode();
523 verify(mOtherFallbackConnection, never()).getResponseCode();
524
525 // Second check uses the URL chosen by Random
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900526 final CaptivePortalProbeResult result = monitor.isCaptivePortal();
527 assertTrue(result.isPortal());
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900528 verify(mOtherFallbackConnection, times(1)).getResponseCode();
529 }
530
531 @Test
532 public void testIsCaptivePortal_AllProbesFailed() throws IOException {
533 setSslException(mHttpsConnection);
534 setStatus(mHttpConnection, 500);
535 setStatus(mFallbackConnection, 404);
536
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900537 runFailedNetworkTest();
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900538 verify(mFallbackConnection, times(1)).getResponseCode();
539 verify(mOtherFallbackConnection, never()).getResponseCode();
540 }
541
542 @Test
543 public void testIsCaptivePortal_InvalidUrlSkipped() throws IOException {
544 setFallbackUrl("invalid");
545 setOtherFallbackUrls("otherinvalid," + TEST_OTHER_FALLBACK_URL + ",yetanotherinvalid");
546
547 setSslException(mHttpsConnection);
548 setStatus(mHttpConnection, 500);
549 setPortal302(mOtherFallbackConnection);
550
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900551 runPortalNetworkTest();
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900552 verify(mOtherFallbackConnection, times(1)).getResponseCode();
553 verify(mFallbackConnection, never()).getResponseCode();
554 }
555
556 private void setupFallbackSpec() throws IOException {
557 setFallbackSpecs("http://example.com@@/@@204@@/@@"
558 + "@@,@@"
559 + TEST_OTHER_FALLBACK_URL + "@@/@@30[12]@@/@@https://(www\\.)?google.com/?.*");
560
561 setSslException(mHttpsConnection);
562 setStatus(mHttpConnection, 500);
563
564 // Use the 2nd fallback spec
565 when(mRandom.nextInt()).thenReturn(1);
566 }
567
568 @Test
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900569 public void testIsCaptivePortal_FallbackSpecIsPartial() throws IOException {
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900570 setupFallbackSpec();
571 set302(mOtherFallbackConnection, "https://www.google.com/test?q=3");
572
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900573 // HTTPS failed, fallback spec went through -> partial connectivity
574 runPartialConnectivityNetworkTest();
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900575 verify(mOtherFallbackConnection, times(1)).getResponseCode();
576 verify(mFallbackConnection, never()).getResponseCode();
577 }
578
579 @Test
580 public void testIsCaptivePortal_FallbackSpecIsPortal() throws IOException {
581 setupFallbackSpec();
582 set302(mOtherFallbackConnection, "http://login.portal.example.com");
583
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900584 runPortalNetworkTest();
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900585 }
586
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800587 @Test
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900588 public void testIsCaptivePortal_IgnorePortals() throws IOException {
589 setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE);
590 setSslException(mHttpsConnection);
591 setPortal302(mHttpConnection);
592
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900593 runNotPortalNetworkTest();
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900594 }
595
596 @Test
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800597 public void testIsDataStall_EvaluationDisabled() {
598 setDataStallEvaluationType(0);
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900599 WrappedNetworkMonitor wrappedMonitor = makeMeteredNetworkMonitor();
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800600 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
601 assertFalse(wrappedMonitor.isDataStall());
602 }
603
604 @Test
605 public void testIsDataStall_EvaluationDnsOnNotMeteredNetwork() {
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900606 WrappedNetworkMonitor wrappedMonitor = makeNotMeteredNetworkMonitor();
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800607 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
Chiachang Wang95489ca2019-02-26 11:32:18 +0800608 makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD);
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800609 assertTrue(wrappedMonitor.isDataStall());
610 }
611
612 @Test
613 public void testIsDataStall_EvaluationDnsOnMeteredNetwork() {
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900614 WrappedNetworkMonitor wrappedMonitor = makeMeteredNetworkMonitor();
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800615 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
616 assertFalse(wrappedMonitor.isDataStall());
617
618 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
Chiachang Wang95489ca2019-02-26 11:32:18 +0800619 makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD);
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800620 assertTrue(wrappedMonitor.isDataStall());
621 }
622
623 @Test
624 public void testIsDataStall_EvaluationDnsWithDnsTimeoutCount() {
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900625 WrappedNetworkMonitor wrappedMonitor = makeMeteredNetworkMonitor();
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800626 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
627 makeDnsTimeoutEvent(wrappedMonitor, 3);
628 assertFalse(wrappedMonitor.isDataStall());
629 // Reset consecutive timeout counts.
630 makeDnsSuccessEvent(wrappedMonitor, 1);
631 makeDnsTimeoutEvent(wrappedMonitor, 2);
632 assertFalse(wrappedMonitor.isDataStall());
633
634 makeDnsTimeoutEvent(wrappedMonitor, 3);
635 assertTrue(wrappedMonitor.isDataStall());
636
637 // Set the value to larger than the default dns log size.
638 setConsecutiveDnsTimeoutThreshold(51);
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900639 wrappedMonitor = makeMeteredNetworkMonitor();
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800640 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
641 makeDnsTimeoutEvent(wrappedMonitor, 50);
642 assertFalse(wrappedMonitor.isDataStall());
643
644 makeDnsTimeoutEvent(wrappedMonitor, 1);
645 assertTrue(wrappedMonitor.isDataStall());
646 }
647
648 @Test
649 public void testIsDataStall_EvaluationDnsWithDnsTimeThreshold() {
650 // Test dns events happened in valid dns time threshold.
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900651 WrappedNetworkMonitor wrappedMonitor = makeMeteredNetworkMonitor();
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800652 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
Chiachang Wang95489ca2019-02-26 11:32:18 +0800653 makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD);
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800654 assertFalse(wrappedMonitor.isDataStall());
655 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
656 assertTrue(wrappedMonitor.isDataStall());
657
658 // Test dns events happened before valid dns time threshold.
659 setValidDataStallDnsTimeThreshold(0);
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900660 wrappedMonitor = makeMeteredNetworkMonitor();
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800661 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
Chiachang Wang95489ca2019-02-26 11:32:18 +0800662 makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD);
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800663 assertFalse(wrappedMonitor.isDataStall());
664 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
665 assertFalse(wrappedMonitor.isDataStall());
666 }
667
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900668 @Test
669 public void testBrokenNetworkNotValidated() throws Exception {
670 setSslException(mHttpsConnection);
671 setStatus(mHttpConnection, 500);
672 setStatus(mFallbackConnection, 404);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900673
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900674 runFailedNetworkTest();
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900675 }
676
677 @Test
678 public void testNoInternetCapabilityValidated() throws Exception {
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900679 runNetworkTest(NO_INTERNET_CAPABILITIES, NETWORK_TEST_RESULT_VALID);
Lorenzo Colittic52bc222019-05-09 05:33:27 -0700680 verify(mCleartextDnsNetwork, never()).openConnection(any());
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900681 }
682
683 @Test
684 public void testLaunchCaptivePortalApp() throws Exception {
685 setSslException(mHttpsConnection);
686 setPortal302(mHttpConnection);
687
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700688 final NetworkMonitor nm = makeMonitor(METERED_CAPABILITIES);
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900689 nm.notifyNetworkConnected(TEST_LINK_PROPERTIES, METERED_CAPABILITIES);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900690
691 verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
Remi NGUYEN VAN9c5d9642019-02-07 21:29:57 +0900692 .showProvisioningNotification(any(), any());
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900693
Lorenzo Colitti5beebde2019-04-10 02:57:17 -0700694 assertEquals(1, mRegisteredReceivers.size());
695
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900696 // Check that startCaptivePortalApp sends the expected intent.
697 nm.launchCaptivePortalApp();
698
Remi NGUYEN VANcfff01e2019-02-13 20:58:59 +0900699 final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
700 final ArgumentCaptor<Network> networkCaptor = ArgumentCaptor.forClass(Network.class);
701 verify(mCm, timeout(HANDLER_TIMEOUT_MS).times(1))
702 .startCaptivePortalApp(networkCaptor.capture(), bundleCaptor.capture());
703 final Bundle bundle = bundleCaptor.getValue();
704 final Network bundleNetwork = bundle.getParcelable(ConnectivityManager.EXTRA_NETWORK);
705 assertEquals(TEST_NETID, bundleNetwork.netId);
706 // network is passed both in bundle and as parameter, as the bundle is opaque to the
707 // framework and only intended for the captive portal app, but the framework needs
708 // the network to identify the right NetworkMonitor.
709 assertEquals(TEST_NETID, networkCaptor.getValue().netId);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900710
711 // Have the app report that the captive portal is dismissed, and check that we revalidate.
712 setStatus(mHttpsConnection, 204);
713 setStatus(mHttpConnection, 204);
Remi NGUYEN VANcfff01e2019-02-13 20:58:59 +0900714
715 nm.notifyCaptivePortalAppFinished(APP_RETURN_DISMISSED);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900716 verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
717 .notifyNetworkTested(NETWORK_TEST_RESULT_VALID, null);
Lorenzo Colitti5beebde2019-04-10 02:57:17 -0700718
719 assertEquals(0, mRegisteredReceivers.size());
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900720 }
721
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800722 @Test
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700723 public void testPrivateDnsSuccess() throws Exception {
724 setStatus(mHttpsConnection, 204);
725 setStatus(mHttpConnection, 204);
726 mFakeDns.setAnswer("dns.google", new String[]{"2001:db8::53"});
727
728 WrappedNetworkMonitor wnm = makeNotMeteredNetworkMonitor();
729 wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
730 wnm.notifyNetworkConnected(TEST_LINK_PROPERTIES, NOT_METERED_CAPABILITIES);
731 verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
732 .notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), eq(null));
733 }
734
735 @Test
736 public void testPrivateDnsResolutionRetryUpdate() throws Exception {
737 // Set a private DNS hostname that doesn't resolve and expect validation to fail.
738 mFakeDns.setAnswer("dns.google", new String[0]);
739 setStatus(mHttpsConnection, 204);
740 setStatus(mHttpConnection, 204);
741
742 WrappedNetworkMonitor wnm = makeNotMeteredNetworkMonitor();
743 wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
744 wnm.notifyNetworkConnected(TEST_LINK_PROPERTIES, NOT_METERED_CAPABILITIES);
745 verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
746 .notifyNetworkTested(eq(NETWORK_TEST_RESULT_INVALID), eq(null));
747
748 // Fix DNS and retry, expect validation to succeed.
749 reset(mCallbacks);
750 mFakeDns.setAnswer("dns.google", new String[]{"2001:db8::1"});
751
752 wnm.forceReevaluation(Process.myUid());
753 verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
754 .notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), eq(null));
755
756 // Change configuration to an invalid DNS name, expect validation to fail.
757 reset(mCallbacks);
758 mFakeDns.setAnswer("dns.bad", new String[0]);
759 wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.bad", new InetAddress[0]));
760 verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
761 .notifyNetworkTested(eq(NETWORK_TEST_RESULT_INVALID), eq(null));
762
763 // Change configuration back to working again, but make private DNS not work.
764 // Expect validation to fail.
765 reset(mCallbacks);
766 mFakeDns.setNonBypassPrivateDnsWorking(false);
767 wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
768 verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
769 .notifyNetworkTested(eq(NETWORK_TEST_RESULT_INVALID), eq(null));
770
771 // Make private DNS work again. Expect validation to succeed.
772 reset(mCallbacks);
773 mFakeDns.setNonBypassPrivateDnsWorking(true);
774 wnm.forceReevaluation(Process.myUid());
775 verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
776 .notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), eq(null));
777 }
778
779 @Test
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800780 public void testDataStall_StallSuspectedAndSendMetrics() throws IOException {
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900781 WrappedNetworkMonitor wrappedMonitor = makeNotMeteredNetworkMonitor();
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800782 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
783 makeDnsTimeoutEvent(wrappedMonitor, 5);
784 assertTrue(wrappedMonitor.isDataStall());
Chiachang Wange43f1622019-03-08 14:34:19 +0800785 verify(mDataStallStatsUtils, times(1)).write(makeEmptyDataStallDetectionStats(), any());
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800786 }
787
788 @Test
789 public void testDataStall_NoStallSuspectedAndSendMetrics() throws IOException {
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900790 WrappedNetworkMonitor wrappedMonitor = makeNotMeteredNetworkMonitor();
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800791 wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
792 makeDnsTimeoutEvent(wrappedMonitor, 3);
793 assertFalse(wrappedMonitor.isDataStall());
Chiachang Wange43f1622019-03-08 14:34:19 +0800794 verify(mDataStallStatsUtils, never()).write(makeEmptyDataStallDetectionStats(), any());
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800795 }
Chiachang Wang95489ca2019-02-26 11:32:18 +0800796
797 @Test
798 public void testCollectDataStallMetrics() {
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900799 WrappedNetworkMonitor wrappedMonitor = makeNotMeteredNetworkMonitor();
Chiachang Wang95489ca2019-02-26 11:32:18 +0800800
801 when(mTelephony.getDataNetworkType()).thenReturn(TelephonyManager.NETWORK_TYPE_LTE);
802 when(mTelephony.getNetworkOperator()).thenReturn(TEST_MCCMNC);
803 when(mTelephony.getSimOperator()).thenReturn(TEST_MCCMNC);
804
805 DataStallDetectionStats.Builder stats =
806 new DataStallDetectionStats.Builder()
807 .setEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS)
808 .setNetworkType(NetworkCapabilities.TRANSPORT_CELLULAR)
809 .setCellData(TelephonyManager.NETWORK_TYPE_LTE /* radioType */,
810 true /* roaming */,
811 TEST_MCCMNC /* networkMccmnc */,
812 TEST_MCCMNC /* simMccmnc */,
813 CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN /* signalStrength */);
814 generateTimeoutDnsEvent(stats, DEFAULT_DNS_TIMEOUT_THRESHOLD);
815
816 assertEquals(wrappedMonitor.buildDataStallDetectionStats(
817 NetworkCapabilities.TRANSPORT_CELLULAR), stats.build());
818
819 when(mWifi.getConnectionInfo()).thenReturn(mWifiInfo);
820
821 stats = new DataStallDetectionStats.Builder()
822 .setEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS)
823 .setNetworkType(NetworkCapabilities.TRANSPORT_WIFI)
824 .setWiFiData(mWifiInfo);
825 generateTimeoutDnsEvent(stats, DEFAULT_DNS_TIMEOUT_THRESHOLD);
826
827 assertEquals(
828 wrappedMonitor.buildDataStallDetectionStats(NetworkCapabilities.TRANSPORT_WIFI),
829 stats.build());
830 }
831
lucasline252a742019-03-12 13:08:03 +0800832 @Test
833 public void testIgnoreHttpsProbe() throws Exception {
834 setSslException(mHttpsConnection);
835 setStatus(mHttpConnection, 204);
836
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900837 final NetworkMonitor nm = runNetworkTest(NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY);
lucasline252a742019-03-12 13:08:03 +0800838
lucaslin43338992019-03-20 18:21:59 +0800839 nm.setAcceptPartialConnectivity();
lucasline252a742019-03-12 13:08:03 +0800840 verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900841 .notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), any());
lucasline252a742019-03-12 13:08:03 +0800842 }
843
844 @Test
845 public void testIsPartialConnectivity() throws IOException {
846 setStatus(mHttpsConnection, 500);
847 setStatus(mHttpConnection, 204);
848 setStatus(mFallbackConnection, 500);
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900849 runPartialConnectivityNetworkTest();
lucasline252a742019-03-12 13:08:03 +0800850
851 setStatus(mHttpsConnection, 500);
852 setStatus(mHttpConnection, 500);
853 setStatus(mFallbackConnection, 204);
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900854 runPartialConnectivityNetworkTest();
lucasline252a742019-03-12 13:08:03 +0800855 }
856
Lorenzo Colitti28c966e2019-04-23 09:57:47 -0700857 private void assertIpAddressArrayEquals(String[] expected, InetAddress[] actual) {
858 String[] actualStrings = new String[actual.length];
859 for (int i = 0; i < actual.length; i++) {
860 actualStrings[i] = actual[i].getHostAddress();
861 }
862 assertArrayEquals("Array of IP addresses differs", expected, actualStrings);
863 }
864
865 @Test
866 public void testSendDnsProbeWithTimeout() throws Exception {
867 WrappedNetworkMonitor wnm = makeNotMeteredNetworkMonitor();
868 final int shortTimeoutMs = 200;
869
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700870 // Clear the wildcard DNS response created in setUp.
871 mFakeDns.setAnswer("*", null);
872
Lorenzo Colitti28c966e2019-04-23 09:57:47 -0700873 String[] expected = new String[]{"2001:db8::"};
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700874 mFakeDns.setAnswer("www.google.com", expected);
Lorenzo Colitti28c966e2019-04-23 09:57:47 -0700875 InetAddress[] actual = wnm.sendDnsProbeWithTimeout("www.google.com", shortTimeoutMs);
876 assertIpAddressArrayEquals(expected, actual);
877
878 expected = new String[]{"2001:db8::", "192.0.2.1"};
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700879 mFakeDns.setAnswer("www.googleapis.com", expected);
880 actual = wnm.sendDnsProbeWithTimeout("www.googleapis.com", shortTimeoutMs);
Lorenzo Colitti28c966e2019-04-23 09:57:47 -0700881 assertIpAddressArrayEquals(expected, actual);
882
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700883 mFakeDns.setAnswer("www.google.com", new String[0]);
Lorenzo Colitti28c966e2019-04-23 09:57:47 -0700884 try {
885 wnm.sendDnsProbeWithTimeout("www.google.com", shortTimeoutMs);
886 fail("No DNS results, expected UnknownHostException");
887 } catch (UnknownHostException e) {
888 }
889
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700890 mFakeDns.setAnswer("www.google.com", null);
Lorenzo Colitti28c966e2019-04-23 09:57:47 -0700891 try {
892 wnm.sendDnsProbeWithTimeout("www.google.com", shortTimeoutMs);
893 fail("DNS query timed out, expected UnknownHostException");
894 } catch (UnknownHostException e) {
895 }
896 }
897
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800898 private void makeDnsTimeoutEvent(WrappedNetworkMonitor wrappedMonitor, int count) {
899 for (int i = 0; i < count; i++) {
900 wrappedMonitor.getDnsStallDetector().accumulateConsecutiveDnsTimeoutCount(
901 RETURN_CODE_DNS_TIMEOUT);
902 }
903 }
904
905 private void makeDnsSuccessEvent(WrappedNetworkMonitor wrappedMonitor, int count) {
906 for (int i = 0; i < count; i++) {
907 wrappedMonitor.getDnsStallDetector().accumulateConsecutiveDnsTimeoutCount(
908 RETURN_CODE_DNS_SUCCESS);
909 }
910 }
911
Chiachang Wange43f1622019-03-08 14:34:19 +0800912 private DataStallDetectionStats makeEmptyDataStallDetectionStats() {
913 return new DataStallDetectionStats.Builder().build();
914 }
915
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800916 private void setDataStallEvaluationType(int type) {
Chiachang Wang0b984412019-04-08 19:06:21 +0800917 when(mDependencies.getDeviceConfigPropertyInt(any(),
918 eq(CONFIG_DATA_STALL_EVALUATION_TYPE), anyInt())).thenReturn(type);
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800919 }
920
921 private void setMinDataStallEvaluateInterval(int time) {
Chiachang Wang0b984412019-04-08 19:06:21 +0800922 when(mDependencies.getDeviceConfigPropertyInt(any(),
923 eq(CONFIG_DATA_STALL_MIN_EVALUATE_INTERVAL), anyInt())).thenReturn(time);
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800924 }
925
926 private void setValidDataStallDnsTimeThreshold(int time) {
Chiachang Wang0b984412019-04-08 19:06:21 +0800927 when(mDependencies.getDeviceConfigPropertyInt(any(),
928 eq(CONFIG_DATA_STALL_VALID_DNS_TIME_THRESHOLD), anyInt())).thenReturn(time);
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800929 }
930
931 private void setConsecutiveDnsTimeoutThreshold(int num) {
Chiachang Wang0b984412019-04-08 19:06:21 +0800932 when(mDependencies.getDeviceConfigPropertyInt(any(),
933 eq(CONFIG_DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD), anyInt())).thenReturn(num);
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800934 }
935
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900936 private void setFallbackUrl(String url) {
937 when(mDependencies.getSetting(any(),
938 eq(Settings.Global.CAPTIVE_PORTAL_FALLBACK_URL), any())).thenReturn(url);
939 }
940
941 private void setOtherFallbackUrls(String urls) {
Chiachang Wang98b02db2019-04-18 01:25:00 -0700942 when(mDependencies.getDeviceConfigProperty(any(),
943 eq(CAPTIVE_PORTAL_OTHER_FALLBACK_URLS), any())).thenReturn(urls);
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900944 }
945
946 private void setFallbackSpecs(String specs) {
Chiachang Wang98b02db2019-04-18 01:25:00 -0700947 when(mDependencies.getDeviceConfigProperty(any(),
948 eq(CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS), any())).thenReturn(specs);
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900949 }
950
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900951 private void setCaptivePortalMode(int mode) {
952 when(mDependencies.getSetting(any(),
953 eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt())).thenReturn(mode);
954 }
955
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900956 private void runPortalNetworkTest() {
957 runNetworkTest(NETWORK_TEST_RESULT_INVALID);
Lorenzo Colitti5beebde2019-04-10 02:57:17 -0700958 assertEquals(1, mRegisteredReceivers.size());
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900959 assertNotNull(mNetworkTestedRedirectUrlCaptor.getValue());
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900960 }
961
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900962 private void runNotPortalNetworkTest() {
963 runNetworkTest(NETWORK_TEST_RESULT_VALID);
Lorenzo Colitti5beebde2019-04-10 02:57:17 -0700964 assertEquals(0, mRegisteredReceivers.size());
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900965 assertNull(mNetworkTestedRedirectUrlCaptor.getValue());
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900966 }
967
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900968 private void runFailedNetworkTest() {
969 runNetworkTest(NETWORK_TEST_RESULT_INVALID);
Lorenzo Colitti5beebde2019-04-10 02:57:17 -0700970 assertEquals(0, mRegisteredReceivers.size());
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900971 assertNull(mNetworkTestedRedirectUrlCaptor.getValue());
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900972 }
973
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900974 private void runPartialConnectivityNetworkTest() {
975 runNetworkTest(NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY);
Lorenzo Colitti5beebde2019-04-10 02:57:17 -0700976 assertEquals(0, mRegisteredReceivers.size());
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900977 assertNull(mNetworkTestedRedirectUrlCaptor.getValue());
978 }
979
980 private NetworkMonitor runNetworkTest(int testResult) {
981 return runNetworkTest(METERED_CAPABILITIES, testResult);
982 }
983
984 private NetworkMonitor runNetworkTest(NetworkCapabilities nc, int testResult) {
Lorenzo Colittiab3611b2019-05-09 05:27:29 -0700985 final NetworkMonitor monitor = makeMonitor(nc);
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900986 monitor.notifyNetworkConnected(TEST_LINK_PROPERTIES, nc);
987 try {
988 verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
989 .notifyNetworkTested(eq(testResult), mNetworkTestedRedirectUrlCaptor.capture());
990 } catch (RemoteException e) {
991 fail("Unexpected exception: " + e);
992 }
Lorenzo Colitti5beebde2019-04-10 02:57:17 -0700993 waitForIdle(monitor.getHandler());
Remi NGUYEN VAN3962f672019-03-27 15:42:53 +0900994
995 return monitor;
lucasline252a742019-03-12 13:08:03 +0800996 }
997
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900998 private void setSslException(HttpURLConnection connection) throws IOException {
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900999 doThrow(new SSLHandshakeException("Invalid cert")).when(connection).getResponseCode();
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +09001000 }
1001
1002 private void set302(HttpURLConnection connection, String location) throws IOException {
1003 setStatus(connection, 302);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001004 doReturn(location).when(connection).getHeaderField(LOCATION_HEADER);
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +09001005 }
1006
1007 private void setPortal302(HttpURLConnection connection) throws IOException {
1008 set302(connection, "http://login.example.com");
1009 }
1010
1011 private void setStatus(HttpURLConnection connection, int status) throws IOException {
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001012 doReturn(status).when(connection).getResponseCode();
Hugo Benichic894b122017-07-31 12:58:20 +09001013 }
Chiachang Wang95489ca2019-02-26 11:32:18 +08001014
1015 private void generateTimeoutDnsEvent(DataStallDetectionStats.Builder stats, int num) {
1016 for (int i = 0; i < num; i++) {
1017 stats.addDnsEvent(RETURN_CODE_DNS_TIMEOUT, 123456789 /* timeMs */);
1018 }
1019 }
Hugo Benichic894b122017-07-31 12:58:20 +09001020}
1021