blob: c3447fdb3d780b937cdbf6c577bb7511a823ad0d [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;
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +090027import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +090028import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
29import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
Remi NGUYEN VANdcb722f2018-07-20 18:13:24 +090030import static android.net.metrics.ValidationProbeEvent.DNS_FAILURE;
31import static android.net.metrics.ValidationProbeEvent.DNS_SUCCESS;
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +090032import static android.net.metrics.ValidationProbeEvent.PROBE_FALLBACK;
Remi NGUYEN VANdcb722f2018-07-20 18:13:24 +090033import static android.net.metrics.ValidationProbeEvent.PROBE_PRIVDNS;
Remi NGUYEN VAN3ba6c0d2019-01-20 13:48:19 +090034import static android.net.util.NetworkStackUtils.isEmpty;
Chiachang Wang4349dc02019-03-05 20:31:57 +080035import static android.provider.Settings.Global.DATA_STALL_EVALUATION_TYPE_DNS;
Paul Jensen49e3edf2015-05-22 10:50:39 -040036
Chiachang Wangf09e3e32019-02-22 11:13:07 +080037import android.annotation.NonNull;
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +090038import android.annotation.Nullable;
Paul Jensen869868be2014-05-15 10:33:05 -040039import android.app.PendingIntent;
40import android.content.BroadcastReceiver;
Paul Jensenca8f16a2014-05-09 12:47:55 -040041import android.content.Context;
Paul Jensen869868be2014-05-15 10:33:05 -040042import android.content.Intent;
43import android.content.IntentFilter;
44import android.net.ConnectivityManager;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +090045import android.net.INetworkMonitor;
46import android.net.INetworkMonitorCallbacks;
47import android.net.LinkProperties;
Lorenzo Colittid8978ac2017-06-28 23:42:41 +090048import android.net.Network;
Erik Klinea24d4592018-01-11 21:07:29 +090049import android.net.NetworkCapabilities;
Paul Jensen8fe17422015-02-02 11:03:03 -050050import android.net.ProxyInfo;
Paul Jensen7ccd3df2014-08-29 09:54:01 -040051import android.net.TrafficStats;
Paul Jensen71b645f2014-10-13 14:13:07 -040052import android.net.Uri;
Remi NGUYEN VANd57329d2018-05-22 09:58:19 +090053import android.net.captiveportal.CaptivePortalProbeResult;
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +090054import android.net.captiveportal.CaptivePortalProbeSpec;
Chiachang Wangf09e3e32019-02-22 11:13:07 +080055import android.net.metrics.DataStallDetectionStats;
56import android.net.metrics.DataStallStatsUtils;
Hugo Benichicfddd682016-05-31 16:28:06 +090057import android.net.metrics.IpConnectivityLog;
Hugo Benichicc92c6e2016-04-21 15:02:38 +090058import android.net.metrics.NetworkEvent;
Hugo Benichicfddd682016-05-31 16:28:06 +090059import android.net.metrics.ValidationProbeEvent;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +090060import android.net.shared.NetworkMonitorUtils;
61import android.net.shared.PrivateDnsConfig;
62import android.net.util.SharedLog;
Hugo Benichid953bf82016-09-27 09:22:35 +090063import android.net.util.Stopwatch;
Paul Jensen306f1a42014-08-04 10:59:01 -040064import android.net.wifi.WifiInfo;
65import android.net.wifi.WifiManager;
Remi NGUYEN VANdc483562019-02-04 11:32:20 +090066import android.os.Bundle;
Paul Jensenca8f16a2014-05-09 12:47:55 -040067import android.os.Message;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +090068import android.os.RemoteException;
Paul Jensen306f1a42014-08-04 10:59:01 -040069import android.os.SystemClock;
Paul Jensen869868be2014-05-15 10:33:05 -040070import android.os.UserHandle;
Paul Jensenca8f16a2014-05-09 12:47:55 -040071import android.provider.Settings;
Chiachang Wang75610712019-02-14 09:30:58 +080072import android.telephony.AccessNetworkConstants;
Chiachang Wangf09e3e32019-02-22 11:13:07 +080073import android.telephony.CellSignalStrength;
Chiachang Wang75610712019-02-14 09:30:58 +080074import android.telephony.NetworkRegistrationState;
75import android.telephony.ServiceState;
Chiachang Wangf09e3e32019-02-22 11:13:07 +080076import android.telephony.SignalStrength;
Paul Jensen306f1a42014-08-04 10:59:01 -040077import android.telephony.TelephonyManager;
Paul Jensen2f0a8972015-06-25 10:07:14 -040078import android.text.TextUtils;
Paul Jensen532b61432014-11-10 09:50:02 -050079import android.util.Log;
Paul Jensenca8f16a2014-05-09 12:47:55 -040080
Paul Jensend7b6ca92015-05-13 14:05:12 -040081import com.android.internal.annotations.VisibleForTesting;
Chiachang Wang7a70a7e2018-11-27 18:00:05 +080082import com.android.internal.util.RingBufferIndices;
Paul Jensenca8f16a2014-05-09 12:47:55 -040083import com.android.internal.util.State;
84import com.android.internal.util.StateMachine;
Paul Jensenca8f16a2014-05-09 12:47:55 -040085
Paul Jensenca8f16a2014-05-09 12:47:55 -040086import java.io.IOException;
Paul Jensenca8f16a2014-05-09 12:47:55 -040087import java.net.HttpURLConnection;
Paul Jensen2f0a8972015-06-25 10:07:14 -040088import java.net.InetAddress;
Lorenzo Colittic5be12e2016-04-19 21:57:31 +090089import java.net.MalformedURLException;
Paul Jensenca8f16a2014-05-09 12:47:55 -040090import java.net.URL;
Hugo Benichid953bf82016-09-27 09:22:35 +090091import java.net.UnknownHostException;
Hugo Benichieef918a2017-04-10 17:43:08 +090092import java.util.ArrayList;
Lorenzo Colittid8978ac2017-06-28 23:42:41 +090093import java.util.Arrays;
Remi NGUYEN VANa4bcc862019-01-28 13:28:35 +090094import java.util.Collection;
Lorenzo Colittid8978ac2017-06-28 23:42:41 +090095import java.util.Collections;
96import java.util.LinkedHashMap;
Paul Jensen306f1a42014-08-04 10:59:01 -040097import java.util.List;
Paul Jensen71b645f2014-10-13 14:13:07 -040098import java.util.Random;
Erik Kline736353a2018-03-21 07:18:33 -070099import java.util.UUID;
Hugo Benichid953bf82016-09-27 09:22:35 +0900100import java.util.concurrent.CountDownLatch;
101import java.util.concurrent.TimeUnit;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400102
103/**
104 * {@hide}
105 */
106public class NetworkMonitor extends StateMachine {
Erik Klinea488c232016-04-15 15:49:42 +0900107 private static final String TAG = NetworkMonitor.class.getSimpleName();
Hugo Benichia4f17bc2016-11-21 13:50:05 +0900108 private static final boolean DBG = true;
109 private static final boolean VDBG = false;
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800110 private static final boolean VDBG_STALL = Log.isLoggable(TAG, Log.DEBUG);
Remi NGUYEN VAN47274272019-01-30 23:39:24 +0900111 // TODO: use another permission for CaptivePortalLoginActivity once it has its own certificate
112 private static final String PERMISSION_NETWORK_SETTINGS = "android.permission.NETWORK_SETTINGS";
Hugo Benichi11ae28f2016-09-27 13:16:19 +0900113 // Default configuration values for captive portal detection probes.
114 // TODO: append a random length parameter to the default HTTPS url.
115 // TODO: randomize browser version ids in the default User-Agent String.
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900116 private static final String DEFAULT_HTTPS_URL = "https://www.google.com/generate_204";
Hugo Benichi11ae28f2016-09-27 13:16:19 +0900117 private static final String DEFAULT_FALLBACK_URL = "http://www.google.com/gen_204";
Hugo Benichieef918a2017-04-10 17:43:08 +0900118 private static final String DEFAULT_OTHER_FALLBACK_URLS =
119 "http://play.googleapis.com/generate_204";
Hugo Benichi11ae28f2016-09-27 13:16:19 +0900120 private static final String DEFAULT_USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) "
121 + "AppleWebKit/537.36 (KHTML, like Gecko) "
Lorenzo Colittid8a06082017-06-28 23:39:40 +0900122 + "Chrome/60.0.3112.32 Safari/537.36";
Hugo Benichi92eb22fd2016-09-27 13:01:41 +0900123
Paul Jensenca8f16a2014-05-09 12:47:55 -0400124 private static final int SOCKET_TIMEOUT_MS = 10000;
Hugo Benichi92eb22fd2016-09-27 13:01:41 +0900125 private static final int PROBE_TIMEOUT_MS = 3000;
126
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800127 // Default configuration values for data stall detection.
128 private static final int DEFAULT_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD = 5;
129 private static final int DEFAULT_DATA_STALL_MIN_EVALUATE_TIME_MS = 60 * 1000;
130 private static final int DEFAULT_DATA_STALL_VALID_DNS_TIME_THRESHOLD_MS = 30 * 60 * 1000;
131
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800132 private static final int DEFAULT_DATA_STALL_EVALUATION_TYPES =
Chiachang Wang4349dc02019-03-05 20:31:57 +0800133 DATA_STALL_EVALUATION_TYPE_DNS;
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800134 // Reevaluate it as intending to increase the number. Larger log size may cause statsd
135 // log buffer bust and have stats log lost.
136 private static final int DEFAULT_DNS_LOG_SIZE = 20;
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800137
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +0900138 enum EvaluationResult {
Hugo Benichidd229822016-11-15 23:23:24 +0900139 VALIDATED(true),
140 CAPTIVE_PORTAL(false);
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +0900141 final boolean mIsValidated;
Hugo Benichidd229822016-11-15 23:23:24 +0900142 EvaluationResult(boolean isValidated) {
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +0900143 this.mIsValidated = isValidated;
Hugo Benichidd229822016-11-15 23:23:24 +0900144 }
145 }
146
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +0900147 enum ValidationStage {
Hugo Benichidd229822016-11-15 23:23:24 +0900148 FIRST_VALIDATION(true),
149 REVALIDATION(false);
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +0900150 final boolean mIsFirstValidation;
Hugo Benichidd229822016-11-15 23:23:24 +0900151 ValidationStage(boolean isFirstValidation) {
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +0900152 this.mIsFirstValidation = isFirstValidation;
Hugo Benichidd229822016-11-15 23:23:24 +0900153 }
154 }
155
Paul Jensenca8f16a2014-05-09 12:47:55 -0400156 /**
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900157 * ConnectivityService has sent a notification to indicate that network has connected.
Paul Jensenca8f16a2014-05-09 12:47:55 -0400158 * Initiates Network Validation.
159 */
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +0900160 private static final int CMD_NETWORK_CONNECTED = 1;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400161
162 /**
Paul Jensenca8f16a2014-05-09 12:47:55 -0400163 * Message to self indicating it's time to evaluate a network's connectivity.
164 * arg1 = Token to ignore old messages.
165 */
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +0900166 private static final int CMD_REEVALUATE = 6;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400167
168 /**
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900169 * ConnectivityService has sent a notification to indicate that network has disconnected.
Paul Jensenca8f16a2014-05-09 12:47:55 -0400170 */
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +0900171 private static final int CMD_NETWORK_DISCONNECTED = 7;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400172
173 /**
174 * Force evaluation even if it has succeeded in the past.
Paul Jensen7ccd3df2014-08-29 09:54:01 -0400175 * arg1 = UID responsible for requesting this reeval. Will be billed for data.
Paul Jensenca8f16a2014-05-09 12:47:55 -0400176 */
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +0900177 private static final int CMD_FORCE_REEVALUATION = 8;
Paul Jensen869868be2014-05-15 10:33:05 -0400178
179 /**
Paul Jensen71b645f2014-10-13 14:13:07 -0400180 * Message to self indicating captive portal app finished.
Paul Jensen49e3edf2015-05-22 10:50:39 -0400181 * arg1 = one of: APP_RETURN_DISMISSED,
182 * APP_RETURN_UNWANTED,
183 * APP_RETURN_WANTED_AS_IS
Paul Jensen25a217c2015-02-27 22:55:47 -0500184 * obj = mCaptivePortalLoggedInResponseToken as String
Paul Jensen869868be2014-05-15 10:33:05 -0400185 */
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +0900186 private static final int CMD_CAPTIVE_PORTAL_APP_FINISHED = 9;
Paul Jensen869868be2014-05-15 10:33:05 -0400187
188 /**
Lorenzo Colitti4734cdb2017-04-27 14:30:21 +0900189 * Message indicating sign-in app should be launched.
Paul Jensen25a217c2015-02-27 22:55:47 -0500190 * Sent by mLaunchCaptivePortalAppBroadcastReceiver when the
Lorenzo Colitti4734cdb2017-04-27 14:30:21 +0900191 * user touches the sign in notification, or sent by
192 * ConnectivityService when the user touches the "sign into
193 * network" button in the wifi access point detail page.
Paul Jensen869868be2014-05-15 10:33:05 -0400194 */
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +0900195 private static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = 11;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400196
Paul Jensenee3e2ce2015-06-17 15:02:54 -0400197 /**
198 * Retest network to see if captive portal is still in place.
199 * arg1 = UID responsible for requesting this reeval. Will be billed for data.
200 * 0 indicates self-initiated, so nobody to blame.
201 */
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +0900202 private static final int CMD_CAPTIVE_PORTAL_RECHECK = 12;
Paul Jensenee3e2ce2015-06-17 15:02:54 -0400203
Erik Klinea24d4592018-01-11 21:07:29 +0900204 /**
205 * ConnectivityService notifies NetworkMonitor of settings changes to
206 * Private DNS. If a DNS resolution is required, e.g. for DNS-over-TLS in
207 * strict mode, then an event is sent back to ConnectivityService with the
208 * result of the resolution attempt.
Erik Kline736353a2018-03-21 07:18:33 -0700209 *
210 * A separate message is used to trigger (re)evaluation of the Private DNS
211 * configuration, so that the message can be handled as needed in different
212 * states, including being ignored until after an ongoing captive portal
213 * validation phase is completed.
Erik Klinea24d4592018-01-11 21:07:29 +0900214 */
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +0900215 private static final int CMD_PRIVATE_DNS_SETTINGS_CHANGED = 13;
216 private static final int CMD_EVALUATE_PRIVATE_DNS = 15;
Erik Klinea24d4592018-01-11 21:07:29 +0900217
Chiachang Wang4d11ec92018-10-23 21:10:57 +0800218 /**
219 * Message to self indicating captive portal detection is completed.
220 * obj = CaptivePortalProbeResult for detection result;
221 */
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +0900222 public static final int CMD_PROBE_COMPLETE = 16;
Chiachang Wang4d11ec92018-10-23 21:10:57 +0800223
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800224 /**
225 * ConnectivityService notifies NetworkMonitor of DNS query responses event.
226 * arg1 = returncode in OnDnsEvent which indicates the response code for the DNS query.
227 */
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +0900228 public static final int EVENT_DNS_NOTIFICATION = 17;
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800229
Paul Jensend0491e9a2015-05-05 14:52:22 -0400230 // Start mReevaluateDelayMs at this value and double.
231 private static final int INITIAL_REEVALUATE_DELAY_MS = 1000;
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +0900232 private static final int MAX_REEVALUATE_DELAY_MS = 10 * 60 * 1000;
Paul Jensend0491e9a2015-05-05 14:52:22 -0400233 // Before network has been evaluated this many times, ignore repeated reevaluate requests.
234 private static final int IGNORE_REEVALUATE_ATTEMPTS = 5;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400235 private int mReevaluateToken = 0;
Erik Kline736353a2018-03-21 07:18:33 -0700236 private static final int NO_UID = 0;
Paul Jensen7ccd3df2014-08-29 09:54:01 -0400237 private static final int INVALID_UID = -1;
238 private int mUidResponsibleForReeval = INVALID_UID;
Paul Jensend9be23f2015-05-19 14:51:47 -0400239 // Stop blaming UID that requested re-evaluation after this many attempts.
240 private static final int BLAME_FOR_EVALUATION_ATTEMPTS = 5;
Paul Jensenee3e2ce2015-06-17 15:02:54 -0400241 // Delay between reevaluations once a captive portal has been found.
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +0900242 private static final int CAPTIVE_PORTAL_REEVALUATE_DELAY_MS = 10 * 60 * 1000;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400243
Erik Kline736353a2018-03-21 07:18:33 -0700244 private String mPrivateDnsProviderHostname = "";
245
Paul Jensenca8f16a2014-05-09 12:47:55 -0400246 private final Context mContext;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900247 private final INetworkMonitorCallbacks mCallback;
Lorenzo Colittid8978ac2017-06-28 23:42:41 +0900248 private final Network mNetwork;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900249 private final Network mNonPrivateDnsBypassNetwork;
Paul Jensen306f1a42014-08-04 10:59:01 -0400250 private final TelephonyManager mTelephonyManager;
251 private final WifiManager mWifiManager;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900252 private final ConnectivityManager mCm;
Hugo Benichif9fdf872016-07-28 17:53:06 +0900253 private final IpConnectivityLog mMetricsLog;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900254 private final Dependencies mDependencies;
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800255 private final DataStallStatsUtils mDetectionStatsUtils;
Hugo Benichic894b122017-07-31 12:58:20 +0900256
257 // Configuration values for captive portal detection probes.
258 private final String mCaptivePortalUserAgent;
259 private final URL mCaptivePortalHttpsUrl;
260 private final URL mCaptivePortalHttpUrl;
261 private final URL[] mCaptivePortalFallbackUrls;
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +0900262 @Nullable
263 private final CaptivePortalProbeSpec[] mCaptivePortalFallbackSpecs;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400264
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900265 private NetworkCapabilities mNetworkCapabilities;
266 private LinkProperties mLinkProperties;
267
Calvin On4bc78eb2016-10-11 15:10:46 -0700268 @VisibleForTesting
269 protected boolean mIsCaptivePortalCheckEnabled;
270
Lorenzo Colittic5be12e2016-04-19 21:57:31 +0900271 private boolean mUseHttps;
Hugo Benichidd229822016-11-15 23:23:24 +0900272 // The total number of captive portal detection attempts for this NetworkMonitor instance.
273 private int mValidations = 0;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400274
Paul Jensenad50a1f2014-09-05 12:06:44 -0400275 // Set if the user explicitly selected "Do not use this network" in captive portal sign-in app.
276 private boolean mUserDoesNotWant = false;
Paul Jensen700f2362015-05-05 14:56:10 -0400277 // Avoids surfacing "Sign in to network" notification.
278 private boolean mDontDisplaySigninNotification = false;
Paul Jensenad50a1f2014-09-05 12:06:44 -0400279
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900280 private volatile boolean mSystemReady = false;
Robert Greenwaltfb68f8f2014-08-13 13:43:32 -0700281
Paul Jensen71b645f2014-10-13 14:13:07 -0400282 private final State mDefaultState = new DefaultState();
Paul Jensen71b645f2014-10-13 14:13:07 -0400283 private final State mValidatedState = new ValidatedState();
284 private final State mMaybeNotifyState = new MaybeNotifyState();
285 private final State mEvaluatingState = new EvaluatingState();
286 private final State mCaptivePortalState = new CaptivePortalState();
Erik Kline736353a2018-03-21 07:18:33 -0700287 private final State mEvaluatingPrivateDnsState = new EvaluatingPrivateDnsState();
Chiachang Wang4d11ec92018-10-23 21:10:57 +0800288 private final State mProbingState = new ProbingState();
Chiachang Wang78b0d4b2018-12-03 17:06:54 +0800289 private final State mWaitingForNextProbeState = new WaitingForNextProbeState();
Paul Jensen71b645f2014-10-13 14:13:07 -0400290
Paul Jensen25a217c2015-02-27 22:55:47 -0500291 private CustomIntentReceiver mLaunchCaptivePortalAppBroadcastReceiver = null;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400292
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900293 private final SharedLog mValidationLogs;
Robert Greenwalt22b4c6a2015-06-23 15:03:33 -0700294
Erik Klinea488c232016-04-15 15:49:42 +0900295 private final Stopwatch mEvaluationTimer = new Stopwatch();
296
Hugo Benichid953bf82016-09-27 09:22:35 +0900297 // This variable is set before transitioning to the mCaptivePortalState.
298 private CaptivePortalProbeResult mLastPortalProbeResult = CaptivePortalProbeResult.FAILED;
299
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900300 // Random generator to select fallback URL index
301 private final Random mRandom;
Hugo Benichieef918a2017-04-10 17:43:08 +0900302 private int mNextFallbackUrlIndex = 0;
Hugo Benichib03272c2017-04-10 22:45:13 +0900303
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900304
Chiachang Wang4d11ec92018-10-23 21:10:57 +0800305 private int mReevaluateDelayMs = INITIAL_REEVALUATE_DELAY_MS;
306 private int mEvaluateAttempts = 0;
Chiachang Wang30cc3672018-11-12 10:52:25 +0800307 private volatile int mProbeToken = 0;
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800308 private final int mConsecutiveDnsTimeoutThreshold;
309 private final int mDataStallMinEvaluateTime;
310 private final int mDataStallValidDnsTimeThreshold;
311 private final int mDataStallEvaluationType;
312 private final DnsStallDetector mDnsStallDetector;
313 private long mLastProbeTime;
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800314 // Set to true if data stall is suspected and reset to false after metrics are sent to statsd.
315 private boolean mCollectDataStallMetrics = false;
Chiachang Wang4d11ec92018-10-23 21:10:57 +0800316
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900317 public NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network,
Remi NGUYEN VAN231b52b2019-01-29 15:38:52 +0900318 SharedLog validationLog) {
319 this(context, cb, network, new IpConnectivityLog(), validationLog,
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800320 Dependencies.DEFAULT, new DataStallStatsUtils());
Hugo Benichif9fdf872016-07-28 17:53:06 +0900321 }
322
323 @VisibleForTesting
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900324 protected NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network,
Remi NGUYEN VAN231b52b2019-01-29 15:38:52 +0900325 IpConnectivityLog logger, SharedLog validationLogs,
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800326 Dependencies deps, DataStallStatsUtils detectionStatsUtils) {
Paul Jensenca8f16a2014-05-09 12:47:55 -0400327 // Add suffix indicating which NetworkMonitor we're talking about.
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +0900328 super(TAG + "/" + network.toString());
Paul Jensenca8f16a2014-05-09 12:47:55 -0400329
Erik Kline736353a2018-03-21 07:18:33 -0700330 // Logs with a tag of the form given just above, e.g.
331 // <timestamp> 862 2402 D NetworkMonitor/NetworkAgentInfo [WIFI () - 100]: ...
332 setDbg(VDBG);
333
Paul Jensenca8f16a2014-05-09 12:47:55 -0400334 mContext = context;
Hugo Benichif9fdf872016-07-28 17:53:06 +0900335 mMetricsLog = logger;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900336 mValidationLogs = validationLogs;
337 mCallback = cb;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900338 mDependencies = deps;
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800339 mDetectionStatsUtils = detectionStatsUtils;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900340 mNonPrivateDnsBypassNetwork = network;
341 mNetwork = deps.getPrivateDnsBypassNetwork(network);
Paul Jensen306f1a42014-08-04 10:59:01 -0400342 mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
343 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900344 mCm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
Paul Jensenca8f16a2014-05-09 12:47:55 -0400345
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900346 // CHECKSTYLE:OFF IndentationCheck
Paul Jensenca8f16a2014-05-09 12:47:55 -0400347 addState(mDefaultState);
Paul Jensen71b645f2014-10-13 14:13:07 -0400348 addState(mMaybeNotifyState, mDefaultState);
349 addState(mEvaluatingState, mMaybeNotifyState);
Chiachang Wang4d11ec92018-10-23 21:10:57 +0800350 addState(mProbingState, mEvaluatingState);
Chiachang Wang78b0d4b2018-12-03 17:06:54 +0800351 addState(mWaitingForNextProbeState, mEvaluatingState);
Paul Jensen71b645f2014-10-13 14:13:07 -0400352 addState(mCaptivePortalState, mMaybeNotifyState);
Erik Kline736353a2018-03-21 07:18:33 -0700353 addState(mEvaluatingPrivateDnsState, mDefaultState);
354 addState(mValidatedState, mDefaultState);
Robert Greenwalt49f63fb2014-09-13 12:04:12 -0700355 setInitialState(mDefaultState);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900356 // CHECKSTYLE:ON IndentationCheck
Paul Jensenca8f16a2014-05-09 12:47:55 -0400357
Hugo Benichic894b122017-07-31 12:58:20 +0900358 mIsCaptivePortalCheckEnabled = getIsCaptivePortalCheckEnabled();
359 mUseHttps = getUseHttpsValidation();
360 mCaptivePortalUserAgent = getCaptivePortalUserAgent();
361 mCaptivePortalHttpsUrl = makeURL(getCaptivePortalServerHttpsUrl());
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900362 mCaptivePortalHttpUrl = makeURL(deps.getCaptivePortalServerHttpUrl(context));
Hugo Benichic894b122017-07-31 12:58:20 +0900363 mCaptivePortalFallbackUrls = makeCaptivePortalFallbackUrls();
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +0900364 mCaptivePortalFallbackSpecs = makeCaptivePortalFallbackProbeSpecs();
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +0900365 mRandom = deps.getRandom();
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800366 // TODO: Evaluate to move data stall configuration to a specific class.
367 mConsecutiveDnsTimeoutThreshold = getConsecutiveDnsTimeoutThreshold();
368 mDnsStallDetector = new DnsStallDetector(mConsecutiveDnsTimeoutThreshold);
369 mDataStallMinEvaluateTime = getDataStallMinEvaluateTime();
370 mDataStallValidDnsTimeThreshold = getDataStallValidDnsTimeThreshold();
371 mDataStallEvaluationType = getDataStallEvalutionType();
Hugo Benichib03272c2017-04-10 22:45:13 +0900372
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900373 // mLinkProperties and mNetworkCapbilities must never be null or we will NPE.
374 // Provide empty objects in case we are started and the network disconnects before
375 // we can ever fetch them.
376 // TODO: Delete ASAP.
377 mLinkProperties = new LinkProperties();
Remi NGUYEN VAN231b52b2019-01-29 15:38:52 +0900378 mNetworkCapabilities = new NetworkCapabilities(null);
Paul Jensenca8f16a2014-05-09 12:47:55 -0400379 }
380
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +0900381 /**
382 * Request the NetworkMonitor to reevaluate the network.
383 */
Erik Kline736353a2018-03-21 07:18:33 -0700384 public void forceReevaluation(int responsibleUid) {
385 sendMessage(CMD_FORCE_REEVALUATION, responsibleUid, 0);
386 }
387
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +0900388 /**
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900389 * Send a notification to NetworkMonitor indicating that there was a DNS query response event.
390 * @param returnCode the DNS return code of the response.
391 */
392 public void notifyDnsResponse(int returnCode) {
393 sendMessage(EVENT_DNS_NOTIFICATION, returnCode);
394 }
395
396 /**
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +0900397 * Send a notification to NetworkMonitor indicating that private DNS settings have changed.
398 * @param newCfg The new private DNS configuration.
399 */
Erik Kline736353a2018-03-21 07:18:33 -0700400 public void notifyPrivateDnsSettingsChanged(PrivateDnsConfig newCfg) {
401 // Cancel any outstanding resolutions.
402 removeMessages(CMD_PRIVATE_DNS_SETTINGS_CHANGED);
403 // Send the update to the proper thread.
404 sendMessage(CMD_PRIVATE_DNS_SETTINGS_CHANGED, newCfg);
405 }
406
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900407 /**
408 * Send a notification to NetworkMonitor indicating that the system is ready.
409 */
410 public void notifySystemReady() {
411 // No need to run on the handler thread: mSystemReady is volatile and read only once on the
412 // isCaptivePortal() thread.
413 mSystemReady = true;
414 }
415
416 /**
417 * Send a notification to NetworkMonitor indicating that the network is now connected.
418 */
419 public void notifyNetworkConnected() {
420 sendMessage(CMD_NETWORK_CONNECTED);
421 }
422
423 /**
424 * Send a notification to NetworkMonitor indicating that the network is now disconnected.
425 */
426 public void notifyNetworkDisconnected() {
427 sendMessage(CMD_NETWORK_DISCONNECTED);
428 }
429
430 /**
431 * Send a notification to NetworkMonitor indicating that link properties have changed.
432 */
433 public void notifyLinkPropertiesChanged() {
434 getHandler().post(() -> {
435 updateLinkProperties();
436 });
437 }
438
439 private void updateLinkProperties() {
440 final LinkProperties lp = mCm.getLinkProperties(mNetwork);
441 // If null, we should soon get a message that the network was disconnected, and will stop.
442 if (lp != null) {
443 // TODO: send LinkProperties parceled in notifyLinkPropertiesChanged() and start().
444 mLinkProperties = lp;
445 }
446 }
447
448 /**
449 * Send a notification to NetworkMonitor indicating that network capabilities have changed.
450 */
451 public void notifyNetworkCapabilitiesChanged() {
452 getHandler().post(() -> {
453 updateNetworkCapabilities();
454 });
455 }
456
457 private void updateNetworkCapabilities() {
458 final NetworkCapabilities nc = mCm.getNetworkCapabilities(mNetwork);
459 // If null, we should soon get a message that the network was disconnected, and will stop.
460 if (nc != null) {
461 // TODO: send NetworkCapabilities parceled in notifyNetworkCapsChanged() and start().
462 mNetworkCapabilities = nc;
463 }
464 }
465
466 /**
467 * Request the captive portal application to be launched.
468 */
469 public void launchCaptivePortalApp() {
470 sendMessage(CMD_LAUNCH_CAPTIVE_PORTAL_APP);
471 }
472
Remi NGUYEN VANcfff01e2019-02-13 20:58:59 +0900473 /**
474 * Notify that the captive portal app was closed with the provided response code.
475 */
476 public void notifyCaptivePortalAppFinished(int response) {
477 sendMessage(CMD_CAPTIVE_PORTAL_APP_FINISHED, response);
478 }
479
Paul Jensen532b61432014-11-10 09:50:02 -0500480 @Override
481 protected void log(String s) {
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +0900482 if (DBG) Log.d(TAG + "/" + mNetwork.toString(), s);
Paul Jensen532b61432014-11-10 09:50:02 -0500483 }
484
Hugo Benichid9ac87e2017-04-06 14:36:39 +0900485 private void validationLog(int probeType, Object url, String msg) {
486 String probeName = ValidationProbeEvent.getProbeName(probeType);
487 validationLog(String.format("%s %s %s", probeName, url, msg));
488 }
489
Robert Greenwalt22b4c6a2015-06-23 15:03:33 -0700490 private void validationLog(String s) {
491 if (DBG) log(s);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900492 mValidationLogs.log(s);
Robert Greenwalt22b4c6a2015-06-23 15:03:33 -0700493 }
494
Hugo Benichidd229822016-11-15 23:23:24 +0900495 private ValidationStage validationStage() {
496 return 0 == mValidations ? ValidationStage.FIRST_VALIDATION : ValidationStage.REVALIDATION;
497 }
498
Erik Klinea24d4592018-01-11 21:07:29 +0900499 private boolean isValidationRequired() {
Lorenzo Colittied3168e2019-01-23 17:54:08 +0900500 return NetworkMonitorUtils.isValidationRequired(mNetworkCapabilities);
Erik Klinea24d4592018-01-11 21:07:29 +0900501 }
502
Erik Kline736353a2018-03-21 07:18:33 -0700503
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900504 private void notifyNetworkTested(int result, @Nullable String redirectUrl) {
505 try {
506 mCallback.notifyNetworkTested(result, redirectUrl);
507 } catch (RemoteException e) {
508 Log.e(TAG, "Error sending network test result", e);
509 }
510 }
511
512 private void showProvisioningNotification(String action) {
513 try {
Remi NGUYEN VAN9c5d9642019-02-07 21:29:57 +0900514 mCallback.showProvisioningNotification(action, mContext.getPackageName());
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900515 } catch (RemoteException e) {
516 Log.e(TAG, "Error showing provisioning notification", e);
517 }
518 }
519
520 private void hideProvisioningNotification() {
521 try {
522 mCallback.hideProvisioningNotification();
523 } catch (RemoteException e) {
524 Log.e(TAG, "Error hiding provisioning notification", e);
525 }
Erik Kline736353a2018-03-21 07:18:33 -0700526 }
527
Paul Jensen71b645f2014-10-13 14:13:07 -0400528 // DefaultState is the parent of all States. It exists only to handle CMD_* messages but
529 // does not entail any real state (hence no enter() or exit() routines).
Paul Jensenca8f16a2014-05-09 12:47:55 -0400530 private class DefaultState extends State {
531 @Override
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900532 public void enter() {
533 // TODO: have those passed parceled in start() and remove this
534 updateLinkProperties();
535 updateNetworkCapabilities();
536 }
537
538 @Override
Paul Jensenca8f16a2014-05-09 12:47:55 -0400539 public boolean processMessage(Message message) {
Paul Jensenca8f16a2014-05-09 12:47:55 -0400540 switch (message.what) {
Paul Jensenca8f16a2014-05-09 12:47:55 -0400541 case CMD_NETWORK_CONNECTED:
Hugo Benichicfddd682016-05-31 16:28:06 +0900542 logNetworkEvent(NetworkEvent.NETWORK_CONNECTED);
Paul Jensenca8f16a2014-05-09 12:47:55 -0400543 transitionTo(mEvaluatingState);
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400544 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400545 case CMD_NETWORK_DISCONNECTED:
Hugo Benichicfddd682016-05-31 16:28:06 +0900546 logNetworkEvent(NetworkEvent.NETWORK_DISCONNECTED);
Paul Jensen25a217c2015-02-27 22:55:47 -0500547 if (mLaunchCaptivePortalAppBroadcastReceiver != null) {
548 mContext.unregisterReceiver(mLaunchCaptivePortalAppBroadcastReceiver);
549 mLaunchCaptivePortalAppBroadcastReceiver = null;
Paul Jensen71b645f2014-10-13 14:13:07 -0400550 }
Robert Greenwalt1fd9aee2014-07-17 16:11:38 -0700551 quit();
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400552 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400553 case CMD_FORCE_REEVALUATION:
Paul Jensenee3e2ce2015-06-17 15:02:54 -0400554 case CMD_CAPTIVE_PORTAL_RECHECK:
Chiachang Wang1cb95492019-01-15 10:32:48 +0800555 final int dnsCount = mDnsStallDetector.getConsecutiveTimeoutCount();
556 validationLog("Forcing reevaluation for UID " + message.arg1
557 + ". Dns signal count: " + dnsCount);
Paul Jensen7ccd3df2014-08-29 09:54:01 -0400558 mUidResponsibleForReeval = message.arg1;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400559 transitionTo(mEvaluatingState);
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400560 return HANDLED;
Paul Jensen71b645f2014-10-13 14:13:07 -0400561 case CMD_CAPTIVE_PORTAL_APP_FINISHED:
Paul Jensen22e547a2015-06-25 09:17:53 -0400562 log("CaptivePortal App responded with " + message.arg1);
Lorenzo Colittic5be12e2016-04-19 21:57:31 +0900563
564 // If the user has seen and acted on a captive portal notification, and the
565 // captive portal app is now closed, disable HTTPS probes. This avoids the
566 // following pathological situation:
567 //
568 // 1. HTTP probe returns a captive portal, HTTPS probe fails or times out.
569 // 2. User opens the app and logs into the captive portal.
570 // 3. HTTP starts working, but HTTPS still doesn't work for some other reason -
571 // perhaps due to the network blocking HTTPS?
572 //
573 // In this case, we'll fail to validate the network even after the app is
574 // dismissed. There is now no way to use this network, because the app is now
575 // gone, so the user cannot select "Use this network as is".
576 mUseHttps = false;
577
Paul Jensen71b645f2014-10-13 14:13:07 -0400578 switch (message.arg1) {
Paul Jensen49e3edf2015-05-22 10:50:39 -0400579 case APP_RETURN_DISMISSED:
Erik Kline736353a2018-03-21 07:18:33 -0700580 sendMessage(CMD_FORCE_REEVALUATION, NO_UID, 0);
Paul Jensen25a217c2015-02-27 22:55:47 -0500581 break;
Paul Jensen49e3edf2015-05-22 10:50:39 -0400582 case APP_RETURN_WANTED_AS_IS:
Paul Jensen700f2362015-05-05 14:56:10 -0400583 mDontDisplaySigninNotification = true;
Paul Jensen25a217c2015-02-27 22:55:47 -0500584 // TODO: Distinguish this from a network that actually validates.
Erik Kline736353a2018-03-21 07:18:33 -0700585 // Displaying the "x" on the system UI icon may still be a good idea.
586 transitionTo(mEvaluatingPrivateDnsState);
Paul Jensen71b645f2014-10-13 14:13:07 -0400587 break;
Paul Jensen49e3edf2015-05-22 10:50:39 -0400588 case APP_RETURN_UNWANTED:
Paul Jensen700f2362015-05-05 14:56:10 -0400589 mDontDisplaySigninNotification = true;
Paul Jensen71b645f2014-10-13 14:13:07 -0400590 mUserDoesNotWant = true;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900591 notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, null);
Paul Jensen71b645f2014-10-13 14:13:07 -0400592 // TODO: Should teardown network.
Paul Jensend0491e9a2015-05-05 14:52:22 -0400593 mUidResponsibleForReeval = 0;
594 transitionTo(mEvaluatingState);
Paul Jensen71b645f2014-10-13 14:13:07 -0400595 break;
596 }
597 return HANDLED;
Erik Kline736353a2018-03-21 07:18:33 -0700598 case CMD_PRIVATE_DNS_SETTINGS_CHANGED: {
599 final PrivateDnsConfig cfg = (PrivateDnsConfig) message.obj;
600 if (!isValidationRequired() || cfg == null || !cfg.inStrictMode()) {
601 // No DNS resolution required.
602 //
603 // We don't force any validation in opportunistic mode
604 // here. Opportunistic mode nameservers are validated
605 // separately within netd.
606 //
607 // Reset Private DNS settings state.
608 mPrivateDnsProviderHostname = "";
609 break;
Erik Klinea24d4592018-01-11 21:07:29 +0900610 }
Erik Kline736353a2018-03-21 07:18:33 -0700611
612 mPrivateDnsProviderHostname = cfg.hostname;
613
614 // DNS resolutions via Private DNS strict mode block for a
615 // few seconds (~4.2) checking for any IP addresses to
616 // arrive and validate. Initiating a (re)evaluation now
617 // should not significantly alter the validation outcome.
618 //
619 // No matter what: enqueue a validation request; one of
620 // three things can happen with this request:
621 // [1] ignored (EvaluatingState or CaptivePortalState)
622 // [2] transition to EvaluatingPrivateDnsState
623 // (DefaultState and ValidatedState)
624 // [3] handled (EvaluatingPrivateDnsState)
625 //
626 // The Private DNS configuration to be evaluated will:
627 // [1] be skipped (not in strict mode), or
628 // [2] validate (huzzah), or
629 // [3] encounter some problem (invalid hostname,
630 // no resolved IP addresses, IPs unreachable,
631 // port 853 unreachable, port 853 is not running a
632 // DNS-over-TLS server, et cetera).
633 sendMessage(CMD_EVALUATE_PRIVATE_DNS);
634 break;
635 }
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800636 case EVENT_DNS_NOTIFICATION:
637 mDnsStallDetector.accumulateConsecutiveDnsTimeoutCount(message.arg1);
638 break;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400639 default:
Erik Kline736353a2018-03-21 07:18:33 -0700640 break;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400641 }
Erik Kline736353a2018-03-21 07:18:33 -0700642 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400643 }
644 }
645
Paul Jensen71b645f2014-10-13 14:13:07 -0400646 // Being in the ValidatedState State indicates a Network is:
647 // - Successfully validated, or
648 // - Wanted "as is" by the user, or
Paul Jensencf4c2c62015-07-01 14:16:32 -0400649 // - Does not satisfy the default NetworkRequest and so validation has been skipped.
Paul Jensenca8f16a2014-05-09 12:47:55 -0400650 private class ValidatedState extends State {
651 @Override
652 public void enter() {
Hugo Benichidd229822016-11-15 23:23:24 +0900653 maybeLogEvaluationResult(
654 networkEventType(validationStage(), EvaluationResult.VALIDATED));
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900655 notifyNetworkTested(INetworkMonitor.NETWORK_TEST_RESULT_VALID, null);
Hugo Benichidd229822016-11-15 23:23:24 +0900656 mValidations++;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400657 }
658
659 @Override
660 public boolean processMessage(Message message) {
Paul Jensenca8f16a2014-05-09 12:47:55 -0400661 switch (message.what) {
662 case CMD_NETWORK_CONNECTED:
663 transitionTo(mValidatedState);
Erik Kline736353a2018-03-21 07:18:33 -0700664 break;
665 case CMD_EVALUATE_PRIVATE_DNS:
666 transitionTo(mEvaluatingPrivateDnsState);
667 break;
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800668 case EVENT_DNS_NOTIFICATION:
669 mDnsStallDetector.accumulateConsecutiveDnsTimeoutCount(message.arg1);
670 if (isDataStall()) {
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800671 mCollectDataStallMetrics = true;
Chiachang Wang7a70a7e2018-11-27 18:00:05 +0800672 validationLog("Suspecting data stall, reevaluate");
673 transitionTo(mEvaluatingState);
674 }
675 break;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400676 default:
677 return NOT_HANDLED;
678 }
Erik Kline736353a2018-03-21 07:18:33 -0700679 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400680 }
681 }
682
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800683 private void writeDataStallStats(@NonNull final CaptivePortalProbeResult result) {
684 /*
685 * Collect data stall detection level information for each transport type. Collect type
686 * specific information for cellular and wifi only currently. Generate
687 * DataStallDetectionStats for each transport type. E.g., if a network supports both
688 * TRANSPORT_WIFI and TRANSPORT_VPN, two DataStallDetectionStats will be generated.
689 */
690 final int[] transports = mNetworkCapabilities.getTransportTypes();
691
692 for (int i = 0; i < transports.length; i++) {
693 DataStallStatsUtils.write(buildDataStallDetectionStats(transports[i]), result);
694 }
695 mCollectDataStallMetrics = false;
696 }
697
698 @VisibleForTesting
699 protected DataStallDetectionStats buildDataStallDetectionStats(int transport) {
700 final DataStallDetectionStats.Builder stats = new DataStallDetectionStats.Builder();
701 if (VDBG_STALL) log("collectDataStallMetrics: type=" + transport);
702 stats.setEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS);
703 stats.setNetworkType(transport);
704 switch (transport) {
705 case NetworkCapabilities.TRANSPORT_WIFI:
706 // TODO: Update it if status query in dual wifi is supported.
707 final WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
708 stats.setWiFiData(wifiInfo);
709 break;
710 case NetworkCapabilities.TRANSPORT_CELLULAR:
711 final boolean isRoaming = !mNetworkCapabilities.hasCapability(
712 NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
713 final SignalStrength ss = mTelephonyManager.getSignalStrength();
714 // TODO(b/120452078): Support multi-sim.
715 stats.setCellData(
716 mTelephonyManager.getDataNetworkType(),
717 isRoaming,
718 mTelephonyManager.getNetworkOperator(),
719 mTelephonyManager.getSimOperator(),
720 (ss != null)
721 ? ss.getLevel() : CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
722 break;
723 default:
724 // No transport type specific information for the other types.
725 break;
726 }
727 addDnsEvents(stats);
728
729 return stats.build();
730 }
731
Chiachang Wang95489ca2019-02-26 11:32:18 +0800732 @VisibleForTesting
733 protected void addDnsEvents(@NonNull final DataStallDetectionStats.Builder stats) {
Chiachang Wangf09e3e32019-02-22 11:13:07 +0800734 final int size = mDnsStallDetector.mResultIndices.size();
735 for (int i = 1; i <= DEFAULT_DNS_LOG_SIZE && i <= size; i++) {
736 final int index = mDnsStallDetector.mResultIndices.indexOf(size - i);
737 stats.addDnsEvent(mDnsStallDetector.mDnsEvents[index].mReturnCode,
738 mDnsStallDetector.mDnsEvents[index].mTimeStamp);
739 }
740 }
741
742
Paul Jensen71b645f2014-10-13 14:13:07 -0400743 // Being in the MaybeNotifyState State indicates the user may have been notified that sign-in
744 // is required. This State takes care to clear the notification upon exit from the State.
745 private class MaybeNotifyState extends State {
746 @Override
Paul Jensen25a217c2015-02-27 22:55:47 -0500747 public boolean processMessage(Message message) {
Paul Jensen25a217c2015-02-27 22:55:47 -0500748 switch (message.what) {
749 case CMD_LAUNCH_CAPTIVE_PORTAL_APP:
Remi NGUYEN VANdc483562019-02-04 11:32:20 +0900750 final Bundle appExtras = new Bundle();
Lorenzo Colittid8978ac2017-06-28 23:42:41 +0900751 // OneAddressPerFamilyNetwork is not parcelable across processes.
Remi NGUYEN VANcfff01e2019-02-13 20:58:59 +0900752 final Network network = new Network(mNetwork);
753 appExtras.putParcelable(ConnectivityManager.EXTRA_NETWORK, network);
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +0900754 final CaptivePortalProbeResult probeRes = mLastPortalProbeResult;
Remi NGUYEN VANdc483562019-02-04 11:32:20 +0900755 appExtras.putString(EXTRA_CAPTIVE_PORTAL_URL, probeRes.detectUrl);
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +0900756 if (probeRes.probeSpec != null) {
757 final String encodedSpec = probeRes.probeSpec.getEncodedSpec();
Remi NGUYEN VANdc483562019-02-04 11:32:20 +0900758 appExtras.putString(EXTRA_CAPTIVE_PORTAL_PROBE_SPEC, encodedSpec);
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +0900759 }
Remi NGUYEN VANdc483562019-02-04 11:32:20 +0900760 appExtras.putString(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_USER_AGENT,
Hugo Benichieef918a2017-04-10 17:43:08 +0900761 mCaptivePortalUserAgent);
Remi NGUYEN VANcfff01e2019-02-13 20:58:59 +0900762 mCm.startCaptivePortalApp(network, appExtras);
Paul Jensen25a217c2015-02-27 22:55:47 -0500763 return HANDLED;
764 default:
765 return NOT_HANDLED;
766 }
767 }
768
769 @Override
Paul Jensen71b645f2014-10-13 14:13:07 -0400770 public void exit() {
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900771 hideProvisioningNotification();
Paul Jensen71b645f2014-10-13 14:13:07 -0400772 }
773 }
774
775 // Being in the EvaluatingState State indicates the Network is being evaluated for internet
Paul Jensend0491e9a2015-05-05 14:52:22 -0400776 // connectivity, or that the user has indicated that this network is unwanted.
Paul Jensenca8f16a2014-05-09 12:47:55 -0400777 private class EvaluatingState extends State {
Paul Jensenca8f16a2014-05-09 12:47:55 -0400778 @Override
779 public void enter() {
Erik Klinea488c232016-04-15 15:49:42 +0900780 // If we have already started to track time spent in EvaluatingState
781 // don't reset the timer due simply to, say, commands or events that
782 // cause us to exit and re-enter EvaluatingState.
783 if (!mEvaluationTimer.isStarted()) {
784 mEvaluationTimer.start();
785 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400786 sendMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
Paul Jensen7ccd3df2014-08-29 09:54:01 -0400787 if (mUidResponsibleForReeval != INVALID_UID) {
788 TrafficStats.setThreadStatsUid(mUidResponsibleForReeval);
789 mUidResponsibleForReeval = INVALID_UID;
790 }
Paul Jensend0491e9a2015-05-05 14:52:22 -0400791 mReevaluateDelayMs = INITIAL_REEVALUATE_DELAY_MS;
Chiachang Wang4d11ec92018-10-23 21:10:57 +0800792 mEvaluateAttempts = 0;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400793 }
794
795 @Override
796 public boolean processMessage(Message message) {
Paul Jensenca8f16a2014-05-09 12:47:55 -0400797 switch (message.what) {
798 case CMD_REEVALUATE:
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +0900799 if (message.arg1 != mReevaluateToken || mUserDoesNotWant) {
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400800 return HANDLED;
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +0900801 }
Erik Kline736353a2018-03-21 07:18:33 -0700802 // Don't bother validating networks that don't satisfy the default request.
Paul Jensen2c311d62014-11-17 12:34:51 -0500803 // This includes:
804 // - VPNs which can be considered explicitly desired by the user and the
805 // user's desire trumps whether the network validates.
Erik Kline736353a2018-03-21 07:18:33 -0700806 // - Networks that don't provide Internet access. It's unclear how to
Paul Jensen2c311d62014-11-17 12:34:51 -0500807 // validate such networks.
808 // - Untrusted networks. It's unsafe to prompt the user to sign-in to
809 // such networks and the user didn't express interest in connecting to
810 // such networks (an app did) so the user may be unhappily surprised when
811 // asked to sign-in to a network they didn't want to connect to in the
812 // first place. Validation could be done to adjust the network scores
813 // however these networks are app-requested and may not be intended for
814 // general usage, in which case general validation may not be an accurate
815 // measure of the network's quality. Only the app knows how to evaluate
816 // the network so don't bother validating here. Furthermore sending HTTP
817 // packets over the network may be undesirable, for example an extremely
818 // expensive metered network, or unwanted leaking of the User Agent string.
Erik Klinea24d4592018-01-11 21:07:29 +0900819 if (!isValidationRequired()) {
Lorenzo Colittic5be12e2016-04-19 21:57:31 +0900820 validationLog("Network would not satisfy default request, not validating");
Paul Jensenca8f16a2014-05-09 12:47:55 -0400821 transitionTo(mValidatedState);
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400822 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400823 }
Chiachang Wang4d11ec92018-10-23 21:10:57 +0800824 mEvaluateAttempts++;
825
826 transitionTo(mProbingState);
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400827 return HANDLED;
Paul Jensen7ccd3df2014-08-29 09:54:01 -0400828 case CMD_FORCE_REEVALUATION:
Paul Jensend0491e9a2015-05-05 14:52:22 -0400829 // Before IGNORE_REEVALUATE_ATTEMPTS attempts are made,
830 // ignore any re-evaluation requests. After, restart the
831 // evaluation process via EvaluatingState#enter.
Chiachang Wang4d11ec92018-10-23 21:10:57 +0800832 return (mEvaluateAttempts < IGNORE_REEVALUATE_ATTEMPTS) ? HANDLED : NOT_HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400833 default:
834 return NOT_HANDLED;
835 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400836 }
Paul Jensen7ccd3df2014-08-29 09:54:01 -0400837
838 @Override
839 public void exit() {
840 TrafficStats.clearThreadStatsUid();
841 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400842 }
843
Paul Jensendcbe8352014-09-16 16:28:34 -0400844 // BroadcastReceiver that waits for a particular Intent and then posts a message.
845 private class CustomIntentReceiver extends BroadcastReceiver {
Paul Jensen71b645f2014-10-13 14:13:07 -0400846 private final int mToken;
847 private final int mWhat;
Paul Jensendcbe8352014-09-16 16:28:34 -0400848 private final String mAction;
Paul Jensen71b645f2014-10-13 14:13:07 -0400849 CustomIntentReceiver(String action, int token, int what) {
850 mToken = token;
851 mWhat = what;
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +0900852 mAction = action + "_" + mNetwork.getNetworkHandle() + "_" + token;
Paul Jensendcbe8352014-09-16 16:28:34 -0400853 mContext.registerReceiver(this, new IntentFilter(mAction));
Paul Jensen869868be2014-05-15 10:33:05 -0400854 }
Paul Jensendcbe8352014-09-16 16:28:34 -0400855 public PendingIntent getPendingIntent() {
Paul Jensen25a217c2015-02-27 22:55:47 -0500856 final Intent intent = new Intent(mAction);
857 intent.setPackage(mContext.getPackageName());
858 return PendingIntent.getBroadcast(mContext, 0, intent, 0);
Paul Jensendcbe8352014-09-16 16:28:34 -0400859 }
860 @Override
861 public void onReceive(Context context, Intent intent) {
Paul Jensen71b645f2014-10-13 14:13:07 -0400862 if (intent.getAction().equals(mAction)) sendMessage(obtainMessage(mWhat, mToken));
Paul Jensendcbe8352014-09-16 16:28:34 -0400863 }
864 }
Paul Jensen869868be2014-05-15 10:33:05 -0400865
Paul Jensen71b645f2014-10-13 14:13:07 -0400866 // Being in the CaptivePortalState State indicates a captive portal was detected and the user
867 // has been shown a notification to sign-in.
868 private class CaptivePortalState extends State {
Paul Jensen25a217c2015-02-27 22:55:47 -0500869 private static final String ACTION_LAUNCH_CAPTIVE_PORTAL_APP =
870 "android.net.netmon.launchCaptivePortalApp";
871
Paul Jensen869868be2014-05-15 10:33:05 -0400872 @Override
873 public void enter() {
Hugo Benichidd229822016-11-15 23:23:24 +0900874 maybeLogEvaluationResult(
875 networkEventType(validationStage(), EvaluationResult.CAPTIVE_PORTAL));
Paul Jensend0491e9a2015-05-05 14:52:22 -0400876 // Don't annoy user with sign-in notifications.
Paul Jensen700f2362015-05-05 14:56:10 -0400877 if (mDontDisplaySigninNotification) return;
Paul Jensen25a217c2015-02-27 22:55:47 -0500878 // Create a CustomIntentReceiver that sends us a
879 // CMD_LAUNCH_CAPTIVE_PORTAL_APP message when the user
880 // touches the notification.
881 if (mLaunchCaptivePortalAppBroadcastReceiver == null) {
Paul Jensen71b645f2014-10-13 14:13:07 -0400882 // Wait for result.
Paul Jensen25a217c2015-02-27 22:55:47 -0500883 mLaunchCaptivePortalAppBroadcastReceiver = new CustomIntentReceiver(
884 ACTION_LAUNCH_CAPTIVE_PORTAL_APP, new Random().nextInt(),
885 CMD_LAUNCH_CAPTIVE_PORTAL_APP);
Paul Jensen71b645f2014-10-13 14:13:07 -0400886 }
Paul Jensen25a217c2015-02-27 22:55:47 -0500887 // Display the sign in notification.
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900888 showProvisioningNotification(mLaunchCaptivePortalAppBroadcastReceiver.mAction);
Paul Jensenee3e2ce2015-06-17 15:02:54 -0400889 // Retest for captive portal occasionally.
890 sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */,
891 CAPTIVE_PORTAL_REEVALUATE_DELAY_MS);
Hugo Benichidd229822016-11-15 23:23:24 +0900892 mValidations++;
Paul Jensen869868be2014-05-15 10:33:05 -0400893 }
894
895 @Override
Paul Jensenee3e2ce2015-06-17 15:02:54 -0400896 public void exit() {
fionaxu1bf6ec22016-05-23 16:33:16 -0700897 removeMessages(CMD_CAPTIVE_PORTAL_RECHECK);
Paul Jensenee3e2ce2015-06-17 15:02:54 -0400898 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400899 }
900
Erik Kline736353a2018-03-21 07:18:33 -0700901 private class EvaluatingPrivateDnsState extends State {
902 private int mPrivateDnsReevalDelayMs;
903 private PrivateDnsConfig mPrivateDnsConfig;
904
905 @Override
906 public void enter() {
907 mPrivateDnsReevalDelayMs = INITIAL_REEVALUATE_DELAY_MS;
908 mPrivateDnsConfig = null;
909 sendMessage(CMD_EVALUATE_PRIVATE_DNS);
910 }
911
912 @Override
913 public boolean processMessage(Message msg) {
914 switch (msg.what) {
915 case CMD_EVALUATE_PRIVATE_DNS:
916 if (inStrictMode()) {
917 if (!isStrictModeHostnameResolved()) {
918 resolveStrictModeHostname();
919
920 if (isStrictModeHostnameResolved()) {
921 notifyPrivateDnsConfigResolved();
922 } else {
923 handlePrivateDnsEvaluationFailure();
924 break;
925 }
926 }
927
928 // Look up a one-time hostname, to bypass caching.
929 //
930 // Note that this will race with ConnectivityService
931 // code programming the DNS-over-TLS server IP addresses
932 // into netd (if invoked, above). If netd doesn't know
933 // the IP addresses yet, or if the connections to the IP
934 // addresses haven't yet been validated, netd will block
935 // for up to a few seconds before failing the lookup.
936 if (!sendPrivateDnsProbe()) {
937 handlePrivateDnsEvaluationFailure();
938 break;
939 }
940 }
941
942 // All good!
943 transitionTo(mValidatedState);
944 break;
945 default:
946 return NOT_HANDLED;
947 }
948 return HANDLED;
949 }
950
951 private boolean inStrictMode() {
952 return !TextUtils.isEmpty(mPrivateDnsProviderHostname);
953 }
954
955 private boolean isStrictModeHostnameResolved() {
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +0900956 return (mPrivateDnsConfig != null)
957 && mPrivateDnsConfig.hostname.equals(mPrivateDnsProviderHostname)
958 && (mPrivateDnsConfig.ips.length > 0);
Erik Kline736353a2018-03-21 07:18:33 -0700959 }
960
961 private void resolveStrictModeHostname() {
962 try {
963 // Do a blocking DNS resolution using the network-assigned nameservers.
Erik Klinef4fa9822018-04-27 22:48:33 +0900964 final InetAddress[] ips = mNetwork.getAllByName(mPrivateDnsProviderHostname);
Erik Kline71d90c42018-04-19 17:58:15 +0900965 mPrivateDnsConfig = new PrivateDnsConfig(mPrivateDnsProviderHostname, ips);
Remi NGUYEN VANdcb722f2018-07-20 18:13:24 +0900966 validationLog("Strict mode hostname resolved: " + mPrivateDnsConfig);
Erik Kline736353a2018-03-21 07:18:33 -0700967 } catch (UnknownHostException uhe) {
968 mPrivateDnsConfig = null;
Remi NGUYEN VANdcb722f2018-07-20 18:13:24 +0900969 validationLog("Strict mode hostname resolution failed: " + uhe.getMessage());
Erik Kline736353a2018-03-21 07:18:33 -0700970 }
971 }
972
973 private void notifyPrivateDnsConfigResolved() {
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900974 try {
975 mCallback.notifyPrivateDnsConfigResolved(mPrivateDnsConfig.toParcel());
976 } catch (RemoteException e) {
977 Log.e(TAG, "Error sending private DNS config resolved notification", e);
978 }
Erik Kline736353a2018-03-21 07:18:33 -0700979 }
980
981 private void handlePrivateDnsEvaluationFailure() {
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +0900982 notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, null);
Erik Kline736353a2018-03-21 07:18:33 -0700983
984 // Queue up a re-evaluation with backoff.
985 //
986 // TODO: Consider abandoning this state after a few attempts and
987 // transitioning back to EvaluatingState, to perhaps give ourselves
988 // the opportunity to (re)detect a captive portal or something.
989 sendMessageDelayed(CMD_EVALUATE_PRIVATE_DNS, mPrivateDnsReevalDelayMs);
990 mPrivateDnsReevalDelayMs *= 2;
991 if (mPrivateDnsReevalDelayMs > MAX_REEVALUATE_DELAY_MS) {
992 mPrivateDnsReevalDelayMs = MAX_REEVALUATE_DELAY_MS;
993 }
994 }
995
996 private boolean sendPrivateDnsProbe() {
997 // q.v. system/netd/server/dns/DnsTlsTransport.cpp
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +0900998 final String oneTimeHostnameSuffix = "-dnsotls-ds.metric.gstatic.com";
999 final String host = UUID.randomUUID().toString().substring(0, 8)
1000 + oneTimeHostnameSuffix;
Remi NGUYEN VANdcb722f2018-07-20 18:13:24 +09001001 final Stopwatch watch = new Stopwatch().start();
Erik Kline736353a2018-03-21 07:18:33 -07001002 try {
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001003 final InetAddress[] ips = mNonPrivateDnsBypassNetwork.getAllByName(host);
Remi NGUYEN VANdcb722f2018-07-20 18:13:24 +09001004 final long time = watch.stop();
1005 final String strIps = Arrays.toString(ips);
1006 final boolean success = (ips != null && ips.length > 0);
1007 validationLog(PROBE_PRIVDNS, host, String.format("%dms: %s", time, strIps));
1008 logValidationProbe(time, PROBE_PRIVDNS, success ? DNS_SUCCESS : DNS_FAILURE);
1009 return success;
1010 } catch (UnknownHostException uhe) {
1011 final long time = watch.stop();
1012 validationLog(PROBE_PRIVDNS, host,
1013 String.format("%dms - Error: %s", time, uhe.getMessage()));
1014 logValidationProbe(time, PROBE_PRIVDNS, DNS_FAILURE);
1015 }
Erik Kline736353a2018-03-21 07:18:33 -07001016 return false;
1017 }
1018 }
1019
Chiachang Wang4d11ec92018-10-23 21:10:57 +08001020 private class ProbingState extends State {
1021 private Thread mThread;
1022
1023 @Override
1024 public void enter() {
Chiachang Wang78b0d4b2018-12-03 17:06:54 +08001025 if (mEvaluateAttempts >= BLAME_FOR_EVALUATION_ATTEMPTS) {
1026 //Don't continue to blame UID forever.
1027 TrafficStats.clearThreadStatsUid();
1028 }
1029
Chiachang Wang30cc3672018-11-12 10:52:25 +08001030 final int token = ++mProbeToken;
1031 mThread = new Thread(() -> sendMessage(obtainMessage(CMD_PROBE_COMPLETE, token, 0,
Chiachang Wang4d11ec92018-10-23 21:10:57 +08001032 isCaptivePortal())));
1033 mThread.start();
1034 }
1035
1036 @Override
1037 public boolean processMessage(Message message) {
1038 switch (message.what) {
1039 case CMD_PROBE_COMPLETE:
Chiachang Wang30cc3672018-11-12 10:52:25 +08001040 // Ensure that CMD_PROBE_COMPLETE from stale threads are ignored.
1041 if (message.arg1 != mProbeToken) {
1042 return HANDLED;
1043 }
1044
Chiachang Wang4d11ec92018-10-23 21:10:57 +08001045 final CaptivePortalProbeResult probeResult =
1046 (CaptivePortalProbeResult) message.obj;
Chiachang Wang7a70a7e2018-11-27 18:00:05 +08001047 mLastProbeTime = SystemClock.elapsedRealtime();
Chiachang Wangf09e3e32019-02-22 11:13:07 +08001048
1049 if (mCollectDataStallMetrics) {
1050 writeDataStallStats(probeResult);
1051 }
1052
Chiachang Wang4d11ec92018-10-23 21:10:57 +08001053 if (probeResult.isSuccessful()) {
1054 // Transit EvaluatingPrivateDnsState to get to Validated
1055 // state (even if no Private DNS validation required).
1056 transitionTo(mEvaluatingPrivateDnsState);
1057 } else if (probeResult.isPortal()) {
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001058 notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, probeResult.redirectUrl);
Chiachang Wang4d11ec92018-10-23 21:10:57 +08001059 mLastPortalProbeResult = probeResult;
1060 transitionTo(mCaptivePortalState);
1061 } else {
Chiachang Wang4d11ec92018-10-23 21:10:57 +08001062 logNetworkEvent(NetworkEvent.NETWORK_VALIDATION_FAILED);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001063 notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, probeResult.redirectUrl);
Chiachang Wang78b0d4b2018-12-03 17:06:54 +08001064 transitionTo(mWaitingForNextProbeState);
Chiachang Wang4d11ec92018-10-23 21:10:57 +08001065 }
1066 return HANDLED;
Chiachang Wang7a70a7e2018-11-27 18:00:05 +08001067 case EVENT_DNS_NOTIFICATION:
Chiachang Wang78b0d4b2018-12-03 17:06:54 +08001068 // Leave the event to DefaultState to record correct dns timestamp.
Chiachang Wang4d11ec92018-10-23 21:10:57 +08001069 return NOT_HANDLED;
1070 default:
Chiachang Wang78b0d4b2018-12-03 17:06:54 +08001071 // Wait for probe result and defer events to next state by default.
Chiachang Wang4d11ec92018-10-23 21:10:57 +08001072 deferMessage(message);
1073 return HANDLED;
1074 }
1075 }
1076
1077 @Override
1078 public void exit() {
Chiachang Wang30cc3672018-11-12 10:52:25 +08001079 if (mThread.isAlive()) {
1080 mThread.interrupt();
1081 }
Chiachang Wang4d11ec92018-10-23 21:10:57 +08001082 mThread = null;
1083 }
1084 }
1085
Chiachang Wang78b0d4b2018-12-03 17:06:54 +08001086 // Being in the WaitingForNextProbeState indicates that evaluating probes failed and state is
1087 // transited from ProbingState. This ensures that the state machine is only in ProbingState
1088 // while a probe is in progress, not while waiting to perform the next probe. That allows
1089 // ProbingState to defer most messages until the probe is complete, which keeps the code simple
1090 // and matches the pre-Q behaviour where probes were a blocking operation performed on the state
1091 // machine thread.
1092 private class WaitingForNextProbeState extends State {
1093 @Override
1094 public void enter() {
Chiachang Wangcfe23562018-12-24 11:05:52 +08001095 scheduleNextProbe();
1096 }
1097
1098 private void scheduleNextProbe() {
Chiachang Wang78b0d4b2018-12-03 17:06:54 +08001099 final Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
1100 sendMessageDelayed(msg, mReevaluateDelayMs);
1101 mReevaluateDelayMs *= 2;
1102 if (mReevaluateDelayMs > MAX_REEVALUATE_DELAY_MS) {
1103 mReevaluateDelayMs = MAX_REEVALUATE_DELAY_MS;
1104 }
1105 }
1106
1107 @Override
1108 public boolean processMessage(Message message) {
1109 return NOT_HANDLED;
1110 }
1111 }
1112
Lorenzo Colittid8978ac2017-06-28 23:42:41 +09001113 // Limits the list of IP addresses returned by getAllByName or tried by openConnection to at
1114 // most one per address family. This ensures we only wait up to 20 seconds for TCP connections
1115 // to complete, regardless of how many IP addresses a host has.
1116 private static class OneAddressPerFamilyNetwork extends Network {
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +09001117 OneAddressPerFamilyNetwork(Network network) {
Erik Klinef4fa9822018-04-27 22:48:33 +09001118 // Always bypass Private DNS.
1119 super(network.getPrivateDnsBypassingCopy());
Lorenzo Colittid8978ac2017-06-28 23:42:41 +09001120 }
1121
1122 @Override
1123 public InetAddress[] getAllByName(String host) throws UnknownHostException {
Erik Klinef4fa9822018-04-27 22:48:33 +09001124 final List<InetAddress> addrs = Arrays.asList(super.getAllByName(host));
Lorenzo Colittid8978ac2017-06-28 23:42:41 +09001125
1126 // Ensure the address family of the first address is tried first.
1127 LinkedHashMap<Class, InetAddress> addressByFamily = new LinkedHashMap<>();
1128 addressByFamily.put(addrs.get(0).getClass(), addrs.get(0));
1129 Collections.shuffle(addrs);
1130
1131 for (InetAddress addr : addrs) {
1132 addressByFamily.put(addr.getClass(), addr);
1133 }
1134
1135 return addressByFamily.values().toArray(new InetAddress[addressByFamily.size()]);
1136 }
1137 }
1138
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001139 private boolean getIsCaptivePortalCheckEnabled() {
Hugo Benichic894b122017-07-31 12:58:20 +09001140 String symbol = Settings.Global.CAPTIVE_PORTAL_MODE;
1141 int defaultValue = Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +09001142 int mode = mDependencies.getSetting(mContext, symbol, defaultValue);
Hugo Benichic894b122017-07-31 12:58:20 +09001143 return mode != Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE;
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001144 }
1145
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001146 private boolean getUseHttpsValidation() {
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +09001147 return mDependencies.getSetting(mContext, Settings.Global.CAPTIVE_PORTAL_USE_HTTPS, 1) == 1;
Hugo Benichic894b122017-07-31 12:58:20 +09001148 }
1149
Hugo Benichic894b122017-07-31 12:58:20 +09001150 private String getCaptivePortalServerHttpsUrl() {
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +09001151 return mDependencies.getSetting(mContext,
Hugo Benichic894b122017-07-31 12:58:20 +09001152 Settings.Global.CAPTIVE_PORTAL_HTTPS_URL, DEFAULT_HTTPS_URL);
1153 }
1154
Chiachang Wang7a70a7e2018-11-27 18:00:05 +08001155 private int getConsecutiveDnsTimeoutThreshold() {
1156 return mDependencies.getSetting(mContext,
1157 Settings.Global.DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD,
1158 DEFAULT_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD);
1159 }
1160
1161 private int getDataStallMinEvaluateTime() {
1162 return mDependencies.getSetting(mContext,
1163 Settings.Global.DATA_STALL_MIN_EVALUATE_INTERVAL,
1164 DEFAULT_DATA_STALL_MIN_EVALUATE_TIME_MS);
1165 }
1166
1167 private int getDataStallValidDnsTimeThreshold() {
1168 return mDependencies.getSetting(mContext,
1169 Settings.Global.DATA_STALL_VALID_DNS_TIME_THRESHOLD,
1170 DEFAULT_DATA_STALL_VALID_DNS_TIME_THRESHOLD_MS);
1171 }
1172
1173 private int getDataStallEvalutionType() {
1174 return mDependencies.getSetting(mContext, Settings.Global.DATA_STALL_EVALUATION_TYPE,
1175 DEFAULT_DATA_STALL_EVALUATION_TYPES);
1176 }
1177
Hugo Benichic894b122017-07-31 12:58:20 +09001178 private URL[] makeCaptivePortalFallbackUrls() {
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +09001179 try {
1180 String separator = ",";
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +09001181 String firstUrl = mDependencies.getSetting(mContext,
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +09001182 Settings.Global.CAPTIVE_PORTAL_FALLBACK_URL, DEFAULT_FALLBACK_URL);
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +09001183 String joinedUrls = firstUrl + separator + mDependencies.getSetting(mContext,
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +09001184 Settings.Global.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS,
1185 DEFAULT_OTHER_FALLBACK_URLS);
1186 List<URL> urls = new ArrayList<>();
1187 for (String s : joinedUrls.split(separator)) {
1188 URL u = makeURL(s);
1189 if (u == null) {
1190 continue;
1191 }
1192 urls.add(u);
Hugo Benichieef918a2017-04-10 17:43:08 +09001193 }
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +09001194 if (urls.isEmpty()) {
1195 Log.e(TAG, String.format("could not create any url from %s", joinedUrls));
1196 }
1197 return urls.toArray(new URL[urls.size()]);
1198 } catch (Exception e) {
1199 // Don't let a misconfiguration bootloop the system.
1200 Log.e(TAG, "Error parsing configured fallback URLs", e);
1201 return new URL[0];
Hugo Benichieef918a2017-04-10 17:43:08 +09001202 }
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +09001203 }
1204
1205 private CaptivePortalProbeSpec[] makeCaptivePortalFallbackProbeSpecs() {
1206 try {
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +09001207 final String settingsValue = mDependencies.getSetting(
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +09001208 mContext, Settings.Global.CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS, null);
1209 // Probe specs only used if configured in settings
1210 if (TextUtils.isEmpty(settingsValue)) {
1211 return null;
1212 }
1213
Remi NGUYEN VANa4bcc862019-01-28 13:28:35 +09001214 final Collection<CaptivePortalProbeSpec> specs =
1215 CaptivePortalProbeSpec.parseCaptivePortalProbeSpecs(settingsValue);
1216 final CaptivePortalProbeSpec[] specsArray = new CaptivePortalProbeSpec[specs.size()];
1217 return specs.toArray(specsArray);
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +09001218 } catch (Exception e) {
1219 // Don't let a misconfiguration bootloop the system.
1220 Log.e(TAG, "Error parsing configured fallback probe specs", e);
1221 return null;
Hugo Benichieef918a2017-04-10 17:43:08 +09001222 }
Hugo Benichi92eb22fd2016-09-27 13:01:41 +09001223 }
1224
Hugo Benichic894b122017-07-31 12:58:20 +09001225 private String getCaptivePortalUserAgent() {
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +09001226 return mDependencies.getSetting(mContext,
Hugo Benichic894b122017-07-31 12:58:20 +09001227 Settings.Global.CAPTIVE_PORTAL_USER_AGENT, DEFAULT_USER_AGENT);
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001228 }
1229
Hugo Benichieef918a2017-04-10 17:43:08 +09001230 private URL nextFallbackUrl() {
1231 if (mCaptivePortalFallbackUrls.length == 0) {
1232 return null;
1233 }
1234 int idx = Math.abs(mNextFallbackUrlIndex) % mCaptivePortalFallbackUrls.length;
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +09001235 mNextFallbackUrlIndex += mRandom.nextInt(); // randomly change url without memory.
Hugo Benichieef918a2017-04-10 17:43:08 +09001236 return mCaptivePortalFallbackUrls[idx];
1237 }
1238
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +09001239 private CaptivePortalProbeSpec nextFallbackSpec() {
Remi NGUYEN VAN3ba6c0d2019-01-20 13:48:19 +09001240 if (isEmpty(mCaptivePortalFallbackSpecs)) {
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +09001241 return null;
1242 }
1243 // Randomly change spec without memory. Also randomize the first attempt.
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +09001244 final int idx = Math.abs(mRandom.nextInt()) % mCaptivePortalFallbackSpecs.length;
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +09001245 return mCaptivePortalFallbackSpecs[idx];
1246 }
1247
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001248 @VisibleForTesting
1249 protected CaptivePortalProbeResult isCaptivePortal() {
Calvin On4bc78eb2016-10-11 15:10:46 -07001250 if (!mIsCaptivePortalCheckEnabled) {
1251 validationLog("Validation disabled.");
Hugo Benichia4158702017-04-10 17:08:06 +09001252 return CaptivePortalProbeResult.SUCCESS;
Calvin On4bc78eb2016-10-11 15:10:46 -07001253 }
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001254
Hugo Benichib03272c2017-04-10 22:45:13 +09001255 URL pacUrl = null;
1256 URL httpsUrl = mCaptivePortalHttpsUrl;
1257 URL httpUrl = mCaptivePortalHttpUrl;
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001258
1259 // On networks with a PAC instead of fetching a URL that should result in a 204
1260 // response, we instead simply fetch the PAC script. This is done for a few reasons:
1261 // 1. At present our PAC code does not yet handle multiple PACs on multiple networks
1262 // until something like https://android-review.googlesource.com/#/c/115180/ lands.
1263 // Network.openConnection() will ignore network-specific PACs and instead fetch
1264 // using NO_PROXY. If a PAC is in place, the only fetch we know will succeed with
1265 // NO_PROXY is the fetch of the PAC itself.
1266 // 2. To proxy the generate_204 fetch through a PAC would require a number of things
1267 // happen before the fetch can commence, namely:
1268 // a) the PAC script be fetched
1269 // b) a PAC script resolver service be fired up and resolve the captive portal
1270 // server.
1271 // Network validation could be delayed until these prerequisities are satisifed or
1272 // could simply be left to race them. Neither is an optimal solution.
1273 // 3. PAC scripts are sometimes used to block or restrict Internet access and may in
1274 // fact block fetching of the generate_204 URL which would lead to false negative
1275 // results for network validation.
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001276 final ProxyInfo proxyInfo = mLinkProperties.getHttpProxy();
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001277 if (proxyInfo != null && !Uri.EMPTY.equals(proxyInfo.getPacFileUrl())) {
Hugo Benichi92eb22fd2016-09-27 13:01:41 +09001278 pacUrl = makeURL(proxyInfo.getPacFileUrl().toString());
1279 if (pacUrl == null) {
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001280 return CaptivePortalProbeResult.FAILED;
1281 }
1282 }
1283
Hugo Benichib03272c2017-04-10 22:45:13 +09001284 if ((pacUrl == null) && (httpUrl == null || httpsUrl == null)) {
1285 return CaptivePortalProbeResult.FAILED;
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001286 }
1287
1288 long startTime = SystemClock.elapsedRealtime();
1289
Hugo Benichieb5e9aa2016-11-16 18:18:08 +09001290 final CaptivePortalProbeResult result;
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001291 if (pacUrl != null) {
Hugo Benichieb5e9aa2016-11-16 18:18:08 +09001292 result = sendDnsAndHttpProbes(null, pacUrl, ValidationProbeEvent.PROBE_PAC);
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001293 } else if (mUseHttps) {
Hugo Benichieef918a2017-04-10 17:43:08 +09001294 result = sendParallelHttpProbes(proxyInfo, httpsUrl, httpUrl);
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001295 } else {
Hugo Benichieb5e9aa2016-11-16 18:18:08 +09001296 result = sendDnsAndHttpProbes(proxyInfo, httpUrl, ValidationProbeEvent.PROBE_HTTP);
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001297 }
1298
1299 long endTime = SystemClock.elapsedRealtime();
1300
1301 sendNetworkConditionsBroadcast(true /* response received */,
1302 result.isPortal() /* isCaptivePortal */,
1303 startTime, endTime);
1304
Chiachang Wang4d11ec92018-10-23 21:10:57 +08001305 log("isCaptivePortal: isSuccessful()=" + result.isSuccessful()
1306 + " isPortal()=" + result.isPortal()
1307 + " RedirectUrl=" + result.redirectUrl
1308 + " Time=" + (endTime - startTime) + "ms");
Yohei, Oshima5225aba2016-08-02 10:34:32 +09001309
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001310 return result;
Udam Sainib7c24872016-01-04 12:16:14 -08001311 }
1312
Paul Jensenca8f16a2014-05-09 12:47:55 -04001313 /**
Hugo Benichieb5e9aa2016-11-16 18:18:08 +09001314 * Do a DNS resolution and URL fetch on a known web server to see if we get the data we expect.
1315 * @return a CaptivePortalProbeResult inferred from the HTTP response.
1316 */
1317 private CaptivePortalProbeResult sendDnsAndHttpProbes(ProxyInfo proxy, URL url, int probeType) {
1318 // Pre-resolve the captive portal server host so we can log it.
1319 // Only do this if HttpURLConnection is about to, to avoid any potentially
1320 // unnecessary resolution.
1321 final String host = (proxy != null) ? proxy.getHost() : url.getHost();
1322 sendDnsProbe(host);
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +09001323 return sendHttpProbe(url, probeType, null);
Hugo Benichieb5e9aa2016-11-16 18:18:08 +09001324 }
1325
1326 /** Do a DNS resolution of the given server. */
1327 private void sendDnsProbe(String host) {
1328 if (TextUtils.isEmpty(host)) {
1329 return;
1330 }
1331
1332 final String name = ValidationProbeEvent.getProbeName(ValidationProbeEvent.PROBE_DNS);
1333 final Stopwatch watch = new Stopwatch().start();
1334 int result;
1335 String connectInfo;
1336 try {
Lorenzo Colittid8978ac2017-06-28 23:42:41 +09001337 InetAddress[] addresses = mNetwork.getAllByName(host);
Hugo Benichid9ac87e2017-04-06 14:36:39 +09001338 StringBuffer buffer = new StringBuffer();
Hugo Benichieb5e9aa2016-11-16 18:18:08 +09001339 for (InetAddress address : addresses) {
Hugo Benichid9ac87e2017-04-06 14:36:39 +09001340 buffer.append(',').append(address.getHostAddress());
Hugo Benichieb5e9aa2016-11-16 18:18:08 +09001341 }
Hugo Benichid9ac87e2017-04-06 14:36:39 +09001342 result = ValidationProbeEvent.DNS_SUCCESS;
1343 connectInfo = "OK " + buffer.substring(1);
Hugo Benichieb5e9aa2016-11-16 18:18:08 +09001344 } catch (UnknownHostException e) {
1345 result = ValidationProbeEvent.DNS_FAILURE;
Hugo Benichid9ac87e2017-04-06 14:36:39 +09001346 connectInfo = "FAIL";
Hugo Benichieb5e9aa2016-11-16 18:18:08 +09001347 }
1348 final long latency = watch.stop();
Hugo Benichid9ac87e2017-04-06 14:36:39 +09001349 validationLog(ValidationProbeEvent.PROBE_DNS, host,
1350 String.format("%dms %s", latency, connectInfo));
Hugo Benichieb5e9aa2016-11-16 18:18:08 +09001351 logValidationProbe(latency, ValidationProbeEvent.PROBE_DNS, result);
1352 }
1353
1354 /**
1355 * Do a URL fetch on a known web server to see if we get the data we expect.
1356 * @return a CaptivePortalProbeResult inferred from the HTTP response.
Paul Jensenca8f16a2014-05-09 12:47:55 -04001357 */
Paul Jensencf4c2c62015-07-01 14:16:32 -04001358 @VisibleForTesting
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +09001359 protected CaptivePortalProbeResult sendHttpProbe(URL url, int probeType,
1360 @Nullable CaptivePortalProbeSpec probeSpec) {
Paul Jensenca8f16a2014-05-09 12:47:55 -04001361 HttpURLConnection urlConnection = null;
Hugo Benichia4158702017-04-10 17:08:06 +09001362 int httpResponseCode = CaptivePortalProbeResult.FAILED_CODE;
Paul Jensen232437312016-04-06 09:51:26 -04001363 String redirectUrl = null;
Erik Klinea488c232016-04-15 15:49:42 +09001364 final Stopwatch probeTimer = new Stopwatch().start();
Jeff Sharkey619a5112017-01-19 11:55:54 -07001365 final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_PROBE);
Paul Jensenca8f16a2014-05-09 12:47:55 -04001366 try {
Lorenzo Colittid8978ac2017-06-28 23:42:41 +09001367 urlConnection = (HttpURLConnection) mNetwork.openConnection(url);
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001368 urlConnection.setInstanceFollowRedirects(probeType == ValidationProbeEvent.PROBE_PAC);
Paul Jensene547ff22014-08-04 09:12:24 -04001369 urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
1370 urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
Hongshik1da51fc2019-01-12 03:09:34 +09001371 urlConnection.setRequestProperty("Connection", "close");
Paul Jensene547ff22014-08-04 09:12:24 -04001372 urlConnection.setUseCaches(false);
Hugo Benichib03272c2017-04-10 22:45:13 +09001373 if (mCaptivePortalUserAgent != null) {
1374 urlConnection.setRequestProperty("User-Agent", mCaptivePortalUserAgent);
Hugo Benichi92eb22fd2016-09-27 13:01:41 +09001375 }
Hugo Benichiec88fd62017-03-07 15:10:03 +09001376 // cannot read request header after connection
1377 String requestHeader = urlConnection.getRequestProperties().toString();
Paul Jensen306f1a42014-08-04 10:59:01 -04001378
1379 // Time how long it takes to get a response to our request
1380 long requestTimestamp = SystemClock.elapsedRealtime();
1381
Pierre Imaibe12d762016-03-10 17:00:50 +09001382 httpResponseCode = urlConnection.getResponseCode();
Paul Jensen232437312016-04-06 09:51:26 -04001383 redirectUrl = urlConnection.getHeaderField("location");
Paul Jensen306f1a42014-08-04 10:59:01 -04001384
1385 // Time how long it takes to get a response to our request
1386 long responseTimestamp = SystemClock.elapsedRealtime();
1387
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +09001388 validationLog(probeType, url, "time=" + (responseTimestamp - requestTimestamp) + "ms"
1389 + " ret=" + httpResponseCode
1390 + " request=" + requestHeader
1391 + " headers=" + urlConnection.getHeaderFields());
Paul Jensene547ff22014-08-04 09:12:24 -04001392 // NOTE: We may want to consider an "HTTP/1.0 204" response to be a captive
1393 // portal. The only example of this seen so far was a captive portal. For
1394 // the time being go with prior behavior of assuming it's not a captive
1395 // portal. If it is considered a captive portal, a different sign-in URL
1396 // is needed (i.e. can't browse a 204). This could be the result of an HTTP
1397 // proxy server.
Hugo Benichicb4aa4d2017-01-11 16:23:26 +09001398 if (httpResponseCode == 200) {
Sehee Park8e9a15b2018-11-16 17:39:34 +09001399 long contentLength = urlConnection.getContentLengthLong();
Hugo Benichicb4aa4d2017-01-11 16:23:26 +09001400 if (probeType == ValidationProbeEvent.PROBE_PAC) {
Hugo Benichid9ac87e2017-04-06 14:36:39 +09001401 validationLog(
1402 probeType, url, "PAC fetch 200 response interpreted as 204 response.");
Hugo Benichia4158702017-04-10 17:08:06 +09001403 httpResponseCode = CaptivePortalProbeResult.SUCCESS_CODE;
Sehee Park8e9a15b2018-11-16 17:39:34 +09001404 } else if (contentLength == -1) {
1405 // When no Content-length (default value == -1), attempt to read a byte
1406 // from the response. Do not use available() as it is unreliable.
1407 // See http://b/33498325.
Hugo Benichicb4aa4d2017-01-11 16:23:26 +09001408 if (urlConnection.getInputStream().read() == -1) {
Sehee Park8e9a15b2018-11-16 17:39:34 +09001409 validationLog(probeType, url,
1410 "Empty 200 response interpreted as failed response.");
1411 httpResponseCode = CaptivePortalProbeResult.FAILED_CODE;
Hugo Benichicb4aa4d2017-01-11 16:23:26 +09001412 }
Sehee Park8e9a15b2018-11-16 17:39:34 +09001413 } else if (contentLength <= 4) {
1414 // Consider 200 response with "Content-length <= 4" to not be a captive
1415 // portal. There's no point in considering this a captive portal as the
1416 // user cannot sign-in to an empty page. Probably the result of a broken
1417 // transparent proxy. See http://b/9972012 and http://b/122999481.
1418 validationLog(probeType, url, "200 response with Content-length <= 4"
1419 + " interpreted as failed response.");
1420 httpResponseCode = CaptivePortalProbeResult.FAILED_CODE;
Hugo Benichicb4aa4d2017-01-11 16:23:26 +09001421 }
Paul Jensen8fe17422015-02-02 11:03:03 -05001422 }
Paul Jensenca8f16a2014-05-09 12:47:55 -04001423 } catch (IOException e) {
Hugo Benichia4158702017-04-10 17:08:06 +09001424 validationLog(probeType, url, "Probe failed with exception " + e);
1425 if (httpResponseCode == CaptivePortalProbeResult.FAILED_CODE) {
Paul Jensen869868be2014-05-15 10:33:05 -04001426 // TODO: Ping gateway and DNS server and log results.
1427 }
Paul Jensenca8f16a2014-05-09 12:47:55 -04001428 } finally {
1429 if (urlConnection != null) {
1430 urlConnection.disconnect();
1431 }
Jeff Sharkey619a5112017-01-19 11:55:54 -07001432 TrafficStats.setThreadStatsTag(oldTag);
Paul Jensenca8f16a2014-05-09 12:47:55 -04001433 }
Hugo Benichicfddd682016-05-31 16:28:06 +09001434 logValidationProbe(probeTimer.stop(), probeType, httpResponseCode);
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +09001435
1436 if (probeSpec == null) {
1437 return new CaptivePortalProbeResult(httpResponseCode, redirectUrl, url.toString());
1438 } else {
1439 return probeSpec.getResult(httpResponseCode, redirectUrl);
1440 }
Paul Jensenca8f16a2014-05-09 12:47:55 -04001441 }
Paul Jensen306f1a42014-08-04 10:59:01 -04001442
Hugo Benichid953bf82016-09-27 09:22:35 +09001443 private CaptivePortalProbeResult sendParallelHttpProbes(
Hugo Benichieef918a2017-04-10 17:43:08 +09001444 ProxyInfo proxy, URL httpsUrl, URL httpUrl) {
Hugo Benichid953bf82016-09-27 09:22:35 +09001445 // Number of probes to wait for. If a probe completes with a conclusive answer
1446 // it shortcuts the latch immediately by forcing the count to 0.
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001447 final CountDownLatch latch = new CountDownLatch(2);
1448
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001449 final class ProbeThread extends Thread {
1450 private final boolean mIsHttps;
Hugo Benichid953bf82016-09-27 09:22:35 +09001451 private volatile CaptivePortalProbeResult mResult = CaptivePortalProbeResult.FAILED;
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001452
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +09001453 ProbeThread(boolean isHttps) {
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001454 mIsHttps = isHttps;
1455 }
1456
Hugo Benichid953bf82016-09-27 09:22:35 +09001457 public CaptivePortalProbeResult result() {
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001458 return mResult;
1459 }
1460
1461 @Override
1462 public void run() {
1463 if (mIsHttps) {
Hugo Benichieb5e9aa2016-11-16 18:18:08 +09001464 mResult =
1465 sendDnsAndHttpProbes(proxy, httpsUrl, ValidationProbeEvent.PROBE_HTTPS);
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001466 } else {
Hugo Benichieb5e9aa2016-11-16 18:18:08 +09001467 mResult = sendDnsAndHttpProbes(proxy, httpUrl, ValidationProbeEvent.PROBE_HTTP);
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001468 }
1469 if ((mIsHttps && mResult.isSuccessful()) || (!mIsHttps && mResult.isPortal())) {
Hugo Benichid953bf82016-09-27 09:22:35 +09001470 // Stop waiting immediately if https succeeds or if http finds a portal.
1471 while (latch.getCount() > 0) {
1472 latch.countDown();
1473 }
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001474 }
Hugo Benichid953bf82016-09-27 09:22:35 +09001475 // Signal this probe has completed.
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001476 latch.countDown();
1477 }
1478 }
1479
Hugo Benichid953bf82016-09-27 09:22:35 +09001480 final ProbeThread httpsProbe = new ProbeThread(true);
1481 final ProbeThread httpProbe = new ProbeThread(false);
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001482
1483 try {
Hugo Benichid953bf82016-09-27 09:22:35 +09001484 httpsProbe.start();
1485 httpProbe.start();
1486 latch.await(PROBE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001487 } catch (InterruptedException e) {
Hugo Benichid953bf82016-09-27 09:22:35 +09001488 validationLog("Error: probes wait interrupted!");
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001489 return CaptivePortalProbeResult.FAILED;
1490 }
1491
Hugo Benichid953bf82016-09-27 09:22:35 +09001492 final CaptivePortalProbeResult httpsResult = httpsProbe.result();
1493 final CaptivePortalProbeResult httpResult = httpProbe.result();
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001494
Hugo Benichid953bf82016-09-27 09:22:35 +09001495 // Look for a conclusive probe result first.
1496 if (httpResult.isPortal()) {
1497 return httpResult;
1498 }
1499 // httpsResult.isPortal() is not expected, but check it nonetheless.
1500 if (httpsResult.isPortal() || httpsResult.isSuccessful()) {
1501 return httpsResult;
1502 }
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +09001503 // If a fallback method exists, use it to retry portal detection.
1504 // If we have new-style probe specs, use those. Otherwise, use the fallback URLs.
1505 final CaptivePortalProbeSpec probeSpec = nextFallbackSpec();
1506 final URL fallbackUrl = (probeSpec != null) ? probeSpec.getUrl() : nextFallbackUrl();
Hugo Benichid953bf82016-09-27 09:22:35 +09001507 if (fallbackUrl != null) {
Remi NGUYEN VAN13e6e212018-05-22 10:01:53 +09001508 CaptivePortalProbeResult result = sendHttpProbe(fallbackUrl, PROBE_FALLBACK, probeSpec);
Hugo Benichid953bf82016-09-27 09:22:35 +09001509 if (result.isPortal()) {
1510 return result;
1511 }
1512 }
Hugo Benichifc474422017-05-17 10:30:40 +09001513 // Otherwise wait until http and https probes completes and use their results.
Hugo Benichid953bf82016-09-27 09:22:35 +09001514 try {
Hugo Benichifc474422017-05-17 10:30:40 +09001515 httpProbe.join();
1516 if (httpProbe.result().isPortal()) {
1517 return httpProbe.result();
1518 }
Hugo Benichid953bf82016-09-27 09:22:35 +09001519 httpsProbe.join();
Hugo Benichifc474422017-05-17 10:30:40 +09001520 return httpsProbe.result();
Hugo Benichid953bf82016-09-27 09:22:35 +09001521 } catch (InterruptedException e) {
Hugo Benichifc474422017-05-17 10:30:40 +09001522 validationLog("Error: http or https probe wait interrupted!");
Hugo Benichid953bf82016-09-27 09:22:35 +09001523 return CaptivePortalProbeResult.FAILED;
1524 }
Lorenzo Colittic5be12e2016-04-19 21:57:31 +09001525 }
1526
Hugo Benichi92eb22fd2016-09-27 13:01:41 +09001527 private URL makeURL(String url) {
1528 if (url != null) {
1529 try {
1530 return new URL(url);
1531 } catch (MalformedURLException e) {
1532 validationLog("Bad URL: " + url);
1533 }
1534 }
1535 return null;
1536 }
1537
Paul Jensen306f1a42014-08-04 10:59:01 -04001538 /**
1539 * @param responseReceived - whether or not we received a valid HTTP response to our request.
1540 * If false, isCaptivePortal and responseTimestampMs are ignored
1541 * TODO: This should be moved to the transports. The latency could be passed to the transports
1542 * along with the captive portal result. Currently the TYPE_MOBILE broadcasts appear unused so
1543 * perhaps this could just be added to the WiFi transport only.
1544 */
1545 private void sendNetworkConditionsBroadcast(boolean responseReceived, boolean isCaptivePortal,
1546 long requestTimestampMs, long responseTimestampMs) {
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001547 if (!mSystemReady) {
Hugo Benichic894b122017-07-31 12:58:20 +09001548 return;
1549 }
Robert Greenwaltfb68f8f2014-08-13 13:43:32 -07001550
Chalard Jean918a68b2018-01-19 17:00:47 +09001551 Intent latencyBroadcast =
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001552 new Intent(NetworkMonitorUtils.ACTION_NETWORK_CONDITIONS_MEASURED);
1553 if (mNetworkCapabilities.hasTransport(TRANSPORT_WIFI)) {
Chiachang Wang75610712019-02-14 09:30:58 +08001554 if (!mWifiManager.isScanAlwaysAvailable()) {
1555 return;
1556 }
1557
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001558 WifiInfo currentWifiInfo = mWifiManager.getConnectionInfo();
1559 if (currentWifiInfo != null) {
1560 // NOTE: getSSID()'s behavior changed in API 17; before that, SSIDs were not
1561 // surrounded by double quotation marks (thus violating the Javadoc), but this
1562 // was changed to match the Javadoc in API 17. Since clients may have started
1563 // sanitizing the output of this method since API 17 was released, we should
1564 // not change it here as it would become impossible to tell whether the SSID is
1565 // simply being surrounded by quotes due to the API, or whether those quotes
1566 // are actually part of the SSID.
1567 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_SSID,
1568 currentWifiInfo.getSSID());
1569 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_BSSID,
1570 currentWifiInfo.getBSSID());
1571 } else {
1572 if (VDBG) logw("network info is TYPE_WIFI but no ConnectionInfo found");
1573 return;
1574 }
1575 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CONNECTIVITY_TYPE, TYPE_WIFI);
1576 } else if (mNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
Chiachang Wang75610712019-02-14 09:30:58 +08001577 // TODO(b/123893112): Support multi-sim.
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001578 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_NETWORK_TYPE,
1579 mTelephonyManager.getNetworkType());
Chiachang Wang75610712019-02-14 09:30:58 +08001580 final ServiceState dataSs = mTelephonyManager.getServiceState();
1581 if (dataSs == null) {
1582 logw("failed to retrieve ServiceState");
1583 return;
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001584 }
Chiachang Wang75610712019-02-14 09:30:58 +08001585 // See if the data sub is registered for PS services on cell.
1586 final NetworkRegistrationState nrs = dataSs.getNetworkRegistrationState(
1587 NetworkRegistrationState.DOMAIN_PS,
1588 AccessNetworkConstants.TransportType.WWAN);
1589 latencyBroadcast.putExtra(
1590 NetworkMonitorUtils.EXTRA_CELL_ID,
1591 nrs == null ? null : nrs.getCellIdentity());
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001592 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CONNECTIVITY_TYPE, TYPE_MOBILE);
1593 } else {
1594 return;
Paul Jensen306f1a42014-08-04 10:59:01 -04001595 }
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001596 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_RESPONSE_RECEIVED,
Chalard Jean918a68b2018-01-19 17:00:47 +09001597 responseReceived);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001598 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_REQUEST_TIMESTAMP_MS,
Chalard Jean918a68b2018-01-19 17:00:47 +09001599 requestTimestampMs);
Paul Jensen306f1a42014-08-04 10:59:01 -04001600
1601 if (responseReceived) {
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001602 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_IS_CAPTIVE_PORTAL,
Chalard Jean918a68b2018-01-19 17:00:47 +09001603 isCaptivePortal);
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001604 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_RESPONSE_TIMESTAMP_MS,
Chalard Jean918a68b2018-01-19 17:00:47 +09001605 responseTimestampMs);
Paul Jensen306f1a42014-08-04 10:59:01 -04001606 }
Paul Jensen55298582014-08-20 11:01:41 -04001607 mContext.sendBroadcastAsUser(latencyBroadcast, UserHandle.CURRENT,
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001608 NetworkMonitorUtils.PERMISSION_ACCESS_NETWORK_CONDITIONS);
Paul Jensen306f1a42014-08-04 10:59:01 -04001609 }
Paul Jensend7b6ca92015-05-13 14:05:12 -04001610
Hugo Benichicfddd682016-05-31 16:28:06 +09001611 private void logNetworkEvent(int evtype) {
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001612 int[] transports = mNetworkCapabilities.getTransportTypes();
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +09001613 mMetricsLog.log(mNetwork, transports, new NetworkEvent(evtype));
Hugo Benichicfddd682016-05-31 16:28:06 +09001614 }
1615
Hugo Benichidd229822016-11-15 23:23:24 +09001616 private int networkEventType(ValidationStage s, EvaluationResult r) {
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +09001617 if (s.mIsFirstValidation) {
1618 if (r.mIsValidated) {
Hugo Benichidd229822016-11-15 23:23:24 +09001619 return NetworkEvent.NETWORK_FIRST_VALIDATION_SUCCESS;
1620 } else {
1621 return NetworkEvent.NETWORK_FIRST_VALIDATION_PORTAL_FOUND;
1622 }
1623 } else {
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +09001624 if (r.mIsValidated) {
Hugo Benichidd229822016-11-15 23:23:24 +09001625 return NetworkEvent.NETWORK_REVALIDATION_SUCCESS;
1626 } else {
1627 return NetworkEvent.NETWORK_REVALIDATION_PORTAL_FOUND;
1628 }
1629 }
1630 }
1631
Hugo Benichicfddd682016-05-31 16:28:06 +09001632 private void maybeLogEvaluationResult(int evtype) {
1633 if (mEvaluationTimer.isRunning()) {
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001634 int[] transports = mNetworkCapabilities.getTransportTypes();
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +09001635 mMetricsLog.log(mNetwork, transports,
1636 new NetworkEvent(evtype, mEvaluationTimer.stop()));
Hugo Benichicfddd682016-05-31 16:28:06 +09001637 mEvaluationTimer.reset();
1638 }
1639 }
1640
1641 private void logValidationProbe(long durationMs, int probeType, int probeResult) {
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001642 int[] transports = mNetworkCapabilities.getTransportTypes();
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +09001643 boolean isFirstValidation = validationStage().mIsFirstValidation;
Remi NGUYEN VAN7b84fb32019-01-19 21:13:24 +09001644 ValidationProbeEvent ev = new ValidationProbeEvent.Builder()
1645 .setProbeType(probeType, isFirstValidation)
1646 .setReturnCode(probeResult)
1647 .setDurationMs(durationMs)
1648 .build();
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +09001649 mMetricsLog.log(mNetwork, transports, ev);
Hugo Benichicfddd682016-05-31 16:28:06 +09001650 }
Hugo Benichic894b122017-07-31 12:58:20 +09001651
1652 @VisibleForTesting
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001653 static class Dependencies {
1654 public Network getPrivateDnsBypassNetwork(Network network) {
1655 return new OneAddressPerFamilyNetwork(network);
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +09001656 }
Hugo Benichic894b122017-07-31 12:58:20 +09001657
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +09001658 public Random getRandom() {
1659 return new Random();
1660 }
Hugo Benichic894b122017-07-31 12:58:20 +09001661
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +09001662 /**
Remi NGUYEN VANe67b0c32018-12-27 16:43:56 +09001663 * Get the captive portal server HTTP URL that is configured on the device.
1664 */
1665 public String getCaptivePortalServerHttpUrl(Context context) {
1666 return NetworkMonitorUtils.getCaptivePortalServerHttpUrl(context);
1667 }
1668
1669 /**
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +09001670 * Get the value of a global integer setting.
1671 * @param symbol Name of the setting
1672 * @param defaultValue Value to return if the setting is not defined.
1673 */
Hugo Benichic894b122017-07-31 12:58:20 +09001674 public int getSetting(Context context, String symbol, int defaultValue) {
1675 return Settings.Global.getInt(context.getContentResolver(), symbol, defaultValue);
1676 }
1677
Remi NGUYEN VAN8708d842019-01-07 11:14:00 +09001678 /**
1679 * Get the value of a global String setting.
1680 * @param symbol Name of the setting
1681 * @param defaultValue Value to return if the setting is not defined.
1682 */
Hugo Benichic894b122017-07-31 12:58:20 +09001683 public String getSetting(Context context, String symbol, String defaultValue) {
1684 final String value = Settings.Global.getString(context.getContentResolver(), symbol);
1685 return value != null ? value : defaultValue;
1686 }
Remi NGUYEN VANd9a1cd72018-05-22 13:11:15 +09001687
1688 public static final Dependencies DEFAULT = new Dependencies();
Hugo Benichic894b122017-07-31 12:58:20 +09001689 }
Chiachang Wang7a70a7e2018-11-27 18:00:05 +08001690
1691 /**
1692 * Methods in this class perform no locking because all accesses are performed on the state
1693 * machine's thread. Need to consider the thread safety if it ever could be accessed outside the
1694 * state machine.
1695 */
1696 @VisibleForTesting
1697 protected class DnsStallDetector {
Chiachang Wang7a70a7e2018-11-27 18:00:05 +08001698 private int mConsecutiveTimeoutCount = 0;
1699 private int mSize;
1700 final DnsResult[] mDnsEvents;
1701 final RingBufferIndices mResultIndices;
1702
1703 DnsStallDetector(int size) {
1704 mSize = Math.max(DEFAULT_DNS_LOG_SIZE, size);
1705 mDnsEvents = new DnsResult[mSize];
1706 mResultIndices = new RingBufferIndices(mSize);
1707 }
1708
1709 @VisibleForTesting
1710 protected void accumulateConsecutiveDnsTimeoutCount(int code) {
1711 final DnsResult result = new DnsResult(code);
1712 mDnsEvents[mResultIndices.add()] = result;
1713 if (result.isTimeout()) {
1714 mConsecutiveTimeoutCount++;
1715 } else {
1716 // Keep the event in mDnsEvents without clearing it so that there are logs to do the
1717 // simulation and analysis.
1718 mConsecutiveTimeoutCount = 0;
1719 }
1720 }
1721
1722 private boolean isDataStallSuspected(int timeoutCountThreshold, int validTime) {
1723 if (timeoutCountThreshold <= 0) {
1724 Log.wtf(TAG, "Timeout count threshold should be larger than 0.");
1725 return false;
1726 }
1727
1728 // Check if the consecutive timeout count reach the threshold or not.
1729 if (mConsecutiveTimeoutCount < timeoutCountThreshold) {
1730 return false;
1731 }
1732
1733 // Check if the target dns event index is valid or not.
1734 final int firstConsecutiveTimeoutIndex =
1735 mResultIndices.indexOf(mResultIndices.size() - timeoutCountThreshold);
1736
1737 // If the dns timeout events happened long time ago, the events are meaningless for
1738 // data stall evaluation. Thus, check if the first consecutive timeout dns event
1739 // considered in the evaluation happened in defined threshold time.
1740 final long now = SystemClock.elapsedRealtime();
1741 final long firstTimeoutTime = now - mDnsEvents[firstConsecutiveTimeoutIndex].mTimeStamp;
1742 return (firstTimeoutTime < validTime);
1743 }
1744
1745 int getConsecutiveTimeoutCount() {
1746 return mConsecutiveTimeoutCount;
1747 }
1748 }
1749
1750 private static class DnsResult {
1751 // TODO: Need to move the DNS return code definition to a specific class once unify DNS
1752 // response code is done.
1753 private static final int RETURN_CODE_DNS_TIMEOUT = 255;
1754
1755 private final long mTimeStamp;
1756 private final int mReturnCode;
1757
1758 DnsResult(int code) {
1759 mTimeStamp = SystemClock.elapsedRealtime();
1760 mReturnCode = code;
1761 }
1762
1763 private boolean isTimeout() {
1764 return mReturnCode == RETURN_CODE_DNS_TIMEOUT;
1765 }
1766 }
1767
1768
1769 @VisibleForTesting
1770 protected DnsStallDetector getDnsStallDetector() {
1771 return mDnsStallDetector;
1772 }
1773
1774 private boolean dataStallEvaluateTypeEnabled(int type) {
Chiachang Wang4349dc02019-03-05 20:31:57 +08001775 return (mDataStallEvaluationType & type) != 0;
Chiachang Wang7a70a7e2018-11-27 18:00:05 +08001776 }
1777
1778 @VisibleForTesting
1779 protected long getLastProbeTime() {
1780 return mLastProbeTime;
1781 }
1782
1783 @VisibleForTesting
1784 protected boolean isDataStall() {
1785 boolean result = false;
1786 // Reevaluation will generate traffic. Thus, set a minimal reevaluation timer to limit the
1787 // possible traffic cost in metered network.
Remi NGUYEN VANc1c02dc2019-01-20 16:50:42 +09001788 if (!mNetworkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED)
Chiachang Wang7a70a7e2018-11-27 18:00:05 +08001789 && (SystemClock.elapsedRealtime() - getLastProbeTime()
1790 < mDataStallMinEvaluateTime)) {
1791 return false;
1792 }
1793
1794 // Check dns signal. Suspect it may be a data stall if both :
Chiachang Wang4349dc02019-03-05 20:31:57 +08001795 // 1. The number of consecutive DNS query timeouts >= mConsecutiveDnsTimeoutThreshold.
Chiachang Wang7a70a7e2018-11-27 18:00:05 +08001796 // 2. Those consecutive DNS queries happened in the last mValidDataStallDnsTimeThreshold ms.
1797 if (dataStallEvaluateTypeEnabled(DATA_STALL_EVALUATION_TYPE_DNS)) {
1798 if (mDnsStallDetector.isDataStallSuspected(mConsecutiveDnsTimeoutThreshold,
1799 mDataStallValidDnsTimeThreshold)) {
1800 result = true;
1801 logNetworkEvent(NetworkEvent.NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND);
1802 }
1803 }
1804
1805 if (VDBG_STALL) {
1806 log("isDataStall: result=" + result + ", consecutive dns timeout count="
1807 + mDnsStallDetector.getConsecutiveTimeoutCount());
1808 }
1809
1810 return result;
1811 }
Paul Jensenca8f16a2014-05-09 12:47:55 -04001812}