blob: bcfc412c6f4eb08bd6293bc0db005ebcbff4e5dc [file] [log] [blame]
Paul Jensenca8f16a2014-05-09 12:47:55 -04001/*
2 * Copyright (C) 2014 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
Paul Jensen49e3edf2015-05-22 10:50:39 -040019import static android.net.CaptivePortal.APP_RETURN_DISMISSED;
20import static android.net.CaptivePortal.APP_RETURN_UNWANTED;
21import static android.net.CaptivePortal.APP_RETURN_WANTED_AS_IS;
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +090022import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_PROBE_SPEC;
23import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +090024import static android.net.ConnectivityManager.TYPE_MOBILE;
25import static android.net.ConnectivityManager.TYPE_WIFI;
26import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
lucasline252a742019-03-12 13:08:03 +080027import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY;
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +090028import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +090029import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
30import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
Remi NGUYEN VANdcb722f2018-07-20 18:13:24 +090031import static android.net.metrics.ValidationProbeEvent.DNS_FAILURE;
32import static android.net.metrics.ValidationProbeEvent.DNS_SUCCESS;
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +090033import static android.net.metrics.ValidationProbeEvent.PROBE_FALLBACK;
Remi NGUYEN VANdcb722f2018-07-20 18:13:24 +090034import static android.net.metrics.ValidationProbeEvent.PROBE_PRIVDNS;
Remi NGUYEN VAN3ba6c0d2019-01-20 13:48:19 +090035import static android.net.util.NetworkStackUtils.isEmpty;
Chiachang Wang4349dc02019-03-05 20:31:57 +080036import static android.provider.Settings.Global.DATA_STALL_EVALUATION_TYPE_DNS;
Paul Jensen49e3edf2015-05-22 10:50:39 -040037
Chiachang Wangf09e3e32019-02-22 11:13:07 +080038import android.annotation.NonNull;
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +090039import android.annotation.Nullable;
Paul Jensen869868be2014-05-15 10:33:05 -040040import android.app.PendingIntent;
41import android.content.BroadcastReceiver;
Paul Jensenca8f16a2014-05-09 12:47:55 -040042import android.content.Context;
Paul Jensen869868be2014-05-15 10:33:05 -040043import android.content.Intent;
44import android.content.IntentFilter;
45import android.net.ConnectivityManager;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +090046import android.net.INetworkMonitor;
47import android.net.INetworkMonitorCallbacks;
48import android.net.LinkProperties;
Lorenzo Colittid8978ac2017-06-28 23:42:41 +090049import android.net.Network;
Erik Klinea24d4592018-01-11 21:07:29 +090050import android.net.NetworkCapabilities;
Paul Jensen8fe17422015-02-02 11:03:03 -050051import android.net.ProxyInfo;
Paul Jensen7ccd3df2014-08-29 09:54:01 -040052import android.net.TrafficStats;
Paul Jensen71b645f2014-10-13 14:13:07 -040053import android.net.Uri;
Remi NGUYEN VANd57329d2018-05-22 09:58:19 +090054import android.net.captiveportal.CaptivePortalProbeResult;
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +090055import android.net.captiveportal.CaptivePortalProbeSpec;
Chiachang Wangf09e3e32019-02-22 11:13:07 +080056import android.net.metrics.DataStallDetectionStats;
57import android.net.metrics.DataStallStatsUtils;
Hugo Benichicfddd682016-05-31 16:28:06 +090058import android.net.metrics.IpConnectivityLog;
Hugo Benichicc92c6e2016-04-21 15:02:38 +090059import android.net.metrics.NetworkEvent;
Hugo Benichicfddd682016-05-31 16:28:06 +090060import android.net.metrics.ValidationProbeEvent;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +090061import android.net.shared.NetworkMonitorUtils;
62import android.net.shared.PrivateDnsConfig;
63import android.net.util.SharedLog;
Hugo Benichid953bf82016-09-27 09:22:35 +090064import android.net.util.Stopwatch;
Paul Jensen306f1a42014-08-04 10:59:01 -040065import android.net.wifi.WifiInfo;
66import android.net.wifi.WifiManager;
Remi NGUYEN VANdc483562019-02-04 11:32:20 +090067import android.os.Bundle;
Paul Jensenca8f16a2014-05-09 12:47:55 -040068import android.os.Message;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +090069import android.os.RemoteException;
Paul Jensen306f1a42014-08-04 10:59:01 -040070import android.os.SystemClock;
Paul Jensen869868be2014-05-15 10:33:05 -040071import android.os.UserHandle;
Paul Jensenca8f16a2014-05-09 12:47:55 -040072import android.provider.Settings;
Chiachang Wang75610712019-02-14 09:30:58 +080073import android.telephony.AccessNetworkConstants;
Chiachang Wangf09e3e32019-02-22 11:13:07 +080074import android.telephony.CellSignalStrength;
Chiachang Wang75610712019-02-14 09:30:58 +080075import android.telephony.NetworkRegistrationState;
76import android.telephony.ServiceState;
Chiachang Wangf09e3e32019-02-22 11:13:07 +080077import android.telephony.SignalStrength;
Paul Jensen306f1a42014-08-04 10:59:01 -040078import android.telephony.TelephonyManager;
Paul Jensen2f0a8972015-06-25 10:07:14 -040079import android.text.TextUtils;
Paul Jensen532b61432014-11-10 09:50:02 -050080import android.util.Log;
Paul Jensenca8f16a2014-05-09 12:47:55 -040081
Paul Jensend7b6ca92015-05-13 14:05:12 -040082import com.android.internal.annotations.VisibleForTesting;
Chiachang Wang7a70a7e2018-11-27 18:00:05 +080083import com.android.internal.util.RingBufferIndices;
Paul Jensenca8f16a2014-05-09 12:47:55 -040084import com.android.internal.util.State;
85import com.android.internal.util.StateMachine;
Paul Jensenca8f16a2014-05-09 12:47:55 -040086
Paul Jensenca8f16a2014-05-09 12:47:55 -040087import java.io.IOException;
Paul Jensenca8f16a2014-05-09 12:47:55 -040088import java.net.HttpURLConnection;
Paul Jensen2f0a8972015-06-25 10:07:14 -040089import java.net.InetAddress;
Lorenzo Colittic5be12e2016-04-19 21:57:31 +090090import java.net.MalformedURLException;
Paul Jensenca8f16a2014-05-09 12:47:55 -040091import java.net.URL;
Hugo Benichid953bf82016-09-27 09:22:35 +090092import java.net.UnknownHostException;
Hugo Benichieef918a2017-04-10 17:43:08 +090093import java.util.ArrayList;
Lorenzo Colittid8978ac2017-06-28 23:42:41 +090094import java.util.Arrays;
Remi NGUYEN VANa4bcc862019-01-28 13:28:35 +090095import java.util.Collection;
Lorenzo Colittid8978ac2017-06-28 23:42:41 +090096import java.util.Collections;
97import java.util.LinkedHashMap;
Paul Jensen306f1a42014-08-04 10:59:01 -040098import java.util.List;
Paul Jensen71b645f2014-10-13 14:13:07 -040099import java.util.Random;
Erik Kline736353a2018-03-21 07:18:33 -0700100import java.util.UUID;
Hugo Benichid953bf82016-09-27 09:22:35 +0900101import java.util.concurrent.CountDownLatch;
102import java.util.concurrent.TimeUnit;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400103
104/**
105 * {@hide}
106 */
107public class NetworkMonitor extends StateMachine {
Erik Klinea488c232016-04-15 15:49:42 +0900108 private static final String TAG = NetworkMonitor.class.getSimpleName();
Hugo Benichia4f17bc2016-11-21 13:50:05 +0900109 private static final boolean DBG = true;
110 private static final boolean VDBG = false;
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800111 private static final boolean VDBG_STALL = Log.isLoggable(TAG, Log.DEBUG);
Remi NGUYEN VAN47274272019-01-30 23:39:24 +0900112 // TODO: use another permission for CaptivePortalLoginActivity once it has its own certificate
113 private static final String PERMISSION_NETWORK_SETTINGS = "android.permission.NETWORK_SETTINGS";
Hugo Benichi11ae28f2016-09-27 13:16:19 +0900114 // Default configuration values for captive portal detection probes.
115 // TODO: append a random length parameter to the default HTTPS url.
116 // TODO: randomize browser version ids in the default User-Agent String.
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900117 private static final String DEFAULT_HTTPS_URL = "https://www.google.com/generate_204";
Hugo Benichi11ae28f2016-09-27 13:16:19 +0900118 private static final String DEFAULT_FALLBACK_URL = "http://www.google.com/gen_204";
Hugo Benichieef918a2017-04-10 17:43:08 +0900119 private static final String DEFAULT_OTHER_FALLBACK_URLS =
120 "http://play.googleapis.com/generate_204";
Hugo Benichi11ae28f2016-09-27 13:16:19 +0900121 private static final String DEFAULT_USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) "
122 + "AppleWebKit/537.36 (KHTML, like Gecko) "
Lorenzo Colittid8a06082017-06-28 23:39:40 +0900123 + "Chrome/60.0.3112.32 Safari/537.36";
Hugo Benichi92eb22fd2016-09-27 13:01:41 +0900124
Paul Jensenca8f16a2014-05-09 12:47:55 -0400125 private static final int SOCKET_TIMEOUT_MS = 10000;
Hugo Benichi92eb22fd2016-09-27 13:01:41 +0900126 private static final int PROBE_TIMEOUT_MS = 3000;
127
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800128 // Default configuration values for data stall detection.
129 private static final int DEFAULT_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD = 5;
130 private static final int DEFAULT_DATA_STALL_MIN_EVALUATE_TIME_MS = 60 * 1000;
131 private static final int DEFAULT_DATA_STALL_VALID_DNS_TIME_THRESHOLD_MS = 30 * 60 * 1000;
132
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800133 private static final int DEFAULT_DATA_STALL_EVALUATION_TYPES =
Chiachang Wang4349dc02019-03-05 20:31:57 +0800134 DATA_STALL_EVALUATION_TYPE_DNS;
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800135 // Reevaluate it as intending to increase the number. Larger log size may cause statsd
136 // log buffer bust and have stats log lost.
137 private static final int DEFAULT_DNS_LOG_SIZE = 20;
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800138
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +0900139 enum EvaluationResult {
Hugo Benichidd229822016-11-15 23:23:24 +0900140 VALIDATED(true),
141 CAPTIVE_PORTAL(false);
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +0900142 final boolean mIsValidated;
Hugo Benichidd229822016-11-15 23:23:24 +0900143 EvaluationResult(boolean isValidated) {
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +0900144 this.mIsValidated = isValidated;
Hugo Benichidd229822016-11-15 23:23:24 +0900145 }
146 }
147
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +0900148 enum ValidationStage {
Hugo Benichidd229822016-11-15 23:23:24 +0900149 FIRST_VALIDATION(true),
150 REVALIDATION(false);
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +0900151 final boolean mIsFirstValidation;
Hugo Benichidd229822016-11-15 23:23:24 +0900152 ValidationStage(boolean isFirstValidation) {
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +0900153 this.mIsFirstValidation = isFirstValidation;
Hugo Benichidd229822016-11-15 23:23:24 +0900154 }
155 }
156
Paul Jensenca8f16a2014-05-09 12:47:55 -0400157 /**
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900158 * ConnectivityService has sent a notification to indicate that network has connected.
Paul Jensenca8f16a2014-05-09 12:47:55 -0400159 * Initiates Network Validation.
160 */
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +0900161 private static final int CMD_NETWORK_CONNECTED = 1;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400162
163 /**
Paul Jensenca8f16a2014-05-09 12:47:55 -0400164 * Message to self indicating it's time to evaluate a network's connectivity.
165 * arg1 = Token to ignore old messages.
166 */
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +0900167 private static final int CMD_REEVALUATE = 6;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400168
169 /**
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900170 * ConnectivityService has sent a notification to indicate that network has disconnected.
Paul Jensenca8f16a2014-05-09 12:47:55 -0400171 */
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +0900172 private static final int CMD_NETWORK_DISCONNECTED = 7;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400173
174 /**
175 * Force evaluation even if it has succeeded in the past.
Paul Jensen7ccd3df2014-08-29 09:54:01 -0400176 * arg1 = UID responsible for requesting this reeval. Will be billed for data.
Paul Jensenca8f16a2014-05-09 12:47:55 -0400177 */
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +0900178 private static final int CMD_FORCE_REEVALUATION = 8;
Paul Jensen869868be2014-05-15 10:33:05 -0400179
180 /**
Paul Jensen71b645f2014-10-13 14:13:07 -0400181 * Message to self indicating captive portal app finished.
Paul Jensen49e3edf2015-05-22 10:50:39 -0400182 * arg1 = one of: APP_RETURN_DISMISSED,
183 * APP_RETURN_UNWANTED,
184 * APP_RETURN_WANTED_AS_IS
Paul Jensen25a217c2015-02-27 22:55:47 -0500185 * obj = mCaptivePortalLoggedInResponseToken as String
Paul Jensen869868be2014-05-15 10:33:05 -0400186 */
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +0900187 private static final int CMD_CAPTIVE_PORTAL_APP_FINISHED = 9;
Paul Jensen869868be2014-05-15 10:33:05 -0400188
189 /**
Lorenzo Colitti4734cdb2017-04-27 14:30:21 +0900190 * Message indicating sign-in app should be launched.
Paul Jensen25a217c2015-02-27 22:55:47 -0500191 * Sent by mLaunchCaptivePortalAppBroadcastReceiver when the
Lorenzo Colitti4734cdb2017-04-27 14:30:21 +0900192 * user touches the sign in notification, or sent by
193 * ConnectivityService when the user touches the "sign into
194 * network" button in the wifi access point detail page.
Paul Jensen869868be2014-05-15 10:33:05 -0400195 */
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +0900196 private static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = 11;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400197
Paul Jensenee3e2ce2015-06-17 15:02:54 -0400198 /**
199 * Retest network to see if captive portal is still in place.
200 * arg1 = UID responsible for requesting this reeval. Will be billed for data.
201 * 0 indicates self-initiated, so nobody to blame.
202 */
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +0900203 private static final int CMD_CAPTIVE_PORTAL_RECHECK = 12;
Paul Jensenee3e2ce2015-06-17 15:02:54 -0400204
Erik Klinea24d4592018-01-11 21:07:29 +0900205 /**
206 * ConnectivityService notifies NetworkMonitor of settings changes to
207 * Private DNS. If a DNS resolution is required, e.g. for DNS-over-TLS in
208 * strict mode, then an event is sent back to ConnectivityService with the
209 * result of the resolution attempt.
Erik Kline736353a2018-03-21 07:18:33 -0700210 *
211 * A separate message is used to trigger (re)evaluation of the Private DNS
212 * configuration, so that the message can be handled as needed in different
213 * states, including being ignored until after an ongoing captive portal
214 * validation phase is completed.
Erik Klinea24d4592018-01-11 21:07:29 +0900215 */
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +0900216 private static final int CMD_PRIVATE_DNS_SETTINGS_CHANGED = 13;
217 private static final int CMD_EVALUATE_PRIVATE_DNS = 15;
Erik Klinea24d4592018-01-11 21:07:29 +0900218
Chiachang Wang4d11ec92018-10-23 21:10:57 +0800219 /**
220 * Message to self indicating captive portal detection is completed.
221 * obj = CaptivePortalProbeResult for detection result;
222 */
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +0900223 public static final int CMD_PROBE_COMPLETE = 16;
Chiachang Wang4d11ec92018-10-23 21:10:57 +0800224
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800225 /**
226 * ConnectivityService notifies NetworkMonitor of DNS query responses event.
227 * arg1 = returncode in OnDnsEvent which indicates the response code for the DNS query.
228 */
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +0900229 public static final int EVENT_DNS_NOTIFICATION = 17;
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800230
lucasline252a742019-03-12 13:08:03 +0800231 /**
232 * ConnectivityService notifies NetworkMonitor that the user accepts partial connectivity and
233 * NetworkMonitor should ignore the https probe.
234 */
235 public static final int EVENT_ACCEPT_PARTIAL_CONNECTIVITY = 18;
236
Paul Jensend0491e9a2015-05-05 14:52:22 -0400237 // Start mReevaluateDelayMs at this value and double.
238 private static final int INITIAL_REEVALUATE_DELAY_MS = 1000;
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +0900239 private static final int MAX_REEVALUATE_DELAY_MS = 10 * 60 * 1000;
Paul Jensend0491e9a2015-05-05 14:52:22 -0400240 // Before network has been evaluated this many times, ignore repeated reevaluate requests.
241 private static final int IGNORE_REEVALUATE_ATTEMPTS = 5;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400242 private int mReevaluateToken = 0;
Erik Kline736353a2018-03-21 07:18:33 -0700243 private static final int NO_UID = 0;
Paul Jensen7ccd3df2014-08-29 09:54:01 -0400244 private static final int INVALID_UID = -1;
245 private int mUidResponsibleForReeval = INVALID_UID;
Paul Jensend9be23f2015-05-19 14:51:47 -0400246 // Stop blaming UID that requested re-evaluation after this many attempts.
247 private static final int BLAME_FOR_EVALUATION_ATTEMPTS = 5;
Paul Jensenee3e2ce2015-06-17 15:02:54 -0400248 // Delay between reevaluations once a captive portal has been found.
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +0900249 private static final int CAPTIVE_PORTAL_REEVALUATE_DELAY_MS = 10 * 60 * 1000;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400250
Erik Kline736353a2018-03-21 07:18:33 -0700251 private String mPrivateDnsProviderHostname = "";
252
Paul Jensenca8f16a2014-05-09 12:47:55 -0400253 private final Context mContext;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900254 private final INetworkMonitorCallbacks mCallback;
Lorenzo Colittid8978ac2017-06-28 23:42:41 +0900255 private final Network mNetwork;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900256 private final Network mNonPrivateDnsBypassNetwork;
Paul Jensen306f1a42014-08-04 10:59:01 -0400257 private final TelephonyManager mTelephonyManager;
258 private final WifiManager mWifiManager;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900259 private final ConnectivityManager mCm;
Hugo Benichif9fdf872016-07-28 17:53:06 +0900260 private final IpConnectivityLog mMetricsLog;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900261 private final Dependencies mDependencies;
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800262 private final DataStallStatsUtils mDetectionStatsUtils;
Hugo Benichic894b122017-07-31 12:58:20 +0900263
264 // Configuration values for captive portal detection probes.
265 private final String mCaptivePortalUserAgent;
266 private final URL mCaptivePortalHttpsUrl;
267 private final URL mCaptivePortalHttpUrl;
268 private final URL[] mCaptivePortalFallbackUrls;
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +0900269 @Nullable
270 private final CaptivePortalProbeSpec[] mCaptivePortalFallbackSpecs;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400271
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900272 private NetworkCapabilities mNetworkCapabilities;
273 private LinkProperties mLinkProperties;
274
Calvin On4bc78eb2016-10-11 15:10:46 -0700275 @VisibleForTesting
276 protected boolean mIsCaptivePortalCheckEnabled;
277
Lorenzo Colittic5be12e2016-04-19 21:57:31 +0900278 private boolean mUseHttps;
Hugo Benichidd229822016-11-15 23:23:24 +0900279 // The total number of captive portal detection attempts for this NetworkMonitor instance.
280 private int mValidations = 0;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400281
Paul Jensenad50a1f2014-09-05 12:06:44 -0400282 // Set if the user explicitly selected "Do not use this network" in captive portal sign-in app.
283 private boolean mUserDoesNotWant = false;
Paul Jensen700f2362015-05-05 14:56:10 -0400284 // Avoids surfacing "Sign in to network" notification.
285 private boolean mDontDisplaySigninNotification = false;
Paul Jensenad50a1f2014-09-05 12:06:44 -0400286
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900287 private volatile boolean mSystemReady = false;
Robert Greenwaltfb68f8f2014-08-13 13:43:32 -0700288
Paul Jensen71b645f2014-10-13 14:13:07 -0400289 private final State mDefaultState = new DefaultState();
Paul Jensen71b645f2014-10-13 14:13:07 -0400290 private final State mValidatedState = new ValidatedState();
291 private final State mMaybeNotifyState = new MaybeNotifyState();
292 private final State mEvaluatingState = new EvaluatingState();
293 private final State mCaptivePortalState = new CaptivePortalState();
Erik Kline736353a2018-03-21 07:18:33 -0700294 private final State mEvaluatingPrivateDnsState = new EvaluatingPrivateDnsState();
Chiachang Wang4d11ec92018-10-23 21:10:57 +0800295 private final State mProbingState = new ProbingState();
Chiachang Wang78b0d4b2018-12-03 17:06:54 +0800296 private final State mWaitingForNextProbeState = new WaitingForNextProbeState();
Paul Jensen71b645f2014-10-13 14:13:07 -0400297
Paul Jensen25a217c2015-02-27 22:55:47 -0500298 private CustomIntentReceiver mLaunchCaptivePortalAppBroadcastReceiver = null;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400299
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900300 private final SharedLog mValidationLogs;
Robert Greenwalt22b4c6a2015-06-23 15:03:33 -0700301
Erik Klinea488c232016-04-15 15:49:42 +0900302 private final Stopwatch mEvaluationTimer = new Stopwatch();
303
Hugo Benichid953bf82016-09-27 09:22:35 +0900304 // This variable is set before transitioning to the mCaptivePortalState.
305 private CaptivePortalProbeResult mLastPortalProbeResult = CaptivePortalProbeResult.FAILED;
306
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900307 // Random generator to select fallback URL index
308 private final Random mRandom;
Hugo Benichieef918a2017-04-10 17:43:08 +0900309 private int mNextFallbackUrlIndex = 0;
Hugo Benichib03272c2017-04-10 22:45:13 +0900310
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900311
Chiachang Wang4d11ec92018-10-23 21:10:57 +0800312 private int mReevaluateDelayMs = INITIAL_REEVALUATE_DELAY_MS;
313 private int mEvaluateAttempts = 0;
Chiachang Wang30cc3672018-11-12 10:52:25 +0800314 private volatile int mProbeToken = 0;
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800315 private final int mConsecutiveDnsTimeoutThreshold;
316 private final int mDataStallMinEvaluateTime;
317 private final int mDataStallValidDnsTimeThreshold;
318 private final int mDataStallEvaluationType;
319 private final DnsStallDetector mDnsStallDetector;
320 private long mLastProbeTime;
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800321 // Set to true if data stall is suspected and reset to false after metrics are sent to statsd.
322 private boolean mCollectDataStallMetrics = false;
Chiachang Wang4d11ec92018-10-23 21:10:57 +0800323
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900324 public NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network,
Remi NGUYEN VAN231b52b2019-01-29 15:38:52 +0900325 SharedLog validationLog) {
326 this(context, cb, network, new IpConnectivityLog(), validationLog,
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800327 Dependencies.DEFAULT, new DataStallStatsUtils());
Hugo Benichif9fdf872016-07-28 17:53:06 +0900328 }
329
330 @VisibleForTesting
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900331 protected NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network,
Remi NGUYEN VAN231b52b2019-01-29 15:38:52 +0900332 IpConnectivityLog logger, SharedLog validationLogs,
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800333 Dependencies deps, DataStallStatsUtils detectionStatsUtils) {
Paul Jensenca8f16a2014-05-09 12:47:55 -0400334 // Add suffix indicating which NetworkMonitor we're talking about.
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +0900335 super(TAG + "/" + network.toString());
Paul Jensenca8f16a2014-05-09 12:47:55 -0400336
Erik Kline736353a2018-03-21 07:18:33 -0700337 // Logs with a tag of the form given just above, e.g.
338 // <timestamp> 862 2402 D NetworkMonitor/NetworkAgentInfo [WIFI () - 100]: ...
339 setDbg(VDBG);
340
Paul Jensenca8f16a2014-05-09 12:47:55 -0400341 mContext = context;
Hugo Benichif9fdf872016-07-28 17:53:06 +0900342 mMetricsLog = logger;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900343 mValidationLogs = validationLogs;
344 mCallback = cb;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900345 mDependencies = deps;
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800346 mDetectionStatsUtils = detectionStatsUtils;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900347 mNonPrivateDnsBypassNetwork = network;
348 mNetwork = deps.getPrivateDnsBypassNetwork(network);
Paul Jensen306f1a42014-08-04 10:59:01 -0400349 mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
350 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900351 mCm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
Paul Jensenca8f16a2014-05-09 12:47:55 -0400352
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900353 // CHECKSTYLE:OFF IndentationCheck
Paul Jensenca8f16a2014-05-09 12:47:55 -0400354 addState(mDefaultState);
Paul Jensen71b645f2014-10-13 14:13:07 -0400355 addState(mMaybeNotifyState, mDefaultState);
356 addState(mEvaluatingState, mMaybeNotifyState);
Chiachang Wang4d11ec92018-10-23 21:10:57 +0800357 addState(mProbingState, mEvaluatingState);
Chiachang Wang78b0d4b2018-12-03 17:06:54 +0800358 addState(mWaitingForNextProbeState, mEvaluatingState);
Paul Jensen71b645f2014-10-13 14:13:07 -0400359 addState(mCaptivePortalState, mMaybeNotifyState);
Erik Kline736353a2018-03-21 07:18:33 -0700360 addState(mEvaluatingPrivateDnsState, mDefaultState);
361 addState(mValidatedState, mDefaultState);
Robert Greenwalt49f63fb2014-09-13 12:04:12 -0700362 setInitialState(mDefaultState);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900363 // CHECKSTYLE:ON IndentationCheck
Paul Jensenca8f16a2014-05-09 12:47:55 -0400364
Hugo Benichic894b122017-07-31 12:58:20 +0900365 mIsCaptivePortalCheckEnabled = getIsCaptivePortalCheckEnabled();
366 mUseHttps = getUseHttpsValidation();
367 mCaptivePortalUserAgent = getCaptivePortalUserAgent();
368 mCaptivePortalHttpsUrl = makeURL(getCaptivePortalServerHttpsUrl());
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900369 mCaptivePortalHttpUrl = makeURL(deps.getCaptivePortalServerHttpUrl(context));
Hugo Benichic894b122017-07-31 12:58:20 +0900370 mCaptivePortalFallbackUrls = makeCaptivePortalFallbackUrls();
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +0900371 mCaptivePortalFallbackSpecs = makeCaptivePortalFallbackProbeSpecs();
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900372 mRandom = deps.getRandom();
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800373 // TODO: Evaluate to move data stall configuration to a specific class.
374 mConsecutiveDnsTimeoutThreshold = getConsecutiveDnsTimeoutThreshold();
375 mDnsStallDetector = new DnsStallDetector(mConsecutiveDnsTimeoutThreshold);
376 mDataStallMinEvaluateTime = getDataStallMinEvaluateTime();
377 mDataStallValidDnsTimeThreshold = getDataStallValidDnsTimeThreshold();
378 mDataStallEvaluationType = getDataStallEvalutionType();
Hugo Benichib03272c2017-04-10 22:45:13 +0900379
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900380 // mLinkProperties and mNetworkCapbilities must never be null or we will NPE.
381 // Provide empty objects in case we are started and the network disconnects before
382 // we can ever fetch them.
383 // TODO: Delete ASAP.
384 mLinkProperties = new LinkProperties();
Remi NGUYEN VAN231b52b2019-01-29 15:38:52 +0900385 mNetworkCapabilities = new NetworkCapabilities(null);
Paul Jensenca8f16a2014-05-09 12:47:55 -0400386 }
387
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +0900388 /**
lucasline252a742019-03-12 13:08:03 +0800389 * ConnectivityService notifies NetworkMonitor that the user accepts partial connectivity and
390 * NetworkMonitor should ignore the https probe.
391 */
392 public void notifyAcceptPartialConnectivity() {
393 sendMessage(EVENT_ACCEPT_PARTIAL_CONNECTIVITY);
394 }
395
396 /**
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +0900397 * Request the NetworkMonitor to reevaluate the network.
398 */
Erik Kline736353a2018-03-21 07:18:33 -0700399 public void forceReevaluation(int responsibleUid) {
400 sendMessage(CMD_FORCE_REEVALUATION, responsibleUid, 0);
401 }
402
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +0900403 /**
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900404 * Send a notification to NetworkMonitor indicating that there was a DNS query response event.
405 * @param returnCode the DNS return code of the response.
406 */
407 public void notifyDnsResponse(int returnCode) {
408 sendMessage(EVENT_DNS_NOTIFICATION, returnCode);
409 }
410
411 /**
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +0900412 * Send a notification to NetworkMonitor indicating that private DNS settings have changed.
413 * @param newCfg The new private DNS configuration.
414 */
Erik Kline736353a2018-03-21 07:18:33 -0700415 public void notifyPrivateDnsSettingsChanged(PrivateDnsConfig newCfg) {
416 // Cancel any outstanding resolutions.
417 removeMessages(CMD_PRIVATE_DNS_SETTINGS_CHANGED);
418 // Send the update to the proper thread.
419 sendMessage(CMD_PRIVATE_DNS_SETTINGS_CHANGED, newCfg);
420 }
421
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900422 /**
423 * Send a notification to NetworkMonitor indicating that the system is ready.
424 */
425 public void notifySystemReady() {
426 // No need to run on the handler thread: mSystemReady is volatile and read only once on the
427 // isCaptivePortal() thread.
428 mSystemReady = true;
429 }
430
431 /**
432 * Send a notification to NetworkMonitor indicating that the network is now connected.
433 */
434 public void notifyNetworkConnected() {
435 sendMessage(CMD_NETWORK_CONNECTED);
436 }
437
438 /**
439 * Send a notification to NetworkMonitor indicating that the network is now disconnected.
440 */
441 public void notifyNetworkDisconnected() {
442 sendMessage(CMD_NETWORK_DISCONNECTED);
443 }
444
445 /**
446 * Send a notification to NetworkMonitor indicating that link properties have changed.
447 */
448 public void notifyLinkPropertiesChanged() {
449 getHandler().post(() -> {
450 updateLinkProperties();
451 });
452 }
453
454 private void updateLinkProperties() {
455 final LinkProperties lp = mCm.getLinkProperties(mNetwork);
456 // If null, we should soon get a message that the network was disconnected, and will stop.
457 if (lp != null) {
458 // TODO: send LinkProperties parceled in notifyLinkPropertiesChanged() and start().
459 mLinkProperties = lp;
460 }
461 }
462
463 /**
464 * Send a notification to NetworkMonitor indicating that network capabilities have changed.
465 */
466 public void notifyNetworkCapabilitiesChanged() {
467 getHandler().post(() -> {
468 updateNetworkCapabilities();
469 });
470 }
471
472 private void updateNetworkCapabilities() {
473 final NetworkCapabilities nc = mCm.getNetworkCapabilities(mNetwork);
474 // If null, we should soon get a message that the network was disconnected, and will stop.
475 if (nc != null) {
476 // TODO: send NetworkCapabilities parceled in notifyNetworkCapsChanged() and start().
477 mNetworkCapabilities = nc;
478 }
479 }
480
481 /**
482 * Request the captive portal application to be launched.
483 */
484 public void launchCaptivePortalApp() {
485 sendMessage(CMD_LAUNCH_CAPTIVE_PORTAL_APP);
486 }
487
Remi NGUYEN VANcfff01e2019-02-13 20:58:59 +0900488 /**
489 * Notify that the captive portal app was closed with the provided response code.
490 */
491 public void notifyCaptivePortalAppFinished(int response) {
492 sendMessage(CMD_CAPTIVE_PORTAL_APP_FINISHED, response);
493 }
494
Paul Jensen532b61432014-11-10 09:50:02 -0500495 @Override
496 protected void log(String s) {
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +0900497 if (DBG) Log.d(TAG + "/" + mNetwork.toString(), s);
Paul Jensen532b61432014-11-10 09:50:02 -0500498 }
499
Hugo Benichid9ac87e2017-04-06 14:36:39 +0900500 private void validationLog(int probeType, Object url, String msg) {
501 String probeName = ValidationProbeEvent.getProbeName(probeType);
502 validationLog(String.format("%s %s %s", probeName, url, msg));
503 }
504
Robert Greenwalt22b4c6a2015-06-23 15:03:33 -0700505 private void validationLog(String s) {
506 if (DBG) log(s);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900507 mValidationLogs.log(s);
Robert Greenwalt22b4c6a2015-06-23 15:03:33 -0700508 }
509
Hugo Benichidd229822016-11-15 23:23:24 +0900510 private ValidationStage validationStage() {
511 return 0 == mValidations ? ValidationStage.FIRST_VALIDATION : ValidationStage.REVALIDATION;
512 }
513
Erik Klinea24d4592018-01-11 21:07:29 +0900514 private boolean isValidationRequired() {
Lorenzo Colittied3168e2019-01-23 17:54:08 +0900515 return NetworkMonitorUtils.isValidationRequired(mNetworkCapabilities);
Erik Klinea24d4592018-01-11 21:07:29 +0900516 }
517
Erik Kline736353a2018-03-21 07:18:33 -0700518
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900519 private void notifyNetworkTested(int result, @Nullable String redirectUrl) {
520 try {
521 mCallback.notifyNetworkTested(result, redirectUrl);
522 } catch (RemoteException e) {
523 Log.e(TAG, "Error sending network test result", e);
524 }
525 }
526
527 private void showProvisioningNotification(String action) {
528 try {
Remi NGUYEN VAN9c5d9642019-02-07 21:29:57 +0900529 mCallback.showProvisioningNotification(action, mContext.getPackageName());
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900530 } catch (RemoteException e) {
531 Log.e(TAG, "Error showing provisioning notification", e);
532 }
533 }
534
535 private void hideProvisioningNotification() {
536 try {
537 mCallback.hideProvisioningNotification();
538 } catch (RemoteException e) {
539 Log.e(TAG, "Error hiding provisioning notification", e);
540 }
Erik Kline736353a2018-03-21 07:18:33 -0700541 }
542
Paul Jensen71b645f2014-10-13 14:13:07 -0400543 // DefaultState is the parent of all States. It exists only to handle CMD_* messages but
544 // does not entail any real state (hence no enter() or exit() routines).
Paul Jensenca8f16a2014-05-09 12:47:55 -0400545 private class DefaultState extends State {
546 @Override
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900547 public void enter() {
548 // TODO: have those passed parceled in start() and remove this
549 updateLinkProperties();
550 updateNetworkCapabilities();
551 }
552
553 @Override
Paul Jensenca8f16a2014-05-09 12:47:55 -0400554 public boolean processMessage(Message message) {
Paul Jensenca8f16a2014-05-09 12:47:55 -0400555 switch (message.what) {
Paul Jensenca8f16a2014-05-09 12:47:55 -0400556 case CMD_NETWORK_CONNECTED:
Hugo Benichicfddd682016-05-31 16:28:06 +0900557 logNetworkEvent(NetworkEvent.NETWORK_CONNECTED);
Paul Jensenca8f16a2014-05-09 12:47:55 -0400558 transitionTo(mEvaluatingState);
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400559 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400560 case CMD_NETWORK_DISCONNECTED:
Hugo Benichicfddd682016-05-31 16:28:06 +0900561 logNetworkEvent(NetworkEvent.NETWORK_DISCONNECTED);
Paul Jensen25a217c2015-02-27 22:55:47 -0500562 if (mLaunchCaptivePortalAppBroadcastReceiver != null) {
563 mContext.unregisterReceiver(mLaunchCaptivePortalAppBroadcastReceiver);
564 mLaunchCaptivePortalAppBroadcastReceiver = null;
Paul Jensen71b645f2014-10-13 14:13:07 -0400565 }
Robert Greenwalt1fd9aee2014-07-17 16:11:38 -0700566 quit();
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400567 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400568 case CMD_FORCE_REEVALUATION:
Paul Jensenee3e2ce2015-06-17 15:02:54 -0400569 case CMD_CAPTIVE_PORTAL_RECHECK:
Chiachang Wang1cb95492019-01-15 10:32:48 +0800570 final int dnsCount = mDnsStallDetector.getConsecutiveTimeoutCount();
571 validationLog("Forcing reevaluation for UID " + message.arg1
572 + ". Dns signal count: " + dnsCount);
Paul Jensen7ccd3df2014-08-29 09:54:01 -0400573 mUidResponsibleForReeval = message.arg1;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400574 transitionTo(mEvaluatingState);
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400575 return HANDLED;
Paul Jensen71b645f2014-10-13 14:13:07 -0400576 case CMD_CAPTIVE_PORTAL_APP_FINISHED:
Paul Jensen22e547a2015-06-25 09:17:53 -0400577 log("CaptivePortal App responded with " + message.arg1);
Lorenzo Colittic5be12e2016-04-19 21:57:31 +0900578
579 // If the user has seen and acted on a captive portal notification, and the
580 // captive portal app is now closed, disable HTTPS probes. This avoids the
581 // following pathological situation:
582 //
583 // 1. HTTP probe returns a captive portal, HTTPS probe fails or times out.
584 // 2. User opens the app and logs into the captive portal.
585 // 3. HTTP starts working, but HTTPS still doesn't work for some other reason -
586 // perhaps due to the network blocking HTTPS?
587 //
588 // In this case, we'll fail to validate the network even after the app is
589 // dismissed. There is now no way to use this network, because the app is now
590 // gone, so the user cannot select "Use this network as is".
591 mUseHttps = false;
592
Paul Jensen71b645f2014-10-13 14:13:07 -0400593 switch (message.arg1) {
Paul Jensen49e3edf2015-05-22 10:50:39 -0400594 case APP_RETURN_DISMISSED:
Erik Kline736353a2018-03-21 07:18:33 -0700595 sendMessage(CMD_FORCE_REEVALUATION, NO_UID, 0);
Paul Jensen25a217c2015-02-27 22:55:47 -0500596 break;
Paul Jensen49e3edf2015-05-22 10:50:39 -0400597 case APP_RETURN_WANTED_AS_IS:
Paul Jensen700f2362015-05-05 14:56:10 -0400598 mDontDisplaySigninNotification = true;
Paul Jensen25a217c2015-02-27 22:55:47 -0500599 // TODO: Distinguish this from a network that actually validates.
Erik Kline736353a2018-03-21 07:18:33 -0700600 // Displaying the "x" on the system UI icon may still be a good idea.
601 transitionTo(mEvaluatingPrivateDnsState);
Paul Jensen71b645f2014-10-13 14:13:07 -0400602 break;
Paul Jensen49e3edf2015-05-22 10:50:39 -0400603 case APP_RETURN_UNWANTED:
Paul Jensen700f2362015-05-05 14:56:10 -0400604 mDontDisplaySigninNotification = true;
Paul Jensen71b645f2014-10-13 14:13:07 -0400605 mUserDoesNotWant = true;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900606 notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, null);
Paul Jensen71b645f2014-10-13 14:13:07 -0400607 // TODO: Should teardown network.
Paul Jensend0491e9a2015-05-05 14:52:22 -0400608 mUidResponsibleForReeval = 0;
609 transitionTo(mEvaluatingState);
Paul Jensen71b645f2014-10-13 14:13:07 -0400610 break;
611 }
612 return HANDLED;
Erik Kline736353a2018-03-21 07:18:33 -0700613 case CMD_PRIVATE_DNS_SETTINGS_CHANGED: {
614 final PrivateDnsConfig cfg = (PrivateDnsConfig) message.obj;
615 if (!isValidationRequired() || cfg == null || !cfg.inStrictMode()) {
616 // No DNS resolution required.
617 //
618 // We don't force any validation in opportunistic mode
619 // here. Opportunistic mode nameservers are validated
620 // separately within netd.
621 //
622 // Reset Private DNS settings state.
623 mPrivateDnsProviderHostname = "";
624 break;
Erik Klinea24d4592018-01-11 21:07:29 +0900625 }
Erik Kline736353a2018-03-21 07:18:33 -0700626
627 mPrivateDnsProviderHostname = cfg.hostname;
628
629 // DNS resolutions via Private DNS strict mode block for a
630 // few seconds (~4.2) checking for any IP addresses to
631 // arrive and validate. Initiating a (re)evaluation now
632 // should not significantly alter the validation outcome.
633 //
634 // No matter what: enqueue a validation request; one of
635 // three things can happen with this request:
636 // [1] ignored (EvaluatingState or CaptivePortalState)
637 // [2] transition to EvaluatingPrivateDnsState
638 // (DefaultState and ValidatedState)
639 // [3] handled (EvaluatingPrivateDnsState)
640 //
641 // The Private DNS configuration to be evaluated will:
642 // [1] be skipped (not in strict mode), or
643 // [2] validate (huzzah), or
644 // [3] encounter some problem (invalid hostname,
645 // no resolved IP addresses, IPs unreachable,
646 // port 853 unreachable, port 853 is not running a
647 // DNS-over-TLS server, et cetera).
648 sendMessage(CMD_EVALUATE_PRIVATE_DNS);
649 break;
650 }
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800651 case EVENT_DNS_NOTIFICATION:
652 mDnsStallDetector.accumulateConsecutiveDnsTimeoutCount(message.arg1);
653 break;
lucasline252a742019-03-12 13:08:03 +0800654 case EVENT_ACCEPT_PARTIAL_CONNECTIVITY:
655 mUseHttps = false;
656 transitionTo(mEvaluatingPrivateDnsState);
657 break;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400658 default:
Erik Kline736353a2018-03-21 07:18:33 -0700659 break;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400660 }
Erik Kline736353a2018-03-21 07:18:33 -0700661 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400662 }
663 }
664
Paul Jensen71b645f2014-10-13 14:13:07 -0400665 // Being in the ValidatedState State indicates a Network is:
666 // - Successfully validated, or
667 // - Wanted "as is" by the user, or
Paul Jensencf4c2c62015-07-01 14:16:32 -0400668 // - Does not satisfy the default NetworkRequest and so validation has been skipped.
Paul Jensenca8f16a2014-05-09 12:47:55 -0400669 private class ValidatedState extends State {
670 @Override
671 public void enter() {
Hugo Benichidd229822016-11-15 23:23:24 +0900672 maybeLogEvaluationResult(
673 networkEventType(validationStage(), EvaluationResult.VALIDATED));
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900674 notifyNetworkTested(INetworkMonitor.NETWORK_TEST_RESULT_VALID, null);
Hugo Benichidd229822016-11-15 23:23:24 +0900675 mValidations++;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400676 }
677
678 @Override
679 public boolean processMessage(Message message) {
Paul Jensenca8f16a2014-05-09 12:47:55 -0400680 switch (message.what) {
681 case CMD_NETWORK_CONNECTED:
682 transitionTo(mValidatedState);
Erik Kline736353a2018-03-21 07:18:33 -0700683 break;
684 case CMD_EVALUATE_PRIVATE_DNS:
685 transitionTo(mEvaluatingPrivateDnsState);
686 break;
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800687 case EVENT_DNS_NOTIFICATION:
688 mDnsStallDetector.accumulateConsecutiveDnsTimeoutCount(message.arg1);
689 if (isDataStall()) {
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800690 mCollectDataStallMetrics = true;
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800691 validationLog("Suspecting data stall, reevaluate");
692 transitionTo(mEvaluatingState);
693 }
694 break;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400695 default:
696 return NOT_HANDLED;
697 }
Erik Kline736353a2018-03-21 07:18:33 -0700698 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400699 }
700 }
701
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800702 private void writeDataStallStats(@NonNull final CaptivePortalProbeResult result) {
703 /*
704 * Collect data stall detection level information for each transport type. Collect type
705 * specific information for cellular and wifi only currently. Generate
706 * DataStallDetectionStats for each transport type. E.g., if a network supports both
707 * TRANSPORT_WIFI and TRANSPORT_VPN, two DataStallDetectionStats will be generated.
708 */
709 final int[] transports = mNetworkCapabilities.getTransportTypes();
710
711 for (int i = 0; i < transports.length; i++) {
712 DataStallStatsUtils.write(buildDataStallDetectionStats(transports[i]), result);
713 }
714 mCollectDataStallMetrics = false;
715 }
716
717 @VisibleForTesting
718 protected DataStallDetectionStats buildDataStallDetectionStats(int transport) {
719 final DataStallDetectionStats.Builder stats = new DataStallDetectionStats.Builder();
720 if (VDBG_STALL) log("collectDataStallMetrics: type=" + transport);
721 stats.setEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS);
722 stats.setNetworkType(transport);
723 switch (transport) {
724 case NetworkCapabilities.TRANSPORT_WIFI:
725 // TODO: Update it if status query in dual wifi is supported.
726 final WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
727 stats.setWiFiData(wifiInfo);
728 break;
729 case NetworkCapabilities.TRANSPORT_CELLULAR:
730 final boolean isRoaming = !mNetworkCapabilities.hasCapability(
731 NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
732 final SignalStrength ss = mTelephonyManager.getSignalStrength();
733 // TODO(b/120452078): Support multi-sim.
734 stats.setCellData(
735 mTelephonyManager.getDataNetworkType(),
736 isRoaming,
737 mTelephonyManager.getNetworkOperator(),
738 mTelephonyManager.getSimOperator(),
739 (ss != null)
740 ? ss.getLevel() : CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
741 break;
742 default:
743 // No transport type specific information for the other types.
744 break;
745 }
746 addDnsEvents(stats);
747
748 return stats.build();
749 }
750
Chiachang Wang95489ca2019-02-26 11:32:18 +0800751 @VisibleForTesting
752 protected void addDnsEvents(@NonNull final DataStallDetectionStats.Builder stats) {
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800753 final int size = mDnsStallDetector.mResultIndices.size();
754 for (int i = 1; i <= DEFAULT_DNS_LOG_SIZE && i <= size; i++) {
755 final int index = mDnsStallDetector.mResultIndices.indexOf(size - i);
756 stats.addDnsEvent(mDnsStallDetector.mDnsEvents[index].mReturnCode,
757 mDnsStallDetector.mDnsEvents[index].mTimeStamp);
758 }
759 }
760
761
Paul Jensen71b645f2014-10-13 14:13:07 -0400762 // Being in the MaybeNotifyState State indicates the user may have been notified that sign-in
763 // is required. This State takes care to clear the notification upon exit from the State.
764 private class MaybeNotifyState extends State {
765 @Override
Paul Jensen25a217c2015-02-27 22:55:47 -0500766 public boolean processMessage(Message message) {
Paul Jensen25a217c2015-02-27 22:55:47 -0500767 switch (message.what) {
768 case CMD_LAUNCH_CAPTIVE_PORTAL_APP:
Remi NGUYEN VANdc483562019-02-04 11:32:20 +0900769 final Bundle appExtras = new Bundle();
Lorenzo Colittid8978ac2017-06-28 23:42:41 +0900770 // OneAddressPerFamilyNetwork is not parcelable across processes.
Remi NGUYEN VANcfff01e2019-02-13 20:58:59 +0900771 final Network network = new Network(mNetwork);
772 appExtras.putParcelable(ConnectivityManager.EXTRA_NETWORK, network);
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +0900773 final CaptivePortalProbeResult probeRes = mLastPortalProbeResult;
Remi NGUYEN VANdc483562019-02-04 11:32:20 +0900774 appExtras.putString(EXTRA_CAPTIVE_PORTAL_URL, probeRes.detectUrl);
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +0900775 if (probeRes.probeSpec != null) {
776 final String encodedSpec = probeRes.probeSpec.getEncodedSpec();
Remi NGUYEN VANdc483562019-02-04 11:32:20 +0900777 appExtras.putString(EXTRA_CAPTIVE_PORTAL_PROBE_SPEC, encodedSpec);
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +0900778 }
Remi NGUYEN VANdc483562019-02-04 11:32:20 +0900779 appExtras.putString(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_USER_AGENT,
Hugo Benichieef918a2017-04-10 17:43:08 +0900780 mCaptivePortalUserAgent);
Remi NGUYEN VANcfff01e2019-02-13 20:58:59 +0900781 mCm.startCaptivePortalApp(network, appExtras);
Paul Jensen25a217c2015-02-27 22:55:47 -0500782 return HANDLED;
783 default:
784 return NOT_HANDLED;
785 }
786 }
787
788 @Override
Paul Jensen71b645f2014-10-13 14:13:07 -0400789 public void exit() {
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900790 hideProvisioningNotification();
Paul Jensen71b645f2014-10-13 14:13:07 -0400791 }
792 }
793
794 // Being in the EvaluatingState State indicates the Network is being evaluated for internet
Paul Jensend0491e9a2015-05-05 14:52:22 -0400795 // connectivity, or that the user has indicated that this network is unwanted.
Paul Jensenca8f16a2014-05-09 12:47:55 -0400796 private class EvaluatingState extends State {
Paul Jensenca8f16a2014-05-09 12:47:55 -0400797 @Override
798 public void enter() {
Erik Klinea488c232016-04-15 15:49:42 +0900799 // If we have already started to track time spent in EvaluatingState
800 // don't reset the timer due simply to, say, commands or events that
801 // cause us to exit and re-enter EvaluatingState.
802 if (!mEvaluationTimer.isStarted()) {
803 mEvaluationTimer.start();
804 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400805 sendMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
Paul Jensen7ccd3df2014-08-29 09:54:01 -0400806 if (mUidResponsibleForReeval != INVALID_UID) {
807 TrafficStats.setThreadStatsUid(mUidResponsibleForReeval);
808 mUidResponsibleForReeval = INVALID_UID;
809 }
Paul Jensend0491e9a2015-05-05 14:52:22 -0400810 mReevaluateDelayMs = INITIAL_REEVALUATE_DELAY_MS;
Chiachang Wang4d11ec92018-10-23 21:10:57 +0800811 mEvaluateAttempts = 0;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400812 }
813
814 @Override
815 public boolean processMessage(Message message) {
Paul Jensenca8f16a2014-05-09 12:47:55 -0400816 switch (message.what) {
817 case CMD_REEVALUATE:
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +0900818 if (message.arg1 != mReevaluateToken || mUserDoesNotWant) {
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400819 return HANDLED;
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +0900820 }
Erik Kline736353a2018-03-21 07:18:33 -0700821 // Don't bother validating networks that don't satisfy the default request.
Paul Jensen2c311d62014-11-17 12:34:51 -0500822 // This includes:
823 // - VPNs which can be considered explicitly desired by the user and the
824 // user's desire trumps whether the network validates.
Erik Kline736353a2018-03-21 07:18:33 -0700825 // - Networks that don't provide Internet access. It's unclear how to
Paul Jensen2c311d62014-11-17 12:34:51 -0500826 // validate such networks.
827 // - Untrusted networks. It's unsafe to prompt the user to sign-in to
828 // such networks and the user didn't express interest in connecting to
829 // such networks (an app did) so the user may be unhappily surprised when
830 // asked to sign-in to a network they didn't want to connect to in the
831 // first place. Validation could be done to adjust the network scores
832 // however these networks are app-requested and may not be intended for
833 // general usage, in which case general validation may not be an accurate
834 // measure of the network's quality. Only the app knows how to evaluate
835 // the network so don't bother validating here. Furthermore sending HTTP
836 // packets over the network may be undesirable, for example an extremely
837 // expensive metered network, or unwanted leaking of the User Agent string.
Erik Klinea24d4592018-01-11 21:07:29 +0900838 if (!isValidationRequired()) {
Lorenzo Colittic5be12e2016-04-19 21:57:31 +0900839 validationLog("Network would not satisfy default request, not validating");
Paul Jensenca8f16a2014-05-09 12:47:55 -0400840 transitionTo(mValidatedState);
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400841 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400842 }
Chiachang Wang4d11ec92018-10-23 21:10:57 +0800843 mEvaluateAttempts++;
844
845 transitionTo(mProbingState);
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400846 return HANDLED;
Paul Jensen7ccd3df2014-08-29 09:54:01 -0400847 case CMD_FORCE_REEVALUATION:
Paul Jensend0491e9a2015-05-05 14:52:22 -0400848 // Before IGNORE_REEVALUATE_ATTEMPTS attempts are made,
849 // ignore any re-evaluation requests. After, restart the
850 // evaluation process via EvaluatingState#enter.
Chiachang Wang4d11ec92018-10-23 21:10:57 +0800851 return (mEvaluateAttempts < IGNORE_REEVALUATE_ATTEMPTS) ? HANDLED : NOT_HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400852 default:
853 return NOT_HANDLED;
854 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400855 }
Paul Jensen7ccd3df2014-08-29 09:54:01 -0400856
857 @Override
858 public void exit() {
859 TrafficStats.clearThreadStatsUid();
860 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400861 }
862
Paul Jensendcbe8352014-09-16 16:28:34 -0400863 // BroadcastReceiver that waits for a particular Intent and then posts a message.
864 private class CustomIntentReceiver extends BroadcastReceiver {
Paul Jensen71b645f2014-10-13 14:13:07 -0400865 private final int mToken;
866 private final int mWhat;
Paul Jensendcbe8352014-09-16 16:28:34 -0400867 private final String mAction;
Paul Jensen71b645f2014-10-13 14:13:07 -0400868 CustomIntentReceiver(String action, int token, int what) {
869 mToken = token;
870 mWhat = what;
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +0900871 mAction = action + "_" + mNetwork.getNetworkHandle() + "_" + token;
Paul Jensendcbe8352014-09-16 16:28:34 -0400872 mContext.registerReceiver(this, new IntentFilter(mAction));
Paul Jensen869868be2014-05-15 10:33:05 -0400873 }
Paul Jensendcbe8352014-09-16 16:28:34 -0400874 public PendingIntent getPendingIntent() {
Paul Jensen25a217c2015-02-27 22:55:47 -0500875 final Intent intent = new Intent(mAction);
876 intent.setPackage(mContext.getPackageName());
877 return PendingIntent.getBroadcast(mContext, 0, intent, 0);
Paul Jensendcbe8352014-09-16 16:28:34 -0400878 }
879 @Override
880 public void onReceive(Context context, Intent intent) {
Paul Jensen71b645f2014-10-13 14:13:07 -0400881 if (intent.getAction().equals(mAction)) sendMessage(obtainMessage(mWhat, mToken));
Paul Jensendcbe8352014-09-16 16:28:34 -0400882 }
883 }
Paul Jensen869868be2014-05-15 10:33:05 -0400884
Paul Jensen71b645f2014-10-13 14:13:07 -0400885 // Being in the CaptivePortalState State indicates a captive portal was detected and the user
886 // has been shown a notification to sign-in.
887 private class CaptivePortalState extends State {
Paul Jensen25a217c2015-02-27 22:55:47 -0500888 private static final String ACTION_LAUNCH_CAPTIVE_PORTAL_APP =
889 "android.net.netmon.launchCaptivePortalApp";
890
Paul Jensen869868be2014-05-15 10:33:05 -0400891 @Override
892 public void enter() {
Hugo Benichidd229822016-11-15 23:23:24 +0900893 maybeLogEvaluationResult(
894 networkEventType(validationStage(), EvaluationResult.CAPTIVE_PORTAL));
Paul Jensend0491e9a2015-05-05 14:52:22 -0400895 // Don't annoy user with sign-in notifications.
Paul Jensen700f2362015-05-05 14:56:10 -0400896 if (mDontDisplaySigninNotification) return;
Paul Jensen25a217c2015-02-27 22:55:47 -0500897 // Create a CustomIntentReceiver that sends us a
898 // CMD_LAUNCH_CAPTIVE_PORTAL_APP message when the user
899 // touches the notification.
900 if (mLaunchCaptivePortalAppBroadcastReceiver == null) {
Paul Jensen71b645f2014-10-13 14:13:07 -0400901 // Wait for result.
Paul Jensen25a217c2015-02-27 22:55:47 -0500902 mLaunchCaptivePortalAppBroadcastReceiver = new CustomIntentReceiver(
903 ACTION_LAUNCH_CAPTIVE_PORTAL_APP, new Random().nextInt(),
904 CMD_LAUNCH_CAPTIVE_PORTAL_APP);
Paul Jensen71b645f2014-10-13 14:13:07 -0400905 }
Paul Jensen25a217c2015-02-27 22:55:47 -0500906 // Display the sign in notification.
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900907 showProvisioningNotification(mLaunchCaptivePortalAppBroadcastReceiver.mAction);
Paul Jensenee3e2ce2015-06-17 15:02:54 -0400908 // Retest for captive portal occasionally.
909 sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */,
910 CAPTIVE_PORTAL_REEVALUATE_DELAY_MS);
Hugo Benichidd229822016-11-15 23:23:24 +0900911 mValidations++;
Paul Jensen869868be2014-05-15 10:33:05 -0400912 }
913
914 @Override
Paul Jensenee3e2ce2015-06-17 15:02:54 -0400915 public void exit() {
fionaxu1bf6ec22016-05-23 16:33:16 -0700916 removeMessages(CMD_CAPTIVE_PORTAL_RECHECK);
Paul Jensenee3e2ce2015-06-17 15:02:54 -0400917 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400918 }
919
Erik Kline736353a2018-03-21 07:18:33 -0700920 private class EvaluatingPrivateDnsState extends State {
921 private int mPrivateDnsReevalDelayMs;
922 private PrivateDnsConfig mPrivateDnsConfig;
923
924 @Override
925 public void enter() {
926 mPrivateDnsReevalDelayMs = INITIAL_REEVALUATE_DELAY_MS;
927 mPrivateDnsConfig = null;
928 sendMessage(CMD_EVALUATE_PRIVATE_DNS);
929 }
930
931 @Override
932 public boolean processMessage(Message msg) {
933 switch (msg.what) {
934 case CMD_EVALUATE_PRIVATE_DNS:
935 if (inStrictMode()) {
936 if (!isStrictModeHostnameResolved()) {
937 resolveStrictModeHostname();
938
939 if (isStrictModeHostnameResolved()) {
940 notifyPrivateDnsConfigResolved();
941 } else {
942 handlePrivateDnsEvaluationFailure();
943 break;
944 }
945 }
946
947 // Look up a one-time hostname, to bypass caching.
948 //
949 // Note that this will race with ConnectivityService
950 // code programming the DNS-over-TLS server IP addresses
951 // into netd (if invoked, above). If netd doesn't know
952 // the IP addresses yet, or if the connections to the IP
953 // addresses haven't yet been validated, netd will block
954 // for up to a few seconds before failing the lookup.
955 if (!sendPrivateDnsProbe()) {
956 handlePrivateDnsEvaluationFailure();
957 break;
958 }
959 }
960
961 // All good!
962 transitionTo(mValidatedState);
963 break;
964 default:
965 return NOT_HANDLED;
966 }
967 return HANDLED;
968 }
969
970 private boolean inStrictMode() {
971 return !TextUtils.isEmpty(mPrivateDnsProviderHostname);
972 }
973
974 private boolean isStrictModeHostnameResolved() {
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +0900975 return (mPrivateDnsConfig != null)
976 && mPrivateDnsConfig.hostname.equals(mPrivateDnsProviderHostname)
977 && (mPrivateDnsConfig.ips.length > 0);
Erik Kline736353a2018-03-21 07:18:33 -0700978 }
979
980 private void resolveStrictModeHostname() {
981 try {
982 // Do a blocking DNS resolution using the network-assigned nameservers.
Erik Klinef4fa9822018-04-27 22:48:33 +0900983 final InetAddress[] ips = mNetwork.getAllByName(mPrivateDnsProviderHostname);
Erik Kline71d90c42018-04-19 17:58:15 +0900984 mPrivateDnsConfig = new PrivateDnsConfig(mPrivateDnsProviderHostname, ips);
Remi NGUYEN VANdcb722f2018-07-20 18:13:24 +0900985 validationLog("Strict mode hostname resolved: " + mPrivateDnsConfig);
Erik Kline736353a2018-03-21 07:18:33 -0700986 } catch (UnknownHostException uhe) {
987 mPrivateDnsConfig = null;
Remi NGUYEN VANdcb722f2018-07-20 18:13:24 +0900988 validationLog("Strict mode hostname resolution failed: " + uhe.getMessage());
Erik Kline736353a2018-03-21 07:18:33 -0700989 }
990 }
991
992 private void notifyPrivateDnsConfigResolved() {
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900993 try {
994 mCallback.notifyPrivateDnsConfigResolved(mPrivateDnsConfig.toParcel());
995 } catch (RemoteException e) {
996 Log.e(TAG, "Error sending private DNS config resolved notification", e);
997 }
Erik Kline736353a2018-03-21 07:18:33 -0700998 }
999
1000 private void handlePrivateDnsEvaluationFailure() {
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001001 notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, null);
Erik Kline736353a2018-03-21 07:18:33 -07001002
1003 // Queue up a re-evaluation with backoff.
1004 //
1005 // TODO: Consider abandoning this state after a few attempts and
1006 // transitioning back to EvaluatingState, to perhaps give ourselves
1007 // the opportunity to (re)detect a captive portal or something.
1008 sendMessageDelayed(CMD_EVALUATE_PRIVATE_DNS, mPrivateDnsReevalDelayMs);
1009 mPrivateDnsReevalDelayMs *= 2;
1010 if (mPrivateDnsReevalDelayMs > MAX_REEVALUATE_DELAY_MS) {
1011 mPrivateDnsReevalDelayMs = MAX_REEVALUATE_DELAY_MS;
1012 }
1013 }
1014
1015 private boolean sendPrivateDnsProbe() {
1016 // q.v. system/netd/server/dns/DnsTlsTransport.cpp
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +09001017 final String oneTimeHostnameSuffix = "-dnsotls-ds.metric.gstatic.com";
1018 final String host = UUID.randomUUID().toString().substring(0, 8)
1019 + oneTimeHostnameSuffix;
Remi NGUYEN VANdcb722f2018-07-20 18:13:24 +09001020 final Stopwatch watch = new Stopwatch().start();
Erik Kline736353a2018-03-21 07:18:33 -07001021 try {
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001022 final InetAddress[] ips = mNonPrivateDnsBypassNetwork.getAllByName(host);
Remi NGUYEN VANdcb722f2018-07-20 18:13:24 +09001023 final long time = watch.stop();
1024 final String strIps = Arrays.toString(ips);
1025 final boolean success = (ips != null && ips.length > 0);
1026 validationLog(PROBE_PRIVDNS, host, String.format("%dms: %s", time, strIps));
1027 logValidationProbe(time, PROBE_PRIVDNS, success ? DNS_SUCCESS : DNS_FAILURE);
1028 return success;
1029 } catch (UnknownHostException uhe) {
1030 final long time = watch.stop();
1031 validationLog(PROBE_PRIVDNS, host,
1032 String.format("%dms - Error: %s", time, uhe.getMessage()));
1033 logValidationProbe(time, PROBE_PRIVDNS, DNS_FAILURE);
1034 }
Erik Kline736353a2018-03-21 07:18:33 -07001035 return false;
1036 }
1037 }
1038
Chiachang Wang4d11ec92018-10-23 21:10:57 +08001039 private class ProbingState extends State {
1040 private Thread mThread;
1041
1042 @Override
1043 public void enter() {
Chiachang Wang78b0d4b2018-12-03 17:06:54 +08001044 if (mEvaluateAttempts >= BLAME_FOR_EVALUATION_ATTEMPTS) {
1045 //Don't continue to blame UID forever.
1046 TrafficStats.clearThreadStatsUid();
1047 }
1048
Chiachang Wang30cc3672018-11-12 10:52:25 +08001049 final int token = ++mProbeToken;
1050 mThread = new Thread(() -> sendMessage(obtainMessage(CMD_PROBE_COMPLETE, token, 0,
Chiachang Wang4d11ec92018-10-23 21:10:57 +08001051 isCaptivePortal())));
1052 mThread.start();
1053 }
1054
1055 @Override
1056 public boolean processMessage(Message message) {
1057 switch (message.what) {
1058 case CMD_PROBE_COMPLETE:
Chiachang Wang30cc3672018-11-12 10:52:25 +08001059 // Ensure that CMD_PROBE_COMPLETE from stale threads are ignored.
1060 if (message.arg1 != mProbeToken) {
1061 return HANDLED;
1062 }
1063
Chiachang Wang4d11ec92018-10-23 21:10:57 +08001064 final CaptivePortalProbeResult probeResult =
1065 (CaptivePortalProbeResult) message.obj;
Chiachang Wang7a70a7e2018-11-27 18:00:05 +08001066 mLastProbeTime = SystemClock.elapsedRealtime();
Chiachang Wangf09e3e32019-02-22 11:13:07 +08001067
1068 if (mCollectDataStallMetrics) {
1069 writeDataStallStats(probeResult);
1070 }
1071
Chiachang Wang4d11ec92018-10-23 21:10:57 +08001072 if (probeResult.isSuccessful()) {
1073 // Transit EvaluatingPrivateDnsState to get to Validated
1074 // state (even if no Private DNS validation required).
1075 transitionTo(mEvaluatingPrivateDnsState);
1076 } else if (probeResult.isPortal()) {
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001077 notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, probeResult.redirectUrl);
Chiachang Wang4d11ec92018-10-23 21:10:57 +08001078 mLastPortalProbeResult = probeResult;
1079 transitionTo(mCaptivePortalState);
lucasline252a742019-03-12 13:08:03 +08001080 } else if (probeResult.isPartialConnectivity()) {
1081 logNetworkEvent(NetworkEvent.NETWORK_PARTIAL_CONNECTIVITY);
1082 notifyNetworkTested(NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY,
1083 probeResult.redirectUrl);
1084 transitionTo(mWaitingForNextProbeState);
Chiachang Wang4d11ec92018-10-23 21:10:57 +08001085 } else {
Chiachang Wang4d11ec92018-10-23 21:10:57 +08001086 logNetworkEvent(NetworkEvent.NETWORK_VALIDATION_FAILED);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001087 notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, probeResult.redirectUrl);
Chiachang Wang78b0d4b2018-12-03 17:06:54 +08001088 transitionTo(mWaitingForNextProbeState);
Chiachang Wang4d11ec92018-10-23 21:10:57 +08001089 }
1090 return HANDLED;
Chiachang Wang7a70a7e2018-11-27 18:00:05 +08001091 case EVENT_DNS_NOTIFICATION:
lucasline252a742019-03-12 13:08:03 +08001092 case EVENT_ACCEPT_PARTIAL_CONNECTIVITY:
1093 // Leave the event to DefaultState.
Chiachang Wang4d11ec92018-10-23 21:10:57 +08001094 return NOT_HANDLED;
1095 default:
Chiachang Wang78b0d4b2018-12-03 17:06:54 +08001096 // Wait for probe result and defer events to next state by default.
Chiachang Wang4d11ec92018-10-23 21:10:57 +08001097 deferMessage(message);
1098 return HANDLED;
1099 }
1100 }
1101
1102 @Override
1103 public void exit() {
Chiachang Wang30cc3672018-11-12 10:52:25 +08001104 if (mThread.isAlive()) {
1105 mThread.interrupt();
1106 }
Chiachang Wang4d11ec92018-10-23 21:10:57 +08001107 mThread = null;
1108 }
1109 }
1110
Chiachang Wang78b0d4b2018-12-03 17:06:54 +08001111 // Being in the WaitingForNextProbeState indicates that evaluating probes failed and state is
1112 // transited from ProbingState. This ensures that the state machine is only in ProbingState
1113 // while a probe is in progress, not while waiting to perform the next probe. That allows
1114 // ProbingState to defer most messages until the probe is complete, which keeps the code simple
1115 // and matches the pre-Q behaviour where probes were a blocking operation performed on the state
1116 // machine thread.
1117 private class WaitingForNextProbeState extends State {
1118 @Override
1119 public void enter() {
Chiachang Wangcfe23562018-12-24 11:05:52 +08001120 scheduleNextProbe();
1121 }
1122
1123 private void scheduleNextProbe() {
Chiachang Wang78b0d4b2018-12-03 17:06:54 +08001124 final Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
1125 sendMessageDelayed(msg, mReevaluateDelayMs);
1126 mReevaluateDelayMs *= 2;
1127 if (mReevaluateDelayMs > MAX_REEVALUATE_DELAY_MS) {
1128 mReevaluateDelayMs = MAX_REEVALUATE_DELAY_MS;
1129 }
1130 }
1131
1132 @Override
1133 public boolean processMessage(Message message) {
1134 return NOT_HANDLED;
1135 }
1136 }
1137
Lorenzo Colittid8978ac2017-06-28 23:42:41 +09001138 // Limits the list of IP addresses returned by getAllByName or tried by openConnection to at
1139 // most one per address family. This ensures we only wait up to 20 seconds for TCP connections
1140 // to complete, regardless of how many IP addresses a host has.
1141 private static class OneAddressPerFamilyNetwork extends Network {
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +09001142 OneAddressPerFamilyNetwork(Network network) {
Erik Klinef4fa9822018-04-27 22:48:33 +09001143 // Always bypass Private DNS.
1144 super(network.getPrivateDnsBypassingCopy());
Lorenzo Colittid8978ac2017-06-28 23:42:41 +09001145 }
1146
1147 @Override
1148 public InetAddress[] getAllByName(String host) throws UnknownHostException {
Erik Klinef4fa9822018-04-27 22:48:33 +09001149 final List<InetAddress> addrs = Arrays.asList(super.getAllByName(host));
Lorenzo Colittid8978ac2017-06-28 23:42:41 +09001150
1151 // Ensure the address family of the first address is tried first.
1152 LinkedHashMap<Class, InetAddress> addressByFamily = new LinkedHashMap<>();
1153 addressByFamily.put(addrs.get(0).getClass(), addrs.get(0));
1154 Collections.shuffle(addrs);
1155
1156 for (InetAddress addr : addrs) {
1157 addressByFamily.put(addr.getClass(), addr);
1158 }
1159
1160 return addressByFamily.values().toArray(new InetAddress[addressByFamily.size()]);
1161 }
1162 }
1163
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001164 private boolean getIsCaptivePortalCheckEnabled() {
Hugo Benichic894b122017-07-31 12:58:20 +09001165 String symbol = Settings.Global.CAPTIVE_PORTAL_MODE;
1166 int defaultValue = Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +09001167 int mode = mDependencies.getSetting(mContext, symbol, defaultValue);
Hugo Benichic894b122017-07-31 12:58:20 +09001168 return mode != Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE;
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001169 }
1170
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001171 private boolean getUseHttpsValidation() {
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +09001172 return mDependencies.getSetting(mContext, Settings.Global.CAPTIVE_PORTAL_USE_HTTPS, 1) == 1;
Hugo Benichic894b122017-07-31 12:58:20 +09001173 }
1174
Hugo Benichic894b122017-07-31 12:58:20 +09001175 private String getCaptivePortalServerHttpsUrl() {
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +09001176 return mDependencies.getSetting(mContext,
Hugo Benichic894b122017-07-31 12:58:20 +09001177 Settings.Global.CAPTIVE_PORTAL_HTTPS_URL, DEFAULT_HTTPS_URL);
1178 }
1179
Chiachang Wang7a70a7e2018-11-27 18:00:05 +08001180 private int getConsecutiveDnsTimeoutThreshold() {
1181 return mDependencies.getSetting(mContext,
1182 Settings.Global.DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD,
1183 DEFAULT_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD);
1184 }
1185
1186 private int getDataStallMinEvaluateTime() {
1187 return mDependencies.getSetting(mContext,
1188 Settings.Global.DATA_STALL_MIN_EVALUATE_INTERVAL,
1189 DEFAULT_DATA_STALL_MIN_EVALUATE_TIME_MS);
1190 }
1191
1192 private int getDataStallValidDnsTimeThreshold() {
1193 return mDependencies.getSetting(mContext,
1194 Settings.Global.DATA_STALL_VALID_DNS_TIME_THRESHOLD,
1195 DEFAULT_DATA_STALL_VALID_DNS_TIME_THRESHOLD_MS);
1196 }
1197
1198 private int getDataStallEvalutionType() {
1199 return mDependencies.getSetting(mContext, Settings.Global.DATA_STALL_EVALUATION_TYPE,
1200 DEFAULT_DATA_STALL_EVALUATION_TYPES);
1201 }
1202
Hugo Benichic894b122017-07-31 12:58:20 +09001203 private URL[] makeCaptivePortalFallbackUrls() {
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +09001204 try {
1205 String separator = ",";
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +09001206 String firstUrl = mDependencies.getSetting(mContext,
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +09001207 Settings.Global.CAPTIVE_PORTAL_FALLBACK_URL, DEFAULT_FALLBACK_URL);
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +09001208 String joinedUrls = firstUrl + separator + mDependencies.getSetting(mContext,
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +09001209 Settings.Global.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS,
1210 DEFAULT_OTHER_FALLBACK_URLS);
1211 List<URL> urls = new ArrayList<>();
1212 for (String s : joinedUrls.split(separator)) {
1213 URL u = makeURL(s);
1214 if (u == null) {
1215 continue;
1216 }
1217 urls.add(u);
Hugo Benichieef918a2017-04-10 17:43:08 +09001218 }
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +09001219 if (urls.isEmpty()) {
1220 Log.e(TAG, String.format("could not create any url from %s", joinedUrls));
1221 }
1222 return urls.toArray(new URL[urls.size()]);
1223 } catch (Exception e) {
1224 // Don't let a misconfiguration bootloop the system.
1225 Log.e(TAG, "Error parsing configured fallback URLs", e);
1226 return new URL[0];
Hugo Benichieef918a2017-04-10 17:43:08 +09001227 }
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +09001228 }
1229
1230 private CaptivePortalProbeSpec[] makeCaptivePortalFallbackProbeSpecs() {
1231 try {
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +09001232 final String settingsValue = mDependencies.getSetting(
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +09001233 mContext, Settings.Global.CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS, null);
1234 // Probe specs only used if configured in settings
1235 if (TextUtils.isEmpty(settingsValue)) {
1236 return null;
1237 }
1238
Remi NGUYEN VANa4bcc862019-01-28 13:28:35 +09001239 final Collection<CaptivePortalProbeSpec> specs =
1240 CaptivePortalProbeSpec.parseCaptivePortalProbeSpecs(settingsValue);
1241 final CaptivePortalProbeSpec[] specsArray = new CaptivePortalProbeSpec[specs.size()];
1242 return specs.toArray(specsArray);
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +09001243 } catch (Exception e) {
1244 // Don't let a misconfiguration bootloop the system.
1245 Log.e(TAG, "Error parsing configured fallback probe specs", e);
1246 return null;
Hugo Benichieef918a2017-04-10 17:43:08 +09001247 }
Hugo Benichi92eb22fd2016-09-27 13:01:41 +09001248 }
1249
Hugo Benichic894b122017-07-31 12:58:20 +09001250 private String getCaptivePortalUserAgent() {
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +09001251 return mDependencies.getSetting(mContext,
Hugo Benichic894b122017-07-31 12:58:20 +09001252 Settings.Global.CAPTIVE_PORTAL_USER_AGENT, DEFAULT_USER_AGENT);
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001253 }
1254
Hugo Benichieef918a2017-04-10 17:43:08 +09001255 private URL nextFallbackUrl() {
1256 if (mCaptivePortalFallbackUrls.length == 0) {
1257 return null;
1258 }
1259 int idx = Math.abs(mNextFallbackUrlIndex) % mCaptivePortalFallbackUrls.length;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +09001260 mNextFallbackUrlIndex += mRandom.nextInt(); // randomly change url without memory.
Hugo Benichieef918a2017-04-10 17:43:08 +09001261 return mCaptivePortalFallbackUrls[idx];
1262 }
1263
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +09001264 private CaptivePortalProbeSpec nextFallbackSpec() {
Remi NGUYEN VAN3ba6c0d2019-01-20 13:48:19 +09001265 if (isEmpty(mCaptivePortalFallbackSpecs)) {
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +09001266 return null;
1267 }
1268 // Randomly change spec without memory. Also randomize the first attempt.
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +09001269 final int idx = Math.abs(mRandom.nextInt()) % mCaptivePortalFallbackSpecs.length;
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +09001270 return mCaptivePortalFallbackSpecs[idx];
1271 }
1272
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001273 @VisibleForTesting
1274 protected CaptivePortalProbeResult isCaptivePortal() {
Calvin On4bc78eb2016-10-11 15:10:46 -07001275 if (!mIsCaptivePortalCheckEnabled) {
1276 validationLog("Validation disabled.");
Hugo Benichia4158702017-04-10 17:08:06 +09001277 return CaptivePortalProbeResult.SUCCESS;
Calvin On4bc78eb2016-10-11 15:10:46 -07001278 }
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001279
Hugo Benichib03272c2017-04-10 22:45:13 +09001280 URL pacUrl = null;
1281 URL httpsUrl = mCaptivePortalHttpsUrl;
1282 URL httpUrl = mCaptivePortalHttpUrl;
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001283
1284 // On networks with a PAC instead of fetching a URL that should result in a 204
1285 // response, we instead simply fetch the PAC script. This is done for a few reasons:
1286 // 1. At present our PAC code does not yet handle multiple PACs on multiple networks
1287 // until something like https://android-review.googlesource.com/#/c/115180/ lands.
1288 // Network.openConnection() will ignore network-specific PACs and instead fetch
1289 // using NO_PROXY. If a PAC is in place, the only fetch we know will succeed with
1290 // NO_PROXY is the fetch of the PAC itself.
1291 // 2. To proxy the generate_204 fetch through a PAC would require a number of things
1292 // happen before the fetch can commence, namely:
1293 // a) the PAC script be fetched
1294 // b) a PAC script resolver service be fired up and resolve the captive portal
1295 // server.
1296 // Network validation could be delayed until these prerequisities are satisifed or
1297 // could simply be left to race them. Neither is an optimal solution.
1298 // 3. PAC scripts are sometimes used to block or restrict Internet access and may in
1299 // fact block fetching of the generate_204 URL which would lead to false negative
1300 // results for network validation.
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001301 final ProxyInfo proxyInfo = mLinkProperties.getHttpProxy();
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001302 if (proxyInfo != null && !Uri.EMPTY.equals(proxyInfo.getPacFileUrl())) {
Hugo Benichi92eb22fd2016-09-27 13:01:41 +09001303 pacUrl = makeURL(proxyInfo.getPacFileUrl().toString());
1304 if (pacUrl == null) {
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001305 return CaptivePortalProbeResult.FAILED;
1306 }
1307 }
1308
Hugo Benichib03272c2017-04-10 22:45:13 +09001309 if ((pacUrl == null) && (httpUrl == null || httpsUrl == null)) {
1310 return CaptivePortalProbeResult.FAILED;
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001311 }
1312
1313 long startTime = SystemClock.elapsedRealtime();
1314
Hugo Benichieb5e9aa2016-11-16 18:18:08 +09001315 final CaptivePortalProbeResult result;
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001316 if (pacUrl != null) {
Hugo Benichieb5e9aa2016-11-16 18:18:08 +09001317 result = sendDnsAndHttpProbes(null, pacUrl, ValidationProbeEvent.PROBE_PAC);
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001318 } else if (mUseHttps) {
Hugo Benichieef918a2017-04-10 17:43:08 +09001319 result = sendParallelHttpProbes(proxyInfo, httpsUrl, httpUrl);
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001320 } else {
Hugo Benichieb5e9aa2016-11-16 18:18:08 +09001321 result = sendDnsAndHttpProbes(proxyInfo, httpUrl, ValidationProbeEvent.PROBE_HTTP);
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001322 }
1323
1324 long endTime = SystemClock.elapsedRealtime();
1325
1326 sendNetworkConditionsBroadcast(true /* response received */,
1327 result.isPortal() /* isCaptivePortal */,
1328 startTime, endTime);
1329
Chiachang Wang4d11ec92018-10-23 21:10:57 +08001330 log("isCaptivePortal: isSuccessful()=" + result.isSuccessful()
1331 + " isPortal()=" + result.isPortal()
1332 + " RedirectUrl=" + result.redirectUrl
1333 + " Time=" + (endTime - startTime) + "ms");
Yohei, Oshima5225aba2016-08-02 10:34:32 +09001334
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001335 return result;
Udam Sainib7c24872016-01-04 12:16:14 -08001336 }
1337
Paul Jensenca8f16a2014-05-09 12:47:55 -04001338 /**
Hugo Benichieb5e9aa2016-11-16 18:18:08 +09001339 * Do a DNS resolution and URL fetch on a known web server to see if we get the data we expect.
1340 * @return a CaptivePortalProbeResult inferred from the HTTP response.
1341 */
1342 private CaptivePortalProbeResult sendDnsAndHttpProbes(ProxyInfo proxy, URL url, int probeType) {
1343 // Pre-resolve the captive portal server host so we can log it.
1344 // Only do this if HttpURLConnection is about to, to avoid any potentially
1345 // unnecessary resolution.
1346 final String host = (proxy != null) ? proxy.getHost() : url.getHost();
1347 sendDnsProbe(host);
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +09001348 return sendHttpProbe(url, probeType, null);
Hugo Benichieb5e9aa2016-11-16 18:18:08 +09001349 }
1350
1351 /** Do a DNS resolution of the given server. */
1352 private void sendDnsProbe(String host) {
1353 if (TextUtils.isEmpty(host)) {
1354 return;
1355 }
1356
1357 final String name = ValidationProbeEvent.getProbeName(ValidationProbeEvent.PROBE_DNS);
1358 final Stopwatch watch = new Stopwatch().start();
1359 int result;
1360 String connectInfo;
1361 try {
Lorenzo Colittid8978ac2017-06-28 23:42:41 +09001362 InetAddress[] addresses = mNetwork.getAllByName(host);
Hugo Benichid9ac87e2017-04-06 14:36:39 +09001363 StringBuffer buffer = new StringBuffer();
Hugo Benichieb5e9aa2016-11-16 18:18:08 +09001364 for (InetAddress address : addresses) {
Hugo Benichid9ac87e2017-04-06 14:36:39 +09001365 buffer.append(',').append(address.getHostAddress());
Hugo Benichieb5e9aa2016-11-16 18:18:08 +09001366 }
Hugo Benichid9ac87e2017-04-06 14:36:39 +09001367 result = ValidationProbeEvent.DNS_SUCCESS;
1368 connectInfo = "OK " + buffer.substring(1);
Hugo Benichieb5e9aa2016-11-16 18:18:08 +09001369 } catch (UnknownHostException e) {
1370 result = ValidationProbeEvent.DNS_FAILURE;
Hugo Benichid9ac87e2017-04-06 14:36:39 +09001371 connectInfo = "FAIL";
Hugo Benichieb5e9aa2016-11-16 18:18:08 +09001372 }
1373 final long latency = watch.stop();
Hugo Benichid9ac87e2017-04-06 14:36:39 +09001374 validationLog(ValidationProbeEvent.PROBE_DNS, host,
1375 String.format("%dms %s", latency, connectInfo));
Hugo Benichieb5e9aa2016-11-16 18:18:08 +09001376 logValidationProbe(latency, ValidationProbeEvent.PROBE_DNS, result);
1377 }
1378
1379 /**
1380 * Do a URL fetch on a known web server to see if we get the data we expect.
1381 * @return a CaptivePortalProbeResult inferred from the HTTP response.
Paul Jensenca8f16a2014-05-09 12:47:55 -04001382 */
Paul Jensencf4c2c62015-07-01 14:16:32 -04001383 @VisibleForTesting
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +09001384 protected CaptivePortalProbeResult sendHttpProbe(URL url, int probeType,
1385 @Nullable CaptivePortalProbeSpec probeSpec) {
Paul Jensenca8f16a2014-05-09 12:47:55 -04001386 HttpURLConnection urlConnection = null;
Hugo Benichia4158702017-04-10 17:08:06 +09001387 int httpResponseCode = CaptivePortalProbeResult.FAILED_CODE;
Paul Jensen232437312016-04-06 09:51:26 -04001388 String redirectUrl = null;
Erik Klinea488c232016-04-15 15:49:42 +09001389 final Stopwatch probeTimer = new Stopwatch().start();
Jeff Sharkey619a5112017-01-19 11:55:54 -07001390 final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_PROBE);
Paul Jensenca8f16a2014-05-09 12:47:55 -04001391 try {
Lorenzo Colittid8978ac2017-06-28 23:42:41 +09001392 urlConnection = (HttpURLConnection) mNetwork.openConnection(url);
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001393 urlConnection.setInstanceFollowRedirects(probeType == ValidationProbeEvent.PROBE_PAC);
Paul Jensene547ff22014-08-04 09:12:24 -04001394 urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
1395 urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
Hongshik1da51fc2019-01-12 03:09:34 +09001396 urlConnection.setRequestProperty("Connection", "close");
Paul Jensene547ff22014-08-04 09:12:24 -04001397 urlConnection.setUseCaches(false);
Hugo Benichib03272c2017-04-10 22:45:13 +09001398 if (mCaptivePortalUserAgent != null) {
1399 urlConnection.setRequestProperty("User-Agent", mCaptivePortalUserAgent);
Hugo Benichi92eb22fd2016-09-27 13:01:41 +09001400 }
Hugo Benichiec88fd62017-03-07 15:10:03 +09001401 // cannot read request header after connection
1402 String requestHeader = urlConnection.getRequestProperties().toString();
Paul Jensen306f1a42014-08-04 10:59:01 -04001403
1404 // Time how long it takes to get a response to our request
1405 long requestTimestamp = SystemClock.elapsedRealtime();
1406
Pierre Imaibe12d762016-03-10 17:00:50 +09001407 httpResponseCode = urlConnection.getResponseCode();
Paul Jensen232437312016-04-06 09:51:26 -04001408 redirectUrl = urlConnection.getHeaderField("location");
Paul Jensen306f1a42014-08-04 10:59:01 -04001409
1410 // Time how long it takes to get a response to our request
1411 long responseTimestamp = SystemClock.elapsedRealtime();
1412
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +09001413 validationLog(probeType, url, "time=" + (responseTimestamp - requestTimestamp) + "ms"
1414 + " ret=" + httpResponseCode
1415 + " request=" + requestHeader
1416 + " headers=" + urlConnection.getHeaderFields());
Paul Jensene547ff22014-08-04 09:12:24 -04001417 // NOTE: We may want to consider an "HTTP/1.0 204" response to be a captive
1418 // portal. The only example of this seen so far was a captive portal. For
1419 // the time being go with prior behavior of assuming it's not a captive
1420 // portal. If it is considered a captive portal, a different sign-in URL
1421 // is needed (i.e. can't browse a 204). This could be the result of an HTTP
1422 // proxy server.
Hugo Benichicb4aa4d2017-01-11 16:23:26 +09001423 if (httpResponseCode == 200) {
Sehee Park8e9a15b2018-11-16 17:39:34 +09001424 long contentLength = urlConnection.getContentLengthLong();
Hugo Benichicb4aa4d2017-01-11 16:23:26 +09001425 if (probeType == ValidationProbeEvent.PROBE_PAC) {
Hugo Benichid9ac87e2017-04-06 14:36:39 +09001426 validationLog(
1427 probeType, url, "PAC fetch 200 response interpreted as 204 response.");
Hugo Benichia4158702017-04-10 17:08:06 +09001428 httpResponseCode = CaptivePortalProbeResult.SUCCESS_CODE;
Sehee Park8e9a15b2018-11-16 17:39:34 +09001429 } else if (contentLength == -1) {
1430 // When no Content-length (default value == -1), attempt to read a byte
1431 // from the response. Do not use available() as it is unreliable.
1432 // See http://b/33498325.
Hugo Benichicb4aa4d2017-01-11 16:23:26 +09001433 if (urlConnection.getInputStream().read() == -1) {
Sehee Park8e9a15b2018-11-16 17:39:34 +09001434 validationLog(probeType, url,
1435 "Empty 200 response interpreted as failed response.");
1436 httpResponseCode = CaptivePortalProbeResult.FAILED_CODE;
Hugo Benichicb4aa4d2017-01-11 16:23:26 +09001437 }
Sehee Park8e9a15b2018-11-16 17:39:34 +09001438 } else if (contentLength <= 4) {
1439 // Consider 200 response with "Content-length <= 4" to not be a captive
1440 // portal. There's no point in considering this a captive portal as the
1441 // user cannot sign-in to an empty page. Probably the result of a broken
1442 // transparent proxy. See http://b/9972012 and http://b/122999481.
1443 validationLog(probeType, url, "200 response with Content-length <= 4"
1444 + " interpreted as failed response.");
1445 httpResponseCode = CaptivePortalProbeResult.FAILED_CODE;
Hugo Benichicb4aa4d2017-01-11 16:23:26 +09001446 }
Paul Jensen8fe17422015-02-02 11:03:03 -05001447 }
Paul Jensenca8f16a2014-05-09 12:47:55 -04001448 } catch (IOException e) {
Hugo Benichia4158702017-04-10 17:08:06 +09001449 validationLog(probeType, url, "Probe failed with exception " + e);
1450 if (httpResponseCode == CaptivePortalProbeResult.FAILED_CODE) {
Paul Jensen869868be2014-05-15 10:33:05 -04001451 // TODO: Ping gateway and DNS server and log results.
1452 }
Paul Jensenca8f16a2014-05-09 12:47:55 -04001453 } finally {
1454 if (urlConnection != null) {
1455 urlConnection.disconnect();
1456 }
Jeff Sharkey619a5112017-01-19 11:55:54 -07001457 TrafficStats.setThreadStatsTag(oldTag);
Paul Jensenca8f16a2014-05-09 12:47:55 -04001458 }
Hugo Benichicfddd682016-05-31 16:28:06 +09001459 logValidationProbe(probeTimer.stop(), probeType, httpResponseCode);
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +09001460
1461 if (probeSpec == null) {
1462 return new CaptivePortalProbeResult(httpResponseCode, redirectUrl, url.toString());
1463 } else {
1464 return probeSpec.getResult(httpResponseCode, redirectUrl);
1465 }
Paul Jensenca8f16a2014-05-09 12:47:55 -04001466 }
Paul Jensen306f1a42014-08-04 10:59:01 -04001467
Hugo Benichid953bf82016-09-27 09:22:35 +09001468 private CaptivePortalProbeResult sendParallelHttpProbes(
Hugo Benichieef918a2017-04-10 17:43:08 +09001469 ProxyInfo proxy, URL httpsUrl, URL httpUrl) {
Hugo Benichid953bf82016-09-27 09:22:35 +09001470 // Number of probes to wait for. If a probe completes with a conclusive answer
1471 // it shortcuts the latch immediately by forcing the count to 0.
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001472 final CountDownLatch latch = new CountDownLatch(2);
1473
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001474 final class ProbeThread extends Thread {
1475 private final boolean mIsHttps;
Hugo Benichid953bf82016-09-27 09:22:35 +09001476 private volatile CaptivePortalProbeResult mResult = CaptivePortalProbeResult.FAILED;
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001477
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +09001478 ProbeThread(boolean isHttps) {
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001479 mIsHttps = isHttps;
1480 }
1481
Hugo Benichid953bf82016-09-27 09:22:35 +09001482 public CaptivePortalProbeResult result() {
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001483 return mResult;
1484 }
1485
1486 @Override
1487 public void run() {
1488 if (mIsHttps) {
Hugo Benichieb5e9aa2016-11-16 18:18:08 +09001489 mResult =
1490 sendDnsAndHttpProbes(proxy, httpsUrl, ValidationProbeEvent.PROBE_HTTPS);
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001491 } else {
Hugo Benichieb5e9aa2016-11-16 18:18:08 +09001492 mResult = sendDnsAndHttpProbes(proxy, httpUrl, ValidationProbeEvent.PROBE_HTTP);
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001493 }
1494 if ((mIsHttps && mResult.isSuccessful()) || (!mIsHttps && mResult.isPortal())) {
Hugo Benichid953bf82016-09-27 09:22:35 +09001495 // Stop waiting immediately if https succeeds or if http finds a portal.
1496 while (latch.getCount() > 0) {
1497 latch.countDown();
1498 }
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001499 }
Hugo Benichid953bf82016-09-27 09:22:35 +09001500 // Signal this probe has completed.
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001501 latch.countDown();
1502 }
1503 }
1504
Hugo Benichid953bf82016-09-27 09:22:35 +09001505 final ProbeThread httpsProbe = new ProbeThread(true);
1506 final ProbeThread httpProbe = new ProbeThread(false);
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001507
1508 try {
Hugo Benichid953bf82016-09-27 09:22:35 +09001509 httpsProbe.start();
1510 httpProbe.start();
1511 latch.await(PROBE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001512 } catch (InterruptedException e) {
Hugo Benichid953bf82016-09-27 09:22:35 +09001513 validationLog("Error: probes wait interrupted!");
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001514 return CaptivePortalProbeResult.FAILED;
1515 }
1516
Hugo Benichid953bf82016-09-27 09:22:35 +09001517 final CaptivePortalProbeResult httpsResult = httpsProbe.result();
1518 final CaptivePortalProbeResult httpResult = httpProbe.result();
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001519
Hugo Benichid953bf82016-09-27 09:22:35 +09001520 // Look for a conclusive probe result first.
1521 if (httpResult.isPortal()) {
1522 return httpResult;
1523 }
1524 // httpsResult.isPortal() is not expected, but check it nonetheless.
1525 if (httpsResult.isPortal() || httpsResult.isSuccessful()) {
1526 return httpsResult;
1527 }
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +09001528 // If a fallback method exists, use it to retry portal detection.
1529 // If we have new-style probe specs, use those. Otherwise, use the fallback URLs.
1530 final CaptivePortalProbeSpec probeSpec = nextFallbackSpec();
1531 final URL fallbackUrl = (probeSpec != null) ? probeSpec.getUrl() : nextFallbackUrl();
lucasline252a742019-03-12 13:08:03 +08001532 CaptivePortalProbeResult fallbackProbeResult = null;
Hugo Benichid953bf82016-09-27 09:22:35 +09001533 if (fallbackUrl != null) {
lucasline252a742019-03-12 13:08:03 +08001534 fallbackProbeResult = sendHttpProbe(fallbackUrl, PROBE_FALLBACK, probeSpec);
1535 if (fallbackProbeResult.isPortal()) {
1536 return fallbackProbeResult;
Hugo Benichid953bf82016-09-27 09:22:35 +09001537 }
1538 }
Hugo Benichifc474422017-05-17 10:30:40 +09001539 // Otherwise wait until http and https probes completes and use their results.
Hugo Benichid953bf82016-09-27 09:22:35 +09001540 try {
Hugo Benichifc474422017-05-17 10:30:40 +09001541 httpProbe.join();
1542 if (httpProbe.result().isPortal()) {
1543 return httpProbe.result();
1544 }
Hugo Benichid953bf82016-09-27 09:22:35 +09001545 httpsProbe.join();
lucasline252a742019-03-12 13:08:03 +08001546 final boolean isHttpSuccessful =
1547 (httpProbe.result().isSuccessful()
1548 || (fallbackProbeResult != null && fallbackProbeResult.isSuccessful()));
1549 if (httpsProbe.result().isFailed() && isHttpSuccessful) {
1550 return CaptivePortalProbeResult.PARTIAL;
1551 }
Hugo Benichifc474422017-05-17 10:30:40 +09001552 return httpsProbe.result();
Hugo Benichid953bf82016-09-27 09:22:35 +09001553 } catch (InterruptedException e) {
Hugo Benichifc474422017-05-17 10:30:40 +09001554 validationLog("Error: http or https probe wait interrupted!");
Hugo Benichid953bf82016-09-27 09:22:35 +09001555 return CaptivePortalProbeResult.FAILED;
1556 }
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001557 }
1558
Hugo Benichi92eb22fd2016-09-27 13:01:41 +09001559 private URL makeURL(String url) {
1560 if (url != null) {
1561 try {
1562 return new URL(url);
1563 } catch (MalformedURLException e) {
1564 validationLog("Bad URL: " + url);
1565 }
1566 }
1567 return null;
1568 }
1569
Paul Jensen306f1a42014-08-04 10:59:01 -04001570 /**
1571 * @param responseReceived - whether or not we received a valid HTTP response to our request.
1572 * If false, isCaptivePortal and responseTimestampMs are ignored
1573 * TODO: This should be moved to the transports. The latency could be passed to the transports
1574 * along with the captive portal result. Currently the TYPE_MOBILE broadcasts appear unused so
1575 * perhaps this could just be added to the WiFi transport only.
1576 */
1577 private void sendNetworkConditionsBroadcast(boolean responseReceived, boolean isCaptivePortal,
1578 long requestTimestampMs, long responseTimestampMs) {
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001579 if (!mSystemReady) {
Hugo Benichic894b122017-07-31 12:58:20 +09001580 return;
1581 }
Robert Greenwaltfb68f8f2014-08-13 13:43:32 -07001582
Chalard Jean918a68b2018-01-19 17:00:47 +09001583 Intent latencyBroadcast =
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001584 new Intent(NetworkMonitorUtils.ACTION_NETWORK_CONDITIONS_MEASURED);
1585 if (mNetworkCapabilities.hasTransport(TRANSPORT_WIFI)) {
Chiachang Wang75610712019-02-14 09:30:58 +08001586 if (!mWifiManager.isScanAlwaysAvailable()) {
1587 return;
1588 }
1589
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001590 WifiInfo currentWifiInfo = mWifiManager.getConnectionInfo();
1591 if (currentWifiInfo != null) {
1592 // NOTE: getSSID()'s behavior changed in API 17; before that, SSIDs were not
1593 // surrounded by double quotation marks (thus violating the Javadoc), but this
1594 // was changed to match the Javadoc in API 17. Since clients may have started
1595 // sanitizing the output of this method since API 17 was released, we should
1596 // not change it here as it would become impossible to tell whether the SSID is
1597 // simply being surrounded by quotes due to the API, or whether those quotes
1598 // are actually part of the SSID.
1599 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_SSID,
1600 currentWifiInfo.getSSID());
1601 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_BSSID,
1602 currentWifiInfo.getBSSID());
1603 } else {
1604 if (VDBG) logw("network info is TYPE_WIFI but no ConnectionInfo found");
1605 return;
1606 }
1607 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CONNECTIVITY_TYPE, TYPE_WIFI);
1608 } else if (mNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
Chiachang Wang75610712019-02-14 09:30:58 +08001609 // TODO(b/123893112): Support multi-sim.
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001610 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_NETWORK_TYPE,
1611 mTelephonyManager.getNetworkType());
Chiachang Wang75610712019-02-14 09:30:58 +08001612 final ServiceState dataSs = mTelephonyManager.getServiceState();
1613 if (dataSs == null) {
1614 logw("failed to retrieve ServiceState");
1615 return;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001616 }
Chiachang Wang75610712019-02-14 09:30:58 +08001617 // See if the data sub is registered for PS services on cell.
1618 final NetworkRegistrationState nrs = dataSs.getNetworkRegistrationState(
1619 NetworkRegistrationState.DOMAIN_PS,
Jack Yu6f7b3042019-03-14 15:42:09 -07001620 AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
Chiachang Wang75610712019-02-14 09:30:58 +08001621 latencyBroadcast.putExtra(
1622 NetworkMonitorUtils.EXTRA_CELL_ID,
1623 nrs == null ? null : nrs.getCellIdentity());
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001624 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CONNECTIVITY_TYPE, TYPE_MOBILE);
1625 } else {
1626 return;
Paul Jensen306f1a42014-08-04 10:59:01 -04001627 }
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001628 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_RESPONSE_RECEIVED,
Chalard Jean918a68b2018-01-19 17:00:47 +09001629 responseReceived);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001630 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_REQUEST_TIMESTAMP_MS,
Chalard Jean918a68b2018-01-19 17:00:47 +09001631 requestTimestampMs);
Paul Jensen306f1a42014-08-04 10:59:01 -04001632
1633 if (responseReceived) {
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001634 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_IS_CAPTIVE_PORTAL,
Chalard Jean918a68b2018-01-19 17:00:47 +09001635 isCaptivePortal);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001636 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_RESPONSE_TIMESTAMP_MS,
Chalard Jean918a68b2018-01-19 17:00:47 +09001637 responseTimestampMs);
Paul Jensen306f1a42014-08-04 10:59:01 -04001638 }
Paul Jensen55298582014-08-20 11:01:41 -04001639 mContext.sendBroadcastAsUser(latencyBroadcast, UserHandle.CURRENT,
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001640 NetworkMonitorUtils.PERMISSION_ACCESS_NETWORK_CONDITIONS);
Paul Jensen306f1a42014-08-04 10:59:01 -04001641 }
Paul Jensend7b6ca92015-05-13 14:05:12 -04001642
Hugo Benichicfddd682016-05-31 16:28:06 +09001643 private void logNetworkEvent(int evtype) {
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001644 int[] transports = mNetworkCapabilities.getTransportTypes();
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +09001645 mMetricsLog.log(mNetwork, transports, new NetworkEvent(evtype));
Hugo Benichicfddd682016-05-31 16:28:06 +09001646 }
1647
Hugo Benichidd229822016-11-15 23:23:24 +09001648 private int networkEventType(ValidationStage s, EvaluationResult r) {
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +09001649 if (s.mIsFirstValidation) {
1650 if (r.mIsValidated) {
Hugo Benichidd229822016-11-15 23:23:24 +09001651 return NetworkEvent.NETWORK_FIRST_VALIDATION_SUCCESS;
1652 } else {
1653 return NetworkEvent.NETWORK_FIRST_VALIDATION_PORTAL_FOUND;
1654 }
1655 } else {
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +09001656 if (r.mIsValidated) {
Hugo Benichidd229822016-11-15 23:23:24 +09001657 return NetworkEvent.NETWORK_REVALIDATION_SUCCESS;
1658 } else {
1659 return NetworkEvent.NETWORK_REVALIDATION_PORTAL_FOUND;
1660 }
1661 }
1662 }
1663
Hugo Benichicfddd682016-05-31 16:28:06 +09001664 private void maybeLogEvaluationResult(int evtype) {
1665 if (mEvaluationTimer.isRunning()) {
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001666 int[] transports = mNetworkCapabilities.getTransportTypes();
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +09001667 mMetricsLog.log(mNetwork, transports,
1668 new NetworkEvent(evtype, mEvaluationTimer.stop()));
Hugo Benichicfddd682016-05-31 16:28:06 +09001669 mEvaluationTimer.reset();
1670 }
1671 }
1672
1673 private void logValidationProbe(long durationMs, int probeType, int probeResult) {
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001674 int[] transports = mNetworkCapabilities.getTransportTypes();
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +09001675 boolean isFirstValidation = validationStage().mIsFirstValidation;
Remi NGUYEN VAN7b84fb32019-01-19 21:13:24 +09001676 ValidationProbeEvent ev = new ValidationProbeEvent.Builder()
1677 .setProbeType(probeType, isFirstValidation)
1678 .setReturnCode(probeResult)
1679 .setDurationMs(durationMs)
1680 .build();
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +09001681 mMetricsLog.log(mNetwork, transports, ev);
Hugo Benichicfddd682016-05-31 16:28:06 +09001682 }
Hugo Benichic894b122017-07-31 12:58:20 +09001683
1684 @VisibleForTesting
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001685 static class Dependencies {
1686 public Network getPrivateDnsBypassNetwork(Network network) {
1687 return new OneAddressPerFamilyNetwork(network);
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +09001688 }
Hugo Benichic894b122017-07-31 12:58:20 +09001689
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +09001690 public Random getRandom() {
1691 return new Random();
1692 }
Hugo Benichic894b122017-07-31 12:58:20 +09001693
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +09001694 /**
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001695 * Get the captive portal server HTTP URL that is configured on the device.
1696 */
1697 public String getCaptivePortalServerHttpUrl(Context context) {
1698 return NetworkMonitorUtils.getCaptivePortalServerHttpUrl(context);
1699 }
1700
1701 /**
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +09001702 * Get the value of a global integer setting.
1703 * @param symbol Name of the setting
1704 * @param defaultValue Value to return if the setting is not defined.
1705 */
Hugo Benichic894b122017-07-31 12:58:20 +09001706 public int getSetting(Context context, String symbol, int defaultValue) {
1707 return Settings.Global.getInt(context.getContentResolver(), symbol, defaultValue);
1708 }
1709
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +09001710 /**
1711 * Get the value of a global String setting.
1712 * @param symbol Name of the setting
1713 * @param defaultValue Value to return if the setting is not defined.
1714 */
Hugo Benichic894b122017-07-31 12:58:20 +09001715 public String getSetting(Context context, String symbol, String defaultValue) {
1716 final String value = Settings.Global.getString(context.getContentResolver(), symbol);
1717 return value != null ? value : defaultValue;
1718 }
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +09001719
1720 public static final Dependencies DEFAULT = new Dependencies();
Hugo Benichic894b122017-07-31 12:58:20 +09001721 }
Chiachang Wang7a70a7e2018-11-27 18:00:05 +08001722
1723 /**
1724 * Methods in this class perform no locking because all accesses are performed on the state
1725 * machine's thread. Need to consider the thread safety if it ever could be accessed outside the
1726 * state machine.
1727 */
1728 @VisibleForTesting
1729 protected class DnsStallDetector {
Chiachang Wang7a70a7e2018-11-27 18:00:05 +08001730 private int mConsecutiveTimeoutCount = 0;
1731 private int mSize;
1732 final DnsResult[] mDnsEvents;
1733 final RingBufferIndices mResultIndices;
1734
1735 DnsStallDetector(int size) {
1736 mSize = Math.max(DEFAULT_DNS_LOG_SIZE, size);
1737 mDnsEvents = new DnsResult[mSize];
1738 mResultIndices = new RingBufferIndices(mSize);
1739 }
1740
1741 @VisibleForTesting
1742 protected void accumulateConsecutiveDnsTimeoutCount(int code) {
1743 final DnsResult result = new DnsResult(code);
1744 mDnsEvents[mResultIndices.add()] = result;
1745 if (result.isTimeout()) {
1746 mConsecutiveTimeoutCount++;
1747 } else {
1748 // Keep the event in mDnsEvents without clearing it so that there are logs to do the
1749 // simulation and analysis.
1750 mConsecutiveTimeoutCount = 0;
1751 }
1752 }
1753
1754 private boolean isDataStallSuspected(int timeoutCountThreshold, int validTime) {
1755 if (timeoutCountThreshold <= 0) {
1756 Log.wtf(TAG, "Timeout count threshold should be larger than 0.");
1757 return false;
1758 }
1759
1760 // Check if the consecutive timeout count reach the threshold or not.
1761 if (mConsecutiveTimeoutCount < timeoutCountThreshold) {
1762 return false;
1763 }
1764
1765 // Check if the target dns event index is valid or not.
1766 final int firstConsecutiveTimeoutIndex =
1767 mResultIndices.indexOf(mResultIndices.size() - timeoutCountThreshold);
1768
1769 // If the dns timeout events happened long time ago, the events are meaningless for
1770 // data stall evaluation. Thus, check if the first consecutive timeout dns event
1771 // considered in the evaluation happened in defined threshold time.
1772 final long now = SystemClock.elapsedRealtime();
1773 final long firstTimeoutTime = now - mDnsEvents[firstConsecutiveTimeoutIndex].mTimeStamp;
1774 return (firstTimeoutTime < validTime);
1775 }
1776
1777 int getConsecutiveTimeoutCount() {
1778 return mConsecutiveTimeoutCount;
1779 }
1780 }
1781
1782 private static class DnsResult {
1783 // TODO: Need to move the DNS return code definition to a specific class once unify DNS
1784 // response code is done.
1785 private static final int RETURN_CODE_DNS_TIMEOUT = 255;
1786
1787 private final long mTimeStamp;
1788 private final int mReturnCode;
1789
1790 DnsResult(int code) {
1791 mTimeStamp = SystemClock.elapsedRealtime();
1792 mReturnCode = code;
1793 }
1794
1795 private boolean isTimeout() {
1796 return mReturnCode == RETURN_CODE_DNS_TIMEOUT;
1797 }
1798 }
1799
1800
1801 @VisibleForTesting
1802 protected DnsStallDetector getDnsStallDetector() {
1803 return mDnsStallDetector;
1804 }
1805
1806 private boolean dataStallEvaluateTypeEnabled(int type) {
Chiachang Wang4349dc02019-03-05 20:31:57 +08001807 return (mDataStallEvaluationType & type) != 0;
Chiachang Wang7a70a7e2018-11-27 18:00:05 +08001808 }
1809
1810 @VisibleForTesting
1811 protected long getLastProbeTime() {
1812 return mLastProbeTime;
1813 }
1814
1815 @VisibleForTesting
1816 protected boolean isDataStall() {
1817 boolean result = false;
1818 // Reevaluation will generate traffic. Thus, set a minimal reevaluation timer to limit the
1819 // possible traffic cost in metered network.
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +09001820 if (!mNetworkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED)
Chiachang Wang7a70a7e2018-11-27 18:00:05 +08001821 && (SystemClock.elapsedRealtime() - getLastProbeTime()
1822 < mDataStallMinEvaluateTime)) {
1823 return false;
1824 }
1825
1826 // Check dns signal. Suspect it may be a data stall if both :
Chiachang Wang4349dc02019-03-05 20:31:57 +08001827 // 1. The number of consecutive DNS query timeouts >= mConsecutiveDnsTimeoutThreshold.
Chiachang Wang7a70a7e2018-11-27 18:00:05 +08001828 // 2. Those consecutive DNS queries happened in the last mValidDataStallDnsTimeThreshold ms.
1829 if (dataStallEvaluateTypeEnabled(DATA_STALL_EVALUATION_TYPE_DNS)) {
1830 if (mDnsStallDetector.isDataStallSuspected(mConsecutiveDnsTimeoutThreshold,
1831 mDataStallValidDnsTimeThreshold)) {
1832 result = true;
1833 logNetworkEvent(NetworkEvent.NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND);
1834 }
1835 }
1836
1837 if (VDBG_STALL) {
1838 log("isDataStall: result=" + result + ", consecutive dns timeout count="
1839 + mDnsStallDetector.getConsecutiveTimeoutCount());
1840 }
1841
1842 return result;
1843 }
Paul Jensenca8f16a2014-05-09 12:47:55 -04001844}