blob: 05402581e64721fdd02871672343ac0261cdf35a [file] [log] [blame]
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001/*
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
19import 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;
22import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_PROBE_SPEC;
23import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL;
24import static android.net.ConnectivityManager.TYPE_MOBILE;
25import static android.net.ConnectivityManager.TYPE_WIFI;
Chiachang Wang1c67f4e2019-05-09 21:28:47 +080026import static android.net.DnsResolver.FLAG_EMPTY;
Remi NGUYEN VANb4af1302019-06-11 16:17:46 +090027import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
28import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY;
29import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID;
Chiachang Wang813ee472019-05-23 16:29:30 +080030import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS;
31import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_FALLBACK;
32import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTP;
33import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTPS;
34import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS;
35import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
36import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID;
Remi NGUYEN VANa0983f72019-01-20 16:50:42 +090037import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +090038import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
39import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
Niklas Lindgren0c904882018-12-07 11:08:04 +010040import static android.net.captiveportal.CaptivePortalProbeSpec.parseCaptivePortalProbeSpecs;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +090041import static android.net.metrics.ValidationProbeEvent.DNS_FAILURE;
42import static android.net.metrics.ValidationProbeEvent.DNS_SUCCESS;
43import static android.net.metrics.ValidationProbeEvent.PROBE_FALLBACK;
44import static android.net.metrics.ValidationProbeEvent.PROBE_PRIVDNS;
Chiachang Wang9a87f802019-04-08 19:06:21 +080045import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD;
46import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_EVALUATION_TYPE;
47import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_MIN_EVALUATE_INTERVAL;
Chiachang Wanga5716bf2019-11-20 16:13:07 +080048import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_TCP_POLLING_INTERVAL;
Chiachang Wang9a87f802019-04-08 19:06:21 +080049import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_VALID_DNS_TIME_THRESHOLD;
50import static android.net.util.DataStallUtils.DATA_STALL_EVALUATION_TYPE_DNS;
Chiachang Wanga5716bf2019-11-20 16:13:07 +080051import static android.net.util.DataStallUtils.DATA_STALL_EVALUATION_TYPE_TCP;
Chiachang Wang9a87f802019-04-08 19:06:21 +080052import static android.net.util.DataStallUtils.DEFAULT_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD;
53import static android.net.util.DataStallUtils.DEFAULT_DATA_STALL_EVALUATION_TYPES;
54import static android.net.util.DataStallUtils.DEFAULT_DATA_STALL_MIN_EVALUATE_TIME_MS;
55import static android.net.util.DataStallUtils.DEFAULT_DATA_STALL_VALID_DNS_TIME_THRESHOLD_MS;
56import static android.net.util.DataStallUtils.DEFAULT_DNS_LOG_SIZE;
Chiachang Wanga5716bf2019-11-20 16:13:07 +080057import static android.net.util.DataStallUtils.DEFAULT_TCP_POLLING_INTERVAL_MS;
Chiachang Wang79a6da32019-04-17 17:00:54 +080058import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS;
Chiachang Wang969eb752019-04-25 09:47:27 +080059import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_FALLBACK_URL;
60import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_HTTPS_URL;
61import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_HTTP_URL;
62import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_MODE;
63import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_MODE_IGNORE;
64import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_MODE_PROMPT;
Chiachang Wang79a6da32019-04-17 17:00:54 +080065import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS;
Chiachang Wanga64bb702020-03-31 07:50:59 +000066import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_OTHER_HTTPS_URLS;
67import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_OTHER_HTTP_URLS;
Chiachang Wang79a6da32019-04-17 17:00:54 +080068import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_USER_AGENT;
69import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_USE_HTTPS;
Chiachang Wanga64bb702020-03-31 07:50:59 +000070import static android.net.util.NetworkStackUtils.DEFAULT_CAPTIVE_PORTAL_HTTPS_URLS;
71import static android.net.util.NetworkStackUtils.DEFAULT_CAPTIVE_PORTAL_HTTP_URLS;
Automerger Merge Workerfaac06e2020-03-09 09:23:38 +000072import static android.net.util.NetworkStackUtils.DISMISS_PORTAL_IN_VALIDATED_NETWORK;
Remi NGUYEN VAN75e9d902020-04-09 06:41:16 +000073import static android.net.util.NetworkStackUtils.DNS_PROBE_PRIVATE_IP_NO_INTERNET_VERSION;
Remi NGUYEN VANabeaaf72019-01-20 13:48:19 +090074import static android.net.util.NetworkStackUtils.isEmpty;
Chiachang Wangbc1a1012019-09-06 15:05:32 +080075import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +090076
Cody Kesting782cbfa2020-01-06 15:51:59 -080077import static com.android.networkstack.apishim.ConstantsShim.DETECTION_METHOD_DNS_EVENTS;
78import static com.android.networkstack.apishim.ConstantsShim.DETECTION_METHOD_TCP_METRICS;
Cody Kesting176bce72020-01-20 18:09:59 -080079import static com.android.networkstack.apishim.ConstantsShim.KEY_DNS_CONSECUTIVE_TIMEOUTS;
80import static com.android.networkstack.apishim.ConstantsShim.KEY_NETWORK_PROBES_ATTEMPTED_BITMASK;
81import static com.android.networkstack.apishim.ConstantsShim.KEY_NETWORK_PROBES_SUCCEEDED_BITMASK;
82import static com.android.networkstack.apishim.ConstantsShim.KEY_NETWORK_VALIDATION_RESULT;
83import static com.android.networkstack.apishim.ConstantsShim.KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS;
84import static com.android.networkstack.apishim.ConstantsShim.KEY_TCP_PACKET_FAIL_RATE;
Chiachang Wangeb619222019-07-03 20:52:08 +080085import static com.android.networkstack.util.DnsUtils.PRIVATE_DNS_PROBE_HOST_SUFFIX;
Chiachang Wang1c67f4e2019-05-09 21:28:47 +080086import static com.android.networkstack.util.DnsUtils.TYPE_ADDRCONFIG;
87
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +090088import android.app.PendingIntent;
89import android.content.BroadcastReceiver;
90import android.content.Context;
91import android.content.Intent;
92import android.content.IntentFilter;
lucaslin9b4dfab2019-12-17 23:06:12 +080093import android.content.pm.PackageManager;
94import android.content.res.Configuration;
Niklas Lindgren0c904882018-12-07 11:08:04 +010095import android.content.res.Resources;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +090096import android.net.ConnectivityManager;
Lorenzo Colitti171cfd22019-04-18 13:44:32 +090097import android.net.DnsResolver;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +090098import android.net.INetworkMonitorCallbacks;
99import android.net.LinkProperties;
100import android.net.Network;
101import android.net.NetworkCapabilities;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900102import android.net.ProxyInfo;
103import android.net.TrafficStats;
104import android.net.Uri;
105import android.net.captiveportal.CaptivePortalProbeResult;
106import android.net.captiveportal.CaptivePortalProbeSpec;
107import android.net.metrics.IpConnectivityLog;
108import android.net.metrics.NetworkEvent;
109import android.net.metrics.ValidationProbeEvent;
110import android.net.shared.NetworkMonitorUtils;
111import android.net.shared.PrivateDnsConfig;
Chiachang Wang9a87f802019-04-08 19:06:21 +0800112import android.net.util.NetworkStackUtils;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900113import android.net.util.SharedLog;
114import android.net.util.Stopwatch;
115import android.net.wifi.WifiInfo;
116import android.net.wifi.WifiManager;
Remi NGUYEN VANb4af1302019-06-11 16:17:46 +0900117import android.os.Build;
Remi NGUYEN VAN56fcae32019-02-04 11:32:20 +0900118import android.os.Bundle;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900119import android.os.Message;
Cody Kesting782cbfa2020-01-06 15:51:59 -0800120import android.os.PersistableBundle;
lucaslin9b4dfab2019-12-17 23:06:12 +0800121import android.os.Process;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900122import android.os.RemoteException;
123import android.os.SystemClock;
124import android.os.UserHandle;
125import android.provider.Settings;
Chiachang Wangb04f81c2019-02-14 09:30:58 +0800126import android.telephony.AccessNetworkConstants;
lucaslin9b4dfab2019-12-17 23:06:12 +0800127import android.telephony.CellIdentityNr;
128import android.telephony.CellInfo;
129import android.telephony.CellInfoGsm;
130import android.telephony.CellInfoLte;
131import android.telephony.CellInfoNr;
132import android.telephony.CellInfoTdscdma;
133import android.telephony.CellInfoWcdma;
Chiachang Wang8b5f84a2019-02-22 11:13:07 +0800134import android.telephony.CellSignalStrength;
Jack Yu6bc18652019-03-15 14:49:53 -0700135import android.telephony.NetworkRegistrationInfo;
Chiachang Wangb04f81c2019-02-14 09:30:58 +0800136import android.telephony.ServiceState;
Chiachang Wang8b5f84a2019-02-22 11:13:07 +0800137import android.telephony.SignalStrength;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900138import android.telephony.TelephonyManager;
139import android.text.TextUtils;
140import android.util.Log;
Remi NGUYEN VANdc6e6402019-03-27 15:42:53 +0900141import android.util.Pair;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900142
Niklas Lindgren0c904882018-12-07 11:08:04 +0100143import androidx.annotation.ArrayRes;
lucaslin9b4dfab2019-12-17 23:06:12 +0800144import androidx.annotation.BoolRes;
Remi NGUYEN VAN87790c22019-09-12 14:39:24 +0900145import androidx.annotation.NonNull;
146import androidx.annotation.Nullable;
Niklas Lindgren0c904882018-12-07 11:08:04 +0100147import androidx.annotation.StringRes;
Remi NGUYEN VAN2fa48c22019-12-09 16:40:02 +0900148import androidx.annotation.VisibleForTesting;
Niklas Lindgren0c904882018-12-07 11:08:04 +0100149
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900150import com.android.internal.util.RingBufferIndices;
151import com.android.internal.util.State;
152import com.android.internal.util.StateMachine;
Chalard Jeanac5b8992019-04-09 11:16:56 +0900153import com.android.internal.util.TrafficStatsConstants;
Remi NGUYEN VAN89cd0262019-12-29 22:46:00 +0900154import com.android.networkstack.NetworkStackNotifier;
Remi NGUYEN VANb4f48692019-03-20 14:22:49 +0900155import com.android.networkstack.R;
Remi NGUYEN VAN2fa48c22019-12-09 16:40:02 +0900156import com.android.networkstack.apishim.CaptivePortalDataShim;
157import com.android.networkstack.apishim.CaptivePortalDataShimImpl;
158import com.android.networkstack.apishim.NetworkInformationShimImpl;
Chiachang Wang4336b912019-11-26 15:39:22 +0800159import com.android.networkstack.apishim.ShimUtils;
Remi NGUYEN VAN2fa48c22019-12-09 16:40:02 +0900160import com.android.networkstack.apishim.UnsupportedApiLevelException;
Chiachang Wang80242272019-04-11 21:24:28 +0800161import com.android.networkstack.metrics.DataStallDetectionStats;
162import com.android.networkstack.metrics.DataStallStatsUtils;
Chiachang Wanga5716bf2019-11-20 16:13:07 +0800163import com.android.networkstack.netlink.TcpSocketTracker;
Chiachang Wang1c67f4e2019-05-09 21:28:47 +0800164import com.android.networkstack.util.DnsUtils;
Remi NGUYEN VAN89cd0262019-12-29 22:46:00 +0900165import com.android.server.NetworkStackService.NetworkStackServiceManager;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900166
Remi NGUYEN VAN2fa48c22019-12-09 16:40:02 +0900167import org.json.JSONException;
168import org.json.JSONObject;
169
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900170import java.io.IOException;
Remi NGUYEN VAN2d909a72019-12-24 18:15:52 +0900171import java.io.InputStream;
172import java.io.InputStreamReader;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900173import java.net.HttpURLConnection;
174import java.net.InetAddress;
175import java.net.MalformedURLException;
176import java.net.URL;
177import java.net.UnknownHostException;
Remi NGUYEN VAN2d909a72019-12-24 18:15:52 +0900178import java.nio.charset.Charset;
179import java.nio.charset.StandardCharsets;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900180import java.util.ArrayList;
181import java.util.Arrays;
182import java.util.Collections;
lucaslin9b4dfab2019-12-17 23:06:12 +0800183import java.util.HashMap;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900184import java.util.LinkedHashMap;
185import java.util.List;
lucaslin9b4dfab2019-12-17 23:06:12 +0800186import java.util.Map;
Remi NGUYEN VAN2fa48c22019-12-09 16:40:02 +0900187import java.util.Objects;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900188import java.util.Random;
Chiachang Wange797c9b2019-11-28 14:18:47 +0800189import java.util.StringJoiner;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900190import java.util.UUID;
191import java.util.concurrent.CountDownLatch;
192import java.util.concurrent.TimeUnit;
Niklas Lindgren0c904882018-12-07 11:08:04 +0100193import java.util.function.Function;
Remi NGUYEN VAN2d909a72019-12-24 18:15:52 +0900194import java.util.regex.Matcher;
195import java.util.regex.Pattern;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900196
197/**
198 * {@hide}
199 */
200public class NetworkMonitor extends StateMachine {
201 private static final String TAG = NetworkMonitor.class.getSimpleName();
202 private static final boolean DBG = true;
203 private static final boolean VDBG = false;
204 private static final boolean VDBG_STALL = Log.isLoggable(TAG, Log.DEBUG);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900205 private static final String DEFAULT_USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) "
206 + "AppleWebKit/537.36 (KHTML, like Gecko) "
207 + "Chrome/60.0.3112.32 Safari/537.36";
208
Lorenzo Colitti171cfd22019-04-18 13:44:32 +0900209 @VisibleForTesting
210 static final String CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT =
211 "captive_portal_dns_probe_timeout";
212
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900213 private static final int SOCKET_TIMEOUT_MS = 10000;
214 private static final int PROBE_TIMEOUT_MS = 3000;
Lorenzo Colitti171cfd22019-04-18 13:44:32 +0900215
Remi NGUYEN VAN2fa48c22019-12-09 16:40:02 +0900216 private static final int CAPPORT_API_MAX_JSON_LENGTH = 4096;
217 private static final String ACCEPT_HEADER = "Accept";
218 private static final String CONTENT_TYPE_HEADER = "Content-Type";
219 private static final String CAPPORT_API_CONTENT_TYPE = "application/captive+json";
220
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900221 enum EvaluationResult {
222 VALIDATED(true),
223 CAPTIVE_PORTAL(false);
224 final boolean mIsValidated;
225 EvaluationResult(boolean isValidated) {
226 this.mIsValidated = isValidated;
227 }
228 }
229
230 enum ValidationStage {
231 FIRST_VALIDATION(true),
232 REVALIDATION(false);
233 final boolean mIsFirstValidation;
234 ValidationStage(boolean isFirstValidation) {
235 this.mIsFirstValidation = isFirstValidation;
236 }
237 }
238
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900239 /**
240 * ConnectivityService has sent a notification to indicate that network has connected.
241 * Initiates Network Validation.
242 */
Remi NGUYEN VANa0983f72019-01-20 16:50:42 +0900243 private static final int CMD_NETWORK_CONNECTED = 1;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900244
245 /**
246 * Message to self indicating it's time to evaluate a network's connectivity.
247 * arg1 = Token to ignore old messages.
248 */
Remi NGUYEN VANa0983f72019-01-20 16:50:42 +0900249 private static final int CMD_REEVALUATE = 6;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900250
251 /**
252 * ConnectivityService has sent a notification to indicate that network has disconnected.
253 */
Remi NGUYEN VANa0983f72019-01-20 16:50:42 +0900254 private static final int CMD_NETWORK_DISCONNECTED = 7;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900255
256 /**
257 * Force evaluation even if it has succeeded in the past.
258 * arg1 = UID responsible for requesting this reeval. Will be billed for data.
259 */
Remi NGUYEN VANa0983f72019-01-20 16:50:42 +0900260 private static final int CMD_FORCE_REEVALUATION = 8;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900261
262 /**
263 * Message to self indicating captive portal app finished.
264 * arg1 = one of: APP_RETURN_DISMISSED,
265 * APP_RETURN_UNWANTED,
266 * APP_RETURN_WANTED_AS_IS
267 * obj = mCaptivePortalLoggedInResponseToken as String
268 */
Remi NGUYEN VANa0983f72019-01-20 16:50:42 +0900269 private static final int CMD_CAPTIVE_PORTAL_APP_FINISHED = 9;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900270
271 /**
272 * Message indicating sign-in app should be launched.
273 * Sent by mLaunchCaptivePortalAppBroadcastReceiver when the
274 * user touches the sign in notification, or sent by
275 * ConnectivityService when the user touches the "sign into
276 * network" button in the wifi access point detail page.
277 */
Remi NGUYEN VANa0983f72019-01-20 16:50:42 +0900278 private static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = 11;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900279
280 /**
281 * Retest network to see if captive portal is still in place.
282 * arg1 = UID responsible for requesting this reeval. Will be billed for data.
283 * 0 indicates self-initiated, so nobody to blame.
284 */
Remi NGUYEN VANa0983f72019-01-20 16:50:42 +0900285 private static final int CMD_CAPTIVE_PORTAL_RECHECK = 12;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900286
287 /**
288 * ConnectivityService notifies NetworkMonitor of settings changes to
289 * Private DNS. If a DNS resolution is required, e.g. for DNS-over-TLS in
290 * strict mode, then an event is sent back to ConnectivityService with the
291 * result of the resolution attempt.
292 *
293 * A separate message is used to trigger (re)evaluation of the Private DNS
294 * configuration, so that the message can be handled as needed in different
295 * states, including being ignored until after an ongoing captive portal
296 * validation phase is completed.
297 */
Remi NGUYEN VANa0983f72019-01-20 16:50:42 +0900298 private static final int CMD_PRIVATE_DNS_SETTINGS_CHANGED = 13;
299 private static final int CMD_EVALUATE_PRIVATE_DNS = 15;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900300
301 /**
302 * Message to self indicating captive portal detection is completed.
303 * obj = CaptivePortalProbeResult for detection result;
304 */
Remi NGUYEN VANdc6e6402019-03-27 15:42:53 +0900305 private static final int CMD_PROBE_COMPLETE = 16;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900306
307 /**
308 * ConnectivityService notifies NetworkMonitor of DNS query responses event.
309 * arg1 = returncode in OnDnsEvent which indicates the response code for the DNS query.
310 */
Remi NGUYEN VANdc6e6402019-03-27 15:42:53 +0900311 private static final int EVENT_DNS_NOTIFICATION = 17;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900312
lucaslinb0573962019-03-12 13:08:03 +0800313 /**
314 * ConnectivityService notifies NetworkMonitor that the user accepts partial connectivity and
315 * NetworkMonitor should ignore the https probe.
316 */
Remi NGUYEN VANdc6e6402019-03-27 15:42:53 +0900317 private static final int EVENT_ACCEPT_PARTIAL_CONNECTIVITY = 18;
318
319 /**
320 * ConnectivityService notifies NetworkMonitor of changed LinkProperties.
321 * obj = new LinkProperties.
322 */
323 private static final int EVENT_LINK_PROPERTIES_CHANGED = 19;
324
325 /**
326 * ConnectivityService notifies NetworkMonitor of changed NetworkCapabilities.
327 * obj = new NetworkCapabilities.
328 */
329 private static final int EVENT_NETWORK_CAPABILITIES_CHANGED = 20;
lucaslinb0573962019-03-12 13:08:03 +0800330
Chiachang Wanga5716bf2019-11-20 16:13:07 +0800331 /**
332 * Message to self to poll current tcp status from kernel.
333 */
334 private static final int EVENT_POLL_TCPINFO = 21;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900335 // Start mReevaluateDelayMs at this value and double.
336 private static final int INITIAL_REEVALUATE_DELAY_MS = 1000;
337 private static final int MAX_REEVALUATE_DELAY_MS = 10 * 60 * 1000;
338 // Before network has been evaluated this many times, ignore repeated reevaluate requests.
339 private static final int IGNORE_REEVALUATE_ATTEMPTS = 5;
340 private int mReevaluateToken = 0;
341 private static final int NO_UID = 0;
342 private static final int INVALID_UID = -1;
343 private int mUidResponsibleForReeval = INVALID_UID;
344 // Stop blaming UID that requested re-evaluation after this many attempts.
345 private static final int BLAME_FOR_EVALUATION_ATTEMPTS = 5;
346 // Delay between reevaluations once a captive portal has been found.
347 private static final int CAPTIVE_PORTAL_REEVALUATE_DELAY_MS = 10 * 60 * 1000;
Chiachang Wang813ee472019-05-23 16:29:30 +0800348 private static final int NETWORK_VALIDATION_RESULT_INVALID = 0;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900349 private String mPrivateDnsProviderHostname = "";
350
351 private final Context mContext;
352 private final INetworkMonitorCallbacks mCallback;
Remi NGUYEN VANb4af1302019-06-11 16:17:46 +0900353 private final int mCallbackVersion;
Lorenzo Colitti7f9734f2019-05-09 12:13:54 +0900354 private final Network mCleartextDnsNetwork;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900355 private final Network mNetwork;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900356 private final TelephonyManager mTelephonyManager;
357 private final WifiManager mWifiManager;
358 private final ConnectivityManager mCm;
Remi NGUYEN VAN89cd0262019-12-29 22:46:00 +0900359 @Nullable
360 private final NetworkStackNotifier mNotifier;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900361 private final IpConnectivityLog mMetricsLog;
362 private final Dependencies mDependencies;
Chiachang Wanga5716bf2019-11-20 16:13:07 +0800363 private final TcpSocketTracker mTcpTracker;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900364 // Configuration values for captive portal detection probes.
365 private final String mCaptivePortalUserAgent;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900366 private final URL[] mCaptivePortalFallbackUrls;
Chiachang Wanga64bb702020-03-31 07:50:59 +0000367 @NonNull
368 private final URL[] mCaptivePortalHttpUrls;
369 @NonNull
370 private final URL[] mCaptivePortalHttpsUrls;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900371 @Nullable
372 private final CaptivePortalProbeSpec[] mCaptivePortalFallbackSpecs;
373
374 private NetworkCapabilities mNetworkCapabilities;
375 private LinkProperties mLinkProperties;
376
377 @VisibleForTesting
378 protected boolean mIsCaptivePortalCheckEnabled;
379
380 private boolean mUseHttps;
381 // The total number of captive portal detection attempts for this NetworkMonitor instance.
382 private int mValidations = 0;
383
384 // Set if the user explicitly selected "Do not use this network" in captive portal sign-in app.
385 private boolean mUserDoesNotWant = false;
386 // Avoids surfacing "Sign in to network" notification.
387 private boolean mDontDisplaySigninNotification = false;
388
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900389 private final State mDefaultState = new DefaultState();
390 private final State mValidatedState = new ValidatedState();
391 private final State mMaybeNotifyState = new MaybeNotifyState();
392 private final State mEvaluatingState = new EvaluatingState();
393 private final State mCaptivePortalState = new CaptivePortalState();
394 private final State mEvaluatingPrivateDnsState = new EvaluatingPrivateDnsState();
395 private final State mProbingState = new ProbingState();
396 private final State mWaitingForNextProbeState = new WaitingForNextProbeState();
397
398 private CustomIntentReceiver mLaunchCaptivePortalAppBroadcastReceiver = null;
399
400 private final SharedLog mValidationLogs;
401
402 private final Stopwatch mEvaluationTimer = new Stopwatch();
403
404 // This variable is set before transitioning to the mCaptivePortalState.
405 private CaptivePortalProbeResult mLastPortalProbeResult = CaptivePortalProbeResult.FAILED;
406
407 // Random generator to select fallback URL index
408 private final Random mRandom;
409 private int mNextFallbackUrlIndex = 0;
410
411
412 private int mReevaluateDelayMs = INITIAL_REEVALUATE_DELAY_MS;
413 private int mEvaluateAttempts = 0;
414 private volatile int mProbeToken = 0;
415 private final int mConsecutiveDnsTimeoutThreshold;
416 private final int mDataStallMinEvaluateTime;
417 private final int mDataStallValidDnsTimeThreshold;
418 private final int mDataStallEvaluationType;
Chiachang Wang8e232042020-01-17 18:01:59 +0800419 @Nullable
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900420 private final DnsStallDetector mDnsStallDetector;
421 private long mLastProbeTime;
Chiachang Wang8b5f84a2019-02-22 11:13:07 +0800422 // Set to true if data stall is suspected and reset to false after metrics are sent to statsd.
lucaslind01ea622019-03-20 18:21:59 +0800423 private boolean mCollectDataStallMetrics;
Chiachang Wang813ee472019-05-23 16:29:30 +0800424 private boolean mAcceptPartialConnectivity = false;
425 private final EvaluationState mEvaluationState = new EvaluationState();
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900426
Remi NGUYEN VAN75e9d902020-04-09 06:41:16 +0000427 private final boolean mPrivateIpNotPortalEnabled;
428
Remi NGUYEN VANb4af1302019-06-11 16:17:46 +0900429 private int getCallbackVersion(INetworkMonitorCallbacks cb) {
430 int version;
431 try {
432 version = cb.getInterfaceVersion();
433 } catch (RemoteException e) {
434 version = 0;
435 }
lucaslin2ce7dcc2019-10-22 16:59:39 +0800436 // The AIDL was freezed from Q beta 5 but it's unfreezing from R before releasing. In order
437 // to distinguish the behavior between R and Q beta 5 and before Q beta 5, add SDK and
438 // CODENAME check here. Basically, it's only expected to return 0 for Q beta 4 and below
439 // because the test result has changed.
440 if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q
441 && Build.VERSION.CODENAME.equals("REL")
442 && version == Build.VERSION_CODES.CUR_DEVELOPMENT) version = 0;
Remi NGUYEN VANb4af1302019-06-11 16:17:46 +0900443 return version;
444 }
445
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900446 public NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network,
Remi NGUYEN VAN89cd0262019-12-29 22:46:00 +0900447 SharedLog validationLog, @NonNull NetworkStackServiceManager serviceManager) {
448 this(context, cb, network, new IpConnectivityLog(), validationLog, serviceManager,
Chiachang Wang9b105a92020-03-30 09:12:37 +0000449 Dependencies.DEFAULT, getTcpSocketTrackerOrNull(context, network));
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900450 }
451
452 @VisibleForTesting
Remi NGUYEN VANea9f7e32019-06-20 18:49:48 +0900453 public NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network,
Remi NGUYEN VAN6e9fdbd2019-01-29 15:38:52 +0900454 IpConnectivityLog logger, SharedLog validationLogs,
Remi NGUYEN VAN89cd0262019-12-29 22:46:00 +0900455 @NonNull NetworkStackServiceManager serviceManager, Dependencies deps,
Chiachang Wang9b105a92020-03-30 09:12:37 +0000456 @Nullable TcpSocketTracker tst) {
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900457 // Add suffix indicating which NetworkMonitor we're talking about.
Remi NGUYEN VANa0983f72019-01-20 16:50:42 +0900458 super(TAG + "/" + network.toString());
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900459
460 // Logs with a tag of the form given just above, e.g.
461 // <timestamp> 862 2402 D NetworkMonitor/NetworkAgentInfo [WIFI () - 100]: ...
462 setDbg(VDBG);
463
464 mContext = context;
465 mMetricsLog = logger;
466 mValidationLogs = validationLogs;
467 mCallback = cb;
Remi NGUYEN VANb4af1302019-06-11 16:17:46 +0900468 mCallbackVersion = getCallbackVersion(cb);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900469 mDependencies = deps;
Lorenzo Colitti7f9734f2019-05-09 12:13:54 +0900470 mNetwork = network;
471 mCleartextDnsNetwork = deps.getPrivateDnsBypassNetwork(network);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900472 mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
473 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
474 mCm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
Remi NGUYEN VAN89cd0262019-12-29 22:46:00 +0900475 mNotifier = serviceManager.getNotifier();
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900476
477 // CHECKSTYLE:OFF IndentationCheck
478 addState(mDefaultState);
479 addState(mMaybeNotifyState, mDefaultState);
480 addState(mEvaluatingState, mMaybeNotifyState);
481 addState(mProbingState, mEvaluatingState);
482 addState(mWaitingForNextProbeState, mEvaluatingState);
483 addState(mCaptivePortalState, mMaybeNotifyState);
484 addState(mEvaluatingPrivateDnsState, mDefaultState);
485 addState(mValidatedState, mDefaultState);
486 setInitialState(mDefaultState);
487 // CHECKSTYLE:ON IndentationCheck
488
489 mIsCaptivePortalCheckEnabled = getIsCaptivePortalCheckEnabled();
Remi NGUYEN VAN75e9d902020-04-09 06:41:16 +0000490 mPrivateIpNotPortalEnabled = getIsPrivateIpNotPortalEnabled();
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900491 mUseHttps = getUseHttpsValidation();
492 mCaptivePortalUserAgent = getCaptivePortalUserAgent();
Chiachang Wanga64bb702020-03-31 07:50:59 +0000493 mCaptivePortalHttpsUrls = makeCaptivePortalHttpsUrls();
494 mCaptivePortalHttpUrls = makeCaptivePortalHttpUrls();
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900495 mCaptivePortalFallbackUrls = makeCaptivePortalFallbackUrls();
496 mCaptivePortalFallbackSpecs = makeCaptivePortalFallbackProbeSpecs();
497 mRandom = deps.getRandom();
498 // TODO: Evaluate to move data stall configuration to a specific class.
499 mConsecutiveDnsTimeoutThreshold = getConsecutiveDnsTimeoutThreshold();
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900500 mDataStallMinEvaluateTime = getDataStallMinEvaluateTime();
501 mDataStallValidDnsTimeThreshold = getDataStallValidDnsTimeThreshold();
Chiachang Wang9a87f802019-04-08 19:06:21 +0800502 mDataStallEvaluationType = getDataStallEvaluationType();
Chiachang Wang8e232042020-01-17 18:01:59 +0800503 mDnsStallDetector = initDnsStallDetectorIfRequired(mDataStallEvaluationType,
504 mConsecutiveDnsTimeoutThreshold);
Chiachang Wang4336b912019-11-26 15:39:22 +0800505 mTcpTracker = tst;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900506
Remi NGUYEN VANdc6e6402019-03-27 15:42:53 +0900507 // Provide empty LinkProperties and NetworkCapabilities to make sure they are never null,
508 // even before notifyNetworkConnected.
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900509 mLinkProperties = new LinkProperties();
Remi NGUYEN VAN6e9fdbd2019-01-29 15:38:52 +0900510 mNetworkCapabilities = new NetworkCapabilities(null);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900511 }
512
513 /**
lucaslind01ea622019-03-20 18:21:59 +0800514 * ConnectivityService notifies NetworkMonitor that the user already accepted partial
515 * connectivity previously, so NetworkMonitor can validate the network even if it has partial
516 * connectivity.
lucaslinb0573962019-03-12 13:08:03 +0800517 */
lucaslind01ea622019-03-20 18:21:59 +0800518 public void setAcceptPartialConnectivity() {
lucaslinb0573962019-03-12 13:08:03 +0800519 sendMessage(EVENT_ACCEPT_PARTIAL_CONNECTIVITY);
520 }
521
522 /**
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900523 * Request the NetworkMonitor to reevaluate the network.
524 */
525 public void forceReevaluation(int responsibleUid) {
526 sendMessage(CMD_FORCE_REEVALUATION, responsibleUid, 0);
527 }
528
529 /**
530 * Send a notification to NetworkMonitor indicating that there was a DNS query response event.
531 * @param returnCode the DNS return code of the response.
532 */
533 public void notifyDnsResponse(int returnCode) {
534 sendMessage(EVENT_DNS_NOTIFICATION, returnCode);
535 }
536
537 /**
538 * Send a notification to NetworkMonitor indicating that private DNS settings have changed.
539 * @param newCfg The new private DNS configuration.
540 */
541 public void notifyPrivateDnsSettingsChanged(PrivateDnsConfig newCfg) {
542 // Cancel any outstanding resolutions.
543 removeMessages(CMD_PRIVATE_DNS_SETTINGS_CHANGED);
544 // Send the update to the proper thread.
545 sendMessage(CMD_PRIVATE_DNS_SETTINGS_CHANGED, newCfg);
546 }
547
548 /**
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900549 * Send a notification to NetworkMonitor indicating that the network is now connected.
550 */
Remi NGUYEN VANdc6e6402019-03-27 15:42:53 +0900551 public void notifyNetworkConnected(LinkProperties lp, NetworkCapabilities nc) {
552 sendMessage(CMD_NETWORK_CONNECTED, new Pair<>(
553 new LinkProperties(lp), new NetworkCapabilities(nc)));
554 }
555
556 private void updateConnectedNetworkAttributes(Message connectedMsg) {
557 final Pair<LinkProperties, NetworkCapabilities> attrs =
558 (Pair<LinkProperties, NetworkCapabilities>) connectedMsg.obj;
559 mLinkProperties = attrs.first;
560 mNetworkCapabilities = attrs.second;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900561 }
562
563 /**
564 * Send a notification to NetworkMonitor indicating that the network is now disconnected.
565 */
566 public void notifyNetworkDisconnected() {
567 sendMessage(CMD_NETWORK_DISCONNECTED);
568 }
569
570 /**
571 * Send a notification to NetworkMonitor indicating that link properties have changed.
572 */
Remi NGUYEN VANdc6e6402019-03-27 15:42:53 +0900573 public void notifyLinkPropertiesChanged(final LinkProperties lp) {
574 sendMessage(EVENT_LINK_PROPERTIES_CHANGED, new LinkProperties(lp));
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900575 }
576
577 /**
578 * Send a notification to NetworkMonitor indicating that network capabilities have changed.
579 */
Remi NGUYEN VANdc6e6402019-03-27 15:42:53 +0900580 public void notifyNetworkCapabilitiesChanged(final NetworkCapabilities nc) {
581 sendMessage(EVENT_NETWORK_CAPABILITIES_CHANGED, new NetworkCapabilities(nc));
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900582 }
583
584 /**
585 * Request the captive portal application to be launched.
586 */
587 public void launchCaptivePortalApp() {
588 sendMessage(CMD_LAUNCH_CAPTIVE_PORTAL_APP);
589 }
590
Remi NGUYEN VANad99e542019-02-13 20:58:59 +0900591 /**
592 * Notify that the captive portal app was closed with the provided response code.
593 */
594 public void notifyCaptivePortalAppFinished(int response) {
595 sendMessage(CMD_CAPTIVE_PORTAL_APP_FINISHED, response);
596 }
597
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900598 @Override
599 protected void log(String s) {
Lorenzo Colitti7f9734f2019-05-09 12:13:54 +0900600 if (DBG) Log.d(TAG + "/" + mCleartextDnsNetwork.toString(), s);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900601 }
602
603 private void validationLog(int probeType, Object url, String msg) {
604 String probeName = ValidationProbeEvent.getProbeName(probeType);
605 validationLog(String.format("%s %s %s", probeName, url, msg));
606 }
607
608 private void validationLog(String s) {
609 if (DBG) log(s);
610 mValidationLogs.log(s);
611 }
612
613 private ValidationStage validationStage() {
614 return 0 == mValidations ? ValidationStage.FIRST_VALIDATION : ValidationStage.REVALIDATION;
615 }
616
617 private boolean isValidationRequired() {
Lorenzo Colittid4476892019-01-23 17:54:08 +0900618 return NetworkMonitorUtils.isValidationRequired(mNetworkCapabilities);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900619 }
620
Lorenzo Colitti6d39cb72019-03-22 00:28:28 +0900621 private boolean isPrivateDnsValidationRequired() {
622 return NetworkMonitorUtils.isPrivateDnsValidationRequired(mNetworkCapabilities);
623 }
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900624
Cody Kesting176bce72020-01-20 18:09:59 -0800625 private void notifyNetworkTested(
626 int result, @Nullable String redirectUrl, PersistableBundle extras) {
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900627 try {
Cody Kestingccf13b52020-03-31 23:12:11 -0700628 if (mCallbackVersion <= 5) {
Cody Kesting176bce72020-01-20 18:09:59 -0800629 mCallback.notifyNetworkTested(result, redirectUrl);
630 } else {
631 mCallback.notifyNetworkTestedWithExtras(
632 result, redirectUrl, SystemClock.elapsedRealtime(), extras);
633 }
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900634 } catch (RemoteException e) {
635 Log.e(TAG, "Error sending network test result", e);
636 }
637 }
638
lucaslin2ce7dcc2019-10-22 16:59:39 +0800639 private void notifyProbeStatusChanged(int probesCompleted, int probesSucceeded) {
640 try {
641 mCallback.notifyProbeStatusChanged(probesCompleted, probesSucceeded);
642 } catch (RemoteException e) {
643 Log.e(TAG, "Error sending probe status", e);
644 }
645 }
646
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900647 private void showProvisioningNotification(String action) {
648 try {
Remi NGUYEN VAN1e3eb372019-02-07 21:29:57 +0900649 mCallback.showProvisioningNotification(action, mContext.getPackageName());
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900650 } catch (RemoteException e) {
651 Log.e(TAG, "Error showing provisioning notification", e);
652 }
653 }
654
655 private void hideProvisioningNotification() {
656 try {
657 mCallback.hideProvisioningNotification();
658 } catch (RemoteException e) {
659 Log.e(TAG, "Error hiding provisioning notification", e);
660 }
661 }
662
Cody Kesting782cbfa2020-01-06 15:51:59 -0800663 private void notifyDataStallSuspected(int detectionMethod, PersistableBundle extras) {
664 try {
665 mCallback.notifyDataStallSuspected(
666 SystemClock.elapsedRealtime(), detectionMethod, extras);
667 } catch (RemoteException e) {
668 Log.e(TAG, "Error sending notification for suspected data stall", e);
669 }
670 }
671
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900672 // DefaultState is the parent of all States. It exists only to handle CMD_* messages but
673 // does not entail any real state (hence no enter() or exit() routines).
674 private class DefaultState extends State {
675 @Override
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900676 public boolean processMessage(Message message) {
677 switch (message.what) {
678 case CMD_NETWORK_CONNECTED:
Remi NGUYEN VANdc6e6402019-03-27 15:42:53 +0900679 updateConnectedNetworkAttributes(message);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900680 logNetworkEvent(NetworkEvent.NETWORK_CONNECTED);
681 transitionTo(mEvaluatingState);
682 return HANDLED;
683 case CMD_NETWORK_DISCONNECTED:
684 logNetworkEvent(NetworkEvent.NETWORK_DISCONNECTED);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900685 quit();
686 return HANDLED;
687 case CMD_FORCE_REEVALUATION:
688 case CMD_CAPTIVE_PORTAL_RECHECK:
Chiachang Wang8e232042020-01-17 18:01:59 +0800689 String msg = "Forcing reevaluation for UID " + message.arg1;
690 final DnsStallDetector dsd = getDnsStallDetector();
691 if (dsd != null) {
692 msg += ". Dns signal count: " + dsd.getConsecutiveTimeoutCount();
693 }
694 validationLog(msg);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900695 mUidResponsibleForReeval = message.arg1;
696 transitionTo(mEvaluatingState);
697 return HANDLED;
698 case CMD_CAPTIVE_PORTAL_APP_FINISHED:
699 log("CaptivePortal App responded with " + message.arg1);
700
701 // If the user has seen and acted on a captive portal notification, and the
702 // captive portal app is now closed, disable HTTPS probes. This avoids the
703 // following pathological situation:
704 //
705 // 1. HTTP probe returns a captive portal, HTTPS probe fails or times out.
706 // 2. User opens the app and logs into the captive portal.
707 // 3. HTTP starts working, but HTTPS still doesn't work for some other reason -
708 // perhaps due to the network blocking HTTPS?
709 //
710 // In this case, we'll fail to validate the network even after the app is
711 // dismissed. There is now no way to use this network, because the app is now
712 // gone, so the user cannot select "Use this network as is".
713 mUseHttps = false;
714
715 switch (message.arg1) {
716 case APP_RETURN_DISMISSED:
717 sendMessage(CMD_FORCE_REEVALUATION, NO_UID, 0);
718 break;
719 case APP_RETURN_WANTED_AS_IS:
720 mDontDisplaySigninNotification = true;
721 // TODO: Distinguish this from a network that actually validates.
722 // Displaying the "x" on the system UI icon may still be a good idea.
723 transitionTo(mEvaluatingPrivateDnsState);
724 break;
725 case APP_RETURN_UNWANTED:
726 mDontDisplaySigninNotification = true;
727 mUserDoesNotWant = true;
Chiachang Wang813ee472019-05-23 16:29:30 +0800728 mEvaluationState.reportEvaluationResult(
729 NETWORK_VALIDATION_RESULT_INVALID, null);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900730 // TODO: Should teardown network.
731 mUidResponsibleForReeval = 0;
732 transitionTo(mEvaluatingState);
733 break;
734 }
735 return HANDLED;
736 case CMD_PRIVATE_DNS_SETTINGS_CHANGED: {
737 final PrivateDnsConfig cfg = (PrivateDnsConfig) message.obj;
Lorenzo Colitti6d39cb72019-03-22 00:28:28 +0900738 if (!isPrivateDnsValidationRequired() || cfg == null || !cfg.inStrictMode()) {
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900739 // No DNS resolution required.
740 //
741 // We don't force any validation in opportunistic mode
742 // here. Opportunistic mode nameservers are validated
743 // separately within netd.
744 //
745 // Reset Private DNS settings state.
746 mPrivateDnsProviderHostname = "";
747 break;
748 }
749
750 mPrivateDnsProviderHostname = cfg.hostname;
751
752 // DNS resolutions via Private DNS strict mode block for a
753 // few seconds (~4.2) checking for any IP addresses to
754 // arrive and validate. Initiating a (re)evaluation now
755 // should not significantly alter the validation outcome.
756 //
757 // No matter what: enqueue a validation request; one of
758 // three things can happen with this request:
759 // [1] ignored (EvaluatingState or CaptivePortalState)
760 // [2] transition to EvaluatingPrivateDnsState
761 // (DefaultState and ValidatedState)
762 // [3] handled (EvaluatingPrivateDnsState)
763 //
764 // The Private DNS configuration to be evaluated will:
765 // [1] be skipped (not in strict mode), or
766 // [2] validate (huzzah), or
767 // [3] encounter some problem (invalid hostname,
768 // no resolved IP addresses, IPs unreachable,
769 // port 853 unreachable, port 853 is not running a
770 // DNS-over-TLS server, et cetera).
Chiachang Wanged5f5192019-10-23 21:23:36 +0800771 // Cancel any outstanding CMD_EVALUATE_PRIVATE_DNS.
772 removeMessages(CMD_EVALUATE_PRIVATE_DNS);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900773 sendMessage(CMD_EVALUATE_PRIVATE_DNS);
774 break;
775 }
776 case EVENT_DNS_NOTIFICATION:
Chiachang Wang8e232042020-01-17 18:01:59 +0800777 final DnsStallDetector detector = getDnsStallDetector();
778 if (detector != null) {
779 detector.accumulateConsecutiveDnsTimeoutCount(message.arg1);
780 }
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900781 break;
lucaslind01ea622019-03-20 18:21:59 +0800782 // Set mAcceptPartialConnectivity to true and if network start evaluating or
783 // re-evaluating and get the result of partial connectivity, ProbingState will
784 // disable HTTPS probe and transition to EvaluatingPrivateDnsState.
lucaslinb0573962019-03-12 13:08:03 +0800785 case EVENT_ACCEPT_PARTIAL_CONNECTIVITY:
Chiachang Wang813ee472019-05-23 16:29:30 +0800786 maybeDisableHttpsProbing(true /* acceptPartial */);
lucaslinb0573962019-03-12 13:08:03 +0800787 break;
Remi NGUYEN VANdc6e6402019-03-27 15:42:53 +0900788 case EVENT_LINK_PROPERTIES_CHANGED:
Remi NGUYEN VAN2fa48c22019-12-09 16:40:02 +0900789 final Uri oldCapportUrl = getCaptivePortalApiUrl(mLinkProperties);
Remi NGUYEN VANdc6e6402019-03-27 15:42:53 +0900790 mLinkProperties = (LinkProperties) message.obj;
Remi NGUYEN VAN2fa48c22019-12-09 16:40:02 +0900791 final Uri newCapportUrl = getCaptivePortalApiUrl(mLinkProperties);
792 if (!Objects.equals(oldCapportUrl, newCapportUrl)) {
793 sendMessage(CMD_FORCE_REEVALUATION, NO_UID, 0);
794 }
Remi NGUYEN VANdc6e6402019-03-27 15:42:53 +0900795 break;
796 case EVENT_NETWORK_CAPABILITIES_CHANGED:
797 mNetworkCapabilities = (NetworkCapabilities) message.obj;
798 break;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900799 default:
800 break;
801 }
802 return HANDLED;
803 }
804 }
805
806 // Being in the ValidatedState State indicates a Network is:
807 // - Successfully validated, or
808 // - Wanted "as is" by the user, or
809 // - Does not satisfy the default NetworkRequest and so validation has been skipped.
810 private class ValidatedState extends State {
811 @Override
812 public void enter() {
813 maybeLogEvaluationResult(
814 networkEventType(validationStage(), EvaluationResult.VALIDATED));
Chiachang Wangf1bf7e72019-05-24 11:20:47 +0800815 // If the user has accepted partial connectivity and HTTPS probing is disabled, then
816 // mark the network as validated and partial so that settings can keep informing the
817 // user that the connection is limited.
Chiachang Wang813ee472019-05-23 16:29:30 +0800818 int result = NETWORK_VALIDATION_RESULT_VALID;
819 if (!mUseHttps && mAcceptPartialConnectivity) {
820 result |= NETWORK_VALIDATION_RESULT_PARTIAL;
821 }
822 mEvaluationState.reportEvaluationResult(result, null /* redirectUrl */);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900823 mValidations++;
Chiachang Wangd740dc32020-01-14 20:49:17 +0800824 initSocketTrackingIfRequired();
825 // start periodical polling.
Chiachang Wanga5716bf2019-11-20 16:13:07 +0800826 sendTcpPollingEvent();
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900827 }
828
Chiachang Wangd740dc32020-01-14 20:49:17 +0800829 private void initSocketTrackingIfRequired() {
830 if (!isValidationRequired()) return;
831
832 final TcpSocketTracker tst = getTcpSocketTracker();
833 if (tst != null) {
834 tst.pollSocketsInfo();
835 }
836 }
837
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900838 @Override
839 public boolean processMessage(Message message) {
840 switch (message.what) {
841 case CMD_NETWORK_CONNECTED:
Remi NGUYEN VANdc6e6402019-03-27 15:42:53 +0900842 updateConnectedNetworkAttributes(message);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900843 transitionTo(mValidatedState);
844 break;
845 case CMD_EVALUATE_PRIVATE_DNS:
846 transitionTo(mEvaluatingPrivateDnsState);
847 break;
848 case EVENT_DNS_NOTIFICATION:
Chiachang Wang8e232042020-01-17 18:01:59 +0800849 final DnsStallDetector dsd = getDnsStallDetector();
850 if (dsd == null) break;
851
852 dsd.accumulateConsecutiveDnsTimeoutCount(message.arg1);
Chiachang Wanga5716bf2019-11-20 16:13:07 +0800853 if (evaluateDataStall()) {
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900854 transitionTo(mEvaluatingState);
855 }
856 break;
Chiachang Wanga5716bf2019-11-20 16:13:07 +0800857 case EVENT_POLL_TCPINFO:
Chiachang Wange797c9b2019-11-28 14:18:47 +0800858 final TcpSocketTracker tst = getTcpSocketTracker();
859 if (tst == null) break;
Chiachang Wanga5716bf2019-11-20 16:13:07 +0800860 // Transit if retrieve socket info is succeeded and suspected as a stall.
Chiachang Wange797c9b2019-11-28 14:18:47 +0800861 if (tst.pollSocketsInfo() && evaluateDataStall()) {
Chiachang Wanga5716bf2019-11-20 16:13:07 +0800862 transitionTo(mEvaluatingState);
863 } else {
864 sendTcpPollingEvent();
865 }
866 break;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900867 default:
868 return NOT_HANDLED;
869 }
870 return HANDLED;
871 }
Chiachang Wanga5716bf2019-11-20 16:13:07 +0800872
873 boolean evaluateDataStall() {
874 if (isDataStall()) {
875 // TODO: Add tcp info into metrics.
876 mCollectDataStallMetrics = true;
877 validationLog("Suspecting data stall, reevaluate");
878 return true;
879 }
880 return false;
881 }
882
883 @Override
884 public void exit() {
885 // Not useful for non-ValidatedState.
886 removeMessages(EVENT_POLL_TCPINFO);
887 }
888 }
889
890 @VisibleForTesting
891 void sendTcpPollingEvent() {
892 if (isValidationRequired()) {
893 sendMessageDelayed(EVENT_POLL_TCPINFO, getTcpPollingInterval());
894 }
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900895 }
896
Chiachang Wang8b5f84a2019-02-22 11:13:07 +0800897 private void writeDataStallStats(@NonNull final CaptivePortalProbeResult result) {
898 /*
899 * Collect data stall detection level information for each transport type. Collect type
900 * specific information for cellular and wifi only currently. Generate
901 * DataStallDetectionStats for each transport type. E.g., if a network supports both
902 * TRANSPORT_WIFI and TRANSPORT_VPN, two DataStallDetectionStats will be generated.
903 */
904 final int[] transports = mNetworkCapabilities.getTransportTypes();
905
906 for (int i = 0; i < transports.length; i++) {
Chiachang Wang9b105a92020-03-30 09:12:37 +0000907 final DataStallDetectionStats stats = buildDataStallDetectionStats(transports[i]);
908 mDependencies.writeDataStallDetectionStats(stats, result);
Chiachang Wang8b5f84a2019-02-22 11:13:07 +0800909 }
910 mCollectDataStallMetrics = false;
911 }
912
913 @VisibleForTesting
914 protected DataStallDetectionStats buildDataStallDetectionStats(int transport) {
915 final DataStallDetectionStats.Builder stats = new DataStallDetectionStats.Builder();
916 if (VDBG_STALL) log("collectDataStallMetrics: type=" + transport);
917 stats.setEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS);
918 stats.setNetworkType(transport);
919 switch (transport) {
920 case NetworkCapabilities.TRANSPORT_WIFI:
921 // TODO: Update it if status query in dual wifi is supported.
922 final WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
923 stats.setWiFiData(wifiInfo);
924 break;
925 case NetworkCapabilities.TRANSPORT_CELLULAR:
926 final boolean isRoaming = !mNetworkCapabilities.hasCapability(
927 NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
928 final SignalStrength ss = mTelephonyManager.getSignalStrength();
929 // TODO(b/120452078): Support multi-sim.
930 stats.setCellData(
931 mTelephonyManager.getDataNetworkType(),
932 isRoaming,
933 mTelephonyManager.getNetworkOperator(),
934 mTelephonyManager.getSimOperator(),
935 (ss != null)
936 ? ss.getLevel() : CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
937 break;
938 default:
939 // No transport type specific information for the other types.
940 break;
941 }
942 addDnsEvents(stats);
943
944 return stats.build();
945 }
946
Chiachang Wangcaa35202019-02-26 11:32:18 +0800947 @VisibleForTesting
948 protected void addDnsEvents(@NonNull final DataStallDetectionStats.Builder stats) {
Chiachang Wang8e232042020-01-17 18:01:59 +0800949 final DnsStallDetector dsd = getDnsStallDetector();
950 if (dsd == null) return;
951
952 final int size = dsd.mResultIndices.size();
Chiachang Wang8b5f84a2019-02-22 11:13:07 +0800953 for (int i = 1; i <= DEFAULT_DNS_LOG_SIZE && i <= size; i++) {
Chiachang Wang8e232042020-01-17 18:01:59 +0800954 final int index = dsd.mResultIndices.indexOf(size - i);
955 stats.addDnsEvent(dsd.mDnsEvents[index].mReturnCode, dsd.mDnsEvents[index].mTimeStamp);
Chiachang Wang8b5f84a2019-02-22 11:13:07 +0800956 }
957 }
958
959
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900960 // Being in the MaybeNotifyState State indicates the user may have been notified that sign-in
961 // is required. This State takes care to clear the notification upon exit from the State.
962 private class MaybeNotifyState extends State {
963 @Override
964 public boolean processMessage(Message message) {
965 switch (message.what) {
966 case CMD_LAUNCH_CAPTIVE_PORTAL_APP:
Remi NGUYEN VAN56fcae32019-02-04 11:32:20 +0900967 final Bundle appExtras = new Bundle();
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900968 // OneAddressPerFamilyNetwork is not parcelable across processes.
Lorenzo Colitti7f9734f2019-05-09 12:13:54 +0900969 final Network network = new Network(mCleartextDnsNetwork);
Remi NGUYEN VANad99e542019-02-13 20:58:59 +0900970 appExtras.putParcelable(ConnectivityManager.EXTRA_NETWORK, network);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900971 final CaptivePortalProbeResult probeRes = mLastPortalProbeResult;
Automerger Merge Workerfaac06e2020-03-09 09:23:38 +0000972 // Use redirect URL from AP if exists.
973 final String portalUrl =
974 (useRedirectUrlForPortal() && probeRes.redirectUrl != null)
975 ? probeRes.redirectUrl : probeRes.detectUrl;
976 appExtras.putString(EXTRA_CAPTIVE_PORTAL_URL, portalUrl);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900977 if (probeRes.probeSpec != null) {
978 final String encodedSpec = probeRes.probeSpec.getEncodedSpec();
Remi NGUYEN VAN56fcae32019-02-04 11:32:20 +0900979 appExtras.putString(EXTRA_CAPTIVE_PORTAL_PROBE_SPEC, encodedSpec);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900980 }
Remi NGUYEN VAN56fcae32019-02-04 11:32:20 +0900981 appExtras.putString(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_USER_AGENT,
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900982 mCaptivePortalUserAgent);
Remi NGUYEN VAN89cd0262019-12-29 22:46:00 +0900983 if (mNotifier != null) {
984 mNotifier.notifyCaptivePortalValidationPending(network);
985 }
Remi NGUYEN VANad99e542019-02-13 20:58:59 +0900986 mCm.startCaptivePortalApp(network, appExtras);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +0900987 return HANDLED;
988 default:
989 return NOT_HANDLED;
990 }
991 }
992
Automerger Merge Workerfaac06e2020-03-09 09:23:38 +0000993 private boolean useRedirectUrlForPortal() {
994 // It must match the conditions in CaptivePortalLogin in which the redirect URL is not
995 // used to validate that the portal is gone.
996 final boolean aboveQ =
997 ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q);
998 return aboveQ && mDependencies.isFeatureEnabled(mContext, NAMESPACE_CONNECTIVITY,
999 DISMISS_PORTAL_IN_VALIDATED_NETWORK, aboveQ /* defaultEnabled */);
1000 }
1001
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001002 @Override
1003 public void exit() {
Lorenzo Colitti687423b2019-04-08 17:14:35 +09001004 if (mLaunchCaptivePortalAppBroadcastReceiver != null) {
1005 mContext.unregisterReceiver(mLaunchCaptivePortalAppBroadcastReceiver);
1006 mLaunchCaptivePortalAppBroadcastReceiver = null;
1007 }
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001008 hideProvisioningNotification();
1009 }
1010 }
1011
1012 // Being in the EvaluatingState State indicates the Network is being evaluated for internet
1013 // connectivity, or that the user has indicated that this network is unwanted.
1014 private class EvaluatingState extends State {
Remi NGUYEN VAN2fa48c22019-12-09 16:40:02 +09001015 private Uri mEvaluatingCapportUrl;
1016
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001017 @Override
1018 public void enter() {
1019 // If we have already started to track time spent in EvaluatingState
1020 // don't reset the timer due simply to, say, commands or events that
1021 // cause us to exit and re-enter EvaluatingState.
1022 if (!mEvaluationTimer.isStarted()) {
1023 mEvaluationTimer.start();
1024 }
1025 sendMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
1026 if (mUidResponsibleForReeval != INVALID_UID) {
1027 TrafficStats.setThreadStatsUid(mUidResponsibleForReeval);
1028 mUidResponsibleForReeval = INVALID_UID;
1029 }
1030 mReevaluateDelayMs = INITIAL_REEVALUATE_DELAY_MS;
1031 mEvaluateAttempts = 0;
Remi NGUYEN VAN2fa48c22019-12-09 16:40:02 +09001032 mEvaluatingCapportUrl = getCaptivePortalApiUrl(mLinkProperties);
Chiachang Wang813ee472019-05-23 16:29:30 +08001033 // Reset all current probe results to zero, but retain current validation state until
1034 // validation succeeds or fails.
1035 mEvaluationState.clearProbeResults();
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001036 }
1037
1038 @Override
1039 public boolean processMessage(Message message) {
1040 switch (message.what) {
1041 case CMD_REEVALUATE:
1042 if (message.arg1 != mReevaluateToken || mUserDoesNotWant) {
1043 return HANDLED;
1044 }
1045 // Don't bother validating networks that don't satisfy the default request.
1046 // This includes:
1047 // - VPNs which can be considered explicitly desired by the user and the
1048 // user's desire trumps whether the network validates.
1049 // - Networks that don't provide Internet access. It's unclear how to
1050 // validate such networks.
1051 // - Untrusted networks. It's unsafe to prompt the user to sign-in to
1052 // such networks and the user didn't express interest in connecting to
1053 // such networks (an app did) so the user may be unhappily surprised when
1054 // asked to sign-in to a network they didn't want to connect to in the
1055 // first place. Validation could be done to adjust the network scores
1056 // however these networks are app-requested and may not be intended for
1057 // general usage, in which case general validation may not be an accurate
1058 // measure of the network's quality. Only the app knows how to evaluate
1059 // the network so don't bother validating here. Furthermore sending HTTP
1060 // packets over the network may be undesirable, for example an extremely
1061 // expensive metered network, or unwanted leaking of the User Agent string.
Lorenzo Colitti6d39cb72019-03-22 00:28:28 +09001062 //
1063 // On networks that need to support private DNS in strict mode (e.g., VPNs, but
1064 // not networks that don't provide Internet access), we still need to perform
1065 // private DNS server resolution.
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001066 if (!isValidationRequired()) {
Lorenzo Colitti6d39cb72019-03-22 00:28:28 +09001067 if (isPrivateDnsValidationRequired()) {
1068 validationLog("Network would not satisfy default request, "
1069 + "resolving private DNS");
1070 transitionTo(mEvaluatingPrivateDnsState);
1071 } else {
1072 validationLog("Network would not satisfy default request, "
1073 + "not validating");
1074 transitionTo(mValidatedState);
1075 }
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001076 return HANDLED;
1077 }
1078 mEvaluateAttempts++;
1079
1080 transitionTo(mProbingState);
1081 return HANDLED;
1082 case CMD_FORCE_REEVALUATION:
Remi NGUYEN VAN2fa48c22019-12-09 16:40:02 +09001083 // The evaluation process restarts via EvaluatingState#enter.
1084 return shouldAcceptForceRevalidation() ? NOT_HANDLED : HANDLED;
lucaslind01ea622019-03-20 18:21:59 +08001085 // Disable HTTPS probe and transition to EvaluatingPrivateDnsState because:
1086 // 1. Network is connected and finish the network validation.
1087 // 2. NetworkMonitor detects network is partial connectivity and user accepts it.
1088 case EVENT_ACCEPT_PARTIAL_CONNECTIVITY:
Chiachang Wang813ee472019-05-23 16:29:30 +08001089 maybeDisableHttpsProbing(true /* acceptPartial */);
lucaslind01ea622019-03-20 18:21:59 +08001090 transitionTo(mEvaluatingPrivateDnsState);
1091 return HANDLED;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001092 default:
1093 return NOT_HANDLED;
1094 }
1095 }
1096
Remi NGUYEN VAN2fa48c22019-12-09 16:40:02 +09001097 private boolean shouldAcceptForceRevalidation() {
1098 // If the captive portal URL has changed since the last evaluation attempt, always
1099 // revalidate. Otherwise, ignore any re-evaluation requests before
1100 // IGNORE_REEVALUATE_ATTEMPTS are made.
1101 return mEvaluateAttempts >= IGNORE_REEVALUATE_ATTEMPTS
1102 || !Objects.equals(
1103 mEvaluatingCapportUrl, getCaptivePortalApiUrl(mLinkProperties));
1104 }
1105
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001106 @Override
1107 public void exit() {
1108 TrafficStats.clearThreadStatsUid();
1109 }
1110 }
1111
1112 // BroadcastReceiver that waits for a particular Intent and then posts a message.
1113 private class CustomIntentReceiver extends BroadcastReceiver {
1114 private final int mToken;
1115 private final int mWhat;
1116 private final String mAction;
1117 CustomIntentReceiver(String action, int token, int what) {
1118 mToken = token;
1119 mWhat = what;
Lorenzo Colitti7f9734f2019-05-09 12:13:54 +09001120 mAction = action + "_" + mCleartextDnsNetwork.getNetworkHandle() + "_" + token;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001121 mContext.registerReceiver(this, new IntentFilter(mAction));
1122 }
1123 public PendingIntent getPendingIntent() {
1124 final Intent intent = new Intent(mAction);
1125 intent.setPackage(mContext.getPackageName());
1126 return PendingIntent.getBroadcast(mContext, 0, intent, 0);
1127 }
1128 @Override
1129 public void onReceive(Context context, Intent intent) {
1130 if (intent.getAction().equals(mAction)) sendMessage(obtainMessage(mWhat, mToken));
1131 }
1132 }
1133
1134 // Being in the CaptivePortalState State indicates a captive portal was detected and the user
1135 // has been shown a notification to sign-in.
1136 private class CaptivePortalState extends State {
1137 private static final String ACTION_LAUNCH_CAPTIVE_PORTAL_APP =
1138 "android.net.netmon.launchCaptivePortalApp";
1139
1140 @Override
1141 public void enter() {
1142 maybeLogEvaluationResult(
1143 networkEventType(validationStage(), EvaluationResult.CAPTIVE_PORTAL));
1144 // Don't annoy user with sign-in notifications.
1145 if (mDontDisplaySigninNotification) return;
1146 // Create a CustomIntentReceiver that sends us a
1147 // CMD_LAUNCH_CAPTIVE_PORTAL_APP message when the user
1148 // touches the notification.
1149 if (mLaunchCaptivePortalAppBroadcastReceiver == null) {
1150 // Wait for result.
1151 mLaunchCaptivePortalAppBroadcastReceiver = new CustomIntentReceiver(
1152 ACTION_LAUNCH_CAPTIVE_PORTAL_APP, new Random().nextInt(),
1153 CMD_LAUNCH_CAPTIVE_PORTAL_APP);
lucaslin12b92a42019-04-08 10:52:46 +08001154 // Display the sign in notification.
1155 // Only do this once for every time we enter MaybeNotifyState. b/122164725
1156 showProvisioningNotification(mLaunchCaptivePortalAppBroadcastReceiver.mAction);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001157 }
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001158 // Retest for captive portal occasionally.
1159 sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */,
1160 CAPTIVE_PORTAL_REEVALUATE_DELAY_MS);
1161 mValidations++;
1162 }
1163
1164 @Override
1165 public void exit() {
1166 removeMessages(CMD_CAPTIVE_PORTAL_RECHECK);
1167 }
1168 }
1169
1170 private class EvaluatingPrivateDnsState extends State {
1171 private int mPrivateDnsReevalDelayMs;
1172 private PrivateDnsConfig mPrivateDnsConfig;
1173
1174 @Override
1175 public void enter() {
1176 mPrivateDnsReevalDelayMs = INITIAL_REEVALUATE_DELAY_MS;
1177 mPrivateDnsConfig = null;
1178 sendMessage(CMD_EVALUATE_PRIVATE_DNS);
1179 }
1180
1181 @Override
1182 public boolean processMessage(Message msg) {
1183 switch (msg.what) {
1184 case CMD_EVALUATE_PRIVATE_DNS:
1185 if (inStrictMode()) {
1186 if (!isStrictModeHostnameResolved()) {
1187 resolveStrictModeHostname();
1188
1189 if (isStrictModeHostnameResolved()) {
1190 notifyPrivateDnsConfigResolved();
1191 } else {
1192 handlePrivateDnsEvaluationFailure();
1193 break;
1194 }
1195 }
1196
1197 // Look up a one-time hostname, to bypass caching.
1198 //
1199 // Note that this will race with ConnectivityService
1200 // code programming the DNS-over-TLS server IP addresses
1201 // into netd (if invoked, above). If netd doesn't know
1202 // the IP addresses yet, or if the connections to the IP
1203 // addresses haven't yet been validated, netd will block
1204 // for up to a few seconds before failing the lookup.
1205 if (!sendPrivateDnsProbe()) {
1206 handlePrivateDnsEvaluationFailure();
1207 break;
1208 }
lucaslin2ce7dcc2019-10-22 16:59:39 +08001209 handlePrivateDnsEvaluationSuccess();
1210 } else {
1211 mEvaluationState.removeProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001212 }
1213
1214 // All good!
1215 transitionTo(mValidatedState);
1216 break;
Chiachang Wanged5f5192019-10-23 21:23:36 +08001217 case CMD_PRIVATE_DNS_SETTINGS_CHANGED:
1218 // When settings change the reevaluation timer must be reset.
1219 mPrivateDnsReevalDelayMs = INITIAL_REEVALUATE_DELAY_MS;
1220 // Let the message bubble up and be handled by parent states as usual.
1221 return NOT_HANDLED;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001222 default:
1223 return NOT_HANDLED;
1224 }
1225 return HANDLED;
1226 }
1227
1228 private boolean inStrictMode() {
1229 return !TextUtils.isEmpty(mPrivateDnsProviderHostname);
1230 }
1231
1232 private boolean isStrictModeHostnameResolved() {
1233 return (mPrivateDnsConfig != null)
1234 && mPrivateDnsConfig.hostname.equals(mPrivateDnsProviderHostname)
1235 && (mPrivateDnsConfig.ips.length > 0);
1236 }
1237
1238 private void resolveStrictModeHostname() {
1239 try {
1240 // Do a blocking DNS resolution using the network-assigned nameservers.
Chiachang Wang1c67f4e2019-05-09 21:28:47 +08001241 final InetAddress[] ips = DnsUtils.getAllByName(mDependencies.getDnsResolver(),
Chiachang Wangddb7da62019-06-03 15:50:53 +08001242 mCleartextDnsNetwork, mPrivateDnsProviderHostname, getDnsProbeTimeout(),
1243 str -> validationLog("Strict mode hostname resolution " + str));
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001244 mPrivateDnsConfig = new PrivateDnsConfig(mPrivateDnsProviderHostname, ips);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001245 } catch (UnknownHostException uhe) {
1246 mPrivateDnsConfig = null;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001247 }
1248 }
1249
1250 private void notifyPrivateDnsConfigResolved() {
1251 try {
1252 mCallback.notifyPrivateDnsConfigResolved(mPrivateDnsConfig.toParcel());
1253 } catch (RemoteException e) {
1254 Log.e(TAG, "Error sending private DNS config resolved notification", e);
1255 }
1256 }
1257
lucaslin2ce7dcc2019-10-22 16:59:39 +08001258 private void handlePrivateDnsEvaluationSuccess() {
1259 mEvaluationState.noteProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS,
1260 true /* succeeded */);
1261 }
1262
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001263 private void handlePrivateDnsEvaluationFailure() {
lucaslin2ce7dcc2019-10-22 16:59:39 +08001264 mEvaluationState.noteProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS,
1265 false /* succeeded */);
Chiachang Wang813ee472019-05-23 16:29:30 +08001266 mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID,
1267 null /* redirectUrl */);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001268 // Queue up a re-evaluation with backoff.
1269 //
1270 // TODO: Consider abandoning this state after a few attempts and
1271 // transitioning back to EvaluatingState, to perhaps give ourselves
1272 // the opportunity to (re)detect a captive portal or something.
Chiachang Wangf1bf7e72019-05-24 11:20:47 +08001273 //
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001274 sendMessageDelayed(CMD_EVALUATE_PRIVATE_DNS, mPrivateDnsReevalDelayMs);
1275 mPrivateDnsReevalDelayMs *= 2;
1276 if (mPrivateDnsReevalDelayMs > MAX_REEVALUATE_DELAY_MS) {
1277 mPrivateDnsReevalDelayMs = MAX_REEVALUATE_DELAY_MS;
1278 }
1279 }
1280
1281 private boolean sendPrivateDnsProbe() {
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001282 final String host = UUID.randomUUID().toString().substring(0, 8)
Chiachang Wangeb619222019-07-03 20:52:08 +08001283 + PRIVATE_DNS_PROBE_HOST_SUFFIX;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001284 final Stopwatch watch = new Stopwatch().start();
Chiachang Wang813ee472019-05-23 16:29:30 +08001285 boolean success = false;
1286 long time;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001287 try {
Lorenzo Colitti7f9734f2019-05-09 12:13:54 +09001288 final InetAddress[] ips = mNetwork.getAllByName(host);
Chiachang Wang813ee472019-05-23 16:29:30 +08001289 time = watch.stop();
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001290 final String strIps = Arrays.toString(ips);
Chiachang Wang813ee472019-05-23 16:29:30 +08001291 success = (ips != null && ips.length > 0);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001292 validationLog(PROBE_PRIVDNS, host, String.format("%dms: %s", time, strIps));
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001293 } catch (UnknownHostException uhe) {
Chiachang Wang813ee472019-05-23 16:29:30 +08001294 time = watch.stop();
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001295 validationLog(PROBE_PRIVDNS, host,
1296 String.format("%dms - Error: %s", time, uhe.getMessage()));
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001297 }
Chiachang Wang813ee472019-05-23 16:29:30 +08001298 logValidationProbe(time, PROBE_PRIVDNS, success ? DNS_SUCCESS : DNS_FAILURE);
Chiachang Wang813ee472019-05-23 16:29:30 +08001299 return success;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001300 }
1301 }
1302
1303 private class ProbingState extends State {
1304 private Thread mThread;
1305
1306 @Override
1307 public void enter() {
1308 if (mEvaluateAttempts >= BLAME_FOR_EVALUATION_ATTEMPTS) {
1309 //Don't continue to blame UID forever.
1310 TrafficStats.clearThreadStatsUid();
1311 }
1312
1313 final int token = ++mProbeToken;
1314 mThread = new Thread(() -> sendMessage(obtainMessage(CMD_PROBE_COMPLETE, token, 0,
1315 isCaptivePortal())));
1316 mThread.start();
1317 }
1318
1319 @Override
1320 public boolean processMessage(Message message) {
1321 switch (message.what) {
1322 case CMD_PROBE_COMPLETE:
1323 // Ensure that CMD_PROBE_COMPLETE from stale threads are ignored.
1324 if (message.arg1 != mProbeToken) {
1325 return HANDLED;
1326 }
1327
1328 final CaptivePortalProbeResult probeResult =
1329 (CaptivePortalProbeResult) message.obj;
1330 mLastProbeTime = SystemClock.elapsedRealtime();
Chiachang Wang8b5f84a2019-02-22 11:13:07 +08001331
1332 if (mCollectDataStallMetrics) {
1333 writeDataStallStats(probeResult);
1334 }
1335
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001336 if (probeResult.isSuccessful()) {
1337 // Transit EvaluatingPrivateDnsState to get to Validated
1338 // state (even if no Private DNS validation required).
1339 transitionTo(mEvaluatingPrivateDnsState);
1340 } else if (probeResult.isPortal()) {
Chiachang Wang813ee472019-05-23 16:29:30 +08001341 mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID,
1342 probeResult.redirectUrl);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001343 mLastPortalProbeResult = probeResult;
1344 transitionTo(mCaptivePortalState);
lucaslinb0573962019-03-12 13:08:03 +08001345 } else if (probeResult.isPartialConnectivity()) {
Chiachang Wang813ee472019-05-23 16:29:30 +08001346 mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_PARTIAL,
1347 null /* redirectUrl */);
Chiachang Wang813ee472019-05-23 16:29:30 +08001348 maybeDisableHttpsProbing(mAcceptPartialConnectivity);
lucaslind01ea622019-03-20 18:21:59 +08001349 if (mAcceptPartialConnectivity) {
lucaslind01ea622019-03-20 18:21:59 +08001350 transitionTo(mEvaluatingPrivateDnsState);
1351 } else {
1352 transitionTo(mWaitingForNextProbeState);
1353 }
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001354 } else {
1355 logNetworkEvent(NetworkEvent.NETWORK_VALIDATION_FAILED);
Chiachang Wang813ee472019-05-23 16:29:30 +08001356 mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID,
1357 null /* redirectUrl */);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001358 transitionTo(mWaitingForNextProbeState);
1359 }
1360 return HANDLED;
1361 case EVENT_DNS_NOTIFICATION:
lucaslinb0573962019-03-12 13:08:03 +08001362 case EVENT_ACCEPT_PARTIAL_CONNECTIVITY:
1363 // Leave the event to DefaultState.
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001364 return NOT_HANDLED;
1365 default:
1366 // Wait for probe result and defer events to next state by default.
1367 deferMessage(message);
1368 return HANDLED;
1369 }
1370 }
1371
1372 @Override
1373 public void exit() {
1374 if (mThread.isAlive()) {
1375 mThread.interrupt();
1376 }
1377 mThread = null;
1378 }
1379 }
1380
1381 // Being in the WaitingForNextProbeState indicates that evaluating probes failed and state is
1382 // transited from ProbingState. This ensures that the state machine is only in ProbingState
1383 // while a probe is in progress, not while waiting to perform the next probe. That allows
1384 // ProbingState to defer most messages until the probe is complete, which keeps the code simple
1385 // and matches the pre-Q behaviour where probes were a blocking operation performed on the state
1386 // machine thread.
1387 private class WaitingForNextProbeState extends State {
1388 @Override
1389 public void enter() {
1390 scheduleNextProbe();
1391 }
1392
1393 private void scheduleNextProbe() {
1394 final Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
1395 sendMessageDelayed(msg, mReevaluateDelayMs);
1396 mReevaluateDelayMs *= 2;
1397 if (mReevaluateDelayMs > MAX_REEVALUATE_DELAY_MS) {
1398 mReevaluateDelayMs = MAX_REEVALUATE_DELAY_MS;
1399 }
1400 }
1401
1402 @Override
1403 public boolean processMessage(Message message) {
1404 return NOT_HANDLED;
1405 }
1406 }
1407
1408 // Limits the list of IP addresses returned by getAllByName or tried by openConnection to at
1409 // most one per address family. This ensures we only wait up to 20 seconds for TCP connections
1410 // to complete, regardless of how many IP addresses a host has.
1411 private static class OneAddressPerFamilyNetwork extends Network {
1412 OneAddressPerFamilyNetwork(Network network) {
1413 // Always bypass Private DNS.
1414 super(network.getPrivateDnsBypassingCopy());
1415 }
1416
1417 @Override
1418 public InetAddress[] getAllByName(String host) throws UnknownHostException {
1419 final List<InetAddress> addrs = Arrays.asList(super.getAllByName(host));
1420
1421 // Ensure the address family of the first address is tried first.
1422 LinkedHashMap<Class, InetAddress> addressByFamily = new LinkedHashMap<>();
1423 addressByFamily.put(addrs.get(0).getClass(), addrs.get(0));
1424 Collections.shuffle(addrs);
1425
1426 for (InetAddress addr : addrs) {
1427 addressByFamily.put(addr.getClass(), addr);
1428 }
1429
1430 return addressByFamily.values().toArray(new InetAddress[addressByFamily.size()]);
1431 }
1432 }
1433
1434 private boolean getIsCaptivePortalCheckEnabled() {
Chiachang Wang969eb752019-04-25 09:47:27 +08001435 String symbol = CAPTIVE_PORTAL_MODE;
1436 int defaultValue = CAPTIVE_PORTAL_MODE_PROMPT;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001437 int mode = mDependencies.getSetting(mContext, symbol, defaultValue);
Chiachang Wang969eb752019-04-25 09:47:27 +08001438 return mode != CAPTIVE_PORTAL_MODE_IGNORE;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001439 }
1440
Remi NGUYEN VAN75e9d902020-04-09 06:41:16 +00001441 private boolean getIsPrivateIpNotPortalEnabled() {
1442 return mDependencies.isFeatureEnabled(mContext, DNS_PROBE_PRIVATE_IP_NO_INTERNET_VERSION)
1443 || mContext.getResources().getBoolean(
1444 R.bool.config_force_dns_probe_private_ip_no_internet);
1445 }
1446
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001447 private boolean getUseHttpsValidation() {
Chiachang Wang79a6da32019-04-17 17:00:54 +08001448 return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY,
1449 CAPTIVE_PORTAL_USE_HTTPS, 1) == 1;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001450 }
1451
lucaslin9b4dfab2019-12-17 23:06:12 +08001452 @Nullable
1453 private String getMccFromCellInfo(final CellInfo cell) {
1454 if (cell instanceof CellInfoGsm) {
1455 return ((CellInfoGsm) cell).getCellIdentity().getMccString();
1456 } else if (cell instanceof CellInfoLte) {
1457 return ((CellInfoLte) cell).getCellIdentity().getMccString();
1458 } else if (cell instanceof CellInfoWcdma) {
1459 return ((CellInfoWcdma) cell).getCellIdentity().getMccString();
1460 } else if (cell instanceof CellInfoTdscdma) {
1461 return ((CellInfoTdscdma) cell).getCellIdentity().getMccString();
1462 } else if (cell instanceof CellInfoNr) {
1463 return ((CellIdentityNr) ((CellInfoNr) cell).getCellIdentity()).getMccString();
1464 } else {
1465 return null;
1466 }
1467 }
1468
1469 /**
1470 * Return location mcc.
1471 */
1472 @VisibleForTesting
1473 @Nullable
1474 protected String getLocationMcc() {
1475 // Adding this check is because the new permission won't be granted by mainline update,
1476 // the new permission only be granted by OTA for current design. Tracking: b/145774617.
1477 if (mContext.checkPermission(android.Manifest.permission.ACCESS_FINE_LOCATION,
1478 Process.myPid(), Process.myUid())
1479 == PackageManager.PERMISSION_DENIED) {
1480 log("getLocationMcc : NetworkStack does not hold ACCESS_FINE_LOCATION");
1481 return null;
1482 }
1483 try {
1484 final List<CellInfo> cells = mTelephonyManager.getAllCellInfo();
1485 final Map<String, Integer> countryCodeMap = new HashMap<>();
1486 int maxCount = 0;
1487 for (final CellInfo cell : cells) {
1488 final String mcc = getMccFromCellInfo(cell);
1489 if (mcc != null) {
1490 final int count = countryCodeMap.getOrDefault(mcc, 0) + 1;
1491 countryCodeMap.put(mcc, count);
1492 }
1493 }
1494 // Return the MCC which occurs most.
1495 if (countryCodeMap.size() <= 0) return null;
1496 return Collections.max(countryCodeMap.entrySet(),
1497 (e1, e2) -> e1.getValue().compareTo(e2.getValue())).getKey();
1498 } catch (SecurityException e) {
1499 log("Permission is not granted:" + e);
1500 return null;
1501 }
1502 }
1503
1504 @VisibleForTesting
1505 protected Context getContextByMccIfNoSimCardOrDefault() {
1506 final boolean useNeighborResource =
1507 getResBooleanConfig(mContext, R.bool.config_no_sim_card_uses_neighbor_mcc);
1508 if (!useNeighborResource
1509 || TelephonyManager.SIM_STATE_READY == mTelephonyManager.getSimState()) {
1510 return mContext;
1511 }
1512 final String mcc = getLocationMcc();
1513 if (TextUtils.isEmpty(mcc)) {
1514 return mContext;
1515 }
1516 final Configuration config = mContext.getResources().getConfiguration();
1517 config.mcc = Integer.parseInt(mcc);
1518 return mContext.createConfigurationContext(config);
1519 }
1520
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001521 private String getCaptivePortalServerHttpsUrl() {
lucaslin9b4dfab2019-12-17 23:06:12 +08001522 final Context targetContext = getContextByMccIfNoSimCardOrDefault();
1523 return getSettingFromResource(targetContext, R.string.config_captive_portal_https_url,
Chiachang Wang969eb752019-04-25 09:47:27 +08001524 R.string.default_captive_portal_https_url, CAPTIVE_PORTAL_HTTPS_URL);
Niklas Lindgren0c904882018-12-07 11:08:04 +01001525 }
1526
Lorenzo Colitti171cfd22019-04-18 13:44:32 +09001527 private int getDnsProbeTimeout() {
1528 return getIntSetting(mContext, R.integer.config_captive_portal_dns_probe_timeout,
1529 CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT,
1530 R.integer.default_captive_portal_dns_probe_timeout);
1531 }
1532
1533 /**
1534 * Gets an integer setting from resources or device config
1535 *
1536 * configResource is used if set, followed by device config if set, followed by defaultResource.
1537 * If none of these are set then an exception is thrown.
1538 *
1539 * TODO: move to a common location such as a ConfigUtils class.
1540 * TODO(b/130324939): test that the resources can be overlayed by an RRO package.
1541 */
1542 @VisibleForTesting
1543 int getIntSetting(@NonNull final Context context, @StringRes int configResource,
1544 @NonNull String symbol, @StringRes int defaultResource) {
1545 final Resources res = context.getResources();
1546 try {
1547 return res.getInteger(configResource);
1548 } catch (Resources.NotFoundException e) {
1549 return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY,
1550 symbol, res.getInteger(defaultResource));
1551 }
1552 }
1553
lucaslin9b4dfab2019-12-17 23:06:12 +08001554 @VisibleForTesting
1555 protected boolean getResBooleanConfig(@NonNull final Context context,
1556 @BoolRes int configResource) {
1557 final Resources res = context.getResources();
1558 try {
1559 return res.getBoolean(configResource);
1560 } catch (Resources.NotFoundException e) {
1561 return false;
1562 }
1563 }
1564
Niklas Lindgren0c904882018-12-07 11:08:04 +01001565 /**
1566 * Get the captive portal server HTTP URL that is configured on the device.
1567 *
1568 * NetworkMonitor does not use {@link ConnectivityManager#getCaptivePortalServerUrl()} as
1569 * it has its own updatable strategies to detect captive portals. The framework only advises
1570 * on one URL that can be used, while NetworkMonitor may implement more complex logic.
1571 */
1572 public String getCaptivePortalServerHttpUrl() {
lucaslin9b4dfab2019-12-17 23:06:12 +08001573 final Context targetContext = getContextByMccIfNoSimCardOrDefault();
1574 return getSettingFromResource(targetContext, R.string.config_captive_portal_http_url,
Chiachang Wang969eb752019-04-25 09:47:27 +08001575 R.string.default_captive_portal_http_url, CAPTIVE_PORTAL_HTTP_URL);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001576 }
1577
1578 private int getConsecutiveDnsTimeoutThreshold() {
Chiachang Wang9a87f802019-04-08 19:06:21 +08001579 return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY,
1580 CONFIG_DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD,
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001581 DEFAULT_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD);
1582 }
1583
1584 private int getDataStallMinEvaluateTime() {
Chiachang Wang9a87f802019-04-08 19:06:21 +08001585 return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY,
1586 CONFIG_DATA_STALL_MIN_EVALUATE_INTERVAL,
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001587 DEFAULT_DATA_STALL_MIN_EVALUATE_TIME_MS);
1588 }
1589
1590 private int getDataStallValidDnsTimeThreshold() {
Chiachang Wang9a87f802019-04-08 19:06:21 +08001591 return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY,
1592 CONFIG_DATA_STALL_VALID_DNS_TIME_THRESHOLD,
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001593 DEFAULT_DATA_STALL_VALID_DNS_TIME_THRESHOLD_MS);
1594 }
1595
Chiachang Wang9a87f802019-04-08 19:06:21 +08001596 private int getDataStallEvaluationType() {
1597 return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY,
1598 CONFIG_DATA_STALL_EVALUATION_TYPE,
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001599 DEFAULT_DATA_STALL_EVALUATION_TYPES);
1600 }
1601
Chiachang Wanga5716bf2019-11-20 16:13:07 +08001602 private int getTcpPollingInterval() {
1603 return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY,
1604 CONFIG_DATA_STALL_TCP_POLLING_INTERVAL,
1605 DEFAULT_TCP_POLLING_INTERVAL_MS);
1606 }
1607
Chiachang Wang91f0b5b2020-03-24 11:23:43 +00001608 @VisibleForTesting
1609 URL[] makeCaptivePortalFallbackUrls() {
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001610 try {
Chiachang Wang969eb752019-04-25 09:47:27 +08001611 final String firstUrl = mDependencies.getSetting(mContext, CAPTIVE_PORTAL_FALLBACK_URL,
1612 null);
Chiachang Wanga64bb702020-03-31 07:50:59 +00001613 final URL[] settingProviderUrls =
1614 combineCaptivePortalUrls(firstUrl, CAPTIVE_PORTAL_OTHER_FALLBACK_URLS);
Chiachang Wang65ba4e72020-03-23 11:52:43 +00001615 return getProbeUrlArrayConfig(settingProviderUrls,
1616 R.array.config_captive_portal_fallback_urls,
Niklas Lindgren0c904882018-12-07 11:08:04 +01001617 R.array.default_captive_portal_fallback_urls, this::makeURL);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001618 } catch (Exception e) {
1619 // Don't let a misconfiguration bootloop the system.
1620 Log.e(TAG, "Error parsing configured fallback URLs", e);
1621 return new URL[0];
1622 }
1623 }
1624
1625 private CaptivePortalProbeSpec[] makeCaptivePortalFallbackProbeSpecs() {
1626 try {
Chiachang Wang79a6da32019-04-17 17:00:54 +08001627 final String settingsValue = mDependencies.getDeviceConfigProperty(
1628 NAMESPACE_CONNECTIVITY, CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS, null);
1629
Niklas Lindgren0c904882018-12-07 11:08:04 +01001630 final CaptivePortalProbeSpec[] emptySpecs = new CaptivePortalProbeSpec[0];
1631 final CaptivePortalProbeSpec[] providerValue = TextUtils.isEmpty(settingsValue)
1632 ? emptySpecs
1633 : parseCaptivePortalProbeSpecs(settingsValue).toArray(emptySpecs);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001634
Chiachang Wang65ba4e72020-03-23 11:52:43 +00001635 return getProbeUrlArrayConfig(providerValue,
1636 R.array.config_captive_portal_fallback_probe_specs,
Niklas Lindgren0c904882018-12-07 11:08:04 +01001637 R.array.default_captive_portal_fallback_probe_specs,
1638 CaptivePortalProbeSpec::parseSpecOrNull);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001639 } catch (Exception e) {
1640 // Don't let a misconfiguration bootloop the system.
1641 Log.e(TAG, "Error parsing configured fallback probe specs", e);
1642 return null;
1643 }
1644 }
1645
Chiachang Wanga64bb702020-03-31 07:50:59 +00001646 private URL[] makeCaptivePortalHttpsUrls() {
1647 final String firstUrl = getCaptivePortalServerHttpsUrl();
1648 try {
1649 final URL[] settingProviderUrls =
1650 combineCaptivePortalUrls(firstUrl, CAPTIVE_PORTAL_OTHER_HTTPS_URLS);
1651 return getProbeUrlArrayConfig(settingProviderUrls,
1652 R.array.config_captive_portal_https_urls,
1653 DEFAULT_CAPTIVE_PORTAL_HTTPS_URLS, this::makeURL);
1654 } catch (Exception e) {
1655 // Don't let a misconfiguration bootloop the system.
1656 Log.e(TAG, "Error parsing configured https URLs", e);
1657 // Ensure URL aligned with legacy configuration.
1658 return new URL[]{makeURL(firstUrl)};
1659 }
1660 }
1661
1662 private URL[] makeCaptivePortalHttpUrls() {
1663 final String firstUrl = getCaptivePortalServerHttpUrl();
1664 try {
1665 final URL[] settingProviderUrls =
1666 combineCaptivePortalUrls(firstUrl, CAPTIVE_PORTAL_OTHER_HTTP_URLS);
1667 return getProbeUrlArrayConfig(settingProviderUrls,
1668 R.array.config_captive_portal_http_urls,
1669 DEFAULT_CAPTIVE_PORTAL_HTTP_URLS, this::makeURL);
1670 } catch (Exception e) {
1671 // Don't let a misconfiguration bootloop the system.
1672 Log.e(TAG, "Error parsing configured http URLs", e);
1673 // Ensure URL aligned with legacy configuration.
1674 return new URL[]{makeURL(firstUrl)};
1675 }
1676 }
1677
1678 private URL[] combineCaptivePortalUrls(final String firstUrl, final String name) {
1679 if (TextUtils.isEmpty(firstUrl)) return new URL[0];
1680
1681 final String otherUrls = mDependencies.getDeviceConfigProperty(
1682 NAMESPACE_CONNECTIVITY, name, "");
1683 // otherUrls may be empty, but .split() ignores trailing empty strings
1684 final String separator = ",";
1685 final String[] urls = (firstUrl + separator + otherUrls).split(separator);
1686 return convertStrings(urls, this::makeURL, new URL[0]);
1687 }
1688
Niklas Lindgren0c904882018-12-07 11:08:04 +01001689 /**
1690 * Read a setting from a resource or the settings provider.
1691 *
1692 * <p>The configuration resource is prioritized, then the provider value, then the default
1693 * resource value.
1694 * @param context The context
1695 * @param configResource The resource id for the configuration parameter
1696 * @param defaultResource The resource id for the default value
1697 * @param symbol The symbol in the settings provider
1698 * @return The best available value
1699 */
1700 @NonNull
1701 private String getSettingFromResource(@NonNull final Context context,
1702 @StringRes int configResource, @StringRes int defaultResource,
1703 @NonNull String symbol) {
1704 final Resources res = context.getResources();
1705 String setting = res.getString(configResource);
1706
1707 if (!TextUtils.isEmpty(setting)) return setting;
1708
1709 setting = mDependencies.getSetting(context, symbol, null);
1710 if (!TextUtils.isEmpty(setting)) return setting;
1711
1712 return res.getString(defaultResource);
1713 }
1714
1715 /**
1716 * Get an array configuration from resources or the settings provider.
1717 *
1718 * <p>The configuration resource is prioritized, then the provider values, then the default
1719 * resource values.
1720 * @param providerValue Values obtained from the setting provider.
1721 * @param configResId ID of the configuration resource.
1722 * @param defaultResId ID of the default resource.
1723 * @param resourceConverter Converter from the resource strings to stored setting class. Null
1724 * return values are ignored.
1725 */
Chiachang Wang65ba4e72020-03-23 11:52:43 +00001726 private <T> T[] getProbeUrlArrayConfig(@NonNull T[] providerValue, @ArrayRes int configResId,
Niklas Lindgren0c904882018-12-07 11:08:04 +01001727 @ArrayRes int defaultResId, @NonNull Function<String, T> resourceConverter) {
Chiachang Wang65ba4e72020-03-23 11:52:43 +00001728 final Resources res = getContextByMccIfNoSimCardOrDefault().getResources();
Chiachang Wanga64bb702020-03-31 07:50:59 +00001729 return getProbeUrlArrayConfig(providerValue, configResId, res.getStringArray(defaultResId),
1730 resourceConverter);
1731 }
1732
1733 /**
1734 * Get an array configuration from resources or the settings provider.
1735 *
1736 * <p>The configuration resource is prioritized, then the provider values, then the default
1737 * resource values.
1738 * @param providerValue Values obtained from the setting provider.
1739 * @param configResId ID of the configuration resource.
1740 * @param defaultConfig Values of default configuration.
1741 * @param resourceConverter Converter from the resource strings to stored setting class. Null
1742 * return values are ignored.
1743 */
1744 private <T> T[] getProbeUrlArrayConfig(@NonNull T[] providerValue, @ArrayRes int configResId,
1745 String[] defaultConfig, @NonNull Function<String, T> resourceConverter) {
1746 final Resources res = getContextByMccIfNoSimCardOrDefault().getResources();
Niklas Lindgren0c904882018-12-07 11:08:04 +01001747 String[] configValue = res.getStringArray(configResId);
1748
1749 if (configValue.length == 0) {
1750 if (providerValue.length > 0) {
1751 return providerValue;
1752 }
1753
Chiachang Wanga64bb702020-03-31 07:50:59 +00001754 configValue = defaultConfig;
Niklas Lindgren0c904882018-12-07 11:08:04 +01001755 }
1756
1757 return convertStrings(configValue, resourceConverter, Arrays.copyOf(providerValue, 0));
1758 }
1759
1760 /**
1761 * Convert a String array to an array of some other type using the specified converter.
1762 *
1763 * <p>Any null value, or value for which the converter throws a {@link RuntimeException}, will
1764 * not be added to the output array, so the output array may be smaller than the input.
1765 */
1766 private <T> T[] convertStrings(
1767 @NonNull String[] strings, Function<String, T> converter, T[] emptyArray) {
1768 final ArrayList<T> convertedValues = new ArrayList<>(strings.length);
1769 for (String configString : strings) {
1770 T convertedValue = null;
1771 try {
1772 convertedValue = converter.apply(configString);
1773 } catch (Exception e) {
1774 Log.e(TAG, "Error parsing configuration", e);
1775 // Fall through
1776 }
1777 if (convertedValue != null) {
1778 convertedValues.add(convertedValue);
1779 }
1780 }
1781 return convertedValues.toArray(emptyArray);
1782 }
1783
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001784 private String getCaptivePortalUserAgent() {
Chiachang Wang79a6da32019-04-17 17:00:54 +08001785 return mDependencies.getDeviceConfigProperty(NAMESPACE_CONNECTIVITY,
1786 CAPTIVE_PORTAL_USER_AGENT, DEFAULT_USER_AGENT);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001787 }
1788
1789 private URL nextFallbackUrl() {
1790 if (mCaptivePortalFallbackUrls.length == 0) {
1791 return null;
1792 }
1793 int idx = Math.abs(mNextFallbackUrlIndex) % mCaptivePortalFallbackUrls.length;
1794 mNextFallbackUrlIndex += mRandom.nextInt(); // randomly change url without memory.
1795 return mCaptivePortalFallbackUrls[idx];
1796 }
1797
1798 private CaptivePortalProbeSpec nextFallbackSpec() {
Remi NGUYEN VANabeaaf72019-01-20 13:48:19 +09001799 if (isEmpty(mCaptivePortalFallbackSpecs)) {
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001800 return null;
1801 }
1802 // Randomly change spec without memory. Also randomize the first attempt.
1803 final int idx = Math.abs(mRandom.nextInt()) % mCaptivePortalFallbackSpecs.length;
1804 return mCaptivePortalFallbackSpecs[idx];
1805 }
1806
1807 @VisibleForTesting
1808 protected CaptivePortalProbeResult isCaptivePortal() {
1809 if (!mIsCaptivePortalCheckEnabled) {
1810 validationLog("Validation disabled.");
1811 return CaptivePortalProbeResult.SUCCESS;
1812 }
1813
1814 URL pacUrl = null;
Chiachang Wanga64bb702020-03-31 07:50:59 +00001815 final URL[] httpsUrls = mCaptivePortalHttpsUrls;
1816 final URL[] httpUrls = mCaptivePortalHttpUrls;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001817
1818 // On networks with a PAC instead of fetching a URL that should result in a 204
1819 // response, we instead simply fetch the PAC script. This is done for a few reasons:
1820 // 1. At present our PAC code does not yet handle multiple PACs on multiple networks
1821 // until something like https://android-review.googlesource.com/#/c/115180/ lands.
1822 // Network.openConnection() will ignore network-specific PACs and instead fetch
1823 // using NO_PROXY. If a PAC is in place, the only fetch we know will succeed with
1824 // NO_PROXY is the fetch of the PAC itself.
1825 // 2. To proxy the generate_204 fetch through a PAC would require a number of things
1826 // happen before the fetch can commence, namely:
1827 // a) the PAC script be fetched
1828 // b) a PAC script resolver service be fired up and resolve the captive portal
1829 // server.
1830 // Network validation could be delayed until these prerequisities are satisifed or
1831 // could simply be left to race them. Neither is an optimal solution.
1832 // 3. PAC scripts are sometimes used to block or restrict Internet access and may in
1833 // fact block fetching of the generate_204 URL which would lead to false negative
1834 // results for network validation.
1835 final ProxyInfo proxyInfo = mLinkProperties.getHttpProxy();
1836 if (proxyInfo != null && !Uri.EMPTY.equals(proxyInfo.getPacFileUrl())) {
1837 pacUrl = makeURL(proxyInfo.getPacFileUrl().toString());
1838 if (pacUrl == null) {
1839 return CaptivePortalProbeResult.FAILED;
1840 }
1841 }
1842
Chiachang Wanga64bb702020-03-31 07:50:59 +00001843 if ((pacUrl == null) && (httpUrls.length == 0 || httpsUrls.length == 0
1844 || httpUrls[0] == null || httpsUrls[0] == null)) {
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001845 return CaptivePortalProbeResult.FAILED;
1846 }
1847
1848 long startTime = SystemClock.elapsedRealtime();
1849
1850 final CaptivePortalProbeResult result;
1851 if (pacUrl != null) {
1852 result = sendDnsAndHttpProbes(null, pacUrl, ValidationProbeEvent.PROBE_PAC);
Chiachang Wang813ee472019-05-23 16:29:30 +08001853 reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, result);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001854 } else if (mUseHttps) {
Chiachang Wang813ee472019-05-23 16:29:30 +08001855 // Probe results are reported inside sendParallelHttpProbes.
Chiachang Wanga64bb702020-03-31 07:50:59 +00001856 result = sendParallelHttpProbes(proxyInfo, httpsUrls[0], httpUrls[0]);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001857 } else {
Chiachang Wanga64bb702020-03-31 07:50:59 +00001858 result = sendDnsAndHttpProbes(proxyInfo, httpUrls[0], ValidationProbeEvent.PROBE_HTTP);
Chiachang Wang813ee472019-05-23 16:29:30 +08001859 reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, result);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001860 }
1861
1862 long endTime = SystemClock.elapsedRealtime();
1863
1864 sendNetworkConditionsBroadcast(true /* response received */,
1865 result.isPortal() /* isCaptivePortal */,
1866 startTime, endTime);
1867
1868 log("isCaptivePortal: isSuccessful()=" + result.isSuccessful()
1869 + " isPortal()=" + result.isPortal()
1870 + " RedirectUrl=" + result.redirectUrl
Chiachang Wang813ee472019-05-23 16:29:30 +08001871 + " isPartialConnectivity()=" + result.isPartialConnectivity()
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001872 + " Time=" + (endTime - startTime) + "ms");
1873
1874 return result;
1875 }
1876
1877 /**
1878 * Do a DNS resolution and URL fetch on a known web server to see if we get the data we expect.
1879 * @return a CaptivePortalProbeResult inferred from the HTTP response.
1880 */
1881 private CaptivePortalProbeResult sendDnsAndHttpProbes(ProxyInfo proxy, URL url, int probeType) {
1882 // Pre-resolve the captive portal server host so we can log it.
1883 // Only do this if HttpURLConnection is about to, to avoid any potentially
1884 // unnecessary resolution.
1885 final String host = (proxy != null) ? proxy.getHost() : url.getHost();
Chiachang Wang813ee472019-05-23 16:29:30 +08001886 // This method cannot safely report probe results because it might not be running on the
1887 // state machine thread. Reporting results here would cause races and potentially send
1888 // information to callers that does not make sense because the state machine has already
1889 // changed state.
Remi NGUYEN VAN75e9d902020-04-09 06:41:16 +00001890 final InetAddress[] resolvedAddr = sendDnsProbe(host);
1891 // The private IP logic only applies to the HTTP probe, not the HTTPS probe (which would
1892 // fail anyway) or the PAC probe.
1893 if (mPrivateIpNotPortalEnabled && probeType == ValidationProbeEvent.PROBE_HTTP
1894 && (proxy == null) && hasPrivateIpAddress(resolvedAddr)) {
1895 return CaptivePortalProbeResult.PRIVATE_IP;
1896 }
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001897 return sendHttpProbe(url, probeType, null);
1898 }
1899
Lorenzo Colitti171cfd22019-04-18 13:44:32 +09001900 /** Do a DNS lookup for the given server, or throw UnknownHostException after timeoutMs */
1901 @VisibleForTesting
1902 protected InetAddress[] sendDnsProbeWithTimeout(String host, int timeoutMs)
1903 throws UnknownHostException {
Chiachang Wang1c67f4e2019-05-09 21:28:47 +08001904 return DnsUtils.getAllByName(mDependencies.getDnsResolver(), mCleartextDnsNetwork, host,
Chiachang Wangddb7da62019-06-03 15:50:53 +08001905 TYPE_ADDRCONFIG, FLAG_EMPTY, timeoutMs,
1906 str -> validationLog(ValidationProbeEvent.PROBE_DNS, host, str));
Lorenzo Colitti171cfd22019-04-18 13:44:32 +09001907 }
1908
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001909 /** Do a DNS resolution of the given server. */
Remi NGUYEN VAN75e9d902020-04-09 06:41:16 +00001910 private InetAddress[] sendDnsProbe(String host) {
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001911 if (TextUtils.isEmpty(host)) {
Remi NGUYEN VAN75e9d902020-04-09 06:41:16 +00001912 return null;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001913 }
1914
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001915 final Stopwatch watch = new Stopwatch().start();
1916 int result;
Remi NGUYEN VAN75e9d902020-04-09 06:41:16 +00001917 InetAddress[] addresses;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001918 try {
Remi NGUYEN VAN75e9d902020-04-09 06:41:16 +00001919 addresses = sendDnsProbeWithTimeout(host, getDnsProbeTimeout());
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001920 result = ValidationProbeEvent.DNS_SUCCESS;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001921 } catch (UnknownHostException e) {
Remi NGUYEN VAN75e9d902020-04-09 06:41:16 +00001922 addresses = null;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001923 result = ValidationProbeEvent.DNS_FAILURE;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001924 }
1925 final long latency = watch.stop();
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001926 logValidationProbe(latency, ValidationProbeEvent.PROBE_DNS, result);
Remi NGUYEN VAN75e9d902020-04-09 06:41:16 +00001927 return addresses;
1928 }
1929
1930 /**
1931 * Check if any of the provided IP addresses include a private IP, as defined by
1932 * {@link com.android.server.util.NetworkStackConstants#PRIVATE_IPV4_RANGES}.
1933 * @return true if an IP address is private.
1934 */
1935 private static boolean hasPrivateIpAddress(@Nullable InetAddress[] addresses) {
1936 if (addresses == null) {
1937 return false;
1938 }
1939 for (InetAddress address : addresses) {
1940 if (address.isLinkLocalAddress() || address.isSiteLocalAddress()) {
1941 return true;
1942 }
1943 }
1944 return false;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001945 }
1946
1947 /**
1948 * Do a URL fetch on a known web server to see if we get the data we expect.
1949 * @return a CaptivePortalProbeResult inferred from the HTTP response.
1950 */
1951 @VisibleForTesting
1952 protected CaptivePortalProbeResult sendHttpProbe(URL url, int probeType,
1953 @Nullable CaptivePortalProbeSpec probeSpec) {
1954 HttpURLConnection urlConnection = null;
1955 int httpResponseCode = CaptivePortalProbeResult.FAILED_CODE;
1956 String redirectUrl = null;
1957 final Stopwatch probeTimer = new Stopwatch().start();
Chalard Jeanac5b8992019-04-09 11:16:56 +09001958 final int oldTag = TrafficStats.getAndSetThreadStatsTag(
1959 TrafficStatsConstants.TAG_SYSTEM_PROBE);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001960 try {
Remi NGUYEN VAN2d909a72019-12-24 18:15:52 +09001961 // Follow redirects for PAC probes as such probes verify connectivity by fetching the
1962 // PAC proxy file, which may be configured behind a redirect.
1963 final boolean followRedirect = probeType == ValidationProbeEvent.PROBE_PAC;
1964 urlConnection = makeProbeConnection(url, followRedirect);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001965 // cannot read request header after connection
1966 String requestHeader = urlConnection.getRequestProperties().toString();
1967
1968 // Time how long it takes to get a response to our request
1969 long requestTimestamp = SystemClock.elapsedRealtime();
1970
1971 httpResponseCode = urlConnection.getResponseCode();
1972 redirectUrl = urlConnection.getHeaderField("location");
1973
1974 // Time how long it takes to get a response to our request
1975 long responseTimestamp = SystemClock.elapsedRealtime();
1976
1977 validationLog(probeType, url, "time=" + (responseTimestamp - requestTimestamp) + "ms"
1978 + " ret=" + httpResponseCode
1979 + " request=" + requestHeader
1980 + " headers=" + urlConnection.getHeaderFields());
1981 // NOTE: We may want to consider an "HTTP/1.0 204" response to be a captive
1982 // portal. The only example of this seen so far was a captive portal. For
1983 // the time being go with prior behavior of assuming it's not a captive
1984 // portal. If it is considered a captive portal, a different sign-in URL
1985 // is needed (i.e. can't browse a 204). This could be the result of an HTTP
1986 // proxy server.
1987 if (httpResponseCode == 200) {
Sehee Park43427c02018-11-16 17:39:34 +09001988 long contentLength = urlConnection.getContentLengthLong();
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001989 if (probeType == ValidationProbeEvent.PROBE_PAC) {
1990 validationLog(
1991 probeType, url, "PAC fetch 200 response interpreted as 204 response.");
1992 httpResponseCode = CaptivePortalProbeResult.SUCCESS_CODE;
Sehee Park43427c02018-11-16 17:39:34 +09001993 } else if (contentLength == -1) {
1994 // When no Content-length (default value == -1), attempt to read a byte
1995 // from the response. Do not use available() as it is unreliable.
1996 // See http://b/33498325.
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09001997 if (urlConnection.getInputStream().read() == -1) {
Sehee Park43427c02018-11-16 17:39:34 +09001998 validationLog(probeType, url,
1999 "Empty 200 response interpreted as failed response.");
2000 httpResponseCode = CaptivePortalProbeResult.FAILED_CODE;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002001 }
Sehee Park43427c02018-11-16 17:39:34 +09002002 } else if (contentLength <= 4) {
2003 // Consider 200 response with "Content-length <= 4" to not be a captive
2004 // portal. There's no point in considering this a captive portal as the
2005 // user cannot sign-in to an empty page. Probably the result of a broken
2006 // transparent proxy. See http://b/9972012 and http://b/122999481.
2007 validationLog(probeType, url, "200 response with Content-length <= 4"
2008 + " interpreted as failed response.");
2009 httpResponseCode = CaptivePortalProbeResult.FAILED_CODE;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002010 }
2011 }
2012 } catch (IOException e) {
2013 validationLog(probeType, url, "Probe failed with exception " + e);
2014 if (httpResponseCode == CaptivePortalProbeResult.FAILED_CODE) {
2015 // TODO: Ping gateway and DNS server and log results.
2016 }
2017 } finally {
2018 if (urlConnection != null) {
2019 urlConnection.disconnect();
2020 }
2021 TrafficStats.setThreadStatsTag(oldTag);
2022 }
2023 logValidationProbe(probeTimer.stop(), probeType, httpResponseCode);
2024
2025 if (probeSpec == null) {
2026 return new CaptivePortalProbeResult(httpResponseCode, redirectUrl, url.toString());
2027 } else {
2028 return probeSpec.getResult(httpResponseCode, redirectUrl);
2029 }
2030 }
2031
Remi NGUYEN VAN2d909a72019-12-24 18:15:52 +09002032 private HttpURLConnection makeProbeConnection(URL url, boolean followRedirects)
2033 throws IOException {
2034 final HttpURLConnection conn = (HttpURLConnection) mCleartextDnsNetwork.openConnection(url);
2035 conn.setInstanceFollowRedirects(followRedirects);
2036 conn.setConnectTimeout(SOCKET_TIMEOUT_MS);
2037 conn.setReadTimeout(SOCKET_TIMEOUT_MS);
2038 conn.setRequestProperty("Connection", "close");
2039 conn.setUseCaches(false);
2040 if (mCaptivePortalUserAgent != null) {
2041 conn.setRequestProperty("User-Agent", mCaptivePortalUserAgent);
2042 }
2043 return conn;
2044 }
2045
2046 @VisibleForTesting
2047 @NonNull
2048 protected static String readAsString(InputStream is, int maxLength, Charset charset)
2049 throws IOException {
2050 final InputStreamReader reader = new InputStreamReader(is, charset);
2051 final char[] buffer = new char[1000];
2052 final StringBuilder builder = new StringBuilder();
2053 int totalReadLength = 0;
2054 while (totalReadLength < maxLength) {
2055 final int availableLength = Math.min(maxLength - totalReadLength, buffer.length);
2056 final int currentLength = reader.read(buffer, 0, availableLength);
2057 if (currentLength < 0) break; // EOF
2058
2059 totalReadLength += currentLength;
2060 builder.append(buffer, 0, currentLength);
2061 }
2062 return builder.toString();
2063 }
2064
2065 /**
2066 * Attempt to extract the {@link Charset} of the response from its Content-Type header.
2067 *
2068 * <p>If the {@link Charset} cannot be extracted, UTF-8 is returned by default.
2069 */
2070 @VisibleForTesting
2071 @NonNull
2072 protected static Charset extractCharset(@Nullable String contentTypeHeader) {
2073 if (contentTypeHeader == null) return StandardCharsets.UTF_8;
2074 // See format in https://tools.ietf.org/html/rfc7231#section-3.1.1.1
2075 final Pattern charsetPattern = Pattern.compile("; *charset=\"?([^ ;\"]+)\"?",
2076 Pattern.CASE_INSENSITIVE);
2077 final Matcher matcher = charsetPattern.matcher(contentTypeHeader);
2078 if (!matcher.find()) return StandardCharsets.UTF_8;
2079
2080 try {
2081 return Charset.forName(matcher.group(1));
2082 } catch (IllegalArgumentException e) {
2083 return StandardCharsets.UTF_8;
2084 }
2085 }
2086
Remi NGUYEN VAN2fa48c22019-12-09 16:40:02 +09002087 private abstract static class ProbeThread extends Thread {
2088 private final CountDownLatch mLatch;
2089 private final ProxyInfo mProxy;
2090 private final URL mUrl;
2091 protected final Uri mCaptivePortalApiUrl;
2092
2093 protected ProbeThread(CountDownLatch latch, ProxyInfo proxy, URL url,
2094 Uri captivePortalApiUrl) {
2095 mLatch = latch;
2096 mProxy = proxy;
2097 mUrl = url;
2098 mCaptivePortalApiUrl = captivePortalApiUrl;
2099 }
2100
2101 private volatile CaptivePortalProbeResult mResult = CaptivePortalProbeResult.FAILED;
2102
2103 public CaptivePortalProbeResult result() {
2104 return mResult;
2105 }
2106
2107 protected abstract CaptivePortalProbeResult sendProbe(ProxyInfo proxy, URL url);
2108 public abstract boolean isConclusiveResult(CaptivePortalProbeResult result);
2109
2110 @Override
2111 public void run() {
2112 mResult = sendProbe(mProxy, mUrl);
2113 if (isConclusiveResult(mResult)) {
2114 // Stop waiting immediately if any probe is conclusive.
2115 while (mLatch.getCount() > 0) {
2116 mLatch.countDown();
2117 }
2118 }
2119 // Signal this probe has completed.
2120 mLatch.countDown();
2121 }
2122 }
2123
2124 final class HttpsProbeThread extends ProbeThread {
2125 HttpsProbeThread(CountDownLatch latch, ProxyInfo proxy, URL url, Uri captivePortalApiUrl) {
2126 super(latch, proxy, url, captivePortalApiUrl);
2127 }
2128
2129 @Override
2130 protected CaptivePortalProbeResult sendProbe(ProxyInfo proxy, URL url) {
2131 return sendDnsAndHttpProbes(proxy, url, ValidationProbeEvent.PROBE_HTTPS);
2132 }
2133
2134 @Override
2135 public boolean isConclusiveResult(CaptivePortalProbeResult result) {
2136 // isPortal() is not expected on the HTTPS probe, but check it nonetheless.
2137 // In case the capport API is available, the API is authoritative on whether there is
2138 // a portal, so the HTTPS probe is not enough to conclude there is connectivity,
2139 // and a determination will be made once the capport API probe returns. Note that the
2140 // API can only force the system to detect a portal even if the HTTPS probe succeeds.
2141 // It cannot force the system to detect no portal if the HTTPS probe fails.
2142 return (result.isPortal() || result.isSuccessful()) && mCaptivePortalApiUrl == null;
2143 }
2144 }
2145
2146 final class HttpProbeThread extends ProbeThread {
2147 private volatile CaptivePortalDataShim mCapportData;
2148 HttpProbeThread(CountDownLatch latch, ProxyInfo proxy, URL url, Uri captivePortalApiUrl) {
2149 super(latch, proxy, url, captivePortalApiUrl);
2150 }
2151
2152 CaptivePortalDataShim getCaptivePortalData() {
2153 return mCapportData;
2154 }
2155
2156 private CaptivePortalDataShim tryCapportApiProbe() {
2157 if (mCaptivePortalApiUrl == null) return null;
2158 validationLog("Fetching captive portal data from " + mCaptivePortalApiUrl);
2159
2160 final String apiContent;
2161 try {
2162 final URL url = new URL(mCaptivePortalApiUrl.toString());
2163 if (!"https".equals(url.getProtocol())) {
2164 validationLog("Invalid captive portal API protocol: " + url.getProtocol());
2165 return null;
2166 }
2167
2168 final HttpURLConnection conn = makeProbeConnection(
2169 url, true /* followRedirects */);
2170 conn.setRequestProperty(ACCEPT_HEADER, CAPPORT_API_CONTENT_TYPE);
2171 final int responseCode = conn.getResponseCode();
2172 if (responseCode != 200) {
2173 validationLog("Non-200 API response code: " + conn.getResponseCode());
2174 return null;
2175 }
2176 final Charset charset = extractCharset(conn.getHeaderField(CONTENT_TYPE_HEADER));
2177 if (charset != StandardCharsets.UTF_8) {
2178 validationLog("Invalid charset for capport API: " + charset);
2179 return null;
2180 }
2181
2182 apiContent = readAsString(conn.getInputStream(),
2183 CAPPORT_API_MAX_JSON_LENGTH, charset);
2184 } catch (IOException e) {
2185 validationLog("I/O error reading capport data: " + e.getMessage());
2186 return null;
2187 }
2188
2189 try {
2190 final JSONObject info = new JSONObject(apiContent);
2191 return CaptivePortalDataShimImpl.fromJson(info);
2192 } catch (JSONException e) {
2193 validationLog("Could not parse capport API JSON: " + e.getMessage());
2194 return null;
2195 } catch (UnsupportedApiLevelException e) {
2196 validationLog("Platform API too low to support capport API");
2197 return null;
2198 }
2199 }
2200
2201 @Override
2202 protected CaptivePortalProbeResult sendProbe(ProxyInfo proxy, URL url) {
2203 mCapportData = tryCapportApiProbe();
2204 if (mCapportData != null && mCapportData.isCaptive()) {
2205 if (mCapportData.getUserPortalUrl() == null) {
2206 validationLog("Missing user-portal-url from capport response");
2207 return sendDnsAndHttpProbes(proxy, url, ValidationProbeEvent.PROBE_HTTP);
2208 }
2209 final String loginUrlString = mCapportData.getUserPortalUrl().toString();
2210 // Starting from R (where CaptivePortalData was introduced), the captive portal app
2211 // delegates to NetworkMonitor for verifying when the network validates instead of
2212 // probing the detectUrl. So pass the detectUrl to have the portal open on that,
2213 // page; CaptivePortalLogin will not use it for probing.
2214 return new CaptivePortalProbeResult(
2215 CaptivePortalProbeResult.PORTAL_CODE,
2216 loginUrlString /* redirectUrl */,
2217 loginUrlString /* detectUrl */);
2218 }
2219
2220 // If the API says it's not captive, still check for HTTP connectivity. This helps
2221 // with partial connectivity detection, and a broken API saying that there is no
2222 // redirect when there is one.
2223 return sendDnsAndHttpProbes(proxy, url, ValidationProbeEvent.PROBE_HTTP);
2224 }
2225
2226 @Override
2227 public boolean isConclusiveResult(CaptivePortalProbeResult result) {
2228 return result.isPortal();
2229 }
2230 }
2231
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002232 private CaptivePortalProbeResult sendParallelHttpProbes(
2233 ProxyInfo proxy, URL httpsUrl, URL httpUrl) {
2234 // Number of probes to wait for. If a probe completes with a conclusive answer
2235 // it shortcuts the latch immediately by forcing the count to 0.
2236 final CountDownLatch latch = new CountDownLatch(2);
2237
Remi NGUYEN VAN2fa48c22019-12-09 16:40:02 +09002238 final Uri capportApiUrl = getCaptivePortalApiUrl(mLinkProperties);
2239 final HttpsProbeThread httpsProbe = new HttpsProbeThread(latch, proxy, httpsUrl,
2240 capportApiUrl);
2241 final HttpProbeThread httpProbe = new HttpProbeThread(latch, proxy, httpUrl, capportApiUrl);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002242
2243 try {
2244 httpsProbe.start();
2245 httpProbe.start();
2246 latch.await(PROBE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
2247 } catch (InterruptedException e) {
2248 validationLog("Error: probes wait interrupted!");
2249 return CaptivePortalProbeResult.FAILED;
2250 }
2251
2252 final CaptivePortalProbeResult httpsResult = httpsProbe.result();
2253 final CaptivePortalProbeResult httpResult = httpProbe.result();
2254
2255 // Look for a conclusive probe result first.
Remi NGUYEN VAN2fa48c22019-12-09 16:40:02 +09002256 if (httpProbe.isConclusiveResult(httpResult)) {
2257 maybeReportCaptivePortalData(httpProbe.getCaptivePortalData());
Chiachang Wang813ee472019-05-23 16:29:30 +08002258 reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, httpResult);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002259 return httpResult;
2260 }
Remi NGUYEN VAN2fa48c22019-12-09 16:40:02 +09002261
2262 if (httpsProbe.isConclusiveResult(httpsResult)) {
2263 maybeReportCaptivePortalData(httpProbe.getCaptivePortalData());
Chiachang Wang813ee472019-05-23 16:29:30 +08002264 reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTPS, httpsResult);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002265 return httpsResult;
2266 }
Remi NGUYEN VAN75e9d902020-04-09 06:41:16 +00002267 // Consider a DNS response with a private IP address on the HTTP probe as an indication that
2268 // the network is not connected to the Internet, and have the whole evaluation fail in that
2269 // case.
2270 // This only applies if the DNS probe completed within PROBE_TIMEOUT_MS, as the fallback
2271 // probe should not be delayed by this check.
2272 if (mPrivateIpNotPortalEnabled && (httpResult.isDnsPrivateIpResponse())) {
2273 validationLog("DNS response to the URL is private IP");
2274 return CaptivePortalProbeResult.FAILED;
2275 }
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002276 // If a fallback method exists, use it to retry portal detection.
2277 // If we have new-style probe specs, use those. Otherwise, use the fallback URLs.
2278 final CaptivePortalProbeSpec probeSpec = nextFallbackSpec();
2279 final URL fallbackUrl = (probeSpec != null) ? probeSpec.getUrl() : nextFallbackUrl();
lucaslinb0573962019-03-12 13:08:03 +08002280 CaptivePortalProbeResult fallbackProbeResult = null;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002281 if (fallbackUrl != null) {
lucaslinb0573962019-03-12 13:08:03 +08002282 fallbackProbeResult = sendHttpProbe(fallbackUrl, PROBE_FALLBACK, probeSpec);
Chiachang Wang813ee472019-05-23 16:29:30 +08002283 reportHttpProbeResult(NETWORK_VALIDATION_PROBE_FALLBACK, fallbackProbeResult);
lucaslinb0573962019-03-12 13:08:03 +08002284 if (fallbackProbeResult.isPortal()) {
2285 return fallbackProbeResult;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002286 }
2287 }
2288 // Otherwise wait until http and https probes completes and use their results.
2289 try {
2290 httpProbe.join();
Remi NGUYEN VAN2fa48c22019-12-09 16:40:02 +09002291 maybeReportCaptivePortalData(httpProbe.getCaptivePortalData());
Chiachang Wang813ee472019-05-23 16:29:30 +08002292 reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, httpProbe.result());
2293
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002294 if (httpProbe.result().isPortal()) {
2295 return httpProbe.result();
2296 }
Chiachang Wang813ee472019-05-23 16:29:30 +08002297
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002298 httpsProbe.join();
Chiachang Wang813ee472019-05-23 16:29:30 +08002299 reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTPS, httpsProbe.result());
2300
lucaslinb0573962019-03-12 13:08:03 +08002301 final boolean isHttpSuccessful =
2302 (httpProbe.result().isSuccessful()
2303 || (fallbackProbeResult != null && fallbackProbeResult.isSuccessful()));
2304 if (httpsProbe.result().isFailed() && isHttpSuccessful) {
2305 return CaptivePortalProbeResult.PARTIAL;
2306 }
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002307 return httpsProbe.result();
2308 } catch (InterruptedException e) {
2309 validationLog("Error: http or https probe wait interrupted!");
2310 return CaptivePortalProbeResult.FAILED;
2311 }
2312 }
2313
2314 private URL makeURL(String url) {
2315 if (url != null) {
2316 try {
2317 return new URL(url);
2318 } catch (MalformedURLException e) {
2319 validationLog("Bad URL: " + url);
2320 }
2321 }
2322 return null;
2323 }
2324
2325 /**
2326 * @param responseReceived - whether or not we received a valid HTTP response to our request.
2327 * If false, isCaptivePortal and responseTimestampMs are ignored
2328 * TODO: This should be moved to the transports. The latency could be passed to the transports
2329 * along with the captive portal result. Currently the TYPE_MOBILE broadcasts appear unused so
2330 * perhaps this could just be added to the WiFi transport only.
2331 */
2332 private void sendNetworkConditionsBroadcast(boolean responseReceived, boolean isCaptivePortal,
2333 long requestTimestampMs, long responseTimestampMs) {
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002334 Intent latencyBroadcast =
2335 new Intent(NetworkMonitorUtils.ACTION_NETWORK_CONDITIONS_MEASURED);
2336 if (mNetworkCapabilities.hasTransport(TRANSPORT_WIFI)) {
Chiachang Wangb04f81c2019-02-14 09:30:58 +08002337 if (!mWifiManager.isScanAlwaysAvailable()) {
2338 return;
2339 }
2340
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002341 WifiInfo currentWifiInfo = mWifiManager.getConnectionInfo();
2342 if (currentWifiInfo != null) {
2343 // NOTE: getSSID()'s behavior changed in API 17; before that, SSIDs were not
2344 // surrounded by double quotation marks (thus violating the Javadoc), but this
2345 // was changed to match the Javadoc in API 17. Since clients may have started
2346 // sanitizing the output of this method since API 17 was released, we should
2347 // not change it here as it would become impossible to tell whether the SSID is
2348 // simply being surrounded by quotes due to the API, or whether those quotes
2349 // are actually part of the SSID.
2350 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_SSID,
2351 currentWifiInfo.getSSID());
2352 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_BSSID,
2353 currentWifiInfo.getBSSID());
2354 } else {
2355 if (VDBG) logw("network info is TYPE_WIFI but no ConnectionInfo found");
2356 return;
2357 }
2358 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CONNECTIVITY_TYPE, TYPE_WIFI);
2359 } else if (mNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
Chiachang Wangb04f81c2019-02-14 09:30:58 +08002360 // TODO(b/123893112): Support multi-sim.
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002361 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_NETWORK_TYPE,
2362 mTelephonyManager.getNetworkType());
Chiachang Wangb04f81c2019-02-14 09:30:58 +08002363 final ServiceState dataSs = mTelephonyManager.getServiceState();
2364 if (dataSs == null) {
2365 logw("failed to retrieve ServiceState");
2366 return;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002367 }
Chiachang Wangb04f81c2019-02-14 09:30:58 +08002368 // See if the data sub is registered for PS services on cell.
Jack Yu1f1d36c2019-03-18 11:01:42 -07002369 final NetworkRegistrationInfo nri = dataSs.getNetworkRegistrationInfo(
Jack Yu6bc18652019-03-15 14:49:53 -07002370 NetworkRegistrationInfo.DOMAIN_PS,
Jack Yu858e6532019-03-14 15:42:09 -07002371 AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
Chiachang Wangb04f81c2019-02-14 09:30:58 +08002372 latencyBroadcast.putExtra(
2373 NetworkMonitorUtils.EXTRA_CELL_ID,
Jack Yu1f1d36c2019-03-18 11:01:42 -07002374 nri == null ? null : nri.getCellIdentity());
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002375 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CONNECTIVITY_TYPE, TYPE_MOBILE);
2376 } else {
2377 return;
2378 }
2379 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_RESPONSE_RECEIVED,
2380 responseReceived);
2381 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_REQUEST_TIMESTAMP_MS,
2382 requestTimestampMs);
2383
2384 if (responseReceived) {
2385 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_IS_CAPTIVE_PORTAL,
2386 isCaptivePortal);
2387 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_RESPONSE_TIMESTAMP_MS,
2388 responseTimestampMs);
2389 }
Remi NGUYEN VANea9f7e32019-06-20 18:49:48 +09002390 mDependencies.sendNetworkConditionsBroadcast(mContext, latencyBroadcast);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002391 }
2392
2393 private void logNetworkEvent(int evtype) {
2394 int[] transports = mNetworkCapabilities.getTransportTypes();
Lorenzo Colitti7f9734f2019-05-09 12:13:54 +09002395 mMetricsLog.log(mCleartextDnsNetwork, transports, new NetworkEvent(evtype));
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002396 }
2397
2398 private int networkEventType(ValidationStage s, EvaluationResult r) {
2399 if (s.mIsFirstValidation) {
2400 if (r.mIsValidated) {
2401 return NetworkEvent.NETWORK_FIRST_VALIDATION_SUCCESS;
2402 } else {
2403 return NetworkEvent.NETWORK_FIRST_VALIDATION_PORTAL_FOUND;
2404 }
2405 } else {
2406 if (r.mIsValidated) {
2407 return NetworkEvent.NETWORK_REVALIDATION_SUCCESS;
2408 } else {
2409 return NetworkEvent.NETWORK_REVALIDATION_PORTAL_FOUND;
2410 }
2411 }
2412 }
2413
2414 private void maybeLogEvaluationResult(int evtype) {
2415 if (mEvaluationTimer.isRunning()) {
2416 int[] transports = mNetworkCapabilities.getTransportTypes();
Lorenzo Colitti7f9734f2019-05-09 12:13:54 +09002417 mMetricsLog.log(mCleartextDnsNetwork, transports,
Remi NGUYEN VANa0983f72019-01-20 16:50:42 +09002418 new NetworkEvent(evtype, mEvaluationTimer.stop()));
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002419 mEvaluationTimer.reset();
2420 }
2421 }
2422
2423 private void logValidationProbe(long durationMs, int probeType, int probeResult) {
2424 int[] transports = mNetworkCapabilities.getTransportTypes();
2425 boolean isFirstValidation = validationStage().mIsFirstValidation;
Remi NGUYEN VANc148d8b2019-01-19 21:13:24 +09002426 ValidationProbeEvent ev = new ValidationProbeEvent.Builder()
2427 .setProbeType(probeType, isFirstValidation)
2428 .setReturnCode(probeResult)
2429 .setDurationMs(durationMs)
2430 .build();
Lorenzo Colitti7f9734f2019-05-09 12:13:54 +09002431 mMetricsLog.log(mCleartextDnsNetwork, transports, ev);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002432 }
2433
2434 @VisibleForTesting
Remi NGUYEN VANea9f7e32019-06-20 18:49:48 +09002435 public static class Dependencies {
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002436 public Network getPrivateDnsBypassNetwork(Network network) {
2437 return new OneAddressPerFamilyNetwork(network);
2438 }
2439
Lorenzo Colitti171cfd22019-04-18 13:44:32 +09002440 public DnsResolver getDnsResolver() {
2441 return DnsResolver.getInstance();
2442 }
2443
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002444 public Random getRandom() {
2445 return new Random();
2446 }
2447
2448 /**
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002449 * Get the value of a global integer setting.
2450 * @param symbol Name of the setting
2451 * @param defaultValue Value to return if the setting is not defined.
2452 */
2453 public int getSetting(Context context, String symbol, int defaultValue) {
2454 return Settings.Global.getInt(context.getContentResolver(), symbol, defaultValue);
2455 }
2456
2457 /**
2458 * Get the value of a global String setting.
2459 * @param symbol Name of the setting
2460 * @param defaultValue Value to return if the setting is not defined.
2461 */
2462 public String getSetting(Context context, String symbol, String defaultValue) {
2463 final String value = Settings.Global.getString(context.getContentResolver(), symbol);
2464 return value != null ? value : defaultValue;
2465 }
2466
Chiachang Wang9a87f802019-04-08 19:06:21 +08002467 /**
2468 * Look up the value of a property in DeviceConfig.
2469 * @param namespace The namespace containing the property to look up.
2470 * @param name The name of the property to look up.
2471 * @param defaultValue The value to return if the property does not exist or has no non-null
2472 * value.
2473 * @return the corresponding value, or defaultValue if none exists.
2474 */
2475 @Nullable
2476 public String getDeviceConfigProperty(@NonNull String namespace, @NonNull String name,
2477 @Nullable String defaultValue) {
2478 return NetworkStackUtils.getDeviceConfigProperty(namespace, name, defaultValue);
2479 }
2480
2481 /**
2482 * Look up the value of a property in DeviceConfig.
2483 * @param namespace The namespace containing the property to look up.
2484 * @param name The name of the property to look up.
2485 * @param defaultValue The value to return if the property does not exist or has no non-null
2486 * value.
2487 * @return the corresponding value, or defaultValue if none exists.
2488 */
2489 public int getDeviceConfigPropertyInt(@NonNull String namespace, @NonNull String name,
2490 int defaultValue) {
2491 return NetworkStackUtils.getDeviceConfigPropertyInt(namespace, name, defaultValue);
2492 }
2493
Remi NGUYEN VANea9f7e32019-06-20 18:49:48 +09002494 /**
Remi NGUYEN VAN75e9d902020-04-09 06:41:16 +00002495 * Check whether or not one experimental feature in the connectivity namespace is
2496 * enabled.
2497 * @param name Flag name of the experiment in the connectivity namespace.
2498 * @see NetworkStackUtils#isFeatureEnabled(Context, String, String)
2499 */
2500 public boolean isFeatureEnabled(@NonNull Context context, @NonNull String name) {
2501 return NetworkStackUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name);
2502 }
2503
2504 /**
Remi NGUYEN VANea9f7e32019-06-20 18:49:48 +09002505 * Send a broadcast indicating network conditions.
2506 */
2507 public void sendNetworkConditionsBroadcast(@NonNull Context context,
2508 @NonNull Intent broadcast) {
2509 context.sendBroadcastAsUser(broadcast, UserHandle.CURRENT,
2510 NetworkMonitorUtils.PERMISSION_ACCESS_NETWORK_CONDITIONS);
2511 }
2512
Automerger Merge Workerfaac06e2020-03-09 09:23:38 +00002513 /**
2514 * Check whether or not one specific experimental feature for a particular namespace from
2515 * {@link DeviceConfig} is enabled by comparing NetworkStack module version
2516 * {@link NetworkStack} with current version of property. If this property version is valid,
2517 * the corresponding experimental feature would be enabled, otherwise disabled.
2518 * @param context The global context information about an app environment.
2519 * @param namespace The namespace containing the property to look up.
2520 * @param name The name of the property to look up.
2521 * @param defaultEnabled The value to return if the property does not exist or its value is
2522 * null.
2523 * @return true if this feature is enabled, or false if disabled.
2524 */
2525 public boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace,
2526 @NonNull String name, boolean defaultEnabled) {
2527 return NetworkStackUtils.isFeatureEnabled(context, namespace, name, defaultEnabled);
2528 }
2529
Chiachang Wang9b105a92020-03-30 09:12:37 +00002530 /**
2531 * Collect data stall detection level information for each transport type. Write metrics
2532 * data to statsd pipeline.
2533 * @param stats a {@link DataStallDetectionStats} that contains the detection level
2534 * information.
2535 * @para result the network reevaluation result.
2536 */
2537 public void writeDataStallDetectionStats(@NonNull final DataStallDetectionStats stats,
2538 @NonNull final CaptivePortalProbeResult result) {
2539 DataStallStatsUtils.write(stats, result);
2540 }
2541
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002542 public static final Dependencies DEFAULT = new Dependencies();
2543 }
2544
2545 /**
2546 * Methods in this class perform no locking because all accesses are performed on the state
2547 * machine's thread. Need to consider the thread safety if it ever could be accessed outside the
2548 * state machine.
2549 */
2550 @VisibleForTesting
2551 protected class DnsStallDetector {
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002552 private int mConsecutiveTimeoutCount = 0;
2553 private int mSize;
2554 final DnsResult[] mDnsEvents;
2555 final RingBufferIndices mResultIndices;
2556
2557 DnsStallDetector(int size) {
2558 mSize = Math.max(DEFAULT_DNS_LOG_SIZE, size);
2559 mDnsEvents = new DnsResult[mSize];
2560 mResultIndices = new RingBufferIndices(mSize);
2561 }
2562
2563 @VisibleForTesting
2564 protected void accumulateConsecutiveDnsTimeoutCount(int code) {
2565 final DnsResult result = new DnsResult(code);
2566 mDnsEvents[mResultIndices.add()] = result;
2567 if (result.isTimeout()) {
2568 mConsecutiveTimeoutCount++;
2569 } else {
2570 // Keep the event in mDnsEvents without clearing it so that there are logs to do the
2571 // simulation and analysis.
2572 mConsecutiveTimeoutCount = 0;
2573 }
2574 }
2575
2576 private boolean isDataStallSuspected(int timeoutCountThreshold, int validTime) {
2577 if (timeoutCountThreshold <= 0) {
2578 Log.wtf(TAG, "Timeout count threshold should be larger than 0.");
2579 return false;
2580 }
2581
2582 // Check if the consecutive timeout count reach the threshold or not.
2583 if (mConsecutiveTimeoutCount < timeoutCountThreshold) {
2584 return false;
2585 }
2586
2587 // Check if the target dns event index is valid or not.
2588 final int firstConsecutiveTimeoutIndex =
2589 mResultIndices.indexOf(mResultIndices.size() - timeoutCountThreshold);
2590
2591 // If the dns timeout events happened long time ago, the events are meaningless for
2592 // data stall evaluation. Thus, check if the first consecutive timeout dns event
2593 // considered in the evaluation happened in defined threshold time.
2594 final long now = SystemClock.elapsedRealtime();
2595 final long firstTimeoutTime = now - mDnsEvents[firstConsecutiveTimeoutIndex].mTimeStamp;
2596 return (firstTimeoutTime < validTime);
2597 }
2598
2599 int getConsecutiveTimeoutCount() {
2600 return mConsecutiveTimeoutCount;
2601 }
2602 }
2603
2604 private static class DnsResult {
2605 // TODO: Need to move the DNS return code definition to a specific class once unify DNS
2606 // response code is done.
2607 private static final int RETURN_CODE_DNS_TIMEOUT = 255;
2608
2609 private final long mTimeStamp;
2610 private final int mReturnCode;
2611
2612 DnsResult(int code) {
2613 mTimeStamp = SystemClock.elapsedRealtime();
2614 mReturnCode = code;
2615 }
2616
2617 private boolean isTimeout() {
2618 return mReturnCode == RETURN_CODE_DNS_TIMEOUT;
2619 }
2620 }
2621
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002622 @VisibleForTesting
Chiachang Wang8e232042020-01-17 18:01:59 +08002623 @Nullable
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002624 protected DnsStallDetector getDnsStallDetector() {
2625 return mDnsStallDetector;
2626 }
2627
Chiachang Wanga5716bf2019-11-20 16:13:07 +08002628 @VisibleForTesting
Chiachang Wange797c9b2019-11-28 14:18:47 +08002629 @Nullable
Chiachang Wanga5716bf2019-11-20 16:13:07 +08002630 protected TcpSocketTracker getTcpSocketTracker() {
2631 return mTcpTracker;
2632 }
2633
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002634 private boolean dataStallEvaluateTypeEnabled(int type) {
Chiachang Wang0e874792019-03-05 20:31:57 +08002635 return (mDataStallEvaluationType & type) != 0;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002636 }
2637
2638 @VisibleForTesting
2639 protected long getLastProbeTime() {
2640 return mLastProbeTime;
2641 }
2642
2643 @VisibleForTesting
2644 protected boolean isDataStall() {
Chiachang Wangb6dbfb42020-01-21 14:47:34 +08002645 if (!isValidationRequired()) {
2646 return false;
2647 }
2648
Chiachang Wanga5716bf2019-11-20 16:13:07 +08002649 Boolean result = null;
Chiachang Wangd740dc32020-01-14 20:49:17 +08002650 final StringJoiner msg = (DBG || VDBG_STALL) ? new StringJoiner(", ") : null;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002651 // Reevaluation will generate traffic. Thus, set a minimal reevaluation timer to limit the
2652 // possible traffic cost in metered network.
Remi NGUYEN VANa0983f72019-01-20 16:50:42 +09002653 if (!mNetworkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED)
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002654 && (SystemClock.elapsedRealtime() - getLastProbeTime()
2655 < mDataStallMinEvaluateTime)) {
2656 return false;
2657 }
Chiachang Wanga5716bf2019-11-20 16:13:07 +08002658 // Check TCP signal. Suspect it may be a data stall if :
2659 // 1. TCP connection fail rate(lost+retrans) is higher than threshold.
2660 // 2. Accumulate enough packets count.
Chiachang Wange797c9b2019-11-28 14:18:47 +08002661 final TcpSocketTracker tst = getTcpSocketTracker();
2662 if (dataStallEvaluateTypeEnabled(DATA_STALL_EVALUATION_TYPE_TCP) && tst != null) {
2663 if (tst.getLatestReceivedCount() > 0) {
Chiachang Wanga5716bf2019-11-20 16:13:07 +08002664 result = false;
Chiachang Wange797c9b2019-11-28 14:18:47 +08002665 } else if (tst.isDataStallSuspected()) {
Chiachang Wanga5716bf2019-11-20 16:13:07 +08002666 result = true;
Cody Kesting782cbfa2020-01-06 15:51:59 -08002667
Cody Kesting176bce72020-01-20 18:09:59 -08002668 final PersistableBundle extras = new PersistableBundle();
2669 extras.putInt(KEY_TCP_PACKET_FAIL_RATE, tst.getLatestPacketFailPercentage());
2670 extras.putInt(KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS, getTcpPollingInterval());
2671 notifyDataStallSuspected(DETECTION_METHOD_TCP_METRICS, extras);
Chiachang Wanga5716bf2019-11-20 16:13:07 +08002672 }
Chiachang Wangd740dc32020-01-14 20:49:17 +08002673 if (DBG || VDBG_STALL) {
Chiachang Wange797c9b2019-11-28 14:18:47 +08002674 msg.add("tcp packets received=" + tst.getLatestReceivedCount())
Chiachang Wangd740dc32020-01-14 20:49:17 +08002675 .add("latest tcp fail rate=" + tst.getLatestPacketFailPercentage());
Chiachang Wange797c9b2019-11-28 14:18:47 +08002676 }
Chiachang Wanga5716bf2019-11-20 16:13:07 +08002677 }
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002678
2679 // Check dns signal. Suspect it may be a data stall if both :
Chiachang Wang0e874792019-03-05 20:31:57 +08002680 // 1. The number of consecutive DNS query timeouts >= mConsecutiveDnsTimeoutThreshold.
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002681 // 2. Those consecutive DNS queries happened in the last mValidDataStallDnsTimeThreshold ms.
Chiachang Wang8e232042020-01-17 18:01:59 +08002682 final DnsStallDetector dsd = getDnsStallDetector();
2683 if ((result == null) && (dsd != null)
2684 && dataStallEvaluateTypeEnabled(DATA_STALL_EVALUATION_TYPE_DNS)) {
2685 if (dsd.isDataStallSuspected(mConsecutiveDnsTimeoutThreshold,
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002686 mDataStallValidDnsTimeThreshold)) {
2687 result = true;
2688 logNetworkEvent(NetworkEvent.NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND);
Cody Kesting782cbfa2020-01-06 15:51:59 -08002689
Cody Kesting176bce72020-01-20 18:09:59 -08002690 final PersistableBundle extras = new PersistableBundle();
2691 extras.putInt(KEY_DNS_CONSECUTIVE_TIMEOUTS,
2692 mDnsStallDetector.getConsecutiveTimeoutCount());
2693 notifyDataStallSuspected(DETECTION_METHOD_DNS_EVENTS, extras);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002694 }
Chiachang Wangd740dc32020-01-14 20:49:17 +08002695 if (DBG || VDBG_STALL) {
Chiachang Wang8e232042020-01-17 18:01:59 +08002696 msg.add("consecutive dns timeout count=" + dsd.getConsecutiveTimeoutCount());
Chiachang Wange797c9b2019-11-28 14:18:47 +08002697 }
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002698 }
Chiachang Wangd740dc32020-01-14 20:49:17 +08002699 // log only data stall suspected.
2700 if ((DBG && Boolean.TRUE.equals(result)) || VDBG_STALL) {
Chiachang Wange797c9b2019-11-28 14:18:47 +08002701 log("isDataStall: result=" + result + ", " + msg);
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002702 }
2703
Chiachang Wanga5716bf2019-11-20 16:13:07 +08002704 return (result == null) ? false : result;
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002705 }
Chiachang Wang813ee472019-05-23 16:29:30 +08002706
2707 // Class to keep state of evaluation results and probe results.
Lorenzo Colittiaa16f2d2019-06-07 18:42:34 +09002708 //
2709 // The main purpose was to ensure NetworkMonitor can notify ConnectivityService of probe results
Chiachang Wang813ee472019-05-23 16:29:30 +08002710 // as soon as they happen, without triggering any other changes. This requires keeping state on
Lorenzo Colittiaa16f2d2019-06-07 18:42:34 +09002711 // the most recent evaluation result. Calling noteProbeResult will ensure that the results
Chiachang Wang813ee472019-05-23 16:29:30 +08002712 // reported to ConnectivityService contain the previous evaluation result, and thus won't
2713 // trigger a validation or partial connectivity state change.
Lorenzo Colittiaa16f2d2019-06-07 18:42:34 +09002714 //
2715 // Note that this class is not currently being used for this purpose. The reason is that some
2716 // of the system behaviour triggered by reporting network validation - notably, NetworkAgent
2717 // behaviour - depends not only on the value passed by notifyNetworkTested, but also on the
2718 // fact that notifyNetworkTested was called. For example, telephony triggers network recovery
2719 // any time it is told that validation failed, i.e., if the result does not contain
2720 // NETWORK_VALIDATION_RESULT_VALID. But with this scheme, the first two or three validation
2721 // reports are all failures, because they are "HTTP succeeded but validation not yet passed",
2722 // "HTTP and HTTPS succeeded but validation not yet passed", etc.
Chiachang Wang813ee472019-05-23 16:29:30 +08002723 @VisibleForTesting
2724 protected class EvaluationState {
2725 // The latest validation result for this network. This is a bitmask of
2726 // INetworkMonitor.NETWORK_VALIDATION_RESULT_* constants.
2727 private int mEvaluationResult = NETWORK_VALIDATION_RESULT_INVALID;
2728 // Indicates which probes have completed since clearProbeResults was called.
2729 // This is a bitmask of INetworkMonitor.NETWORK_VALIDATION_PROBE_* constants.
2730 private int mProbeResults = 0;
lucaslin2ce7dcc2019-10-22 16:59:39 +08002731 // A bitmask to record which probes are completed.
2732 private int mProbeCompleted = 0;
Chiachang Wang813ee472019-05-23 16:29:30 +08002733 // The latest redirect URL.
2734 private String mRedirectUrl;
2735
2736 protected void clearProbeResults() {
2737 mProbeResults = 0;
lucaslin2ce7dcc2019-10-22 16:59:39 +08002738 mProbeCompleted = 0;
Chiachang Wang813ee472019-05-23 16:29:30 +08002739 }
2740
lucaslin2ce7dcc2019-10-22 16:59:39 +08002741 private void maybeNotifyProbeResults(@NonNull final Runnable modif) {
2742 final int oldCompleted = mProbeCompleted;
2743 final int oldResults = mProbeResults;
2744 modif.run();
2745 if (oldCompleted != mProbeCompleted || oldResults != mProbeResults) {
2746 notifyProbeStatusChanged(mProbeCompleted, mProbeResults);
Chiachang Wang813ee472019-05-23 16:29:30 +08002747 }
Chiachang Wang813ee472019-05-23 16:29:30 +08002748 }
2749
lucaslin2ce7dcc2019-10-22 16:59:39 +08002750 protected void removeProbeResult(final int probeResult) {
2751 maybeNotifyProbeResults(() -> {
2752 mProbeCompleted &= ~probeResult;
2753 mProbeResults &= ~probeResult;
2754 });
2755 }
2756
2757 protected void noteProbeResult(final int probeResult, final boolean succeeded) {
2758 maybeNotifyProbeResults(() -> {
2759 mProbeCompleted |= probeResult;
2760 if (succeeded) {
2761 mProbeResults |= probeResult;
2762 } else {
2763 mProbeResults &= ~probeResult;
2764 }
2765 });
2766 }
2767
Chiachang Wang813ee472019-05-23 16:29:30 +08002768 protected void reportEvaluationResult(int result, @Nullable String redirectUrl) {
2769 mEvaluationResult = result;
2770 mRedirectUrl = redirectUrl;
Cody Kesting176bce72020-01-20 18:09:59 -08002771 final PersistableBundle extras = new PersistableBundle();
2772
2773 extras.putInt(KEY_NETWORK_VALIDATION_RESULT, result);
2774 extras.putInt(KEY_NETWORK_PROBES_SUCCEEDED_BITMASK, mProbeResults);
2775 extras.putInt(KEY_NETWORK_PROBES_ATTEMPTED_BITMASK, mProbeCompleted);
2776 notifyNetworkTested(getNetworkTestResult(), mRedirectUrl, extras);
Chiachang Wang813ee472019-05-23 16:29:30 +08002777 }
2778
2779 protected int getNetworkTestResult() {
Remi NGUYEN VANb4af1302019-06-11 16:17:46 +09002780 if (mCallbackVersion < 3) {
2781 if ((mEvaluationResult & NETWORK_VALIDATION_RESULT_VALID) != 0) {
2782 return NETWORK_TEST_RESULT_VALID;
2783 }
2784 if ((mEvaluationResult & NETWORK_VALIDATION_RESULT_PARTIAL) != 0) {
2785 return NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY;
2786 }
2787 return NETWORK_TEST_RESULT_INVALID;
2788 }
Chiachang Wang813ee472019-05-23 16:29:30 +08002789 return mEvaluationResult | mProbeResults;
2790 }
lucaslin2ce7dcc2019-10-22 16:59:39 +08002791
2792 @VisibleForTesting
2793 protected int getProbeCompletedResult() {
2794 return mProbeCompleted;
2795 }
Chiachang Wang813ee472019-05-23 16:29:30 +08002796 }
2797
2798 @VisibleForTesting
2799 protected EvaluationState getEvaluationState() {
2800 return mEvaluationState;
2801 }
2802
2803 private void maybeDisableHttpsProbing(boolean acceptPartial) {
2804 mAcceptPartialConnectivity = acceptPartial;
2805 // Ignore https probe in next validation if user accept partial connectivity on a partial
2806 // connectivity network.
2807 if (((mEvaluationState.getNetworkTestResult() & NETWORK_VALIDATION_RESULT_PARTIAL) != 0)
2808 && mAcceptPartialConnectivity) {
2809 mUseHttps = false;
2810 }
2811 }
2812
2813 // Report HTTP, HTTP or FALLBACK probe result.
2814 @VisibleForTesting
2815 protected void reportHttpProbeResult(int probeResult,
2816 @NonNull final CaptivePortalProbeResult result) {
2817 boolean succeeded = result.isSuccessful();
2818 // The success of a HTTP probe does not tell us whether the DNS probe succeeded.
2819 // The DNS and HTTP probes run one after the other in sendDnsAndHttpProbes, and that
2820 // method cannot report the result of the DNS probe because that it could be running
2821 // on a different thread which is racing with the main state machine thread. So, if
2822 // an HTTP or HTTPS probe succeeded, assume that the DNS probe succeeded. But if an
2823 // HTTP or HTTPS probe failed, don't assume that DNS is not working.
2824 // TODO: fix this.
2825 if (succeeded) {
2826 probeResult |= NETWORK_VALIDATION_PROBE_DNS;
2827 }
Lorenzo Colittiaa16f2d2019-06-07 18:42:34 +09002828 mEvaluationState.noteProbeResult(probeResult, succeeded);
Chiachang Wang813ee472019-05-23 16:29:30 +08002829 }
Chiachang Wangddb7da62019-06-03 15:50:53 +08002830
Remi NGUYEN VAN2fa48c22019-12-09 16:40:02 +09002831 private void maybeReportCaptivePortalData(@Nullable CaptivePortalDataShim data) {
2832 // Do not clear data even if it is null: access points should not stop serving the API, so
2833 // if the API disappears this is treated as a temporary failure, and previous data should
2834 // remain valid.
2835 if (data == null) return;
2836 try {
2837 data.notifyChanged(mCallback);
2838 } catch (RemoteException e) {
2839 Log.e(TAG, "Error notifying ConnectivityService of new capport data", e);
2840 }
2841 }
2842
Chiachang Wangddb7da62019-06-03 15:50:53 +08002843 /**
2844 * Interface for logging dns results.
2845 */
2846 public interface DnsLogFunc {
2847 /**
2848 * Log function.
2849 */
2850 void log(String s);
2851 }
Chiachang Wange797c9b2019-11-28 14:18:47 +08002852
2853 @Nullable
Chiachang Wangcded6ce2019-12-18 17:27:57 +08002854 private static TcpSocketTracker getTcpSocketTrackerOrNull(Context context, Network network) {
Chiachang Wange797c9b2019-11-28 14:18:47 +08002855 return ((Dependencies.DEFAULT.getDeviceConfigPropertyInt(
2856 NAMESPACE_CONNECTIVITY,
2857 CONFIG_DATA_STALL_EVALUATION_TYPE,
2858 DEFAULT_DATA_STALL_EVALUATION_TYPES)
2859 & DATA_STALL_EVALUATION_TYPE_TCP) != 0)
2860 ? new TcpSocketTracker(new TcpSocketTracker.Dependencies(context,
Chiachang Wangcded6ce2019-12-18 17:27:57 +08002861 ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q)), network)
Chiachang Wange797c9b2019-11-28 14:18:47 +08002862 : null;
2863 }
Chiachang Wang8e232042020-01-17 18:01:59 +08002864
2865 @Nullable
2866 private DnsStallDetector initDnsStallDetectorIfRequired(int type, int threshold) {
2867 return ((type & DATA_STALL_EVALUATION_TYPE_DNS) != 0)
2868 ? new DnsStallDetector(threshold) : null;
2869 }
Remi NGUYEN VAN2fa48c22019-12-09 16:40:02 +09002870
2871 private static Uri getCaptivePortalApiUrl(LinkProperties lp) {
2872 return NetworkInformationShimImpl.newInstance().getCaptivePortalApiUrl(lp);
2873 }
Remi NGUYEN VAN5daa3702018-12-27 16:43:56 +09002874}