Move NetworkMonitor to NetworkStack
Bug: b/112869080
Test: atest FrameworksNetTests NetworkStackTests
Change-Id: I1b8b6a3f4390adbabf92fb9e48da61c47b08b2ec
diff --git a/services/core/java/com/android/server/connectivity/ConnectivityConstants.java b/services/core/java/com/android/server/connectivity/ConnectivityConstants.java
index 24865bc..6fa98b8 100644
--- a/services/core/java/com/android/server/connectivity/ConnectivityConstants.java
+++ b/services/core/java/com/android/server/connectivity/ConnectivityConstants.java
@@ -21,22 +21,6 @@
* @hide
*/
public class ConnectivityConstants {
- // IPC constants
- public static final String ACTION_NETWORK_CONDITIONS_MEASURED =
- "android.net.conn.NETWORK_CONDITIONS_MEASURED";
- public static final String EXTRA_CONNECTIVITY_TYPE = "extra_connectivity_type";
- public static final String EXTRA_NETWORK_TYPE = "extra_network_type";
- public static final String EXTRA_RESPONSE_RECEIVED = "extra_response_received";
- public static final String EXTRA_IS_CAPTIVE_PORTAL = "extra_is_captive_portal";
- public static final String EXTRA_CELL_ID = "extra_cellid";
- public static final String EXTRA_SSID = "extra_ssid";
- public static final String EXTRA_BSSID = "extra_bssid";
- /** real time since boot */
- public static final String EXTRA_REQUEST_TIMESTAMP_MS = "extra_request_timestamp_ms";
- public static final String EXTRA_RESPONSE_TIMESTAMP_MS = "extra_response_timestamp_ms";
-
- public static final String PERMISSION_ACCESS_NETWORK_CONDITIONS =
- "android.permission.ACCESS_NETWORK_CONDITIONS";
// Penalty applied to scores of Networks that have not been validated.
public static final int UNVALIDATED_SCORE_PENALTY = 40;
diff --git a/services/core/java/com/android/server/connectivity/DnsManager.java b/services/core/java/com/android/server/connectivity/DnsManager.java
index b8f057d..d8bb635 100644
--- a/services/core/java/com/android/server/connectivity/DnsManager.java
+++ b/services/core/java/com/android/server/connectivity/DnsManager.java
@@ -18,10 +18,9 @@
import static android.net.ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE_FALLBACK;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
-import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
-import static android.provider.Settings.Global.DNS_RESOLVER_MIN_SAMPLES;
import static android.provider.Settings.Global.DNS_RESOLVER_MAX_SAMPLES;
+import static android.provider.Settings.Global.DNS_RESOLVER_MIN_SAMPLES;
import static android.provider.Settings.Global.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS;
import static android.provider.Settings.Global.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT;
import static android.provider.Settings.Global.PRIVATE_DNS_DEFAULT_MODE;
@@ -35,6 +34,7 @@
import android.net.Network;
import android.net.NetworkUtils;
import android.net.Uri;
+import android.net.shared.PrivateDnsConfig;
import android.os.Binder;
import android.os.INetworkManagementService;
import android.os.UserHandle;
@@ -43,10 +43,7 @@
import android.util.Pair;
import android.util.Slog;
-import com.android.server.connectivity.MockableSystemProperties;
-
import java.net.InetAddress;
-import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -54,10 +51,8 @@
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
-import java.util.Objects;
-import java.util.stream.Collectors;
import java.util.Set;
-import java.util.StringJoiner;
+import java.util.stream.Collectors;
/**
@@ -123,43 +118,6 @@
private static final int DNS_RESOLVER_DEFAULT_MIN_SAMPLES = 8;
private static final int DNS_RESOLVER_DEFAULT_MAX_SAMPLES = 64;
- public static class PrivateDnsConfig {
- public final boolean useTls;
- public final String hostname;
- public final InetAddress[] ips;
-
- public PrivateDnsConfig() {
- this(false);
- }
-
- public PrivateDnsConfig(boolean useTls) {
- this.useTls = useTls;
- this.hostname = "";
- this.ips = new InetAddress[0];
- }
-
- public PrivateDnsConfig(String hostname, InetAddress[] ips) {
- this.useTls = !TextUtils.isEmpty(hostname);
- this.hostname = useTls ? hostname : "";
- this.ips = (ips != null) ? ips : new InetAddress[0];
- }
-
- public PrivateDnsConfig(PrivateDnsConfig cfg) {
- useTls = cfg.useTls;
- hostname = cfg.hostname;
- ips = cfg.ips;
- }
-
- public boolean inStrictMode() {
- return useTls && !TextUtils.isEmpty(hostname);
- }
-
- public String toString() {
- return PrivateDnsConfig.class.getSimpleName() +
- "{" + useTls + ":" + hostname + "/" + Arrays.toString(ips) + "}";
- }
- }
-
public static PrivateDnsConfig getPrivateDnsConfig(ContentResolver cr) {
final String mode = getPrivateDnsMode(cr);
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 262184b..54c89aa 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -16,9 +16,8 @@
package com.android.server.connectivity;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
-
import android.content.Context;
+import android.net.INetworkMonitor;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
@@ -29,7 +28,6 @@
import android.os.Handler;
import android.os.INetworkManagementService;
import android.os.Messenger;
-import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
import android.util.SparseArray;
@@ -37,11 +35,8 @@
import com.android.internal.util.AsyncChannel;
import com.android.internal.util.WakeupMessage;
import com.android.server.ConnectivityService;
-import com.android.server.connectivity.NetworkMonitor;
import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Comparator;
import java.util.Objects;
import java.util.SortedSet;
import java.util.TreeSet;
@@ -126,7 +121,6 @@
public LinkProperties linkProperties;
// This should only be modified via ConnectivityService.updateCapabilities().
public NetworkCapabilities networkCapabilities;
- public final NetworkMonitor networkMonitor;
public final NetworkMisc networkMisc;
// Indicates if netd has been told to create this Network. From this point on the appropriate
// routing rules are setup and routes are added so packets can begin flowing over the Network.
@@ -239,6 +233,9 @@
// Used by ConnectivityService to keep track of 464xlat.
public Nat464Xlat clatd;
+ // Set after asynchronous creation of the NetworkMonitor.
+ private volatile INetworkMonitor mNetworkMonitor;
+
private static final String TAG = ConnectivityService.class.getSimpleName();
private static final boolean VDBG = false;
private final ConnectivityService mConnService;
@@ -247,7 +244,7 @@
public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info,
LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler,
- NetworkMisc misc, NetworkRequest defaultRequest, ConnectivityService connService) {
+ NetworkMisc misc, ConnectivityService connService) {
this.messenger = messenger;
asyncChannel = ac;
network = net;
@@ -258,10 +255,16 @@
mConnService = connService;
mContext = context;
mHandler = handler;
- networkMonitor = mConnService.createNetworkMonitor(context, handler, this, defaultRequest);
networkMisc = misc;
}
+ /**
+ * Inform NetworkAgentInfo that a new NetworkMonitor was created.
+ */
+ public void onNetworkMonitorCreated(INetworkMonitor networkMonitor) {
+ mNetworkMonitor = networkMonitor;
+ }
+
public ConnectivityService connService() {
return mConnService;
}
@@ -278,6 +281,15 @@
return network;
}
+ /**
+ * Get the INetworkMonitor in this NetworkAgentInfo.
+ *
+ * <p>This will be null before {@link #onNetworkMonitorCreated(INetworkMonitor)} is called.
+ */
+ public INetworkMonitor networkMonitor() {
+ return mNetworkMonitor;
+ }
+
// Functions for manipulating the requests satisfied by this network.
//
// These functions must only called on ConnectivityService's main thread.
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
deleted file mode 100644
index 2a00025..0000000
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ /dev/null
@@ -1,1693 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.connectivity;
-
-import static android.net.CaptivePortal.APP_RETURN_DISMISSED;
-import static android.net.CaptivePortal.APP_RETURN_UNWANTED;
-import static android.net.CaptivePortal.APP_RETURN_WANTED_AS_IS;
-import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_PROBE_SPEC;
-import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL;
-import static android.net.metrics.ValidationProbeEvent.DNS_FAILURE;
-import static android.net.metrics.ValidationProbeEvent.DNS_SUCCESS;
-import static android.net.metrics.ValidationProbeEvent.PROBE_FALLBACK;
-import static android.net.metrics.ValidationProbeEvent.PROBE_PRIVDNS;
-
-import android.annotation.Nullable;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.CaptivePortal;
-import android.net.ConnectivityManager;
-import android.net.ICaptivePortal;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkRequest;
-import android.net.ProxyInfo;
-import android.net.TrafficStats;
-import android.net.Uri;
-import android.net.captiveportal.CaptivePortalProbeResult;
-import android.net.captiveportal.CaptivePortalProbeSpec;
-import android.net.metrics.IpConnectivityLog;
-import android.net.metrics.NetworkEvent;
-import android.net.metrics.ValidationProbeEvent;
-import android.net.util.Stopwatch;
-import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiManager;
-import android.os.Handler;
-import android.os.Message;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.telephony.CellIdentityCdma;
-import android.telephony.CellIdentityGsm;
-import android.telephony.CellIdentityLte;
-import android.telephony.CellIdentityWcdma;
-import android.telephony.CellInfo;
-import android.telephony.CellInfoCdma;
-import android.telephony.CellInfoGsm;
-import android.telephony.CellInfoLte;
-import android.telephony.CellInfoWcdma;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import android.util.LocalLog;
-import android.util.LocalLog.ReadOnlyLocalLog;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.Protocol;
-import com.android.internal.util.RingBufferIndices;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
-import com.android.server.connectivity.DnsManager.PrivateDnsConfig;
-
-import java.io.IOException;
-import java.net.HttpURLConnection;
-import java.net.InetAddress;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Random;
-import java.util.UUID;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * {@hide}
- */
-public class NetworkMonitor extends StateMachine {
- private static final String TAG = NetworkMonitor.class.getSimpleName();
- private static final boolean DBG = true;
- private static final boolean VDBG = false;
- private static final boolean VDBG_STALL = Log.isLoggable(TAG, Log.DEBUG);
- // Default configuration values for captive portal detection probes.
- // TODO: append a random length parameter to the default HTTPS url.
- // TODO: randomize browser version ids in the default User-Agent String.
- private static final String DEFAULT_HTTPS_URL = "https://www.google.com/generate_204";
- private static final String DEFAULT_HTTP_URL =
- "http://connectivitycheck.gstatic.com/generate_204";
- private static final String DEFAULT_FALLBACK_URL = "http://www.google.com/gen_204";
- private static final String DEFAULT_OTHER_FALLBACK_URLS =
- "http://play.googleapis.com/generate_204";
- private static final String DEFAULT_USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) "
- + "AppleWebKit/537.36 (KHTML, like Gecko) "
- + "Chrome/60.0.3112.32 Safari/537.36";
-
- private static final int SOCKET_TIMEOUT_MS = 10000;
- private static final int PROBE_TIMEOUT_MS = 3000;
-
- // Default configuration values for data stall detection.
- private static final int DEFAULT_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD = 5;
- private static final int DEFAULT_DATA_STALL_MIN_EVALUATE_TIME_MS = 60 * 1000;
- private static final int DEFAULT_DATA_STALL_VALID_DNS_TIME_THRESHOLD_MS = 30 * 60 * 1000;
-
- private static final int DATA_STALL_EVALUATION_TYPE_DNS = 1;
- private static final int DEFAULT_DATA_STALL_EVALUATION_TYPES =
- (1 << DATA_STALL_EVALUATION_TYPE_DNS);
-
- enum EvaluationResult {
- VALIDATED(true),
- CAPTIVE_PORTAL(false);
- final boolean mIsValidated;
- EvaluationResult(boolean isValidated) {
- this.mIsValidated = isValidated;
- }
- }
-
- enum ValidationStage {
- FIRST_VALIDATION(true),
- REVALIDATION(false);
- final boolean mIsFirstValidation;
- ValidationStage(boolean isFirstValidation) {
- this.mIsFirstValidation = isFirstValidation;
- }
- }
-
- // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED.
- // The network should be used as a default internet connection. It was found to be:
- // 1. a functioning network providing internet access, or
- // 2. a captive portal and the user decided to use it as is.
- public static final int NETWORK_TEST_RESULT_VALID = 0;
- // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED.
- // The network should not be used as a default internet connection. It was found to be:
- // 1. a captive portal and the user is prompted to sign-in, or
- // 2. a captive portal and the user did not want to use it, or
- // 3. a broken network (e.g. DNS failed, connect failed, HTTP request failed).
- public static final int NETWORK_TEST_RESULT_INVALID = 1;
-
- private static final int BASE = Protocol.BASE_NETWORK_MONITOR;
-
- /**
- * Inform NetworkMonitor that their network is connected.
- * Initiates Network Validation.
- */
- public static final int CMD_NETWORK_CONNECTED = BASE + 1;
-
- /**
- * Inform ConnectivityService that the network has been tested.
- * obj = String representing URL that Internet probe was redirect to, if it was redirected.
- * arg1 = One of the NETWORK_TESTED_RESULT_* constants.
- * arg2 = NetID.
- */
- public static final int EVENT_NETWORK_TESTED = BASE + 2;
-
- /**
- * Message to self indicating it's time to evaluate a network's connectivity.
- * arg1 = Token to ignore old messages.
- */
- private static final int CMD_REEVALUATE = BASE + 6;
-
- /**
- * Inform NetworkMonitor that the network has disconnected.
- */
- public static final int CMD_NETWORK_DISCONNECTED = BASE + 7;
-
- /**
- * Force evaluation even if it has succeeded in the past.
- * arg1 = UID responsible for requesting this reeval. Will be billed for data.
- */
- private static final int CMD_FORCE_REEVALUATION = BASE + 8;
-
- /**
- * Message to self indicating captive portal app finished.
- * arg1 = one of: APP_RETURN_DISMISSED,
- * APP_RETURN_UNWANTED,
- * APP_RETURN_WANTED_AS_IS
- * obj = mCaptivePortalLoggedInResponseToken as String
- */
- private static final int CMD_CAPTIVE_PORTAL_APP_FINISHED = BASE + 9;
-
- /**
- * Request ConnectivityService display provisioning notification.
- * arg1 = Whether to make the notification visible.
- * arg2 = NetID.
- * obj = Intent to be launched when notification selected by user, null if !arg1.
- */
- public static final int EVENT_PROVISIONING_NOTIFICATION = BASE + 10;
-
- /**
- * Message indicating sign-in app should be launched.
- * Sent by mLaunchCaptivePortalAppBroadcastReceiver when the
- * user touches the sign in notification, or sent by
- * ConnectivityService when the user touches the "sign into
- * network" button in the wifi access point detail page.
- */
- public static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = BASE + 11;
-
- /**
- * Retest network to see if captive portal is still in place.
- * arg1 = UID responsible for requesting this reeval. Will be billed for data.
- * 0 indicates self-initiated, so nobody to blame.
- */
- private static final int CMD_CAPTIVE_PORTAL_RECHECK = BASE + 12;
-
- /**
- * ConnectivityService notifies NetworkMonitor of settings changes to
- * Private DNS. If a DNS resolution is required, e.g. for DNS-over-TLS in
- * strict mode, then an event is sent back to ConnectivityService with the
- * result of the resolution attempt.
- *
- * A separate message is used to trigger (re)evaluation of the Private DNS
- * configuration, so that the message can be handled as needed in different
- * states, including being ignored until after an ongoing captive portal
- * validation phase is completed.
- */
- private static final int CMD_PRIVATE_DNS_SETTINGS_CHANGED = BASE + 13;
- public static final int EVENT_PRIVATE_DNS_CONFIG_RESOLVED = BASE + 14;
- private static final int CMD_EVALUATE_PRIVATE_DNS = BASE + 15;
-
- /**
- * Message to self indicating captive portal detection is completed.
- * obj = CaptivePortalProbeResult for detection result;
- */
- public static final int CMD_PROBE_COMPLETE = BASE + 16;
-
- /**
- * ConnectivityService notifies NetworkMonitor of DNS query responses event.
- * arg1 = returncode in OnDnsEvent which indicates the response code for the DNS query.
- */
- public static final int EVENT_DNS_NOTIFICATION = BASE + 17;
-
- // Start mReevaluateDelayMs at this value and double.
- private static final int INITIAL_REEVALUATE_DELAY_MS = 1000;
- private static final int MAX_REEVALUATE_DELAY_MS = 10 * 60 * 1000;
- // Before network has been evaluated this many times, ignore repeated reevaluate requests.
- private static final int IGNORE_REEVALUATE_ATTEMPTS = 5;
- private int mReevaluateToken = 0;
- private static final int NO_UID = 0;
- private static final int INVALID_UID = -1;
- private int mUidResponsibleForReeval = INVALID_UID;
- // Stop blaming UID that requested re-evaluation after this many attempts.
- private static final int BLAME_FOR_EVALUATION_ATTEMPTS = 5;
- // Delay between reevaluations once a captive portal has been found.
- private static final int CAPTIVE_PORTAL_REEVALUATE_DELAY_MS = 10 * 60 * 1000;
-
- private static final int NUM_VALIDATION_LOG_LINES = 20;
-
- private String mPrivateDnsProviderHostname = "";
-
- public static boolean isValidationRequired(
- NetworkCapabilities dfltNetCap, NetworkCapabilities nc) {
- // TODO: Consider requiring validation for DUN networks.
- return dfltNetCap.satisfiedByNetworkCapabilities(nc);
- }
-
- private final Context mContext;
- private final Handler mConnectivityServiceHandler;
- private final NetworkAgentInfo mNetworkAgentInfo;
- private final Network mNetwork;
- private final int mNetId;
- private final TelephonyManager mTelephonyManager;
- private final WifiManager mWifiManager;
- private final NetworkRequest mDefaultRequest;
- private final IpConnectivityLog mMetricsLog;
- private final Dependencies mDependencies;
-
- // Configuration values for captive portal detection probes.
- private final String mCaptivePortalUserAgent;
- private final URL mCaptivePortalHttpsUrl;
- private final URL mCaptivePortalHttpUrl;
- private final URL[] mCaptivePortalFallbackUrls;
- @Nullable
- private final CaptivePortalProbeSpec[] mCaptivePortalFallbackSpecs;
-
- @VisibleForTesting
- protected boolean mIsCaptivePortalCheckEnabled;
-
- private boolean mUseHttps;
- // The total number of captive portal detection attempts for this NetworkMonitor instance.
- private int mValidations = 0;
-
- // Set if the user explicitly selected "Do not use this network" in captive portal sign-in app.
- private boolean mUserDoesNotWant = false;
- // Avoids surfacing "Sign in to network" notification.
- private boolean mDontDisplaySigninNotification = false;
-
- public boolean systemReady = false;
-
- private final State mDefaultState = new DefaultState();
- private final State mValidatedState = new ValidatedState();
- private final State mMaybeNotifyState = new MaybeNotifyState();
- private final State mEvaluatingState = new EvaluatingState();
- private final State mCaptivePortalState = new CaptivePortalState();
- private final State mEvaluatingPrivateDnsState = new EvaluatingPrivateDnsState();
- private final State mProbingState = new ProbingState();
- private final State mWaitingForNextProbeState = new WaitingForNextProbeState();
-
- private CustomIntentReceiver mLaunchCaptivePortalAppBroadcastReceiver = null;
-
- private final LocalLog validationLogs = new LocalLog(NUM_VALIDATION_LOG_LINES);
-
- private final Stopwatch mEvaluationTimer = new Stopwatch();
-
- // This variable is set before transitioning to the mCaptivePortalState.
- private CaptivePortalProbeResult mLastPortalProbeResult = CaptivePortalProbeResult.FAILED;
-
- // Random generator to select fallback URL index
- private final Random mRandom;
- private int mNextFallbackUrlIndex = 0;
-
- private int mReevaluateDelayMs = INITIAL_REEVALUATE_DELAY_MS;
- private int mEvaluateAttempts = 0;
- private volatile int mProbeToken = 0;
- private final int mConsecutiveDnsTimeoutThreshold;
- private final int mDataStallMinEvaluateTime;
- private final int mDataStallValidDnsTimeThreshold;
- private final int mDataStallEvaluationType;
- private final DnsStallDetector mDnsStallDetector;
- private long mLastProbeTime;
-
- public NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo,
- NetworkRequest defaultRequest) {
- this(context, handler, networkAgentInfo, defaultRequest, new IpConnectivityLog(),
- Dependencies.DEFAULT);
- }
-
- @VisibleForTesting
- protected NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo,
- NetworkRequest defaultRequest, IpConnectivityLog logger, Dependencies deps) {
- // Add suffix indicating which NetworkMonitor we're talking about.
- super(TAG + networkAgentInfo.name());
-
- // Logs with a tag of the form given just above, e.g.
- // <timestamp> 862 2402 D NetworkMonitor/NetworkAgentInfo [WIFI () - 100]: ...
- setDbg(VDBG);
-
- mContext = context;
- mMetricsLog = logger;
- mConnectivityServiceHandler = handler;
- mDependencies = deps;
- mNetworkAgentInfo = networkAgentInfo;
- mNetwork = deps.getNetwork(networkAgentInfo).getPrivateDnsBypassingCopy();
- mNetId = mNetwork.netId;
- mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
- mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
- mDefaultRequest = defaultRequest;
-
- addState(mDefaultState);
- addState(mMaybeNotifyState, mDefaultState);
- addState(mEvaluatingState, mMaybeNotifyState);
- addState(mProbingState, mEvaluatingState);
- addState(mWaitingForNextProbeState, mEvaluatingState);
- addState(mCaptivePortalState, mMaybeNotifyState);
- addState(mEvaluatingPrivateDnsState, mDefaultState);
- addState(mValidatedState, mDefaultState);
- setInitialState(mDefaultState);
-
- mIsCaptivePortalCheckEnabled = getIsCaptivePortalCheckEnabled();
- mUseHttps = getUseHttpsValidation();
- mCaptivePortalUserAgent = getCaptivePortalUserAgent();
- mCaptivePortalHttpsUrl = makeURL(getCaptivePortalServerHttpsUrl());
- mCaptivePortalHttpUrl = makeURL(getCaptivePortalServerHttpUrl(deps, context));
- mCaptivePortalFallbackUrls = makeCaptivePortalFallbackUrls();
- mCaptivePortalFallbackSpecs = makeCaptivePortalFallbackProbeSpecs();
- mRandom = deps.getRandom();
- // TODO: Evaluate to move data stall configuration to a specific class.
- mConsecutiveDnsTimeoutThreshold = getConsecutiveDnsTimeoutThreshold();
- mDnsStallDetector = new DnsStallDetector(mConsecutiveDnsTimeoutThreshold);
- mDataStallMinEvaluateTime = getDataStallMinEvaluateTime();
- mDataStallValidDnsTimeThreshold = getDataStallValidDnsTimeThreshold();
- mDataStallEvaluationType = getDataStallEvalutionType();
-
- start();
- }
-
- /**
- * Request the NetworkMonitor to reevaluate the network.
- */
- public void forceReevaluation(int responsibleUid) {
- sendMessage(CMD_FORCE_REEVALUATION, responsibleUid, 0);
- }
-
- /**
- * Send a notification to NetworkMonitor indicating that private DNS settings have changed.
- * @param newCfg The new private DNS configuration.
- */
- public void notifyPrivateDnsSettingsChanged(PrivateDnsConfig newCfg) {
- // Cancel any outstanding resolutions.
- removeMessages(CMD_PRIVATE_DNS_SETTINGS_CHANGED);
- // Send the update to the proper thread.
- sendMessage(CMD_PRIVATE_DNS_SETTINGS_CHANGED, newCfg);
- }
-
- @Override
- protected void log(String s) {
- if (DBG) Log.d(TAG + "/" + mNetworkAgentInfo.name(), s);
- }
-
- private void validationLog(int probeType, Object url, String msg) {
- String probeName = ValidationProbeEvent.getProbeName(probeType);
- validationLog(String.format("%s %s %s", probeName, url, msg));
- }
-
- private void validationLog(String s) {
- if (DBG) log(s);
- validationLogs.log(s);
- }
-
- public ReadOnlyLocalLog getValidationLogs() {
- return validationLogs.readOnlyLocalLog();
- }
-
- private ValidationStage validationStage() {
- return 0 == mValidations ? ValidationStage.FIRST_VALIDATION : ValidationStage.REVALIDATION;
- }
-
- private boolean isValidationRequired() {
- return isValidationRequired(
- mDefaultRequest.networkCapabilities, mNetworkAgentInfo.networkCapabilities);
- }
-
-
- private void notifyNetworkTestResultInvalid(Object obj) {
- mConnectivityServiceHandler.sendMessage(obtainMessage(
- EVENT_NETWORK_TESTED, NETWORK_TEST_RESULT_INVALID, mNetId, obj));
- }
-
- // DefaultState is the parent of all States. It exists only to handle CMD_* messages but
- // does not entail any real state (hence no enter() or exit() routines).
- private class DefaultState extends State {
- @Override
- public boolean processMessage(Message message) {
- switch (message.what) {
- case CMD_NETWORK_CONNECTED:
- logNetworkEvent(NetworkEvent.NETWORK_CONNECTED);
- transitionTo(mEvaluatingState);
- return HANDLED;
- case CMD_NETWORK_DISCONNECTED:
- logNetworkEvent(NetworkEvent.NETWORK_DISCONNECTED);
- if (mLaunchCaptivePortalAppBroadcastReceiver != null) {
- mContext.unregisterReceiver(mLaunchCaptivePortalAppBroadcastReceiver);
- mLaunchCaptivePortalAppBroadcastReceiver = null;
- }
- quit();
- return HANDLED;
- case CMD_FORCE_REEVALUATION:
- case CMD_CAPTIVE_PORTAL_RECHECK:
- log("Forcing reevaluation for UID " + message.arg1);
- mUidResponsibleForReeval = message.arg1;
- transitionTo(mEvaluatingState);
- return HANDLED;
- case CMD_CAPTIVE_PORTAL_APP_FINISHED:
- log("CaptivePortal App responded with " + message.arg1);
-
- // If the user has seen and acted on a captive portal notification, and the
- // captive portal app is now closed, disable HTTPS probes. This avoids the
- // following pathological situation:
- //
- // 1. HTTP probe returns a captive portal, HTTPS probe fails or times out.
- // 2. User opens the app and logs into the captive portal.
- // 3. HTTP starts working, but HTTPS still doesn't work for some other reason -
- // perhaps due to the network blocking HTTPS?
- //
- // In this case, we'll fail to validate the network even after the app is
- // dismissed. There is now no way to use this network, because the app is now
- // gone, so the user cannot select "Use this network as is".
- mUseHttps = false;
-
- switch (message.arg1) {
- case APP_RETURN_DISMISSED:
- sendMessage(CMD_FORCE_REEVALUATION, NO_UID, 0);
- break;
- case APP_RETURN_WANTED_AS_IS:
- mDontDisplaySigninNotification = true;
- // TODO: Distinguish this from a network that actually validates.
- // Displaying the "x" on the system UI icon may still be a good idea.
- transitionTo(mEvaluatingPrivateDnsState);
- break;
- case APP_RETURN_UNWANTED:
- mDontDisplaySigninNotification = true;
- mUserDoesNotWant = true;
- notifyNetworkTestResultInvalid(null);
- // TODO: Should teardown network.
- mUidResponsibleForReeval = 0;
- transitionTo(mEvaluatingState);
- break;
- }
- return HANDLED;
- case CMD_PRIVATE_DNS_SETTINGS_CHANGED: {
- final PrivateDnsConfig cfg = (PrivateDnsConfig) message.obj;
- if (!isValidationRequired() || cfg == null || !cfg.inStrictMode()) {
- // No DNS resolution required.
- //
- // We don't force any validation in opportunistic mode
- // here. Opportunistic mode nameservers are validated
- // separately within netd.
- //
- // Reset Private DNS settings state.
- mPrivateDnsProviderHostname = "";
- break;
- }
-
- mPrivateDnsProviderHostname = cfg.hostname;
-
- // DNS resolutions via Private DNS strict mode block for a
- // few seconds (~4.2) checking for any IP addresses to
- // arrive and validate. Initiating a (re)evaluation now
- // should not significantly alter the validation outcome.
- //
- // No matter what: enqueue a validation request; one of
- // three things can happen with this request:
- // [1] ignored (EvaluatingState or CaptivePortalState)
- // [2] transition to EvaluatingPrivateDnsState
- // (DefaultState and ValidatedState)
- // [3] handled (EvaluatingPrivateDnsState)
- //
- // The Private DNS configuration to be evaluated will:
- // [1] be skipped (not in strict mode), or
- // [2] validate (huzzah), or
- // [3] encounter some problem (invalid hostname,
- // no resolved IP addresses, IPs unreachable,
- // port 853 unreachable, port 853 is not running a
- // DNS-over-TLS server, et cetera).
- sendMessage(CMD_EVALUATE_PRIVATE_DNS);
- break;
- }
- case EVENT_DNS_NOTIFICATION:
- mDnsStallDetector.accumulateConsecutiveDnsTimeoutCount(message.arg1);
- break;
- default:
- break;
- }
- return HANDLED;
- }
- }
-
- // Being in the ValidatedState State indicates a Network is:
- // - Successfully validated, or
- // - Wanted "as is" by the user, or
- // - Does not satisfy the default NetworkRequest and so validation has been skipped.
- private class ValidatedState extends State {
- @Override
- public void enter() {
- maybeLogEvaluationResult(
- networkEventType(validationStage(), EvaluationResult.VALIDATED));
- mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED,
- NETWORK_TEST_RESULT_VALID, mNetId, null));
- mValidations++;
- }
-
- @Override
- public boolean processMessage(Message message) {
- switch (message.what) {
- case CMD_NETWORK_CONNECTED:
- transitionTo(mValidatedState);
- break;
- case CMD_EVALUATE_PRIVATE_DNS:
- transitionTo(mEvaluatingPrivateDnsState);
- break;
- case EVENT_DNS_NOTIFICATION:
- mDnsStallDetector.accumulateConsecutiveDnsTimeoutCount(message.arg1);
- if (isDataStall()) {
- validationLog("Suspecting data stall, reevaluate");
- transitionTo(mEvaluatingState);
- }
- break;
- default:
- return NOT_HANDLED;
- }
- return HANDLED;
- }
- }
-
- // Being in the MaybeNotifyState State indicates the user may have been notified that sign-in
- // is required. This State takes care to clear the notification upon exit from the State.
- private class MaybeNotifyState extends State {
- @Override
- public boolean processMessage(Message message) {
- switch (message.what) {
- case CMD_LAUNCH_CAPTIVE_PORTAL_APP:
- final Intent intent = new Intent(
- ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN);
- // OneAddressPerFamilyNetwork is not parcelable across processes.
- intent.putExtra(ConnectivityManager.EXTRA_NETWORK, new Network(mNetwork));
- intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL,
- new CaptivePortal(new ICaptivePortal.Stub() {
- @Override
- public void appResponse(int response) {
- if (response == APP_RETURN_WANTED_AS_IS) {
- mContext.enforceCallingPermission(
- android.Manifest.permission.CONNECTIVITY_INTERNAL,
- "CaptivePortal");
- }
- sendMessage(CMD_CAPTIVE_PORTAL_APP_FINISHED, response);
- }
- }));
- final CaptivePortalProbeResult probeRes = mLastPortalProbeResult;
- intent.putExtra(EXTRA_CAPTIVE_PORTAL_URL, probeRes.detectUrl);
- if (probeRes.probeSpec != null) {
- final String encodedSpec = probeRes.probeSpec.getEncodedSpec();
- intent.putExtra(EXTRA_CAPTIVE_PORTAL_PROBE_SPEC, encodedSpec);
- }
- intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_USER_AGENT,
- mCaptivePortalUserAgent);
- intent.setFlags(
- Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivityAsUser(intent, UserHandle.CURRENT);
- return HANDLED;
- default:
- return NOT_HANDLED;
- }
- }
-
- @Override
- public void exit() {
- Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 0, mNetId, null);
- mConnectivityServiceHandler.sendMessage(message);
- }
- }
-
- // Being in the EvaluatingState State indicates the Network is being evaluated for internet
- // connectivity, or that the user has indicated that this network is unwanted.
- private class EvaluatingState extends State {
- @Override
- public void enter() {
- // If we have already started to track time spent in EvaluatingState
- // don't reset the timer due simply to, say, commands or events that
- // cause us to exit and re-enter EvaluatingState.
- if (!mEvaluationTimer.isStarted()) {
- mEvaluationTimer.start();
- }
- sendMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
- if (mUidResponsibleForReeval != INVALID_UID) {
- TrafficStats.setThreadStatsUid(mUidResponsibleForReeval);
- mUidResponsibleForReeval = INVALID_UID;
- }
- mReevaluateDelayMs = INITIAL_REEVALUATE_DELAY_MS;
- mEvaluateAttempts = 0;
- }
-
- @Override
- public boolean processMessage(Message message) {
- switch (message.what) {
- case CMD_REEVALUATE:
- if (message.arg1 != mReevaluateToken || mUserDoesNotWant) {
- return HANDLED;
- }
- // Don't bother validating networks that don't satisfy the default request.
- // This includes:
- // - VPNs which can be considered explicitly desired by the user and the
- // user's desire trumps whether the network validates.
- // - Networks that don't provide Internet access. It's unclear how to
- // validate such networks.
- // - Untrusted networks. It's unsafe to prompt the user to sign-in to
- // such networks and the user didn't express interest in connecting to
- // such networks (an app did) so the user may be unhappily surprised when
- // asked to sign-in to a network they didn't want to connect to in the
- // first place. Validation could be done to adjust the network scores
- // however these networks are app-requested and may not be intended for
- // general usage, in which case general validation may not be an accurate
- // measure of the network's quality. Only the app knows how to evaluate
- // the network so don't bother validating here. Furthermore sending HTTP
- // packets over the network may be undesirable, for example an extremely
- // expensive metered network, or unwanted leaking of the User Agent string.
- if (!isValidationRequired()) {
- validationLog("Network would not satisfy default request, not validating");
- transitionTo(mValidatedState);
- return HANDLED;
- }
- mEvaluateAttempts++;
-
- transitionTo(mProbingState);
- return HANDLED;
- case CMD_FORCE_REEVALUATION:
- // Before IGNORE_REEVALUATE_ATTEMPTS attempts are made,
- // ignore any re-evaluation requests. After, restart the
- // evaluation process via EvaluatingState#enter.
- return (mEvaluateAttempts < IGNORE_REEVALUATE_ATTEMPTS) ? HANDLED : NOT_HANDLED;
- default:
- return NOT_HANDLED;
- }
- }
-
- @Override
- public void exit() {
- TrafficStats.clearThreadStatsUid();
- }
- }
-
- // BroadcastReceiver that waits for a particular Intent and then posts a message.
- private class CustomIntentReceiver extends BroadcastReceiver {
- private final int mToken;
- private final int mWhat;
- private final String mAction;
- CustomIntentReceiver(String action, int token, int what) {
- mToken = token;
- mWhat = what;
- mAction = action + "_" + mNetId + "_" + token;
- mContext.registerReceiver(this, new IntentFilter(mAction));
- }
- public PendingIntent getPendingIntent() {
- final Intent intent = new Intent(mAction);
- intent.setPackage(mContext.getPackageName());
- return PendingIntent.getBroadcast(mContext, 0, intent, 0);
- }
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(mAction)) sendMessage(obtainMessage(mWhat, mToken));
- }
- }
-
- // Being in the CaptivePortalState State indicates a captive portal was detected and the user
- // has been shown a notification to sign-in.
- private class CaptivePortalState extends State {
- private static final String ACTION_LAUNCH_CAPTIVE_PORTAL_APP =
- "android.net.netmon.launchCaptivePortalApp";
-
- @Override
- public void enter() {
- maybeLogEvaluationResult(
- networkEventType(validationStage(), EvaluationResult.CAPTIVE_PORTAL));
- // Don't annoy user with sign-in notifications.
- if (mDontDisplaySigninNotification) return;
- // Create a CustomIntentReceiver that sends us a
- // CMD_LAUNCH_CAPTIVE_PORTAL_APP message when the user
- // touches the notification.
- if (mLaunchCaptivePortalAppBroadcastReceiver == null) {
- // Wait for result.
- mLaunchCaptivePortalAppBroadcastReceiver = new CustomIntentReceiver(
- ACTION_LAUNCH_CAPTIVE_PORTAL_APP, new Random().nextInt(),
- CMD_LAUNCH_CAPTIVE_PORTAL_APP);
- }
- // Display the sign in notification.
- Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 1, mNetId,
- mLaunchCaptivePortalAppBroadcastReceiver.getPendingIntent());
- mConnectivityServiceHandler.sendMessage(message);
- // Retest for captive portal occasionally.
- sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */,
- CAPTIVE_PORTAL_REEVALUATE_DELAY_MS);
- mValidations++;
- }
-
- @Override
- public void exit() {
- removeMessages(CMD_CAPTIVE_PORTAL_RECHECK);
- }
- }
-
- private class EvaluatingPrivateDnsState extends State {
- private int mPrivateDnsReevalDelayMs;
- private PrivateDnsConfig mPrivateDnsConfig;
-
- @Override
- public void enter() {
- mPrivateDnsReevalDelayMs = INITIAL_REEVALUATE_DELAY_MS;
- mPrivateDnsConfig = null;
- sendMessage(CMD_EVALUATE_PRIVATE_DNS);
- }
-
- @Override
- public boolean processMessage(Message msg) {
- switch (msg.what) {
- case CMD_EVALUATE_PRIVATE_DNS:
- if (inStrictMode()) {
- if (!isStrictModeHostnameResolved()) {
- resolveStrictModeHostname();
-
- if (isStrictModeHostnameResolved()) {
- notifyPrivateDnsConfigResolved();
- } else {
- handlePrivateDnsEvaluationFailure();
- break;
- }
- }
-
- // Look up a one-time hostname, to bypass caching.
- //
- // Note that this will race with ConnectivityService
- // code programming the DNS-over-TLS server IP addresses
- // into netd (if invoked, above). If netd doesn't know
- // the IP addresses yet, or if the connections to the IP
- // addresses haven't yet been validated, netd will block
- // for up to a few seconds before failing the lookup.
- if (!sendPrivateDnsProbe()) {
- handlePrivateDnsEvaluationFailure();
- break;
- }
- }
-
- // All good!
- transitionTo(mValidatedState);
- break;
- default:
- return NOT_HANDLED;
- }
- return HANDLED;
- }
-
- private boolean inStrictMode() {
- return !TextUtils.isEmpty(mPrivateDnsProviderHostname);
- }
-
- private boolean isStrictModeHostnameResolved() {
- return (mPrivateDnsConfig != null)
- && mPrivateDnsConfig.hostname.equals(mPrivateDnsProviderHostname)
- && (mPrivateDnsConfig.ips.length > 0);
- }
-
- private void resolveStrictModeHostname() {
- try {
- // Do a blocking DNS resolution using the network-assigned nameservers.
- final InetAddress[] ips = mNetwork.getAllByName(mPrivateDnsProviderHostname);
- mPrivateDnsConfig = new PrivateDnsConfig(mPrivateDnsProviderHostname, ips);
- validationLog("Strict mode hostname resolved: " + mPrivateDnsConfig);
- } catch (UnknownHostException uhe) {
- mPrivateDnsConfig = null;
- validationLog("Strict mode hostname resolution failed: " + uhe.getMessage());
- }
- }
-
- private void notifyPrivateDnsConfigResolved() {
- mConnectivityServiceHandler.sendMessage(obtainMessage(
- EVENT_PRIVATE_DNS_CONFIG_RESOLVED, 0, mNetId, mPrivateDnsConfig));
- }
-
- private void handlePrivateDnsEvaluationFailure() {
- notifyNetworkTestResultInvalid(null);
-
- // Queue up a re-evaluation with backoff.
- //
- // TODO: Consider abandoning this state after a few attempts and
- // transitioning back to EvaluatingState, to perhaps give ourselves
- // the opportunity to (re)detect a captive portal or something.
- sendMessageDelayed(CMD_EVALUATE_PRIVATE_DNS, mPrivateDnsReevalDelayMs);
- mPrivateDnsReevalDelayMs *= 2;
- if (mPrivateDnsReevalDelayMs > MAX_REEVALUATE_DELAY_MS) {
- mPrivateDnsReevalDelayMs = MAX_REEVALUATE_DELAY_MS;
- }
- }
-
- private boolean sendPrivateDnsProbe() {
- // q.v. system/netd/server/dns/DnsTlsTransport.cpp
- final String oneTimeHostnameSuffix = "-dnsotls-ds.metric.gstatic.com";
- final String host = UUID.randomUUID().toString().substring(0, 8)
- + oneTimeHostnameSuffix;
- final Stopwatch watch = new Stopwatch().start();
- try {
- final InetAddress[] ips = mNetworkAgentInfo.network().getAllByName(host);
- final long time = watch.stop();
- final String strIps = Arrays.toString(ips);
- final boolean success = (ips != null && ips.length > 0);
- validationLog(PROBE_PRIVDNS, host, String.format("%dms: %s", time, strIps));
- logValidationProbe(time, PROBE_PRIVDNS, success ? DNS_SUCCESS : DNS_FAILURE);
- return success;
- } catch (UnknownHostException uhe) {
- final long time = watch.stop();
- validationLog(PROBE_PRIVDNS, host,
- String.format("%dms - Error: %s", time, uhe.getMessage()));
- logValidationProbe(time, PROBE_PRIVDNS, DNS_FAILURE);
- }
- return false;
- }
- }
-
- private class ProbingState extends State {
- private Thread mThread;
-
- @Override
- public void enter() {
- if (mEvaluateAttempts >= BLAME_FOR_EVALUATION_ATTEMPTS) {
- //Don't continue to blame UID forever.
- TrafficStats.clearThreadStatsUid();
- }
-
- final int token = ++mProbeToken;
- mThread = new Thread(() -> sendMessage(obtainMessage(CMD_PROBE_COMPLETE, token, 0,
- isCaptivePortal())));
- mThread.start();
- }
-
- @Override
- public boolean processMessage(Message message) {
- switch (message.what) {
- case CMD_PROBE_COMPLETE:
- // Ensure that CMD_PROBE_COMPLETE from stale threads are ignored.
- if (message.arg1 != mProbeToken) {
- return HANDLED;
- }
-
- final CaptivePortalProbeResult probeResult =
- (CaptivePortalProbeResult) message.obj;
- mLastProbeTime = SystemClock.elapsedRealtime();
- if (probeResult.isSuccessful()) {
- // Transit EvaluatingPrivateDnsState to get to Validated
- // state (even if no Private DNS validation required).
- transitionTo(mEvaluatingPrivateDnsState);
- } else if (probeResult.isPortal()) {
- notifyNetworkTestResultInvalid(probeResult.redirectUrl);
- mLastPortalProbeResult = probeResult;
- transitionTo(mCaptivePortalState);
- } else {
- logNetworkEvent(NetworkEvent.NETWORK_VALIDATION_FAILED);
- notifyNetworkTestResultInvalid(probeResult.redirectUrl);
- transitionTo(mWaitingForNextProbeState);
- }
- return HANDLED;
- case EVENT_DNS_NOTIFICATION:
- // Leave the event to DefaultState to record correct dns timestamp.
- return NOT_HANDLED;
- default:
- // Wait for probe result and defer events to next state by default.
- deferMessage(message);
- return HANDLED;
- }
- }
-
- @Override
- public void exit() {
- if (mThread.isAlive()) {
- mThread.interrupt();
- }
- mThread = null;
- }
- }
-
- // Being in the WaitingForNextProbeState indicates that evaluating probes failed and state is
- // transited from ProbingState. This ensures that the state machine is only in ProbingState
- // while a probe is in progress, not while waiting to perform the next probe. That allows
- // ProbingState to defer most messages until the probe is complete, which keeps the code simple
- // and matches the pre-Q behaviour where probes were a blocking operation performed on the state
- // machine thread.
- private class WaitingForNextProbeState extends State {
- @Override
- public void enter() {
- scheduleNextProbe();
- }
-
- private void scheduleNextProbe() {
- final Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
- sendMessageDelayed(msg, mReevaluateDelayMs);
- mReevaluateDelayMs *= 2;
- if (mReevaluateDelayMs > MAX_REEVALUATE_DELAY_MS) {
- mReevaluateDelayMs = MAX_REEVALUATE_DELAY_MS;
- }
- }
-
- @Override
- public boolean processMessage(Message message) {
- return NOT_HANDLED;
- }
- }
-
- // Limits the list of IP addresses returned by getAllByName or tried by openConnection to at
- // most one per address family. This ensures we only wait up to 20 seconds for TCP connections
- // to complete, regardless of how many IP addresses a host has.
- private static class OneAddressPerFamilyNetwork extends Network {
- OneAddressPerFamilyNetwork(Network network) {
- // Always bypass Private DNS.
- super(network.getPrivateDnsBypassingCopy());
- }
-
- @Override
- public InetAddress[] getAllByName(String host) throws UnknownHostException {
- final List<InetAddress> addrs = Arrays.asList(super.getAllByName(host));
-
- // Ensure the address family of the first address is tried first.
- LinkedHashMap<Class, InetAddress> addressByFamily = new LinkedHashMap<>();
- addressByFamily.put(addrs.get(0).getClass(), addrs.get(0));
- Collections.shuffle(addrs);
-
- for (InetAddress addr : addrs) {
- addressByFamily.put(addr.getClass(), addr);
- }
-
- return addressByFamily.values().toArray(new InetAddress[addressByFamily.size()]);
- }
- }
-
- public boolean getIsCaptivePortalCheckEnabled() {
- String symbol = Settings.Global.CAPTIVE_PORTAL_MODE;
- int defaultValue = Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT;
- int mode = mDependencies.getSetting(mContext, symbol, defaultValue);
- return mode != Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE;
- }
-
- public boolean getUseHttpsValidation() {
- return mDependencies.getSetting(mContext, Settings.Global.CAPTIVE_PORTAL_USE_HTTPS, 1) == 1;
- }
-
- public boolean getWifiScansAlwaysAvailableDisabled() {
- return mDependencies.getSetting(
- mContext, Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 0;
- }
-
- private String getCaptivePortalServerHttpsUrl() {
- return mDependencies.getSetting(mContext,
- Settings.Global.CAPTIVE_PORTAL_HTTPS_URL, DEFAULT_HTTPS_URL);
- }
-
- private int getConsecutiveDnsTimeoutThreshold() {
- return mDependencies.getSetting(mContext,
- Settings.Global.DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD,
- DEFAULT_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD);
- }
-
- private int getDataStallMinEvaluateTime() {
- return mDependencies.getSetting(mContext,
- Settings.Global.DATA_STALL_MIN_EVALUATE_INTERVAL,
- DEFAULT_DATA_STALL_MIN_EVALUATE_TIME_MS);
- }
-
- private int getDataStallValidDnsTimeThreshold() {
- return mDependencies.getSetting(mContext,
- Settings.Global.DATA_STALL_VALID_DNS_TIME_THRESHOLD,
- DEFAULT_DATA_STALL_VALID_DNS_TIME_THRESHOLD_MS);
- }
-
- private int getDataStallEvalutionType() {
- return mDependencies.getSetting(mContext, Settings.Global.DATA_STALL_EVALUATION_TYPE,
- DEFAULT_DATA_STALL_EVALUATION_TYPES);
- }
-
- // Static for direct access by ConnectivityService
- public static String getCaptivePortalServerHttpUrl(Context context) {
- return getCaptivePortalServerHttpUrl(Dependencies.DEFAULT, context);
- }
-
- public static String getCaptivePortalServerHttpUrl(Dependencies deps, Context context) {
- return deps.getSetting(context, Settings.Global.CAPTIVE_PORTAL_HTTP_URL, DEFAULT_HTTP_URL);
- }
-
- private URL[] makeCaptivePortalFallbackUrls() {
- try {
- String separator = ",";
- String firstUrl = mDependencies.getSetting(mContext,
- Settings.Global.CAPTIVE_PORTAL_FALLBACK_URL, DEFAULT_FALLBACK_URL);
- String joinedUrls = firstUrl + separator + mDependencies.getSetting(mContext,
- Settings.Global.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS,
- DEFAULT_OTHER_FALLBACK_URLS);
- List<URL> urls = new ArrayList<>();
- for (String s : joinedUrls.split(separator)) {
- URL u = makeURL(s);
- if (u == null) {
- continue;
- }
- urls.add(u);
- }
- if (urls.isEmpty()) {
- Log.e(TAG, String.format("could not create any url from %s", joinedUrls));
- }
- return urls.toArray(new URL[urls.size()]);
- } catch (Exception e) {
- // Don't let a misconfiguration bootloop the system.
- Log.e(TAG, "Error parsing configured fallback URLs", e);
- return new URL[0];
- }
- }
-
- private CaptivePortalProbeSpec[] makeCaptivePortalFallbackProbeSpecs() {
- try {
- final String settingsValue = mDependencies.getSetting(
- mContext, Settings.Global.CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS, null);
- // Probe specs only used if configured in settings
- if (TextUtils.isEmpty(settingsValue)) {
- return null;
- }
-
- return CaptivePortalProbeSpec.parseCaptivePortalProbeSpecs(settingsValue);
- } catch (Exception e) {
- // Don't let a misconfiguration bootloop the system.
- Log.e(TAG, "Error parsing configured fallback probe specs", e);
- return null;
- }
- }
-
- private String getCaptivePortalUserAgent() {
- return mDependencies.getSetting(mContext,
- Settings.Global.CAPTIVE_PORTAL_USER_AGENT, DEFAULT_USER_AGENT);
- }
-
- private URL nextFallbackUrl() {
- if (mCaptivePortalFallbackUrls.length == 0) {
- return null;
- }
- int idx = Math.abs(mNextFallbackUrlIndex) % mCaptivePortalFallbackUrls.length;
- mNextFallbackUrlIndex += mRandom.nextInt(); // randomly change url without memory.
- return mCaptivePortalFallbackUrls[idx];
- }
-
- private CaptivePortalProbeSpec nextFallbackSpec() {
- if (ArrayUtils.isEmpty(mCaptivePortalFallbackSpecs)) {
- return null;
- }
- // Randomly change spec without memory. Also randomize the first attempt.
- final int idx = Math.abs(mRandom.nextInt()) % mCaptivePortalFallbackSpecs.length;
- return mCaptivePortalFallbackSpecs[idx];
- }
-
- @VisibleForTesting
- protected CaptivePortalProbeResult isCaptivePortal() {
- if (!mIsCaptivePortalCheckEnabled) {
- validationLog("Validation disabled.");
- return CaptivePortalProbeResult.SUCCESS;
- }
-
- URL pacUrl = null;
- URL httpsUrl = mCaptivePortalHttpsUrl;
- URL httpUrl = mCaptivePortalHttpUrl;
-
- // On networks with a PAC instead of fetching a URL that should result in a 204
- // response, we instead simply fetch the PAC script. This is done for a few reasons:
- // 1. At present our PAC code does not yet handle multiple PACs on multiple networks
- // until something like https://android-review.googlesource.com/#/c/115180/ lands.
- // Network.openConnection() will ignore network-specific PACs and instead fetch
- // using NO_PROXY. If a PAC is in place, the only fetch we know will succeed with
- // NO_PROXY is the fetch of the PAC itself.
- // 2. To proxy the generate_204 fetch through a PAC would require a number of things
- // happen before the fetch can commence, namely:
- // a) the PAC script be fetched
- // b) a PAC script resolver service be fired up and resolve the captive portal
- // server.
- // Network validation could be delayed until these prerequisities are satisifed or
- // could simply be left to race them. Neither is an optimal solution.
- // 3. PAC scripts are sometimes used to block or restrict Internet access and may in
- // fact block fetching of the generate_204 URL which would lead to false negative
- // results for network validation.
- final ProxyInfo proxyInfo = mNetworkAgentInfo.linkProperties.getHttpProxy();
- if (proxyInfo != null && !Uri.EMPTY.equals(proxyInfo.getPacFileUrl())) {
- pacUrl = makeURL(proxyInfo.getPacFileUrl().toString());
- if (pacUrl == null) {
- return CaptivePortalProbeResult.FAILED;
- }
- }
-
- if ((pacUrl == null) && (httpUrl == null || httpsUrl == null)) {
- return CaptivePortalProbeResult.FAILED;
- }
-
- long startTime = SystemClock.elapsedRealtime();
-
- final CaptivePortalProbeResult result;
- if (pacUrl != null) {
- result = sendDnsAndHttpProbes(null, pacUrl, ValidationProbeEvent.PROBE_PAC);
- } else if (mUseHttps) {
- result = sendParallelHttpProbes(proxyInfo, httpsUrl, httpUrl);
- } else {
- result = sendDnsAndHttpProbes(proxyInfo, httpUrl, ValidationProbeEvent.PROBE_HTTP);
- }
-
- long endTime = SystemClock.elapsedRealtime();
-
- sendNetworkConditionsBroadcast(true /* response received */,
- result.isPortal() /* isCaptivePortal */,
- startTime, endTime);
-
- log("isCaptivePortal: isSuccessful()=" + result.isSuccessful()
- + " isPortal()=" + result.isPortal()
- + " RedirectUrl=" + result.redirectUrl
- + " Time=" + (endTime - startTime) + "ms");
-
- return result;
- }
-
- /**
- * Do a DNS resolution and URL fetch on a known web server to see if we get the data we expect.
- * @return a CaptivePortalProbeResult inferred from the HTTP response.
- */
- private CaptivePortalProbeResult sendDnsAndHttpProbes(ProxyInfo proxy, URL url, int probeType) {
- // Pre-resolve the captive portal server host so we can log it.
- // Only do this if HttpURLConnection is about to, to avoid any potentially
- // unnecessary resolution.
- final String host = (proxy != null) ? proxy.getHost() : url.getHost();
- sendDnsProbe(host);
- return sendHttpProbe(url, probeType, null);
- }
-
- /** Do a DNS resolution of the given server. */
- private void sendDnsProbe(String host) {
- if (TextUtils.isEmpty(host)) {
- return;
- }
-
- final String name = ValidationProbeEvent.getProbeName(ValidationProbeEvent.PROBE_DNS);
- final Stopwatch watch = new Stopwatch().start();
- int result;
- String connectInfo;
- try {
- InetAddress[] addresses = mNetwork.getAllByName(host);
- StringBuffer buffer = new StringBuffer();
- for (InetAddress address : addresses) {
- buffer.append(',').append(address.getHostAddress());
- }
- result = ValidationProbeEvent.DNS_SUCCESS;
- connectInfo = "OK " + buffer.substring(1);
- } catch (UnknownHostException e) {
- result = ValidationProbeEvent.DNS_FAILURE;
- connectInfo = "FAIL";
- }
- final long latency = watch.stop();
- validationLog(ValidationProbeEvent.PROBE_DNS, host,
- String.format("%dms %s", latency, connectInfo));
- logValidationProbe(latency, ValidationProbeEvent.PROBE_DNS, result);
- }
-
- /**
- * Do a URL fetch on a known web server to see if we get the data we expect.
- * @return a CaptivePortalProbeResult inferred from the HTTP response.
- */
- @VisibleForTesting
- protected CaptivePortalProbeResult sendHttpProbe(URL url, int probeType,
- @Nullable CaptivePortalProbeSpec probeSpec) {
- HttpURLConnection urlConnection = null;
- int httpResponseCode = CaptivePortalProbeResult.FAILED_CODE;
- String redirectUrl = null;
- final Stopwatch probeTimer = new Stopwatch().start();
- final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_PROBE);
- try {
- urlConnection = (HttpURLConnection) mNetwork.openConnection(url);
- urlConnection.setInstanceFollowRedirects(probeType == ValidationProbeEvent.PROBE_PAC);
- urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
- urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
- urlConnection.setUseCaches(false);
- if (mCaptivePortalUserAgent != null) {
- urlConnection.setRequestProperty("User-Agent", mCaptivePortalUserAgent);
- }
- // cannot read request header after connection
- String requestHeader = urlConnection.getRequestProperties().toString();
-
- // Time how long it takes to get a response to our request
- long requestTimestamp = SystemClock.elapsedRealtime();
-
- httpResponseCode = urlConnection.getResponseCode();
- redirectUrl = urlConnection.getHeaderField("location");
-
- // Time how long it takes to get a response to our request
- long responseTimestamp = SystemClock.elapsedRealtime();
-
- validationLog(probeType, url, "time=" + (responseTimestamp - requestTimestamp) + "ms"
- + " ret=" + httpResponseCode
- + " request=" + requestHeader
- + " headers=" + urlConnection.getHeaderFields());
- // NOTE: We may want to consider an "HTTP/1.0 204" response to be a captive
- // portal. The only example of this seen so far was a captive portal. For
- // the time being go with prior behavior of assuming it's not a captive
- // portal. If it is considered a captive portal, a different sign-in URL
- // is needed (i.e. can't browse a 204). This could be the result of an HTTP
- // proxy server.
- if (httpResponseCode == 200) {
- if (probeType == ValidationProbeEvent.PROBE_PAC) {
- validationLog(
- probeType, url, "PAC fetch 200 response interpreted as 204 response.");
- httpResponseCode = CaptivePortalProbeResult.SUCCESS_CODE;
- } else if (urlConnection.getContentLengthLong() == 0) {
- // Consider 200 response with "Content-length=0" to not be a captive portal.
- // There's no point in considering this a captive portal as the user cannot
- // sign-in to an empty page. Probably the result of a broken transparent proxy.
- // See http://b/9972012.
- validationLog(probeType, url,
- "200 response with Content-length=0 interpreted as 204 response.");
- httpResponseCode = CaptivePortalProbeResult.SUCCESS_CODE;
- } else if (urlConnection.getContentLengthLong() == -1) {
- // When no Content-length (default value == -1), attempt to read a byte from the
- // response. Do not use available() as it is unreliable. See http://b/33498325.
- if (urlConnection.getInputStream().read() == -1) {
- validationLog(
- probeType, url, "Empty 200 response interpreted as 204 response.");
- httpResponseCode = CaptivePortalProbeResult.SUCCESS_CODE;
- }
- }
- }
- } catch (IOException e) {
- validationLog(probeType, url, "Probe failed with exception " + e);
- if (httpResponseCode == CaptivePortalProbeResult.FAILED_CODE) {
- // TODO: Ping gateway and DNS server and log results.
- }
- } finally {
- if (urlConnection != null) {
- urlConnection.disconnect();
- }
- TrafficStats.setThreadStatsTag(oldTag);
- }
- logValidationProbe(probeTimer.stop(), probeType, httpResponseCode);
-
- if (probeSpec == null) {
- return new CaptivePortalProbeResult(httpResponseCode, redirectUrl, url.toString());
- } else {
- return probeSpec.getResult(httpResponseCode, redirectUrl);
- }
- }
-
- private CaptivePortalProbeResult sendParallelHttpProbes(
- ProxyInfo proxy, URL httpsUrl, URL httpUrl) {
- // Number of probes to wait for. If a probe completes with a conclusive answer
- // it shortcuts the latch immediately by forcing the count to 0.
- final CountDownLatch latch = new CountDownLatch(2);
-
- final class ProbeThread extends Thread {
- private final boolean mIsHttps;
- private volatile CaptivePortalProbeResult mResult = CaptivePortalProbeResult.FAILED;
-
- ProbeThread(boolean isHttps) {
- mIsHttps = isHttps;
- }
-
- public CaptivePortalProbeResult result() {
- return mResult;
- }
-
- @Override
- public void run() {
- if (mIsHttps) {
- mResult =
- sendDnsAndHttpProbes(proxy, httpsUrl, ValidationProbeEvent.PROBE_HTTPS);
- } else {
- mResult = sendDnsAndHttpProbes(proxy, httpUrl, ValidationProbeEvent.PROBE_HTTP);
- }
- if ((mIsHttps && mResult.isSuccessful()) || (!mIsHttps && mResult.isPortal())) {
- // Stop waiting immediately if https succeeds or if http finds a portal.
- while (latch.getCount() > 0) {
- latch.countDown();
- }
- }
- // Signal this probe has completed.
- latch.countDown();
- }
- }
-
- final ProbeThread httpsProbe = new ProbeThread(true);
- final ProbeThread httpProbe = new ProbeThread(false);
-
- try {
- httpsProbe.start();
- httpProbe.start();
- latch.await(PROBE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- validationLog("Error: probes wait interrupted!");
- return CaptivePortalProbeResult.FAILED;
- }
-
- final CaptivePortalProbeResult httpsResult = httpsProbe.result();
- final CaptivePortalProbeResult httpResult = httpProbe.result();
-
- // Look for a conclusive probe result first.
- if (httpResult.isPortal()) {
- return httpResult;
- }
- // httpsResult.isPortal() is not expected, but check it nonetheless.
- if (httpsResult.isPortal() || httpsResult.isSuccessful()) {
- return httpsResult;
- }
- // If a fallback method exists, use it to retry portal detection.
- // If we have new-style probe specs, use those. Otherwise, use the fallback URLs.
- final CaptivePortalProbeSpec probeSpec = nextFallbackSpec();
- final URL fallbackUrl = (probeSpec != null) ? probeSpec.getUrl() : nextFallbackUrl();
- if (fallbackUrl != null) {
- CaptivePortalProbeResult result = sendHttpProbe(fallbackUrl, PROBE_FALLBACK, probeSpec);
- if (result.isPortal()) {
- return result;
- }
- }
- // Otherwise wait until http and https probes completes and use their results.
- try {
- httpProbe.join();
- if (httpProbe.result().isPortal()) {
- return httpProbe.result();
- }
- httpsProbe.join();
- return httpsProbe.result();
- } catch (InterruptedException e) {
- validationLog("Error: http or https probe wait interrupted!");
- return CaptivePortalProbeResult.FAILED;
- }
- }
-
- private URL makeURL(String url) {
- if (url != null) {
- try {
- return new URL(url);
- } catch (MalformedURLException e) {
- validationLog("Bad URL: " + url);
- }
- }
- return null;
- }
-
- /**
- * @param responseReceived - whether or not we received a valid HTTP response to our request.
- * If false, isCaptivePortal and responseTimestampMs are ignored
- * TODO: This should be moved to the transports. The latency could be passed to the transports
- * along with the captive portal result. Currently the TYPE_MOBILE broadcasts appear unused so
- * perhaps this could just be added to the WiFi transport only.
- */
- private void sendNetworkConditionsBroadcast(boolean responseReceived, boolean isCaptivePortal,
- long requestTimestampMs, long responseTimestampMs) {
- if (getWifiScansAlwaysAvailableDisabled()) {
- return;
- }
-
- if (!systemReady) {
- return;
- }
-
- Intent latencyBroadcast =
- new Intent(ConnectivityConstants.ACTION_NETWORK_CONDITIONS_MEASURED);
- switch (mNetworkAgentInfo.networkInfo.getType()) {
- case ConnectivityManager.TYPE_WIFI:
- WifiInfo currentWifiInfo = mWifiManager.getConnectionInfo();
- if (currentWifiInfo != null) {
- // NOTE: getSSID()'s behavior changed in API 17; before that, SSIDs were not
- // surrounded by double quotation marks (thus violating the Javadoc), but this
- // was changed to match the Javadoc in API 17. Since clients may have started
- // sanitizing the output of this method since API 17 was released, we should
- // not change it here as it would become impossible to tell whether the SSID is
- // simply being surrounded by quotes due to the API, or whether those quotes
- // are actually part of the SSID.
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_SSID,
- currentWifiInfo.getSSID());
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_BSSID,
- currentWifiInfo.getBSSID());
- } else {
- if (VDBG) logw("network info is TYPE_WIFI but no ConnectionInfo found");
- return;
- }
- break;
- case ConnectivityManager.TYPE_MOBILE:
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_NETWORK_TYPE,
- mTelephonyManager.getNetworkType());
- List<CellInfo> info = mTelephonyManager.getAllCellInfo();
- if (info == null) return;
- int numRegisteredCellInfo = 0;
- for (CellInfo cellInfo : info) {
- if (cellInfo.isRegistered()) {
- numRegisteredCellInfo++;
- if (numRegisteredCellInfo > 1) {
- if (VDBG) {
- logw("more than one registered CellInfo."
- + " Can't tell which is active. Bailing.");
- }
- return;
- }
- if (cellInfo instanceof CellInfoCdma) {
- CellIdentityCdma cellId = ((CellInfoCdma) cellInfo).getCellIdentity();
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CELL_ID, cellId);
- } else if (cellInfo instanceof CellInfoGsm) {
- CellIdentityGsm cellId = ((CellInfoGsm) cellInfo).getCellIdentity();
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CELL_ID, cellId);
- } else if (cellInfo instanceof CellInfoLte) {
- CellIdentityLte cellId = ((CellInfoLte) cellInfo).getCellIdentity();
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CELL_ID, cellId);
- } else if (cellInfo instanceof CellInfoWcdma) {
- CellIdentityWcdma cellId = ((CellInfoWcdma) cellInfo).getCellIdentity();
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CELL_ID, cellId);
- } else {
- if (VDBG) logw("Registered cellinfo is unrecognized");
- return;
- }
- }
- }
- break;
- default:
- return;
- }
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CONNECTIVITY_TYPE,
- mNetworkAgentInfo.networkInfo.getType());
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_RESPONSE_RECEIVED,
- responseReceived);
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_REQUEST_TIMESTAMP_MS,
- requestTimestampMs);
-
- if (responseReceived) {
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_IS_CAPTIVE_PORTAL,
- isCaptivePortal);
- latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_RESPONSE_TIMESTAMP_MS,
- responseTimestampMs);
- }
- mContext.sendBroadcastAsUser(latencyBroadcast, UserHandle.CURRENT,
- ConnectivityConstants.PERMISSION_ACCESS_NETWORK_CONDITIONS);
- }
-
- private void logNetworkEvent(int evtype) {
- int[] transports = mNetworkAgentInfo.networkCapabilities.getTransportTypes();
- mMetricsLog.log(mNetId, transports, new NetworkEvent(evtype));
- }
-
- private int networkEventType(ValidationStage s, EvaluationResult r) {
- if (s.mIsFirstValidation) {
- if (r.mIsValidated) {
- return NetworkEvent.NETWORK_FIRST_VALIDATION_SUCCESS;
- } else {
- return NetworkEvent.NETWORK_FIRST_VALIDATION_PORTAL_FOUND;
- }
- } else {
- if (r.mIsValidated) {
- return NetworkEvent.NETWORK_REVALIDATION_SUCCESS;
- } else {
- return NetworkEvent.NETWORK_REVALIDATION_PORTAL_FOUND;
- }
- }
- }
-
- private void maybeLogEvaluationResult(int evtype) {
- if (mEvaluationTimer.isRunning()) {
- int[] transports = mNetworkAgentInfo.networkCapabilities.getTransportTypes();
- mMetricsLog.log(mNetId, transports, new NetworkEvent(evtype, mEvaluationTimer.stop()));
- mEvaluationTimer.reset();
- }
- }
-
- private void logValidationProbe(long durationMs, int probeType, int probeResult) {
- int[] transports = mNetworkAgentInfo.networkCapabilities.getTransportTypes();
- boolean isFirstValidation = validationStage().mIsFirstValidation;
- ValidationProbeEvent ev = new ValidationProbeEvent();
- ev.probeType = ValidationProbeEvent.makeProbeType(probeType, isFirstValidation);
- ev.returnCode = probeResult;
- ev.durationMs = durationMs;
- mMetricsLog.log(mNetId, transports, ev);
- }
-
- @VisibleForTesting
- public static class Dependencies {
- public Network getNetwork(NetworkAgentInfo networkAgentInfo) {
- return new OneAddressPerFamilyNetwork(networkAgentInfo.network());
- }
-
- public Random getRandom() {
- return new Random();
- }
-
- /**
- * Get the value of a global integer setting.
- * @param symbol Name of the setting
- * @param defaultValue Value to return if the setting is not defined.
- */
- public int getSetting(Context context, String symbol, int defaultValue) {
- return Settings.Global.getInt(context.getContentResolver(), symbol, defaultValue);
- }
-
- /**
- * Get the value of a global String setting.
- * @param symbol Name of the setting
- * @param defaultValue Value to return if the setting is not defined.
- */
- public String getSetting(Context context, String symbol, String defaultValue) {
- final String value = Settings.Global.getString(context.getContentResolver(), symbol);
- return value != null ? value : defaultValue;
- }
-
- public static final Dependencies DEFAULT = new Dependencies();
- }
-
- /**
- * Methods in this class perform no locking because all accesses are performed on the state
- * machine's thread. Need to consider the thread safety if it ever could be accessed outside the
- * state machine.
- */
- @VisibleForTesting
- protected class DnsStallDetector {
- private static final int DEFAULT_DNS_LOG_SIZE = 50;
- private int mConsecutiveTimeoutCount = 0;
- private int mSize;
- final DnsResult[] mDnsEvents;
- final RingBufferIndices mResultIndices;
-
- DnsStallDetector(int size) {
- mSize = Math.max(DEFAULT_DNS_LOG_SIZE, size);
- mDnsEvents = new DnsResult[mSize];
- mResultIndices = new RingBufferIndices(mSize);
- }
-
- @VisibleForTesting
- protected void accumulateConsecutiveDnsTimeoutCount(int code) {
- final DnsResult result = new DnsResult(code);
- mDnsEvents[mResultIndices.add()] = result;
- if (result.isTimeout()) {
- mConsecutiveTimeoutCount++;
- } else {
- // Keep the event in mDnsEvents without clearing it so that there are logs to do the
- // simulation and analysis.
- mConsecutiveTimeoutCount = 0;
- }
- }
-
- private boolean isDataStallSuspected(int timeoutCountThreshold, int validTime) {
- if (timeoutCountThreshold <= 0) {
- Log.wtf(TAG, "Timeout count threshold should be larger than 0.");
- return false;
- }
-
- // Check if the consecutive timeout count reach the threshold or not.
- if (mConsecutiveTimeoutCount < timeoutCountThreshold) {
- return false;
- }
-
- // Check if the target dns event index is valid or not.
- final int firstConsecutiveTimeoutIndex =
- mResultIndices.indexOf(mResultIndices.size() - timeoutCountThreshold);
-
- // If the dns timeout events happened long time ago, the events are meaningless for
- // data stall evaluation. Thus, check if the first consecutive timeout dns event
- // considered in the evaluation happened in defined threshold time.
- final long now = SystemClock.elapsedRealtime();
- final long firstTimeoutTime = now - mDnsEvents[firstConsecutiveTimeoutIndex].mTimeStamp;
- return (firstTimeoutTime < validTime);
- }
-
- int getConsecutiveTimeoutCount() {
- return mConsecutiveTimeoutCount;
- }
- }
-
- private static class DnsResult {
- // TODO: Need to move the DNS return code definition to a specific class once unify DNS
- // response code is done.
- private static final int RETURN_CODE_DNS_TIMEOUT = 255;
-
- private final long mTimeStamp;
- private final int mReturnCode;
-
- DnsResult(int code) {
- mTimeStamp = SystemClock.elapsedRealtime();
- mReturnCode = code;
- }
-
- private boolean isTimeout() {
- return mReturnCode == RETURN_CODE_DNS_TIMEOUT;
- }
- }
-
-
- @VisibleForTesting
- protected DnsStallDetector getDnsStallDetector() {
- return mDnsStallDetector;
- }
-
- private boolean dataStallEvaluateTypeEnabled(int type) {
- return (mDataStallEvaluationType & (1 << type)) != 0;
- }
-
- @VisibleForTesting
- protected long getLastProbeTime() {
- return mLastProbeTime;
- }
-
- @VisibleForTesting
- protected boolean isDataStall() {
- boolean result = false;
- // Reevaluation will generate traffic. Thus, set a minimal reevaluation timer to limit the
- // possible traffic cost in metered network.
- if (mNetworkAgentInfo.networkCapabilities.isMetered()
- && (SystemClock.elapsedRealtime() - getLastProbeTime()
- < mDataStallMinEvaluateTime)) {
- return false;
- }
-
- // Check dns signal. Suspect it may be a data stall if both :
- // 1. The number of consecutive DNS query timeouts > mConsecutiveDnsTimeoutThreshold.
- // 2. Those consecutive DNS queries happened in the last mValidDataStallDnsTimeThreshold ms.
- if (dataStallEvaluateTypeEnabled(DATA_STALL_EVALUATION_TYPE_DNS)) {
- if (mDnsStallDetector.isDataStallSuspected(mConsecutiveDnsTimeoutThreshold,
- mDataStallValidDnsTimeThreshold)) {
- result = true;
- logNetworkEvent(NetworkEvent.NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND);
- }
- }
-
- if (VDBG_STALL) {
- log("isDataStall: result=" + result + ", consecutive dns timeout count="
- + mDnsStallDetector.getConsecutiveTimeoutCount());
- }
-
- return result;
- }
-}