• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.server.connectivity;
18 
19 import static android.net.CaptivePortal.APP_RETURN_DISMISSED;
20 import static android.net.CaptivePortal.APP_RETURN_UNWANTED;
21 import static android.net.CaptivePortal.APP_RETURN_WANTED_AS_IS;
22 import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_PROBE_SPEC;
23 import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL;
24 import static android.net.ConnectivityManager.TYPE_MOBILE;
25 import static android.net.ConnectivityManager.TYPE_WIFI;
26 import static android.net.DnsResolver.FLAG_EMPTY;
27 import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
28 import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY;
29 import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID;
30 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS;
31 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_FALLBACK;
32 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTP;
33 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTPS;
34 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS;
35 import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
36 import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID;
37 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
38 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
39 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
40 import static android.net.captiveportal.CaptivePortalProbeSpec.parseCaptivePortalProbeSpecs;
41 import static android.net.metrics.ValidationProbeEvent.DNS_FAILURE;
42 import static android.net.metrics.ValidationProbeEvent.DNS_SUCCESS;
43 import static android.net.metrics.ValidationProbeEvent.PROBE_FALLBACK;
44 import static android.net.metrics.ValidationProbeEvent.PROBE_PRIVDNS;
45 import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD;
46 import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_EVALUATION_TYPE;
47 import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_MIN_EVALUATE_INTERVAL;
48 import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_VALID_DNS_TIME_THRESHOLD;
49 import static android.net.util.DataStallUtils.DATA_STALL_EVALUATION_TYPE_DNS;
50 import static android.net.util.DataStallUtils.DEFAULT_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD;
51 import static android.net.util.DataStallUtils.DEFAULT_DATA_STALL_EVALUATION_TYPES;
52 import static android.net.util.DataStallUtils.DEFAULT_DATA_STALL_MIN_EVALUATE_TIME_MS;
53 import static android.net.util.DataStallUtils.DEFAULT_DATA_STALL_VALID_DNS_TIME_THRESHOLD_MS;
54 import static android.net.util.DataStallUtils.DEFAULT_DNS_LOG_SIZE;
55 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS;
56 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_FALLBACK_URL;
57 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_HTTPS_URL;
58 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_HTTP_URL;
59 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_MODE;
60 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_MODE_IGNORE;
61 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_MODE_PROMPT;
62 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS;
63 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_USER_AGENT;
64 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_USE_HTTPS;
65 import static android.net.util.NetworkStackUtils.NAMESPACE_CONNECTIVITY;
66 import static android.net.util.NetworkStackUtils.isEmpty;
67 
68 import static com.android.networkstack.util.DnsUtils.TYPE_ADDRCONFIG;
69 
70 import android.annotation.NonNull;
71 import android.annotation.Nullable;
72 import android.app.PendingIntent;
73 import android.content.BroadcastReceiver;
74 import android.content.Context;
75 import android.content.Intent;
76 import android.content.IntentFilter;
77 import android.content.res.Resources;
78 import android.net.ConnectivityManager;
79 import android.net.DnsResolver;
80 import android.net.INetworkMonitorCallbacks;
81 import android.net.LinkProperties;
82 import android.net.Network;
83 import android.net.NetworkCapabilities;
84 import android.net.ProxyInfo;
85 import android.net.TrafficStats;
86 import android.net.Uri;
87 import android.net.captiveportal.CaptivePortalProbeResult;
88 import android.net.captiveportal.CaptivePortalProbeSpec;
89 import android.net.metrics.IpConnectivityLog;
90 import android.net.metrics.NetworkEvent;
91 import android.net.metrics.ValidationProbeEvent;
92 import android.net.shared.NetworkMonitorUtils;
93 import android.net.shared.PrivateDnsConfig;
94 import android.net.util.NetworkStackUtils;
95 import android.net.util.SharedLog;
96 import android.net.util.Stopwatch;
97 import android.net.wifi.WifiInfo;
98 import android.net.wifi.WifiManager;
99 import android.os.Build;
100 import android.os.Bundle;
101 import android.os.Message;
102 import android.os.RemoteException;
103 import android.os.SystemClock;
104 import android.os.UserHandle;
105 import android.provider.Settings;
106 import android.telephony.AccessNetworkConstants;
107 import android.telephony.CellSignalStrength;
108 import android.telephony.NetworkRegistrationInfo;
109 import android.telephony.ServiceState;
110 import android.telephony.SignalStrength;
111 import android.telephony.TelephonyManager;
112 import android.text.TextUtils;
113 import android.util.Log;
114 import android.util.Pair;
115 
116 import androidx.annotation.ArrayRes;
117 import androidx.annotation.StringRes;
118 
119 import com.android.internal.annotations.VisibleForTesting;
120 import com.android.internal.util.RingBufferIndices;
121 import com.android.internal.util.State;
122 import com.android.internal.util.StateMachine;
123 import com.android.internal.util.TrafficStatsConstants;
124 import com.android.networkstack.R;
125 import com.android.networkstack.metrics.DataStallDetectionStats;
126 import com.android.networkstack.metrics.DataStallStatsUtils;
127 import com.android.networkstack.util.DnsUtils;
128 
129 import java.io.IOException;
130 import java.net.HttpURLConnection;
131 import java.net.InetAddress;
132 import java.net.MalformedURLException;
133 import java.net.URL;
134 import java.net.UnknownHostException;
135 import java.util.ArrayList;
136 import java.util.Arrays;
137 import java.util.Collections;
138 import java.util.LinkedHashMap;
139 import java.util.List;
140 import java.util.Random;
141 import java.util.UUID;
142 import java.util.concurrent.CountDownLatch;
143 import java.util.concurrent.TimeUnit;
144 import java.util.function.Function;
145 
146 /**
147  * {@hide}
148  */
149 public class NetworkMonitor extends StateMachine {
150     private static final String TAG = NetworkMonitor.class.getSimpleName();
151     private static final boolean DBG  = true;
152     private static final boolean VDBG = false;
153     private static final boolean VDBG_STALL = Log.isLoggable(TAG, Log.DEBUG);
154     private static final String DEFAULT_USER_AGENT    = "Mozilla/5.0 (X11; Linux x86_64) "
155                                                       + "AppleWebKit/537.36 (KHTML, like Gecko) "
156                                                       + "Chrome/60.0.3112.32 Safari/537.36";
157 
158     @VisibleForTesting
159     static final String CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT =
160             "captive_portal_dns_probe_timeout";
161 
162     private static final int SOCKET_TIMEOUT_MS = 10000;
163     private static final int PROBE_TIMEOUT_MS  = 3000;
164 
165     enum EvaluationResult {
166         VALIDATED(true),
167         CAPTIVE_PORTAL(false);
168         final boolean mIsValidated;
EvaluationResult(boolean isValidated)169         EvaluationResult(boolean isValidated) {
170             this.mIsValidated = isValidated;
171         }
172     }
173 
174     enum ValidationStage {
175         FIRST_VALIDATION(true),
176         REVALIDATION(false);
177         final boolean mIsFirstValidation;
ValidationStage(boolean isFirstValidation)178         ValidationStage(boolean isFirstValidation) {
179             this.mIsFirstValidation = isFirstValidation;
180         }
181     }
182 
183     /**
184      * ConnectivityService has sent a notification to indicate that network has connected.
185      * Initiates Network Validation.
186      */
187     private static final int CMD_NETWORK_CONNECTED = 1;
188 
189     /**
190      * Message to self indicating it's time to evaluate a network's connectivity.
191      * arg1 = Token to ignore old messages.
192      */
193     private static final int CMD_REEVALUATE = 6;
194 
195     /**
196      * ConnectivityService has sent a notification to indicate that network has disconnected.
197      */
198     private static final int CMD_NETWORK_DISCONNECTED = 7;
199 
200     /**
201      * Force evaluation even if it has succeeded in the past.
202      * arg1 = UID responsible for requesting this reeval.  Will be billed for data.
203      */
204     private static final int CMD_FORCE_REEVALUATION = 8;
205 
206     /**
207      * Message to self indicating captive portal app finished.
208      * arg1 = one of: APP_RETURN_DISMISSED,
209      *                APP_RETURN_UNWANTED,
210      *                APP_RETURN_WANTED_AS_IS
211      * obj = mCaptivePortalLoggedInResponseToken as String
212      */
213     private static final int CMD_CAPTIVE_PORTAL_APP_FINISHED = 9;
214 
215     /**
216      * Message indicating sign-in app should be launched.
217      * Sent by mLaunchCaptivePortalAppBroadcastReceiver when the
218      * user touches the sign in notification, or sent by
219      * ConnectivityService when the user touches the "sign into
220      * network" button in the wifi access point detail page.
221      */
222     private static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = 11;
223 
224     /**
225      * Retest network to see if captive portal is still in place.
226      * arg1 = UID responsible for requesting this reeval.  Will be billed for data.
227      *        0 indicates self-initiated, so nobody to blame.
228      */
229     private static final int CMD_CAPTIVE_PORTAL_RECHECK = 12;
230 
231     /**
232      * ConnectivityService notifies NetworkMonitor of settings changes to
233      * Private DNS. If a DNS resolution is required, e.g. for DNS-over-TLS in
234      * strict mode, then an event is sent back to ConnectivityService with the
235      * result of the resolution attempt.
236      *
237      * A separate message is used to trigger (re)evaluation of the Private DNS
238      * configuration, so that the message can be handled as needed in different
239      * states, including being ignored until after an ongoing captive portal
240      * validation phase is completed.
241      */
242     private static final int CMD_PRIVATE_DNS_SETTINGS_CHANGED = 13;
243     private static final int CMD_EVALUATE_PRIVATE_DNS = 15;
244 
245     /**
246      * Message to self indicating captive portal detection is completed.
247      * obj = CaptivePortalProbeResult for detection result;
248      */
249     private static final int CMD_PROBE_COMPLETE = 16;
250 
251     /**
252      * ConnectivityService notifies NetworkMonitor of DNS query responses event.
253      * arg1 = returncode in OnDnsEvent which indicates the response code for the DNS query.
254      */
255     private static final int EVENT_DNS_NOTIFICATION = 17;
256 
257     /**
258      * ConnectivityService notifies NetworkMonitor that the user accepts partial connectivity and
259      * NetworkMonitor should ignore the https probe.
260      */
261     private static final int EVENT_ACCEPT_PARTIAL_CONNECTIVITY = 18;
262 
263     /**
264      * ConnectivityService notifies NetworkMonitor of changed LinkProperties.
265      * obj = new LinkProperties.
266      */
267     private static final int EVENT_LINK_PROPERTIES_CHANGED = 19;
268 
269     /**
270      * ConnectivityService notifies NetworkMonitor of changed NetworkCapabilities.
271      * obj = new NetworkCapabilities.
272      */
273     private static final int EVENT_NETWORK_CAPABILITIES_CHANGED = 20;
274 
275     // Start mReevaluateDelayMs at this value and double.
276     private static final int INITIAL_REEVALUATE_DELAY_MS = 1000;
277     private static final int MAX_REEVALUATE_DELAY_MS = 10 * 60 * 1000;
278     // Before network has been evaluated this many times, ignore repeated reevaluate requests.
279     private static final int IGNORE_REEVALUATE_ATTEMPTS = 5;
280     private int mReevaluateToken = 0;
281     private static final int NO_UID = 0;
282     private static final int INVALID_UID = -1;
283     private int mUidResponsibleForReeval = INVALID_UID;
284     // Stop blaming UID that requested re-evaluation after this many attempts.
285     private static final int BLAME_FOR_EVALUATION_ATTEMPTS = 5;
286     // Delay between reevaluations once a captive portal has been found.
287     private static final int CAPTIVE_PORTAL_REEVALUATE_DELAY_MS = 10 * 60 * 1000;
288     private static final int NETWORK_VALIDATION_RESULT_INVALID = 0;
289     private String mPrivateDnsProviderHostname = "";
290 
291     private final Context mContext;
292     private final INetworkMonitorCallbacks mCallback;
293     private final int mCallbackVersion;
294     private final Network mCleartextDnsNetwork;
295     private final Network mNetwork;
296     private final TelephonyManager mTelephonyManager;
297     private final WifiManager mWifiManager;
298     private final ConnectivityManager mCm;
299     private final IpConnectivityLog mMetricsLog;
300     private final Dependencies mDependencies;
301     private final DataStallStatsUtils mDetectionStatsUtils;
302 
303     // Configuration values for captive portal detection probes.
304     private final String mCaptivePortalUserAgent;
305     private final URL mCaptivePortalHttpsUrl;
306     private final URL mCaptivePortalHttpUrl;
307     private final URL[] mCaptivePortalFallbackUrls;
308     @Nullable
309     private final CaptivePortalProbeSpec[] mCaptivePortalFallbackSpecs;
310 
311     private NetworkCapabilities mNetworkCapabilities;
312     private LinkProperties mLinkProperties;
313 
314     @VisibleForTesting
315     protected boolean mIsCaptivePortalCheckEnabled;
316 
317     private boolean mUseHttps;
318     // The total number of captive portal detection attempts for this NetworkMonitor instance.
319     private int mValidations = 0;
320 
321     // Set if the user explicitly selected "Do not use this network" in captive portal sign-in app.
322     private boolean mUserDoesNotWant = false;
323     // Avoids surfacing "Sign in to network" notification.
324     private boolean mDontDisplaySigninNotification = false;
325 
326     private final State mDefaultState = new DefaultState();
327     private final State mValidatedState = new ValidatedState();
328     private final State mMaybeNotifyState = new MaybeNotifyState();
329     private final State mEvaluatingState = new EvaluatingState();
330     private final State mCaptivePortalState = new CaptivePortalState();
331     private final State mEvaluatingPrivateDnsState = new EvaluatingPrivateDnsState();
332     private final State mProbingState = new ProbingState();
333     private final State mWaitingForNextProbeState = new WaitingForNextProbeState();
334 
335     private CustomIntentReceiver mLaunchCaptivePortalAppBroadcastReceiver = null;
336 
337     private final SharedLog mValidationLogs;
338 
339     private final Stopwatch mEvaluationTimer = new Stopwatch();
340 
341     // This variable is set before transitioning to the mCaptivePortalState.
342     private CaptivePortalProbeResult mLastPortalProbeResult = CaptivePortalProbeResult.FAILED;
343 
344     // Random generator to select fallback URL index
345     private final Random mRandom;
346     private int mNextFallbackUrlIndex = 0;
347 
348 
349     private int mReevaluateDelayMs = INITIAL_REEVALUATE_DELAY_MS;
350     private int mEvaluateAttempts = 0;
351     private volatile int mProbeToken = 0;
352     private final int mConsecutiveDnsTimeoutThreshold;
353     private final int mDataStallMinEvaluateTime;
354     private final int mDataStallValidDnsTimeThreshold;
355     private final int mDataStallEvaluationType;
356     private final DnsStallDetector mDnsStallDetector;
357     private long mLastProbeTime;
358     // Set to true if data stall is suspected and reset to false after metrics are sent to statsd.
359     private boolean mCollectDataStallMetrics;
360     private boolean mAcceptPartialConnectivity = false;
361     private final EvaluationState mEvaluationState = new EvaluationState();
362 
getCallbackVersion(INetworkMonitorCallbacks cb)363     private int getCallbackVersion(INetworkMonitorCallbacks cb) {
364         int version;
365         try {
366             version = cb.getInterfaceVersion();
367         } catch (RemoteException e) {
368             version = 0;
369         }
370         if (version == Build.VERSION_CODES.CUR_DEVELOPMENT) version = 0;
371         return version;
372     }
373 
NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network, SharedLog validationLog)374     public NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network,
375             SharedLog validationLog) {
376         this(context, cb, network, new IpConnectivityLog(), validationLog,
377                 Dependencies.DEFAULT, new DataStallStatsUtils());
378     }
379 
380     @VisibleForTesting
NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network, IpConnectivityLog logger, SharedLog validationLogs, Dependencies deps, DataStallStatsUtils detectionStatsUtils)381     protected NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network,
382             IpConnectivityLog logger, SharedLog validationLogs,
383             Dependencies deps, DataStallStatsUtils detectionStatsUtils) {
384         // Add suffix indicating which NetworkMonitor we're talking about.
385         super(TAG + "/" + network.toString());
386 
387         // Logs with a tag of the form given just above, e.g.
388         //     <timestamp>   862  2402 D NetworkMonitor/NetworkAgentInfo [WIFI () - 100]: ...
389         setDbg(VDBG);
390 
391         mContext = context;
392         mMetricsLog = logger;
393         mValidationLogs = validationLogs;
394         mCallback = cb;
395         mCallbackVersion = getCallbackVersion(cb);
396         mDependencies = deps;
397         mDetectionStatsUtils = detectionStatsUtils;
398         mNetwork = network;
399         mCleartextDnsNetwork = deps.getPrivateDnsBypassNetwork(network);
400         mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
401         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
402         mCm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
403 
404         // CHECKSTYLE:OFF IndentationCheck
405         addState(mDefaultState);
406         addState(mMaybeNotifyState, mDefaultState);
407             addState(mEvaluatingState, mMaybeNotifyState);
408                 addState(mProbingState, mEvaluatingState);
409                 addState(mWaitingForNextProbeState, mEvaluatingState);
410             addState(mCaptivePortalState, mMaybeNotifyState);
411         addState(mEvaluatingPrivateDnsState, mDefaultState);
412         addState(mValidatedState, mDefaultState);
413         setInitialState(mDefaultState);
414         // CHECKSTYLE:ON IndentationCheck
415 
416         mIsCaptivePortalCheckEnabled = getIsCaptivePortalCheckEnabled();
417         mUseHttps = getUseHttpsValidation();
418         mCaptivePortalUserAgent = getCaptivePortalUserAgent();
419         mCaptivePortalHttpsUrl = makeURL(getCaptivePortalServerHttpsUrl());
420         mCaptivePortalHttpUrl = makeURL(getCaptivePortalServerHttpUrl());
421         mCaptivePortalFallbackUrls = makeCaptivePortalFallbackUrls();
422         mCaptivePortalFallbackSpecs = makeCaptivePortalFallbackProbeSpecs();
423         mRandom = deps.getRandom();
424         // TODO: Evaluate to move data stall configuration to a specific class.
425         mConsecutiveDnsTimeoutThreshold = getConsecutiveDnsTimeoutThreshold();
426         mDnsStallDetector = new DnsStallDetector(mConsecutiveDnsTimeoutThreshold);
427         mDataStallMinEvaluateTime = getDataStallMinEvaluateTime();
428         mDataStallValidDnsTimeThreshold = getDataStallValidDnsTimeThreshold();
429         mDataStallEvaluationType = getDataStallEvaluationType();
430 
431         // Provide empty LinkProperties and NetworkCapabilities to make sure they are never null,
432         // even before notifyNetworkConnected.
433         mLinkProperties = new LinkProperties();
434         mNetworkCapabilities = new NetworkCapabilities(null);
435     }
436 
437     /**
438      * ConnectivityService notifies NetworkMonitor that the user already accepted partial
439      * connectivity previously, so NetworkMonitor can validate the network even if it has partial
440      * connectivity.
441      */
setAcceptPartialConnectivity()442     public void setAcceptPartialConnectivity() {
443         sendMessage(EVENT_ACCEPT_PARTIAL_CONNECTIVITY);
444     }
445 
446     /**
447      * Request the NetworkMonitor to reevaluate the network.
448      */
forceReevaluation(int responsibleUid)449     public void forceReevaluation(int responsibleUid) {
450         sendMessage(CMD_FORCE_REEVALUATION, responsibleUid, 0);
451     }
452 
453     /**
454      * Send a notification to NetworkMonitor indicating that there was a DNS query response event.
455      * @param returnCode the DNS return code of the response.
456      */
notifyDnsResponse(int returnCode)457     public void notifyDnsResponse(int returnCode) {
458         sendMessage(EVENT_DNS_NOTIFICATION, returnCode);
459     }
460 
461     /**
462      * Send a notification to NetworkMonitor indicating that private DNS settings have changed.
463      * @param newCfg The new private DNS configuration.
464      */
notifyPrivateDnsSettingsChanged(PrivateDnsConfig newCfg)465     public void notifyPrivateDnsSettingsChanged(PrivateDnsConfig newCfg) {
466         // Cancel any outstanding resolutions.
467         removeMessages(CMD_PRIVATE_DNS_SETTINGS_CHANGED);
468         // Send the update to the proper thread.
469         sendMessage(CMD_PRIVATE_DNS_SETTINGS_CHANGED, newCfg);
470     }
471 
472     /**
473      * Send a notification to NetworkMonitor indicating that the network is now connected.
474      */
notifyNetworkConnected(LinkProperties lp, NetworkCapabilities nc)475     public void notifyNetworkConnected(LinkProperties lp, NetworkCapabilities nc) {
476         sendMessage(CMD_NETWORK_CONNECTED, new Pair<>(
477                 new LinkProperties(lp), new NetworkCapabilities(nc)));
478     }
479 
updateConnectedNetworkAttributes(Message connectedMsg)480     private void updateConnectedNetworkAttributes(Message connectedMsg) {
481         final Pair<LinkProperties, NetworkCapabilities> attrs =
482                 (Pair<LinkProperties, NetworkCapabilities>) connectedMsg.obj;
483         mLinkProperties = attrs.first;
484         mNetworkCapabilities = attrs.second;
485     }
486 
487     /**
488      * Send a notification to NetworkMonitor indicating that the network is now disconnected.
489      */
notifyNetworkDisconnected()490     public void notifyNetworkDisconnected() {
491         sendMessage(CMD_NETWORK_DISCONNECTED);
492     }
493 
494     /**
495      * Send a notification to NetworkMonitor indicating that link properties have changed.
496      */
notifyLinkPropertiesChanged(final LinkProperties lp)497     public void notifyLinkPropertiesChanged(final LinkProperties lp) {
498         sendMessage(EVENT_LINK_PROPERTIES_CHANGED, new LinkProperties(lp));
499     }
500 
501     /**
502      * Send a notification to NetworkMonitor indicating that network capabilities have changed.
503      */
notifyNetworkCapabilitiesChanged(final NetworkCapabilities nc)504     public void notifyNetworkCapabilitiesChanged(final NetworkCapabilities nc) {
505         sendMessage(EVENT_NETWORK_CAPABILITIES_CHANGED, new NetworkCapabilities(nc));
506     }
507 
508     /**
509      * Request the captive portal application to be launched.
510      */
launchCaptivePortalApp()511     public void launchCaptivePortalApp() {
512         sendMessage(CMD_LAUNCH_CAPTIVE_PORTAL_APP);
513     }
514 
515     /**
516      * Notify that the captive portal app was closed with the provided response code.
517      */
notifyCaptivePortalAppFinished(int response)518     public void notifyCaptivePortalAppFinished(int response) {
519         sendMessage(CMD_CAPTIVE_PORTAL_APP_FINISHED, response);
520     }
521 
522     @Override
log(String s)523     protected void log(String s) {
524         if (DBG) Log.d(TAG + "/" + mCleartextDnsNetwork.toString(), s);
525     }
526 
validationLog(int probeType, Object url, String msg)527     private void validationLog(int probeType, Object url, String msg) {
528         String probeName = ValidationProbeEvent.getProbeName(probeType);
529         validationLog(String.format("%s %s %s", probeName, url, msg));
530     }
531 
validationLog(String s)532     private void validationLog(String s) {
533         if (DBG) log(s);
534         mValidationLogs.log(s);
535     }
536 
validationStage()537     private ValidationStage validationStage() {
538         return 0 == mValidations ? ValidationStage.FIRST_VALIDATION : ValidationStage.REVALIDATION;
539     }
540 
isValidationRequired()541     private boolean isValidationRequired() {
542         return NetworkMonitorUtils.isValidationRequired(mNetworkCapabilities);
543     }
544 
isPrivateDnsValidationRequired()545     private boolean isPrivateDnsValidationRequired() {
546         return NetworkMonitorUtils.isPrivateDnsValidationRequired(mNetworkCapabilities);
547     }
548 
notifyNetworkTested(int result, @Nullable String redirectUrl)549     private void notifyNetworkTested(int result, @Nullable String redirectUrl) {
550         try {
551             mCallback.notifyNetworkTested(result, redirectUrl);
552         } catch (RemoteException e) {
553             Log.e(TAG, "Error sending network test result", e);
554         }
555     }
556 
showProvisioningNotification(String action)557     private void showProvisioningNotification(String action) {
558         try {
559             mCallback.showProvisioningNotification(action, mContext.getPackageName());
560         } catch (RemoteException e) {
561             Log.e(TAG, "Error showing provisioning notification", e);
562         }
563     }
564 
hideProvisioningNotification()565     private void hideProvisioningNotification() {
566         try {
567             mCallback.hideProvisioningNotification();
568         } catch (RemoteException e) {
569             Log.e(TAG, "Error hiding provisioning notification", e);
570         }
571     }
572 
573     // DefaultState is the parent of all States.  It exists only to handle CMD_* messages but
574     // does not entail any real state (hence no enter() or exit() routines).
575     private class DefaultState extends State {
576         @Override
processMessage(Message message)577         public boolean processMessage(Message message) {
578             switch (message.what) {
579                 case CMD_NETWORK_CONNECTED:
580                     updateConnectedNetworkAttributes(message);
581                     logNetworkEvent(NetworkEvent.NETWORK_CONNECTED);
582                     transitionTo(mEvaluatingState);
583                     return HANDLED;
584                 case CMD_NETWORK_DISCONNECTED:
585                     logNetworkEvent(NetworkEvent.NETWORK_DISCONNECTED);
586                     quit();
587                     return HANDLED;
588                 case CMD_FORCE_REEVALUATION:
589                 case CMD_CAPTIVE_PORTAL_RECHECK:
590                     final int dnsCount = mDnsStallDetector.getConsecutiveTimeoutCount();
591                     validationLog("Forcing reevaluation for UID " + message.arg1
592                             + ". Dns signal count: " + dnsCount);
593                     mUidResponsibleForReeval = message.arg1;
594                     transitionTo(mEvaluatingState);
595                     return HANDLED;
596                 case CMD_CAPTIVE_PORTAL_APP_FINISHED:
597                     log("CaptivePortal App responded with " + message.arg1);
598 
599                     // If the user has seen and acted on a captive portal notification, and the
600                     // captive portal app is now closed, disable HTTPS probes. This avoids the
601                     // following pathological situation:
602                     //
603                     // 1. HTTP probe returns a captive portal, HTTPS probe fails or times out.
604                     // 2. User opens the app and logs into the captive portal.
605                     // 3. HTTP starts working, but HTTPS still doesn't work for some other reason -
606                     //    perhaps due to the network blocking HTTPS?
607                     //
608                     // In this case, we'll fail to validate the network even after the app is
609                     // dismissed. There is now no way to use this network, because the app is now
610                     // gone, so the user cannot select "Use this network as is".
611                     mUseHttps = false;
612 
613                     switch (message.arg1) {
614                         case APP_RETURN_DISMISSED:
615                             sendMessage(CMD_FORCE_REEVALUATION, NO_UID, 0);
616                             break;
617                         case APP_RETURN_WANTED_AS_IS:
618                             mDontDisplaySigninNotification = true;
619                             // TODO: Distinguish this from a network that actually validates.
620                             // Displaying the "x" on the system UI icon may still be a good idea.
621                             transitionTo(mEvaluatingPrivateDnsState);
622                             break;
623                         case APP_RETURN_UNWANTED:
624                             mDontDisplaySigninNotification = true;
625                             mUserDoesNotWant = true;
626                             mEvaluationState.reportEvaluationResult(
627                                     NETWORK_VALIDATION_RESULT_INVALID, null);
628                             // TODO: Should teardown network.
629                             mUidResponsibleForReeval = 0;
630                             transitionTo(mEvaluatingState);
631                             break;
632                     }
633                     return HANDLED;
634                 case CMD_PRIVATE_DNS_SETTINGS_CHANGED: {
635                     final PrivateDnsConfig cfg = (PrivateDnsConfig) message.obj;
636                     if (!isPrivateDnsValidationRequired() || cfg == null || !cfg.inStrictMode()) {
637                         // No DNS resolution required.
638                         //
639                         // We don't force any validation in opportunistic mode
640                         // here. Opportunistic mode nameservers are validated
641                         // separately within netd.
642                         //
643                         // Reset Private DNS settings state.
644                         mPrivateDnsProviderHostname = "";
645                         break;
646                     }
647 
648                     mPrivateDnsProviderHostname = cfg.hostname;
649 
650                     // DNS resolutions via Private DNS strict mode block for a
651                     // few seconds (~4.2) checking for any IP addresses to
652                     // arrive and validate. Initiating a (re)evaluation now
653                     // should not significantly alter the validation outcome.
654                     //
655                     // No matter what: enqueue a validation request; one of
656                     // three things can happen with this request:
657                     //     [1] ignored (EvaluatingState or CaptivePortalState)
658                     //     [2] transition to EvaluatingPrivateDnsState
659                     //         (DefaultState and ValidatedState)
660                     //     [3] handled (EvaluatingPrivateDnsState)
661                     //
662                     // The Private DNS configuration to be evaluated will:
663                     //     [1] be skipped (not in strict mode), or
664                     //     [2] validate (huzzah), or
665                     //     [3] encounter some problem (invalid hostname,
666                     //         no resolved IP addresses, IPs unreachable,
667                     //         port 853 unreachable, port 853 is not running a
668                     //         DNS-over-TLS server, et cetera).
669                     sendMessage(CMD_EVALUATE_PRIVATE_DNS);
670                     break;
671                 }
672                 case EVENT_DNS_NOTIFICATION:
673                     mDnsStallDetector.accumulateConsecutiveDnsTimeoutCount(message.arg1);
674                     break;
675                 // Set mAcceptPartialConnectivity to true and if network start evaluating or
676                 // re-evaluating and get the result of partial connectivity, ProbingState will
677                 // disable HTTPS probe and transition to EvaluatingPrivateDnsState.
678                 case EVENT_ACCEPT_PARTIAL_CONNECTIVITY:
679                     maybeDisableHttpsProbing(true /* acceptPartial */);
680                     break;
681                 case EVENT_LINK_PROPERTIES_CHANGED:
682                     mLinkProperties = (LinkProperties) message.obj;
683                     break;
684                 case EVENT_NETWORK_CAPABILITIES_CHANGED:
685                     mNetworkCapabilities = (NetworkCapabilities) message.obj;
686                     break;
687                 default:
688                     break;
689             }
690             return HANDLED;
691         }
692     }
693 
694     // Being in the ValidatedState State indicates a Network is:
695     // - Successfully validated, or
696     // - Wanted "as is" by the user, or
697     // - Does not satisfy the default NetworkRequest and so validation has been skipped.
698     private class ValidatedState extends State {
699         @Override
enter()700         public void enter() {
701             maybeLogEvaluationResult(
702                     networkEventType(validationStage(), EvaluationResult.VALIDATED));
703             // If the user has accepted partial connectivity and HTTPS probing is disabled, then
704             // mark the network as validated and partial so that settings can keep informing the
705             // user that the connection is limited.
706             int result = NETWORK_VALIDATION_RESULT_VALID;
707             if (!mUseHttps && mAcceptPartialConnectivity) {
708                 result |= NETWORK_VALIDATION_RESULT_PARTIAL;
709             }
710             mEvaluationState.reportEvaluationResult(result, null /* redirectUrl */);
711             mValidations++;
712         }
713 
714         @Override
processMessage(Message message)715         public boolean processMessage(Message message) {
716             switch (message.what) {
717                 case CMD_NETWORK_CONNECTED:
718                     updateConnectedNetworkAttributes(message);
719                     transitionTo(mValidatedState);
720                     break;
721                 case CMD_EVALUATE_PRIVATE_DNS:
722                     transitionTo(mEvaluatingPrivateDnsState);
723                     break;
724                 case EVENT_DNS_NOTIFICATION:
725                     mDnsStallDetector.accumulateConsecutiveDnsTimeoutCount(message.arg1);
726                     if (isDataStall()) {
727                         mCollectDataStallMetrics = true;
728                         validationLog("Suspecting data stall, reevaluate");
729                         transitionTo(mEvaluatingState);
730                     }
731                     break;
732                 default:
733                     return NOT_HANDLED;
734             }
735             return HANDLED;
736         }
737     }
738 
writeDataStallStats(@onNull final CaptivePortalProbeResult result)739     private void writeDataStallStats(@NonNull final CaptivePortalProbeResult result) {
740         /*
741          * Collect data stall detection level information for each transport type. Collect type
742          * specific information for cellular and wifi only currently. Generate
743          * DataStallDetectionStats for each transport type. E.g., if a network supports both
744          * TRANSPORT_WIFI and TRANSPORT_VPN, two DataStallDetectionStats will be generated.
745          */
746         final int[] transports = mNetworkCapabilities.getTransportTypes();
747 
748         for (int i = 0; i < transports.length; i++) {
749             DataStallStatsUtils.write(buildDataStallDetectionStats(transports[i]), result);
750         }
751         mCollectDataStallMetrics = false;
752     }
753 
754     @VisibleForTesting
buildDataStallDetectionStats(int transport)755     protected DataStallDetectionStats buildDataStallDetectionStats(int transport) {
756         final DataStallDetectionStats.Builder stats = new DataStallDetectionStats.Builder();
757         if (VDBG_STALL) log("collectDataStallMetrics: type=" + transport);
758         stats.setEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS);
759         stats.setNetworkType(transport);
760         switch (transport) {
761             case NetworkCapabilities.TRANSPORT_WIFI:
762                 // TODO: Update it if status query in dual wifi is supported.
763                 final WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
764                 stats.setWiFiData(wifiInfo);
765                 break;
766             case NetworkCapabilities.TRANSPORT_CELLULAR:
767                 final boolean isRoaming = !mNetworkCapabilities.hasCapability(
768                         NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
769                 final SignalStrength ss = mTelephonyManager.getSignalStrength();
770                 // TODO(b/120452078): Support multi-sim.
771                 stats.setCellData(
772                         mTelephonyManager.getDataNetworkType(),
773                         isRoaming,
774                         mTelephonyManager.getNetworkOperator(),
775                         mTelephonyManager.getSimOperator(),
776                         (ss != null)
777                         ? ss.getLevel() : CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
778                 break;
779             default:
780                 // No transport type specific information for the other types.
781                 break;
782         }
783         addDnsEvents(stats);
784 
785         return stats.build();
786     }
787 
788     @VisibleForTesting
addDnsEvents(@onNull final DataStallDetectionStats.Builder stats)789     protected void addDnsEvents(@NonNull final DataStallDetectionStats.Builder stats) {
790         final int size = mDnsStallDetector.mResultIndices.size();
791         for (int i = 1; i <= DEFAULT_DNS_LOG_SIZE && i <= size; i++) {
792             final int index = mDnsStallDetector.mResultIndices.indexOf(size - i);
793             stats.addDnsEvent(mDnsStallDetector.mDnsEvents[index].mReturnCode,
794                     mDnsStallDetector.mDnsEvents[index].mTimeStamp);
795         }
796     }
797 
798 
799     // Being in the MaybeNotifyState State indicates the user may have been notified that sign-in
800     // is required.  This State takes care to clear the notification upon exit from the State.
801     private class MaybeNotifyState extends State {
802         @Override
processMessage(Message message)803         public boolean processMessage(Message message) {
804             switch (message.what) {
805                 case CMD_LAUNCH_CAPTIVE_PORTAL_APP:
806                     final Bundle appExtras = new Bundle();
807                     // OneAddressPerFamilyNetwork is not parcelable across processes.
808                     final Network network = new Network(mCleartextDnsNetwork);
809                     appExtras.putParcelable(ConnectivityManager.EXTRA_NETWORK, network);
810                     final CaptivePortalProbeResult probeRes = mLastPortalProbeResult;
811                     appExtras.putString(EXTRA_CAPTIVE_PORTAL_URL, probeRes.detectUrl);
812                     if (probeRes.probeSpec != null) {
813                         final String encodedSpec = probeRes.probeSpec.getEncodedSpec();
814                         appExtras.putString(EXTRA_CAPTIVE_PORTAL_PROBE_SPEC, encodedSpec);
815                     }
816                     appExtras.putString(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_USER_AGENT,
817                             mCaptivePortalUserAgent);
818                     mCm.startCaptivePortalApp(network, appExtras);
819                     return HANDLED;
820                 default:
821                     return NOT_HANDLED;
822             }
823         }
824 
825         @Override
exit()826         public void exit() {
827             if (mLaunchCaptivePortalAppBroadcastReceiver != null) {
828                 mContext.unregisterReceiver(mLaunchCaptivePortalAppBroadcastReceiver);
829                 mLaunchCaptivePortalAppBroadcastReceiver = null;
830             }
831             hideProvisioningNotification();
832         }
833     }
834 
835     // Being in the EvaluatingState State indicates the Network is being evaluated for internet
836     // connectivity, or that the user has indicated that this network is unwanted.
837     private class EvaluatingState extends State {
838         @Override
enter()839         public void enter() {
840             // If we have already started to track time spent in EvaluatingState
841             // don't reset the timer due simply to, say, commands or events that
842             // cause us to exit and re-enter EvaluatingState.
843             if (!mEvaluationTimer.isStarted()) {
844                 mEvaluationTimer.start();
845             }
846             sendMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
847             if (mUidResponsibleForReeval != INVALID_UID) {
848                 TrafficStats.setThreadStatsUid(mUidResponsibleForReeval);
849                 mUidResponsibleForReeval = INVALID_UID;
850             }
851             mReevaluateDelayMs = INITIAL_REEVALUATE_DELAY_MS;
852             mEvaluateAttempts = 0;
853             // Reset all current probe results to zero, but retain current validation state until
854             // validation succeeds or fails.
855             mEvaluationState.clearProbeResults();
856         }
857 
858         @Override
processMessage(Message message)859         public boolean processMessage(Message message) {
860             switch (message.what) {
861                 case CMD_REEVALUATE:
862                     if (message.arg1 != mReevaluateToken || mUserDoesNotWant) {
863                         return HANDLED;
864                     }
865                     // Don't bother validating networks that don't satisfy the default request.
866                     // This includes:
867                     //  - VPNs which can be considered explicitly desired by the user and the
868                     //    user's desire trumps whether the network validates.
869                     //  - Networks that don't provide Internet access.  It's unclear how to
870                     //    validate such networks.
871                     //  - Untrusted networks.  It's unsafe to prompt the user to sign-in to
872                     //    such networks and the user didn't express interest in connecting to
873                     //    such networks (an app did) so the user may be unhappily surprised when
874                     //    asked to sign-in to a network they didn't want to connect to in the
875                     //    first place.  Validation could be done to adjust the network scores
876                     //    however these networks are app-requested and may not be intended for
877                     //    general usage, in which case general validation may not be an accurate
878                     //    measure of the network's quality.  Only the app knows how to evaluate
879                     //    the network so don't bother validating here.  Furthermore sending HTTP
880                     //    packets over the network may be undesirable, for example an extremely
881                     //    expensive metered network, or unwanted leaking of the User Agent string.
882                     //
883                     // On networks that need to support private DNS in strict mode (e.g., VPNs, but
884                     // not networks that don't provide Internet access), we still need to perform
885                     // private DNS server resolution.
886                     if (!isValidationRequired()) {
887                         if (isPrivateDnsValidationRequired()) {
888                             validationLog("Network would not satisfy default request, "
889                                     + "resolving private DNS");
890                             transitionTo(mEvaluatingPrivateDnsState);
891                         } else {
892                             validationLog("Network would not satisfy default request, "
893                                     + "not validating");
894                             transitionTo(mValidatedState);
895                         }
896                         return HANDLED;
897                     }
898                     mEvaluateAttempts++;
899 
900                     transitionTo(mProbingState);
901                     return HANDLED;
902                 case CMD_FORCE_REEVALUATION:
903                     // Before IGNORE_REEVALUATE_ATTEMPTS attempts are made,
904                     // ignore any re-evaluation requests. After, restart the
905                     // evaluation process via EvaluatingState#enter.
906                     return (mEvaluateAttempts < IGNORE_REEVALUATE_ATTEMPTS) ? HANDLED : NOT_HANDLED;
907                 // Disable HTTPS probe and transition to EvaluatingPrivateDnsState because:
908                 // 1. Network is connected and finish the network validation.
909                 // 2. NetworkMonitor detects network is partial connectivity and user accepts it.
910                 case EVENT_ACCEPT_PARTIAL_CONNECTIVITY:
911                     maybeDisableHttpsProbing(true /* acceptPartial */);
912                     transitionTo(mEvaluatingPrivateDnsState);
913                     return HANDLED;
914                 default:
915                     return NOT_HANDLED;
916             }
917         }
918 
919         @Override
exit()920         public void exit() {
921             TrafficStats.clearThreadStatsUid();
922         }
923     }
924 
925     // BroadcastReceiver that waits for a particular Intent and then posts a message.
926     private class CustomIntentReceiver extends BroadcastReceiver {
927         private final int mToken;
928         private final int mWhat;
929         private final String mAction;
CustomIntentReceiver(String action, int token, int what)930         CustomIntentReceiver(String action, int token, int what) {
931             mToken = token;
932             mWhat = what;
933             mAction = action + "_" + mCleartextDnsNetwork.getNetworkHandle() + "_" + token;
934             mContext.registerReceiver(this, new IntentFilter(mAction));
935         }
getPendingIntent()936         public PendingIntent getPendingIntent() {
937             final Intent intent = new Intent(mAction);
938             intent.setPackage(mContext.getPackageName());
939             return PendingIntent.getBroadcast(mContext, 0, intent, 0);
940         }
941         @Override
onReceive(Context context, Intent intent)942         public void onReceive(Context context, Intent intent) {
943             if (intent.getAction().equals(mAction)) sendMessage(obtainMessage(mWhat, mToken));
944         }
945     }
946 
947     // Being in the CaptivePortalState State indicates a captive portal was detected and the user
948     // has been shown a notification to sign-in.
949     private class CaptivePortalState extends State {
950         private static final String ACTION_LAUNCH_CAPTIVE_PORTAL_APP =
951                 "android.net.netmon.launchCaptivePortalApp";
952 
953         @Override
enter()954         public void enter() {
955             maybeLogEvaluationResult(
956                     networkEventType(validationStage(), EvaluationResult.CAPTIVE_PORTAL));
957             // Don't annoy user with sign-in notifications.
958             if (mDontDisplaySigninNotification) return;
959             // Create a CustomIntentReceiver that sends us a
960             // CMD_LAUNCH_CAPTIVE_PORTAL_APP message when the user
961             // touches the notification.
962             if (mLaunchCaptivePortalAppBroadcastReceiver == null) {
963                 // Wait for result.
964                 mLaunchCaptivePortalAppBroadcastReceiver = new CustomIntentReceiver(
965                         ACTION_LAUNCH_CAPTIVE_PORTAL_APP, new Random().nextInt(),
966                         CMD_LAUNCH_CAPTIVE_PORTAL_APP);
967                 // Display the sign in notification.
968                 // Only do this once for every time we enter MaybeNotifyState. b/122164725
969                 showProvisioningNotification(mLaunchCaptivePortalAppBroadcastReceiver.mAction);
970             }
971             // Retest for captive portal occasionally.
972             sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */,
973                     CAPTIVE_PORTAL_REEVALUATE_DELAY_MS);
974             mValidations++;
975         }
976 
977         @Override
exit()978         public void exit() {
979             removeMessages(CMD_CAPTIVE_PORTAL_RECHECK);
980         }
981     }
982 
983     private class EvaluatingPrivateDnsState extends State {
984         private int mPrivateDnsReevalDelayMs;
985         private PrivateDnsConfig mPrivateDnsConfig;
986 
987         @Override
enter()988         public void enter() {
989             mPrivateDnsReevalDelayMs = INITIAL_REEVALUATE_DELAY_MS;
990             mPrivateDnsConfig = null;
991             sendMessage(CMD_EVALUATE_PRIVATE_DNS);
992         }
993 
994         @Override
processMessage(Message msg)995         public boolean processMessage(Message msg) {
996             switch (msg.what) {
997                 case CMD_EVALUATE_PRIVATE_DNS:
998                     if (inStrictMode()) {
999                         if (!isStrictModeHostnameResolved()) {
1000                             resolveStrictModeHostname();
1001 
1002                             if (isStrictModeHostnameResolved()) {
1003                                 notifyPrivateDnsConfigResolved();
1004                             } else {
1005                                 handlePrivateDnsEvaluationFailure();
1006                                 break;
1007                             }
1008                         }
1009 
1010                         // Look up a one-time hostname, to bypass caching.
1011                         //
1012                         // Note that this will race with ConnectivityService
1013                         // code programming the DNS-over-TLS server IP addresses
1014                         // into netd (if invoked, above). If netd doesn't know
1015                         // the IP addresses yet, or if the connections to the IP
1016                         // addresses haven't yet been validated, netd will block
1017                         // for up to a few seconds before failing the lookup.
1018                         if (!sendPrivateDnsProbe()) {
1019                             handlePrivateDnsEvaluationFailure();
1020                             break;
1021                         }
1022                     }
1023 
1024                     // All good!
1025                     transitionTo(mValidatedState);
1026                     break;
1027                 default:
1028                     return NOT_HANDLED;
1029             }
1030             return HANDLED;
1031         }
1032 
inStrictMode()1033         private boolean inStrictMode() {
1034             return !TextUtils.isEmpty(mPrivateDnsProviderHostname);
1035         }
1036 
isStrictModeHostnameResolved()1037         private boolean isStrictModeHostnameResolved() {
1038             return (mPrivateDnsConfig != null)
1039                     && mPrivateDnsConfig.hostname.equals(mPrivateDnsProviderHostname)
1040                     && (mPrivateDnsConfig.ips.length > 0);
1041         }
1042 
resolveStrictModeHostname()1043         private void resolveStrictModeHostname() {
1044             try {
1045                 // Do a blocking DNS resolution using the network-assigned nameservers.
1046                 final InetAddress[] ips = DnsUtils.getAllByName(mDependencies.getDnsResolver(),
1047                         mCleartextDnsNetwork, mPrivateDnsProviderHostname, getDnsProbeTimeout());
1048                 mPrivateDnsConfig = new PrivateDnsConfig(mPrivateDnsProviderHostname, ips);
1049                 validationLog("Strict mode hostname resolved: " + mPrivateDnsConfig);
1050             } catch (UnknownHostException uhe) {
1051                 mPrivateDnsConfig = null;
1052                 validationLog("Strict mode hostname resolution failed: " + uhe.getMessage());
1053             }
1054             mEvaluationState.noteProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS,
1055                     (mPrivateDnsConfig != null) /* succeeded */);
1056         }
1057 
notifyPrivateDnsConfigResolved()1058         private void notifyPrivateDnsConfigResolved() {
1059             try {
1060                 mCallback.notifyPrivateDnsConfigResolved(mPrivateDnsConfig.toParcel());
1061             } catch (RemoteException e) {
1062                 Log.e(TAG, "Error sending private DNS config resolved notification", e);
1063             }
1064         }
1065 
handlePrivateDnsEvaluationFailure()1066         private void handlePrivateDnsEvaluationFailure() {
1067             mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID,
1068                     null /* redirectUrl */);
1069             // Queue up a re-evaluation with backoff.
1070             //
1071             // TODO: Consider abandoning this state after a few attempts and
1072             // transitioning back to EvaluatingState, to perhaps give ourselves
1073             // the opportunity to (re)detect a captive portal or something.
1074             //
1075             // TODO: distinguish between CMD_EVALUATE_PRIVATE_DNS messages that are caused by server
1076             // lookup failures (which should continue to do exponential backoff) and
1077             // CMD_EVALUATE_PRIVATE_DNS messages that are caused by user reconfiguration (which
1078             // should be processed immediately.
1079             sendMessageDelayed(CMD_EVALUATE_PRIVATE_DNS, mPrivateDnsReevalDelayMs);
1080             mPrivateDnsReevalDelayMs *= 2;
1081             if (mPrivateDnsReevalDelayMs > MAX_REEVALUATE_DELAY_MS) {
1082                 mPrivateDnsReevalDelayMs = MAX_REEVALUATE_DELAY_MS;
1083             }
1084         }
1085 
sendPrivateDnsProbe()1086         private boolean sendPrivateDnsProbe() {
1087             // q.v. system/netd/server/dns/DnsTlsTransport.cpp
1088             final String oneTimeHostnameSuffix = "-dnsotls-ds.metric.gstatic.com";
1089             final String host = UUID.randomUUID().toString().substring(0, 8)
1090                     + oneTimeHostnameSuffix;
1091             final Stopwatch watch = new Stopwatch().start();
1092             boolean success = false;
1093             long time;
1094             try {
1095                 final InetAddress[] ips = mNetwork.getAllByName(host);
1096                 time = watch.stop();
1097                 final String strIps = Arrays.toString(ips);
1098                 success = (ips != null && ips.length > 0);
1099                 validationLog(PROBE_PRIVDNS, host, String.format("%dms: %s", time, strIps));
1100             } catch (UnknownHostException uhe) {
1101                 time = watch.stop();
1102                 validationLog(PROBE_PRIVDNS, host,
1103                         String.format("%dms - Error: %s", time, uhe.getMessage()));
1104             }
1105             logValidationProbe(time, PROBE_PRIVDNS, success ? DNS_SUCCESS : DNS_FAILURE);
1106             mEvaluationState.noteProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS, success);
1107             return success;
1108         }
1109     }
1110 
1111     private class ProbingState extends State {
1112         private Thread mThread;
1113 
1114         @Override
enter()1115         public void enter() {
1116             if (mEvaluateAttempts >= BLAME_FOR_EVALUATION_ATTEMPTS) {
1117                 //Don't continue to blame UID forever.
1118                 TrafficStats.clearThreadStatsUid();
1119             }
1120 
1121             final int token = ++mProbeToken;
1122             mThread = new Thread(() -> sendMessage(obtainMessage(CMD_PROBE_COMPLETE, token, 0,
1123                     isCaptivePortal())));
1124             mThread.start();
1125         }
1126 
1127         @Override
processMessage(Message message)1128         public boolean processMessage(Message message) {
1129             switch (message.what) {
1130                 case CMD_PROBE_COMPLETE:
1131                     // Ensure that CMD_PROBE_COMPLETE from stale threads are ignored.
1132                     if (message.arg1 != mProbeToken) {
1133                         return HANDLED;
1134                     }
1135 
1136                     final CaptivePortalProbeResult probeResult =
1137                             (CaptivePortalProbeResult) message.obj;
1138                     mLastProbeTime = SystemClock.elapsedRealtime();
1139 
1140                     if (mCollectDataStallMetrics) {
1141                         writeDataStallStats(probeResult);
1142                     }
1143 
1144                     if (probeResult.isSuccessful()) {
1145                         // Transit EvaluatingPrivateDnsState to get to Validated
1146                         // state (even if no Private DNS validation required).
1147                         transitionTo(mEvaluatingPrivateDnsState);
1148                     } else if (probeResult.isPortal()) {
1149                         mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID,
1150                                 probeResult.redirectUrl);
1151                         mLastPortalProbeResult = probeResult;
1152                         transitionTo(mCaptivePortalState);
1153                     } else if (probeResult.isPartialConnectivity()) {
1154                         mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_PARTIAL,
1155                                 null /* redirectUrl */);
1156                         // Check if disable https probing needed.
1157                         maybeDisableHttpsProbing(mAcceptPartialConnectivity);
1158                         if (mAcceptPartialConnectivity) {
1159                             transitionTo(mEvaluatingPrivateDnsState);
1160                         } else {
1161                             transitionTo(mWaitingForNextProbeState);
1162                         }
1163                     } else {
1164                         logNetworkEvent(NetworkEvent.NETWORK_VALIDATION_FAILED);
1165                         mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID,
1166                                 null /* redirectUrl */);
1167                         transitionTo(mWaitingForNextProbeState);
1168                     }
1169                     return HANDLED;
1170                 case EVENT_DNS_NOTIFICATION:
1171                 case EVENT_ACCEPT_PARTIAL_CONNECTIVITY:
1172                     // Leave the event to DefaultState.
1173                     return NOT_HANDLED;
1174                 default:
1175                     // Wait for probe result and defer events to next state by default.
1176                     deferMessage(message);
1177                     return HANDLED;
1178             }
1179         }
1180 
1181         @Override
exit()1182         public void exit() {
1183             if (mThread.isAlive()) {
1184                 mThread.interrupt();
1185             }
1186             mThread = null;
1187         }
1188     }
1189 
1190     // Being in the WaitingForNextProbeState indicates that evaluating probes failed and state is
1191     // transited from ProbingState. This ensures that the state machine is only in ProbingState
1192     // while a probe is in progress, not while waiting to perform the next probe. That allows
1193     // ProbingState to defer most messages until the probe is complete, which keeps the code simple
1194     // and matches the pre-Q behaviour where probes were a blocking operation performed on the state
1195     // machine thread.
1196     private class WaitingForNextProbeState extends State {
1197         @Override
enter()1198         public void enter() {
1199             scheduleNextProbe();
1200         }
1201 
scheduleNextProbe()1202         private void scheduleNextProbe() {
1203             final Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
1204             sendMessageDelayed(msg, mReevaluateDelayMs);
1205             mReevaluateDelayMs *= 2;
1206             if (mReevaluateDelayMs > MAX_REEVALUATE_DELAY_MS) {
1207                 mReevaluateDelayMs = MAX_REEVALUATE_DELAY_MS;
1208             }
1209         }
1210 
1211         @Override
processMessage(Message message)1212         public boolean processMessage(Message message) {
1213             return NOT_HANDLED;
1214         }
1215     }
1216 
1217     // Limits the list of IP addresses returned by getAllByName or tried by openConnection to at
1218     // most one per address family. This ensures we only wait up to 20 seconds for TCP connections
1219     // to complete, regardless of how many IP addresses a host has.
1220     private static class OneAddressPerFamilyNetwork extends Network {
OneAddressPerFamilyNetwork(Network network)1221         OneAddressPerFamilyNetwork(Network network) {
1222             // Always bypass Private DNS.
1223             super(network.getPrivateDnsBypassingCopy());
1224         }
1225 
1226         @Override
getAllByName(String host)1227         public InetAddress[] getAllByName(String host) throws UnknownHostException {
1228             final List<InetAddress> addrs = Arrays.asList(super.getAllByName(host));
1229 
1230             // Ensure the address family of the first address is tried first.
1231             LinkedHashMap<Class, InetAddress> addressByFamily = new LinkedHashMap<>();
1232             addressByFamily.put(addrs.get(0).getClass(), addrs.get(0));
1233             Collections.shuffle(addrs);
1234 
1235             for (InetAddress addr : addrs) {
1236                 addressByFamily.put(addr.getClass(), addr);
1237             }
1238 
1239             return addressByFamily.values().toArray(new InetAddress[addressByFamily.size()]);
1240         }
1241     }
1242 
getIsCaptivePortalCheckEnabled()1243     private boolean getIsCaptivePortalCheckEnabled() {
1244         String symbol = CAPTIVE_PORTAL_MODE;
1245         int defaultValue = CAPTIVE_PORTAL_MODE_PROMPT;
1246         int mode = mDependencies.getSetting(mContext, symbol, defaultValue);
1247         return mode != CAPTIVE_PORTAL_MODE_IGNORE;
1248     }
1249 
getUseHttpsValidation()1250     private boolean getUseHttpsValidation() {
1251         return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY,
1252                 CAPTIVE_PORTAL_USE_HTTPS, 1) == 1;
1253     }
1254 
getCaptivePortalServerHttpsUrl()1255     private String getCaptivePortalServerHttpsUrl() {
1256         return getSettingFromResource(mContext, R.string.config_captive_portal_https_url,
1257                 R.string.default_captive_portal_https_url, CAPTIVE_PORTAL_HTTPS_URL);
1258     }
1259 
getDnsProbeTimeout()1260     private int getDnsProbeTimeout() {
1261         return getIntSetting(mContext, R.integer.config_captive_portal_dns_probe_timeout,
1262                 CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT,
1263                 R.integer.default_captive_portal_dns_probe_timeout);
1264     }
1265 
1266     /**
1267      * Gets an integer setting from resources or device config
1268      *
1269      * configResource is used if set, followed by device config if set, followed by defaultResource.
1270      * If none of these are set then an exception is thrown.
1271      *
1272      * TODO: move to a common location such as a ConfigUtils class.
1273      * TODO(b/130324939): test that the resources can be overlayed by an RRO package.
1274      */
1275     @VisibleForTesting
getIntSetting(@onNull final Context context, @StringRes int configResource, @NonNull String symbol, @StringRes int defaultResource)1276     int getIntSetting(@NonNull final Context context, @StringRes int configResource,
1277             @NonNull String symbol, @StringRes int defaultResource) {
1278         final Resources res = context.getResources();
1279         try {
1280             return res.getInteger(configResource);
1281         } catch (Resources.NotFoundException e) {
1282             return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY,
1283                     symbol, res.getInteger(defaultResource));
1284         }
1285     }
1286 
1287     /**
1288      * Get the captive portal server HTTP URL that is configured on the device.
1289      *
1290      * NetworkMonitor does not use {@link ConnectivityManager#getCaptivePortalServerUrl()} as
1291      * it has its own updatable strategies to detect captive portals. The framework only advises
1292      * on one URL that can be used, while NetworkMonitor may implement more complex logic.
1293      */
getCaptivePortalServerHttpUrl()1294     public String getCaptivePortalServerHttpUrl() {
1295         return getSettingFromResource(mContext, R.string.config_captive_portal_http_url,
1296                 R.string.default_captive_portal_http_url, CAPTIVE_PORTAL_HTTP_URL);
1297     }
1298 
getConsecutiveDnsTimeoutThreshold()1299     private int getConsecutiveDnsTimeoutThreshold() {
1300         return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY,
1301                 CONFIG_DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD,
1302                 DEFAULT_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD);
1303     }
1304 
getDataStallMinEvaluateTime()1305     private int getDataStallMinEvaluateTime() {
1306         return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY,
1307                 CONFIG_DATA_STALL_MIN_EVALUATE_INTERVAL,
1308                 DEFAULT_DATA_STALL_MIN_EVALUATE_TIME_MS);
1309     }
1310 
getDataStallValidDnsTimeThreshold()1311     private int getDataStallValidDnsTimeThreshold() {
1312         return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY,
1313                 CONFIG_DATA_STALL_VALID_DNS_TIME_THRESHOLD,
1314                 DEFAULT_DATA_STALL_VALID_DNS_TIME_THRESHOLD_MS);
1315     }
1316 
getDataStallEvaluationType()1317     private int getDataStallEvaluationType() {
1318         return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY,
1319                 CONFIG_DATA_STALL_EVALUATION_TYPE,
1320                 DEFAULT_DATA_STALL_EVALUATION_TYPES);
1321     }
1322 
makeCaptivePortalFallbackUrls()1323     private URL[] makeCaptivePortalFallbackUrls() {
1324         try {
1325             final String firstUrl = mDependencies.getSetting(mContext, CAPTIVE_PORTAL_FALLBACK_URL,
1326                     null);
1327 
1328             final URL[] settingProviderUrls;
1329             if (!TextUtils.isEmpty(firstUrl)) {
1330                 final String otherUrls = mDependencies.getDeviceConfigProperty(
1331                         NAMESPACE_CONNECTIVITY, CAPTIVE_PORTAL_OTHER_FALLBACK_URLS, "");
1332                 // otherUrls may be empty, but .split() ignores trailing empty strings
1333                 final String separator = ",";
1334                 final String[] urls = (firstUrl + separator + otherUrls).split(separator);
1335                 settingProviderUrls = convertStrings(urls, this::makeURL, new URL[0]);
1336             } else {
1337                 settingProviderUrls = new URL[0];
1338             }
1339 
1340             return getArrayConfig(settingProviderUrls, R.array.config_captive_portal_fallback_urls,
1341                     R.array.default_captive_portal_fallback_urls, this::makeURL);
1342         } catch (Exception e) {
1343             // Don't let a misconfiguration bootloop the system.
1344             Log.e(TAG, "Error parsing configured fallback URLs", e);
1345             return new URL[0];
1346         }
1347     }
1348 
makeCaptivePortalFallbackProbeSpecs()1349     private CaptivePortalProbeSpec[] makeCaptivePortalFallbackProbeSpecs() {
1350         try {
1351             final String settingsValue = mDependencies.getDeviceConfigProperty(
1352                     NAMESPACE_CONNECTIVITY, CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS, null);
1353 
1354             final CaptivePortalProbeSpec[] emptySpecs = new CaptivePortalProbeSpec[0];
1355             final CaptivePortalProbeSpec[] providerValue = TextUtils.isEmpty(settingsValue)
1356                     ? emptySpecs
1357                     : parseCaptivePortalProbeSpecs(settingsValue).toArray(emptySpecs);
1358 
1359             return getArrayConfig(providerValue, R.array.config_captive_portal_fallback_probe_specs,
1360                     R.array.default_captive_portal_fallback_probe_specs,
1361                     CaptivePortalProbeSpec::parseSpecOrNull);
1362         } catch (Exception e) {
1363             // Don't let a misconfiguration bootloop the system.
1364             Log.e(TAG, "Error parsing configured fallback probe specs", e);
1365             return null;
1366         }
1367     }
1368 
1369     /**
1370      * Read a setting from a resource or the settings provider.
1371      *
1372      * <p>The configuration resource is prioritized, then the provider value, then the default
1373      * resource value.
1374      * @param context The context
1375      * @param configResource The resource id for the configuration parameter
1376      * @param defaultResource The resource id for the default value
1377      * @param symbol The symbol in the settings provider
1378      * @return The best available value
1379      */
1380     @NonNull
getSettingFromResource(@onNull final Context context, @StringRes int configResource, @StringRes int defaultResource, @NonNull String symbol)1381     private String getSettingFromResource(@NonNull final Context context,
1382             @StringRes int configResource, @StringRes int defaultResource,
1383             @NonNull String symbol) {
1384         final Resources res = context.getResources();
1385         String setting = res.getString(configResource);
1386 
1387         if (!TextUtils.isEmpty(setting)) return setting;
1388 
1389         setting = mDependencies.getSetting(context, symbol, null);
1390         if (!TextUtils.isEmpty(setting)) return setting;
1391 
1392         return res.getString(defaultResource);
1393     }
1394 
1395     /**
1396      * Get an array configuration from resources or the settings provider.
1397      *
1398      * <p>The configuration resource is prioritized, then the provider values, then the default
1399      * resource values.
1400      * @param providerValue Values obtained from the setting provider.
1401      * @param configResId ID of the configuration resource.
1402      * @param defaultResId ID of the default resource.
1403      * @param resourceConverter Converter from the resource strings to stored setting class. Null
1404      *                          return values are ignored.
1405      */
getArrayConfig(@onNull T[] providerValue, @ArrayRes int configResId, @ArrayRes int defaultResId, @NonNull Function<String, T> resourceConverter)1406     private <T> T[] getArrayConfig(@NonNull T[] providerValue, @ArrayRes int configResId,
1407             @ArrayRes int defaultResId, @NonNull Function<String, T> resourceConverter) {
1408         final Resources res = mContext.getResources();
1409         String[] configValue = res.getStringArray(configResId);
1410 
1411         if (configValue.length == 0) {
1412             if (providerValue.length > 0) {
1413                 return providerValue;
1414             }
1415 
1416             configValue = res.getStringArray(defaultResId);
1417         }
1418 
1419         return convertStrings(configValue, resourceConverter, Arrays.copyOf(providerValue, 0));
1420     }
1421 
1422     /**
1423      * Convert a String array to an array of some other type using the specified converter.
1424      *
1425      * <p>Any null value, or value for which the converter throws a {@link RuntimeException}, will
1426      * not be added to the output array, so the output array may be smaller than the input.
1427      */
convertStrings( @onNull String[] strings, Function<String, T> converter, T[] emptyArray)1428     private <T> T[] convertStrings(
1429             @NonNull String[] strings, Function<String, T> converter, T[] emptyArray) {
1430         final ArrayList<T> convertedValues = new ArrayList<>(strings.length);
1431         for (String configString : strings) {
1432             T convertedValue = null;
1433             try {
1434                 convertedValue = converter.apply(configString);
1435             } catch (Exception e) {
1436                 Log.e(TAG, "Error parsing configuration", e);
1437                 // Fall through
1438             }
1439             if (convertedValue != null) {
1440                 convertedValues.add(convertedValue);
1441             }
1442         }
1443         return convertedValues.toArray(emptyArray);
1444     }
1445 
getCaptivePortalUserAgent()1446     private String getCaptivePortalUserAgent() {
1447         return mDependencies.getDeviceConfigProperty(NAMESPACE_CONNECTIVITY,
1448                 CAPTIVE_PORTAL_USER_AGENT, DEFAULT_USER_AGENT);
1449     }
1450 
nextFallbackUrl()1451     private URL nextFallbackUrl() {
1452         if (mCaptivePortalFallbackUrls.length == 0) {
1453             return null;
1454         }
1455         int idx = Math.abs(mNextFallbackUrlIndex) % mCaptivePortalFallbackUrls.length;
1456         mNextFallbackUrlIndex += mRandom.nextInt(); // randomly change url without memory.
1457         return mCaptivePortalFallbackUrls[idx];
1458     }
1459 
nextFallbackSpec()1460     private CaptivePortalProbeSpec nextFallbackSpec() {
1461         if (isEmpty(mCaptivePortalFallbackSpecs)) {
1462             return null;
1463         }
1464         // Randomly change spec without memory. Also randomize the first attempt.
1465         final int idx = Math.abs(mRandom.nextInt()) % mCaptivePortalFallbackSpecs.length;
1466         return mCaptivePortalFallbackSpecs[idx];
1467     }
1468 
1469     @VisibleForTesting
isCaptivePortal()1470     protected CaptivePortalProbeResult isCaptivePortal() {
1471         if (!mIsCaptivePortalCheckEnabled) {
1472             validationLog("Validation disabled.");
1473             return CaptivePortalProbeResult.SUCCESS;
1474         }
1475 
1476         URL pacUrl = null;
1477         URL httpsUrl = mCaptivePortalHttpsUrl;
1478         URL httpUrl = mCaptivePortalHttpUrl;
1479 
1480         // On networks with a PAC instead of fetching a URL that should result in a 204
1481         // response, we instead simply fetch the PAC script.  This is done for a few reasons:
1482         // 1. At present our PAC code does not yet handle multiple PACs on multiple networks
1483         //    until something like https://android-review.googlesource.com/#/c/115180/ lands.
1484         //    Network.openConnection() will ignore network-specific PACs and instead fetch
1485         //    using NO_PROXY.  If a PAC is in place, the only fetch we know will succeed with
1486         //    NO_PROXY is the fetch of the PAC itself.
1487         // 2. To proxy the generate_204 fetch through a PAC would require a number of things
1488         //    happen before the fetch can commence, namely:
1489         //        a) the PAC script be fetched
1490         //        b) a PAC script resolver service be fired up and resolve the captive portal
1491         //           server.
1492         //    Network validation could be delayed until these prerequisities are satisifed or
1493         //    could simply be left to race them.  Neither is an optimal solution.
1494         // 3. PAC scripts are sometimes used to block or restrict Internet access and may in
1495         //    fact block fetching of the generate_204 URL which would lead to false negative
1496         //    results for network validation.
1497         final ProxyInfo proxyInfo = mLinkProperties.getHttpProxy();
1498         if (proxyInfo != null && !Uri.EMPTY.equals(proxyInfo.getPacFileUrl())) {
1499             pacUrl = makeURL(proxyInfo.getPacFileUrl().toString());
1500             if (pacUrl == null) {
1501                 return CaptivePortalProbeResult.FAILED;
1502             }
1503         }
1504 
1505         if ((pacUrl == null) && (httpUrl == null || httpsUrl == null)) {
1506             return CaptivePortalProbeResult.FAILED;
1507         }
1508 
1509         long startTime = SystemClock.elapsedRealtime();
1510 
1511         final CaptivePortalProbeResult result;
1512         if (pacUrl != null) {
1513             result = sendDnsAndHttpProbes(null, pacUrl, ValidationProbeEvent.PROBE_PAC);
1514             reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, result);
1515         } else if (mUseHttps) {
1516             // Probe results are reported inside sendParallelHttpProbes.
1517             result = sendParallelHttpProbes(proxyInfo, httpsUrl, httpUrl);
1518         } else {
1519             result = sendDnsAndHttpProbes(proxyInfo, httpUrl, ValidationProbeEvent.PROBE_HTTP);
1520             reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, result);
1521         }
1522 
1523         long endTime = SystemClock.elapsedRealtime();
1524 
1525         sendNetworkConditionsBroadcast(true /* response received */,
1526                 result.isPortal() /* isCaptivePortal */,
1527                 startTime, endTime);
1528 
1529         log("isCaptivePortal: isSuccessful()=" + result.isSuccessful()
1530                 + " isPortal()=" + result.isPortal()
1531                 + " RedirectUrl=" + result.redirectUrl
1532                 + " isPartialConnectivity()=" + result.isPartialConnectivity()
1533                 + " Time=" + (endTime - startTime) + "ms");
1534 
1535         return result;
1536     }
1537 
1538     /**
1539      * Do a DNS resolution and URL fetch on a known web server to see if we get the data we expect.
1540      * @return a CaptivePortalProbeResult inferred from the HTTP response.
1541      */
sendDnsAndHttpProbes(ProxyInfo proxy, URL url, int probeType)1542     private CaptivePortalProbeResult sendDnsAndHttpProbes(ProxyInfo proxy, URL url, int probeType) {
1543         // Pre-resolve the captive portal server host so we can log it.
1544         // Only do this if HttpURLConnection is about to, to avoid any potentially
1545         // unnecessary resolution.
1546         final String host = (proxy != null) ? proxy.getHost() : url.getHost();
1547         // This method cannot safely report probe results because it might not be running on the
1548         // state machine thread. Reporting results here would cause races and potentially send
1549         // information to callers that does not make sense because the state machine has already
1550         // changed state.
1551         sendDnsProbe(host);
1552         return sendHttpProbe(url, probeType, null);
1553     }
1554 
1555     /** Do a DNS lookup for the given server, or throw UnknownHostException after timeoutMs */
1556     @VisibleForTesting
sendDnsProbeWithTimeout(String host, int timeoutMs)1557     protected InetAddress[] sendDnsProbeWithTimeout(String host, int timeoutMs)
1558                 throws UnknownHostException {
1559         return DnsUtils.getAllByName(mDependencies.getDnsResolver(), mCleartextDnsNetwork, host,
1560                 TYPE_ADDRCONFIG, FLAG_EMPTY, timeoutMs);
1561     }
1562 
1563     /** Do a DNS resolution of the given server. */
sendDnsProbe(String host)1564     private void sendDnsProbe(String host) {
1565         if (TextUtils.isEmpty(host)) {
1566             return;
1567         }
1568 
1569         final String name = ValidationProbeEvent.getProbeName(ValidationProbeEvent.PROBE_DNS);
1570         final Stopwatch watch = new Stopwatch().start();
1571         int result;
1572         String connectInfo;
1573         try {
1574             InetAddress[] addresses = sendDnsProbeWithTimeout(host, getDnsProbeTimeout());
1575             StringBuffer buffer = new StringBuffer();
1576             for (InetAddress address : addresses) {
1577                 buffer.append(',').append(address.getHostAddress());
1578             }
1579             result = ValidationProbeEvent.DNS_SUCCESS;
1580             connectInfo = "OK " + buffer.substring(1);
1581         } catch (UnknownHostException e) {
1582             result = ValidationProbeEvent.DNS_FAILURE;
1583             connectInfo = "FAIL";
1584         }
1585         final long latency = watch.stop();
1586         validationLog(ValidationProbeEvent.PROBE_DNS, host,
1587                 String.format("%dms %s", latency, connectInfo));
1588         logValidationProbe(latency, ValidationProbeEvent.PROBE_DNS, result);
1589     }
1590 
1591     /**
1592      * Do a URL fetch on a known web server to see if we get the data we expect.
1593      * @return a CaptivePortalProbeResult inferred from the HTTP response.
1594      */
1595     @VisibleForTesting
sendHttpProbe(URL url, int probeType, @Nullable CaptivePortalProbeSpec probeSpec)1596     protected CaptivePortalProbeResult sendHttpProbe(URL url, int probeType,
1597             @Nullable CaptivePortalProbeSpec probeSpec) {
1598         HttpURLConnection urlConnection = null;
1599         int httpResponseCode = CaptivePortalProbeResult.FAILED_CODE;
1600         String redirectUrl = null;
1601         final Stopwatch probeTimer = new Stopwatch().start();
1602         final int oldTag = TrafficStats.getAndSetThreadStatsTag(
1603                 TrafficStatsConstants.TAG_SYSTEM_PROBE);
1604         try {
1605             urlConnection = (HttpURLConnection) mCleartextDnsNetwork.openConnection(url);
1606             urlConnection.setInstanceFollowRedirects(probeType == ValidationProbeEvent.PROBE_PAC);
1607             urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
1608             urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
1609             urlConnection.setRequestProperty("Connection", "close");
1610             urlConnection.setUseCaches(false);
1611             if (mCaptivePortalUserAgent != null) {
1612                 urlConnection.setRequestProperty("User-Agent", mCaptivePortalUserAgent);
1613             }
1614             // cannot read request header after connection
1615             String requestHeader = urlConnection.getRequestProperties().toString();
1616 
1617             // Time how long it takes to get a response to our request
1618             long requestTimestamp = SystemClock.elapsedRealtime();
1619 
1620             httpResponseCode = urlConnection.getResponseCode();
1621             redirectUrl = urlConnection.getHeaderField("location");
1622 
1623             // Time how long it takes to get a response to our request
1624             long responseTimestamp = SystemClock.elapsedRealtime();
1625 
1626             validationLog(probeType, url, "time=" + (responseTimestamp - requestTimestamp) + "ms"
1627                     + " ret=" + httpResponseCode
1628                     + " request=" + requestHeader
1629                     + " headers=" + urlConnection.getHeaderFields());
1630             // NOTE: We may want to consider an "HTTP/1.0 204" response to be a captive
1631             // portal.  The only example of this seen so far was a captive portal.  For
1632             // the time being go with prior behavior of assuming it's not a captive
1633             // portal.  If it is considered a captive portal, a different sign-in URL
1634             // is needed (i.e. can't browse a 204).  This could be the result of an HTTP
1635             // proxy server.
1636             if (httpResponseCode == 200) {
1637                 long contentLength = urlConnection.getContentLengthLong();
1638                 if (probeType == ValidationProbeEvent.PROBE_PAC) {
1639                     validationLog(
1640                             probeType, url, "PAC fetch 200 response interpreted as 204 response.");
1641                     httpResponseCode = CaptivePortalProbeResult.SUCCESS_CODE;
1642                 } else if (contentLength == -1) {
1643                     // When no Content-length (default value == -1), attempt to read a byte
1644                     // from the response. Do not use available() as it is unreliable.
1645                     // See http://b/33498325.
1646                     if (urlConnection.getInputStream().read() == -1) {
1647                         validationLog(probeType, url,
1648                                 "Empty 200 response interpreted as failed response.");
1649                         httpResponseCode = CaptivePortalProbeResult.FAILED_CODE;
1650                     }
1651                 } else if (contentLength <= 4) {
1652                     // Consider 200 response with "Content-length <= 4" to not be a captive
1653                     // portal. There's no point in considering this a captive portal as the
1654                     // user cannot sign-in to an empty page. Probably the result of a broken
1655                     // transparent proxy. See http://b/9972012 and http://b/122999481.
1656                     validationLog(probeType, url, "200 response with Content-length <= 4"
1657                             + " interpreted as failed response.");
1658                     httpResponseCode = CaptivePortalProbeResult.FAILED_CODE;
1659                 }
1660             }
1661         } catch (IOException e) {
1662             validationLog(probeType, url, "Probe failed with exception " + e);
1663             if (httpResponseCode == CaptivePortalProbeResult.FAILED_CODE) {
1664                 // TODO: Ping gateway and DNS server and log results.
1665             }
1666         } finally {
1667             if (urlConnection != null) {
1668                 urlConnection.disconnect();
1669             }
1670             TrafficStats.setThreadStatsTag(oldTag);
1671         }
1672         logValidationProbe(probeTimer.stop(), probeType, httpResponseCode);
1673 
1674         if (probeSpec == null) {
1675             return new CaptivePortalProbeResult(httpResponseCode, redirectUrl, url.toString());
1676         } else {
1677             return probeSpec.getResult(httpResponseCode, redirectUrl);
1678         }
1679     }
1680 
sendParallelHttpProbes( ProxyInfo proxy, URL httpsUrl, URL httpUrl)1681     private CaptivePortalProbeResult sendParallelHttpProbes(
1682             ProxyInfo proxy, URL httpsUrl, URL httpUrl) {
1683         // Number of probes to wait for. If a probe completes with a conclusive answer
1684         // it shortcuts the latch immediately by forcing the count to 0.
1685         final CountDownLatch latch = new CountDownLatch(2);
1686 
1687         final class ProbeThread extends Thread {
1688             private final boolean mIsHttps;
1689             private volatile CaptivePortalProbeResult mResult = CaptivePortalProbeResult.FAILED;
1690 
1691             ProbeThread(boolean isHttps) {
1692                 mIsHttps = isHttps;
1693             }
1694 
1695             public CaptivePortalProbeResult result() {
1696                 return mResult;
1697             }
1698 
1699             @Override
1700             public void run() {
1701                 if (mIsHttps) {
1702                     mResult =
1703                             sendDnsAndHttpProbes(proxy, httpsUrl, ValidationProbeEvent.PROBE_HTTPS);
1704                 } else {
1705                     mResult = sendDnsAndHttpProbes(proxy, httpUrl, ValidationProbeEvent.PROBE_HTTP);
1706                 }
1707                 if ((mIsHttps && mResult.isSuccessful()) || (!mIsHttps && mResult.isPortal())) {
1708                     // Stop waiting immediately if https succeeds or if http finds a portal.
1709                     while (latch.getCount() > 0) {
1710                         latch.countDown();
1711                     }
1712                 }
1713                 // Signal this probe has completed.
1714                 latch.countDown();
1715             }
1716         }
1717 
1718         final ProbeThread httpsProbe = new ProbeThread(true);
1719         final ProbeThread httpProbe = new ProbeThread(false);
1720 
1721         try {
1722             httpsProbe.start();
1723             httpProbe.start();
1724             latch.await(PROBE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
1725         } catch (InterruptedException e) {
1726             validationLog("Error: probes wait interrupted!");
1727             return CaptivePortalProbeResult.FAILED;
1728         }
1729 
1730         final CaptivePortalProbeResult httpsResult = httpsProbe.result();
1731         final CaptivePortalProbeResult httpResult = httpProbe.result();
1732 
1733         // Look for a conclusive probe result first.
1734         if (httpResult.isPortal()) {
1735             reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, httpResult);
1736             return httpResult;
1737         }
1738         // httpsResult.isPortal() is not expected, but check it nonetheless.
1739         if (httpsResult.isPortal() || httpsResult.isSuccessful()) {
1740             reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTPS, httpsResult);
1741             return httpsResult;
1742         }
1743         // If a fallback method exists, use it to retry portal detection.
1744         // If we have new-style probe specs, use those. Otherwise, use the fallback URLs.
1745         final CaptivePortalProbeSpec probeSpec = nextFallbackSpec();
1746         final URL fallbackUrl = (probeSpec != null) ? probeSpec.getUrl() : nextFallbackUrl();
1747         CaptivePortalProbeResult fallbackProbeResult = null;
1748         if (fallbackUrl != null) {
1749             fallbackProbeResult = sendHttpProbe(fallbackUrl, PROBE_FALLBACK, probeSpec);
1750             reportHttpProbeResult(NETWORK_VALIDATION_PROBE_FALLBACK, fallbackProbeResult);
1751             if (fallbackProbeResult.isPortal()) {
1752                 return fallbackProbeResult;
1753             }
1754         }
1755         // Otherwise wait until http and https probes completes and use their results.
1756         try {
1757             httpProbe.join();
1758             reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, httpProbe.result());
1759 
1760             if (httpProbe.result().isPortal()) {
1761                 return httpProbe.result();
1762             }
1763 
1764             httpsProbe.join();
1765             reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTPS, httpsProbe.result());
1766 
1767             final boolean isHttpSuccessful =
1768                     (httpProbe.result().isSuccessful()
1769                     || (fallbackProbeResult != null && fallbackProbeResult.isSuccessful()));
1770             if (httpsProbe.result().isFailed() && isHttpSuccessful) {
1771                 return CaptivePortalProbeResult.PARTIAL;
1772             }
1773             return httpsProbe.result();
1774         } catch (InterruptedException e) {
1775             validationLog("Error: http or https probe wait interrupted!");
1776             return CaptivePortalProbeResult.FAILED;
1777         }
1778     }
1779 
makeURL(String url)1780     private URL makeURL(String url) {
1781         if (url != null) {
1782             try {
1783                 return new URL(url);
1784             } catch (MalformedURLException e) {
1785                 validationLog("Bad URL: " + url);
1786             }
1787         }
1788         return null;
1789     }
1790 
1791     /**
1792      * @param responseReceived - whether or not we received a valid HTTP response to our request.
1793      * If false, isCaptivePortal and responseTimestampMs are ignored
1794      * TODO: This should be moved to the transports.  The latency could be passed to the transports
1795      * along with the captive portal result.  Currently the TYPE_MOBILE broadcasts appear unused so
1796      * perhaps this could just be added to the WiFi transport only.
1797      */
sendNetworkConditionsBroadcast(boolean responseReceived, boolean isCaptivePortal, long requestTimestampMs, long responseTimestampMs)1798     private void sendNetworkConditionsBroadcast(boolean responseReceived, boolean isCaptivePortal,
1799             long requestTimestampMs, long responseTimestampMs) {
1800         Intent latencyBroadcast =
1801                 new Intent(NetworkMonitorUtils.ACTION_NETWORK_CONDITIONS_MEASURED);
1802         if (mNetworkCapabilities.hasTransport(TRANSPORT_WIFI)) {
1803             if (!mWifiManager.isScanAlwaysAvailable()) {
1804                 return;
1805             }
1806 
1807             WifiInfo currentWifiInfo = mWifiManager.getConnectionInfo();
1808             if (currentWifiInfo != null) {
1809                 // NOTE: getSSID()'s behavior changed in API 17; before that, SSIDs were not
1810                 // surrounded by double quotation marks (thus violating the Javadoc), but this
1811                 // was changed to match the Javadoc in API 17. Since clients may have started
1812                 // sanitizing the output of this method since API 17 was released, we should
1813                 // not change it here as it would become impossible to tell whether the SSID is
1814                 // simply being surrounded by quotes due to the API, or whether those quotes
1815                 // are actually part of the SSID.
1816                 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_SSID,
1817                         currentWifiInfo.getSSID());
1818                 latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_BSSID,
1819                         currentWifiInfo.getBSSID());
1820             } else {
1821                 if (VDBG) logw("network info is TYPE_WIFI but no ConnectionInfo found");
1822                 return;
1823             }
1824             latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CONNECTIVITY_TYPE, TYPE_WIFI);
1825         } else if (mNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
1826             // TODO(b/123893112): Support multi-sim.
1827             latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_NETWORK_TYPE,
1828                     mTelephonyManager.getNetworkType());
1829             final ServiceState dataSs = mTelephonyManager.getServiceState();
1830             if (dataSs == null) {
1831                 logw("failed to retrieve ServiceState");
1832                 return;
1833             }
1834             // See if the data sub is registered for PS services on cell.
1835             final NetworkRegistrationInfo nri = dataSs.getNetworkRegistrationInfo(
1836                     NetworkRegistrationInfo.DOMAIN_PS,
1837                     AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
1838             latencyBroadcast.putExtra(
1839                     NetworkMonitorUtils.EXTRA_CELL_ID,
1840                     nri == null ? null : nri.getCellIdentity());
1841             latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CONNECTIVITY_TYPE, TYPE_MOBILE);
1842         } else {
1843             return;
1844         }
1845         latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_RESPONSE_RECEIVED,
1846                 responseReceived);
1847         latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_REQUEST_TIMESTAMP_MS,
1848                 requestTimestampMs);
1849 
1850         if (responseReceived) {
1851             latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_IS_CAPTIVE_PORTAL,
1852                     isCaptivePortal);
1853             latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_RESPONSE_TIMESTAMP_MS,
1854                     responseTimestampMs);
1855         }
1856         mContext.sendBroadcastAsUser(latencyBroadcast, UserHandle.CURRENT,
1857                 NetworkMonitorUtils.PERMISSION_ACCESS_NETWORK_CONDITIONS);
1858     }
1859 
logNetworkEvent(int evtype)1860     private void logNetworkEvent(int evtype) {
1861         int[] transports = mNetworkCapabilities.getTransportTypes();
1862         mMetricsLog.log(mCleartextDnsNetwork, transports, new NetworkEvent(evtype));
1863     }
1864 
networkEventType(ValidationStage s, EvaluationResult r)1865     private int networkEventType(ValidationStage s, EvaluationResult r) {
1866         if (s.mIsFirstValidation) {
1867             if (r.mIsValidated) {
1868                 return NetworkEvent.NETWORK_FIRST_VALIDATION_SUCCESS;
1869             } else {
1870                 return NetworkEvent.NETWORK_FIRST_VALIDATION_PORTAL_FOUND;
1871             }
1872         } else {
1873             if (r.mIsValidated) {
1874                 return NetworkEvent.NETWORK_REVALIDATION_SUCCESS;
1875             } else {
1876                 return NetworkEvent.NETWORK_REVALIDATION_PORTAL_FOUND;
1877             }
1878         }
1879     }
1880 
maybeLogEvaluationResult(int evtype)1881     private void maybeLogEvaluationResult(int evtype) {
1882         if (mEvaluationTimer.isRunning()) {
1883             int[] transports = mNetworkCapabilities.getTransportTypes();
1884             mMetricsLog.log(mCleartextDnsNetwork, transports,
1885                     new NetworkEvent(evtype, mEvaluationTimer.stop()));
1886             mEvaluationTimer.reset();
1887         }
1888     }
1889 
logValidationProbe(long durationMs, int probeType, int probeResult)1890     private void logValidationProbe(long durationMs, int probeType, int probeResult) {
1891         int[] transports = mNetworkCapabilities.getTransportTypes();
1892         boolean isFirstValidation = validationStage().mIsFirstValidation;
1893         ValidationProbeEvent ev = new ValidationProbeEvent.Builder()
1894                 .setProbeType(probeType, isFirstValidation)
1895                 .setReturnCode(probeResult)
1896                 .setDurationMs(durationMs)
1897                 .build();
1898         mMetricsLog.log(mCleartextDnsNetwork, transports, ev);
1899     }
1900 
1901     @VisibleForTesting
1902     static class Dependencies {
getPrivateDnsBypassNetwork(Network network)1903         public Network getPrivateDnsBypassNetwork(Network network) {
1904             return new OneAddressPerFamilyNetwork(network);
1905         }
1906 
getDnsResolver()1907         public DnsResolver getDnsResolver() {
1908             return DnsResolver.getInstance();
1909         }
1910 
getRandom()1911         public Random getRandom() {
1912             return new Random();
1913         }
1914 
1915         /**
1916          * Get the value of a global integer setting.
1917          * @param symbol Name of the setting
1918          * @param defaultValue Value to return if the setting is not defined.
1919          */
getSetting(Context context, String symbol, int defaultValue)1920         public int getSetting(Context context, String symbol, int defaultValue) {
1921             return Settings.Global.getInt(context.getContentResolver(), symbol, defaultValue);
1922         }
1923 
1924         /**
1925          * Get the value of a global String setting.
1926          * @param symbol Name of the setting
1927          * @param defaultValue Value to return if the setting is not defined.
1928          */
getSetting(Context context, String symbol, String defaultValue)1929         public String getSetting(Context context, String symbol, String defaultValue) {
1930             final String value = Settings.Global.getString(context.getContentResolver(), symbol);
1931             return value != null ? value : defaultValue;
1932         }
1933 
1934         /**
1935          * Look up the value of a property in DeviceConfig.
1936          * @param namespace The namespace containing the property to look up.
1937          * @param name The name of the property to look up.
1938          * @param defaultValue The value to return if the property does not exist or has no non-null
1939          *                     value.
1940          * @return the corresponding value, or defaultValue if none exists.
1941          */
1942         @Nullable
getDeviceConfigProperty(@onNull String namespace, @NonNull String name, @Nullable String defaultValue)1943         public String getDeviceConfigProperty(@NonNull String namespace, @NonNull String name,
1944                 @Nullable String defaultValue) {
1945             return NetworkStackUtils.getDeviceConfigProperty(namespace, name, defaultValue);
1946         }
1947 
1948         /**
1949          * Look up the value of a property in DeviceConfig.
1950          * @param namespace The namespace containing the property to look up.
1951          * @param name The name of the property to look up.
1952          * @param defaultValue The value to return if the property does not exist or has no non-null
1953          *                     value.
1954          * @return the corresponding value, or defaultValue if none exists.
1955          */
getDeviceConfigPropertyInt(@onNull String namespace, @NonNull String name, int defaultValue)1956         public int getDeviceConfigPropertyInt(@NonNull String namespace, @NonNull String name,
1957                 int defaultValue) {
1958             return NetworkStackUtils.getDeviceConfigPropertyInt(namespace, name, defaultValue);
1959         }
1960 
1961         public static final Dependencies DEFAULT = new Dependencies();
1962     }
1963 
1964     /**
1965      * Methods in this class perform no locking because all accesses are performed on the state
1966      * machine's thread. Need to consider the thread safety if it ever could be accessed outside the
1967      * state machine.
1968      */
1969     @VisibleForTesting
1970     protected class DnsStallDetector {
1971         private int mConsecutiveTimeoutCount = 0;
1972         private int mSize;
1973         final DnsResult[] mDnsEvents;
1974         final RingBufferIndices mResultIndices;
1975 
DnsStallDetector(int size)1976         DnsStallDetector(int size) {
1977             mSize = Math.max(DEFAULT_DNS_LOG_SIZE, size);
1978             mDnsEvents = new DnsResult[mSize];
1979             mResultIndices = new RingBufferIndices(mSize);
1980         }
1981 
1982         @VisibleForTesting
accumulateConsecutiveDnsTimeoutCount(int code)1983         protected void accumulateConsecutiveDnsTimeoutCount(int code) {
1984             final DnsResult result = new DnsResult(code);
1985             mDnsEvents[mResultIndices.add()] = result;
1986             if (result.isTimeout()) {
1987                 mConsecutiveTimeoutCount++;
1988             } else {
1989                 // Keep the event in mDnsEvents without clearing it so that there are logs to do the
1990                 // simulation and analysis.
1991                 mConsecutiveTimeoutCount = 0;
1992             }
1993         }
1994 
isDataStallSuspected(int timeoutCountThreshold, int validTime)1995         private boolean isDataStallSuspected(int timeoutCountThreshold, int validTime) {
1996             if (timeoutCountThreshold <= 0) {
1997                 Log.wtf(TAG, "Timeout count threshold should be larger than 0.");
1998                 return false;
1999             }
2000 
2001             // Check if the consecutive timeout count reach the threshold or not.
2002             if (mConsecutiveTimeoutCount < timeoutCountThreshold) {
2003                 return false;
2004             }
2005 
2006             // Check if the target dns event index is valid or not.
2007             final int firstConsecutiveTimeoutIndex =
2008                     mResultIndices.indexOf(mResultIndices.size() - timeoutCountThreshold);
2009 
2010             // If the dns timeout events happened long time ago, the events are meaningless for
2011             // data stall evaluation. Thus, check if the first consecutive timeout dns event
2012             // considered in the evaluation happened in defined threshold time.
2013             final long now = SystemClock.elapsedRealtime();
2014             final long firstTimeoutTime = now - mDnsEvents[firstConsecutiveTimeoutIndex].mTimeStamp;
2015             return (firstTimeoutTime < validTime);
2016         }
2017 
getConsecutiveTimeoutCount()2018         int getConsecutiveTimeoutCount() {
2019             return mConsecutiveTimeoutCount;
2020         }
2021     }
2022 
2023     private static class DnsResult {
2024         // TODO: Need to move the DNS return code definition to a specific class once unify DNS
2025         // response code is done.
2026         private static final int RETURN_CODE_DNS_TIMEOUT = 255;
2027 
2028         private final long mTimeStamp;
2029         private final int mReturnCode;
2030 
DnsResult(int code)2031         DnsResult(int code) {
2032             mTimeStamp = SystemClock.elapsedRealtime();
2033             mReturnCode = code;
2034         }
2035 
isTimeout()2036         private boolean isTimeout() {
2037             return mReturnCode == RETURN_CODE_DNS_TIMEOUT;
2038         }
2039     }
2040 
2041 
2042     @VisibleForTesting
getDnsStallDetector()2043     protected DnsStallDetector getDnsStallDetector() {
2044         return mDnsStallDetector;
2045     }
2046 
dataStallEvaluateTypeEnabled(int type)2047     private boolean dataStallEvaluateTypeEnabled(int type) {
2048         return (mDataStallEvaluationType & type) != 0;
2049     }
2050 
2051     @VisibleForTesting
getLastProbeTime()2052     protected long getLastProbeTime() {
2053         return mLastProbeTime;
2054     }
2055 
2056     @VisibleForTesting
isDataStall()2057     protected boolean isDataStall() {
2058         boolean result = false;
2059         // Reevaluation will generate traffic. Thus, set a minimal reevaluation timer to limit the
2060         // possible traffic cost in metered network.
2061         if (!mNetworkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED)
2062                 && (SystemClock.elapsedRealtime() - getLastProbeTime()
2063                 < mDataStallMinEvaluateTime)) {
2064             return false;
2065         }
2066 
2067         // Check dns signal. Suspect it may be a data stall if both :
2068         // 1. The number of consecutive DNS query timeouts >= mConsecutiveDnsTimeoutThreshold.
2069         // 2. Those consecutive DNS queries happened in the last mValidDataStallDnsTimeThreshold ms.
2070         if (dataStallEvaluateTypeEnabled(DATA_STALL_EVALUATION_TYPE_DNS)) {
2071             if (mDnsStallDetector.isDataStallSuspected(mConsecutiveDnsTimeoutThreshold,
2072                     mDataStallValidDnsTimeThreshold)) {
2073                 result = true;
2074                 logNetworkEvent(NetworkEvent.NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND);
2075             }
2076         }
2077 
2078         if (VDBG_STALL) {
2079             log("isDataStall: result=" + result + ", consecutive dns timeout count="
2080                     + mDnsStallDetector.getConsecutiveTimeoutCount());
2081         }
2082 
2083         return result;
2084     }
2085 
2086     // Class to keep state of evaluation results and probe results.
2087     //
2088     // The main purpose was to ensure NetworkMonitor can notify ConnectivityService of probe results
2089     // as soon as they happen, without triggering any other changes. This requires keeping state on
2090     // the most recent evaluation result. Calling noteProbeResult will ensure that the results
2091     // reported to ConnectivityService contain the previous evaluation result, and thus won't
2092     // trigger a validation or partial connectivity state change.
2093     //
2094     // Note that this class is not currently being used for this purpose. The reason is that some
2095     // of the system behaviour triggered by reporting network validation - notably, NetworkAgent
2096     // behaviour - depends not only on the value passed by notifyNetworkTested, but also on the
2097     // fact that notifyNetworkTested was called. For example, telephony triggers network recovery
2098     // any time it is told that validation failed, i.e., if the result does not contain
2099     // NETWORK_VALIDATION_RESULT_VALID. But with this scheme, the first two or three validation
2100     // reports are all failures, because they are "HTTP succeeded but validation not yet passed",
2101     // "HTTP and HTTPS succeeded but validation not yet passed", etc.
2102     @VisibleForTesting
2103     protected class EvaluationState {
2104         // The latest validation result for this network. This is a bitmask of
2105         // INetworkMonitor.NETWORK_VALIDATION_RESULT_* constants.
2106         private int mEvaluationResult = NETWORK_VALIDATION_RESULT_INVALID;
2107         // Indicates which probes have completed since clearProbeResults was called.
2108         // This is a bitmask of INetworkMonitor.NETWORK_VALIDATION_PROBE_* constants.
2109         private int mProbeResults = 0;
2110         // The latest redirect URL.
2111         private String mRedirectUrl;
2112 
clearProbeResults()2113         protected void clearProbeResults() {
2114             mProbeResults = 0;
2115         }
2116 
2117         // Probe result for http probe should be updated from reportHttpProbeResult().
noteProbeResult(int probeResult, boolean succeeded)2118         protected void noteProbeResult(int probeResult, boolean succeeded) {
2119             if (succeeded) {
2120                 mProbeResults |= probeResult;
2121             } else {
2122                 mProbeResults &= ~probeResult;
2123             }
2124         }
2125 
reportEvaluationResult(int result, @Nullable String redirectUrl)2126         protected void reportEvaluationResult(int result, @Nullable String redirectUrl) {
2127             mEvaluationResult = result;
2128             mRedirectUrl = redirectUrl;
2129             notifyNetworkTested(getNetworkTestResult(), mRedirectUrl);
2130         }
2131 
getNetworkTestResult()2132         protected int getNetworkTestResult() {
2133             if (mCallbackVersion < 3) {
2134                 if ((mEvaluationResult & NETWORK_VALIDATION_RESULT_VALID) != 0) {
2135                     return NETWORK_TEST_RESULT_VALID;
2136                 }
2137                 if ((mEvaluationResult & NETWORK_VALIDATION_RESULT_PARTIAL) != 0) {
2138                     return NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY;
2139                 }
2140                 return NETWORK_TEST_RESULT_INVALID;
2141             }
2142             return mEvaluationResult | mProbeResults;
2143         }
2144     }
2145 
2146     @VisibleForTesting
getEvaluationState()2147     protected EvaluationState getEvaluationState() {
2148         return mEvaluationState;
2149     }
2150 
maybeDisableHttpsProbing(boolean acceptPartial)2151     private void maybeDisableHttpsProbing(boolean acceptPartial) {
2152         mAcceptPartialConnectivity = acceptPartial;
2153         // Ignore https probe in next validation if user accept partial connectivity on a partial
2154         // connectivity network.
2155         if (((mEvaluationState.getNetworkTestResult() & NETWORK_VALIDATION_RESULT_PARTIAL) != 0)
2156                 && mAcceptPartialConnectivity) {
2157             mUseHttps = false;
2158         }
2159     }
2160 
2161     // Report HTTP, HTTP or FALLBACK probe result.
2162     @VisibleForTesting
reportHttpProbeResult(int probeResult, @NonNull final CaptivePortalProbeResult result)2163     protected void reportHttpProbeResult(int probeResult,
2164                 @NonNull final CaptivePortalProbeResult result) {
2165         boolean succeeded = result.isSuccessful();
2166         // The success of a HTTP probe does not tell us whether the DNS probe succeeded.
2167         // The DNS and HTTP probes run one after the other in sendDnsAndHttpProbes, and that
2168         // method cannot report the result of the DNS probe because that it could be running
2169         // on a different thread which is racing with the main state machine thread. So, if
2170         // an HTTP or HTTPS probe succeeded, assume that the DNS probe succeeded. But if an
2171         // HTTP or HTTPS probe failed, don't assume that DNS is not working.
2172         // TODO: fix this.
2173         if (succeeded) {
2174             probeResult |= NETWORK_VALIDATION_PROBE_DNS;
2175         }
2176         mEvaluationState.noteProbeResult(probeResult, succeeded);
2177     }
2178 }
2179