Loading core/java/android/net/metrics/NetworkEvent.java +3 −0 Original line number Diff line number Diff line Loading @@ -44,6 +44,8 @@ public final class NetworkEvent implements Parcelable { public static final int NETWORK_FIRST_VALIDATION_PORTAL_FOUND = 10; public static final int NETWORK_REVALIDATION_PORTAL_FOUND = 11; public static final int NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND = 12; @IntDef(value = { NETWORK_CONNECTED, NETWORK_VALIDATED, Loading @@ -56,6 +58,7 @@ public final class NetworkEvent implements Parcelable { NETWORK_REVALIDATION_SUCCESS, NETWORK_FIRST_VALIDATION_PORTAL_FOUND, NETWORK_REVALIDATION_PORTAL_FOUND, NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND, }) @Retention(RetentionPolicy.SOURCE) public @interface EventType {} Loading core/java/android/provider/Settings.java +35 −0 Original line number Diff line number Diff line Loading @@ -10435,6 +10435,41 @@ public final class Settings { */ public static final String CAPTIVE_PORTAL_USER_AGENT = "captive_portal_user_agent"; /** * The threshold value for the number of consecutive dns timeout events received to be a * signal of data stall. Set the value to 0 or less than 0 to disable. Note that the value * should be larger than 0 if the DNS data stall detection is enabled. * * @hide */ public static final String DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD = "data_stall_consecutive_dns_timeout_threshold"; /** * The minimal time interval in milliseconds for data stall reevaluation. * * @hide */ public static final String DATA_STALL_MIN_EVALUATE_INTERVAL = "data_stall_min_evaluate_interval"; /** * DNS timeouts older than this timeout (in milliseconds) are not considered for detecting * a data stall. * * @hide */ public static final String DATA_STALL_VALID_DNS_TIME_THRESHOLD = "data_stall_valid_dns_time_threshold"; /** * Which data stall detection signal to use. Possible values are a union of the powers of 2 * of DATA_STALL_EVALUATION_TYPE_*. * * @hide */ public static final String DATA_STALL_EVALUATION_TYPE = "data_stall_evaluation_type"; /** * Whether network service discovery is enabled. * Loading core/tests/coretests/src/android/provider/SettingsBackupTest.java +4 −0 Original line number Diff line number Diff line Loading @@ -185,6 +185,10 @@ public class SettingsBackupTest { Settings.Global.DATA_ROAMING, Settings.Global.DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS, Settings.Global.DATA_STALL_ALARM_NON_AGGRESSIVE_DELAY_IN_MS, Settings.Global.DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD, Settings.Global.DATA_STALL_EVALUATION_TYPE, Settings.Global.DATA_STALL_MIN_EVALUATE_INTERVAL, Settings.Global.DATA_STALL_VALID_DNS_TIME_THRESHOLD, Settings.Global.DEBUG_APP, Settings.Global.DEBUG_VIEW_ATTRIBUTES, Settings.Global.DEFAULT_DNS_SERVER, Loading services/core/java/com/android/server/ConnectivityService.java +18 −0 Original line number Diff line number Diff line Loading @@ -1667,6 +1667,24 @@ public class ConnectivityService extends IConnectivityManager.Stub loge("Error parsing ip address in validation event"); } } @Override public void onDnsEvent(int netId, int eventType, int returnCode, String hostname, String[] ipAddresses, int ipAddressesCount, long timestamp, int uid) { NetworkAgentInfo nai = getNetworkAgentInfoForNetId(netId); // Netd event only allow registrants from system. Each NetworkMonitor thread is under // the caller thread of registerNetworkAgent. Thus, it's not allowed to register netd // event callback for certain nai. e.g. cellular. Register here to pass to // NetworkMonitor instead. // TODO: Move the Dns Event to NetworkMonitor. Use Binder.clearCallingIdentity() in // registerNetworkAgent to have NetworkMonitor created with system process as design // expectation. Also, NetdEventListenerService only allow one callback from each // caller type. Need to re-factor NetdEventListenerService to allow multiple // NetworkMonitor registrants. if (nai != null && nai.satisfies(mDefaultRequest)) { nai.networkMonitor.sendMessage(NetworkMonitor.EVENT_DNS_NOTIFICATION, returnCode); } } }; @VisibleForTesting Loading services/core/java/com/android/server/connectivity/NetworkMonitor.java +187 −1 Original line number Diff line number Diff line Loading @@ -72,6 +72,7 @@ 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; Loading Loading @@ -99,7 +100,7 @@ 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. Loading @@ -116,6 +117,15 @@ public class NetworkMonitor extends StateMachine { 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); static enum EvaluationResult { VALIDATED(true), CAPTIVE_PORTAL(false); Loading Loading @@ -233,6 +243,12 @@ public class NetworkMonitor extends StateMachine { */ 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; Loading Loading @@ -314,6 +330,12 @@ public class NetworkMonitor extends StateMachine { 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) { Loading Loading @@ -359,6 +381,12 @@ public class NetworkMonitor extends StateMachine { 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(); } Loading Loading @@ -507,6 +535,9 @@ public class NetworkMonitor extends StateMachine { sendMessage(CMD_EVALUATE_PRIVATE_DNS); break; } case EVENT_DNS_NOTIFICATION: mDnsStallDetector.accumulateConsecutiveDnsTimeoutCount(message.arg1); break; default: break; } Loading Loading @@ -537,6 +568,13 @@ public class NetworkMonitor extends StateMachine { 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; } Loading Loading @@ -856,6 +894,7 @@ public class NetworkMonitor extends StateMachine { 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). Loading Loading @@ -883,6 +922,7 @@ public class NetworkMonitor extends StateMachine { // Leave the event to EvaluatingState. Defer this message will result in reset // of mReevaluateDelayMs and mEvaluateAttempts. case CMD_NETWORK_DISCONNECTED: case EVENT_DNS_NOTIFICATION: return NOT_HANDLED; default: // TODO: Some events may able to handle in this state, instead of deferring to Loading Loading @@ -947,6 +987,29 @@ public class NetworkMonitor extends StateMachine { 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); Loading Loading @@ -1462,4 +1525,127 @@ public class NetworkMonitor extends StateMachine { 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; } } Loading
core/java/android/net/metrics/NetworkEvent.java +3 −0 Original line number Diff line number Diff line Loading @@ -44,6 +44,8 @@ public final class NetworkEvent implements Parcelable { public static final int NETWORK_FIRST_VALIDATION_PORTAL_FOUND = 10; public static final int NETWORK_REVALIDATION_PORTAL_FOUND = 11; public static final int NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND = 12; @IntDef(value = { NETWORK_CONNECTED, NETWORK_VALIDATED, Loading @@ -56,6 +58,7 @@ public final class NetworkEvent implements Parcelable { NETWORK_REVALIDATION_SUCCESS, NETWORK_FIRST_VALIDATION_PORTAL_FOUND, NETWORK_REVALIDATION_PORTAL_FOUND, NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND, }) @Retention(RetentionPolicy.SOURCE) public @interface EventType {} Loading
core/java/android/provider/Settings.java +35 −0 Original line number Diff line number Diff line Loading @@ -10435,6 +10435,41 @@ public final class Settings { */ public static final String CAPTIVE_PORTAL_USER_AGENT = "captive_portal_user_agent"; /** * The threshold value for the number of consecutive dns timeout events received to be a * signal of data stall. Set the value to 0 or less than 0 to disable. Note that the value * should be larger than 0 if the DNS data stall detection is enabled. * * @hide */ public static final String DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD = "data_stall_consecutive_dns_timeout_threshold"; /** * The minimal time interval in milliseconds for data stall reevaluation. * * @hide */ public static final String DATA_STALL_MIN_EVALUATE_INTERVAL = "data_stall_min_evaluate_interval"; /** * DNS timeouts older than this timeout (in milliseconds) are not considered for detecting * a data stall. * * @hide */ public static final String DATA_STALL_VALID_DNS_TIME_THRESHOLD = "data_stall_valid_dns_time_threshold"; /** * Which data stall detection signal to use. Possible values are a union of the powers of 2 * of DATA_STALL_EVALUATION_TYPE_*. * * @hide */ public static final String DATA_STALL_EVALUATION_TYPE = "data_stall_evaluation_type"; /** * Whether network service discovery is enabled. * Loading
core/tests/coretests/src/android/provider/SettingsBackupTest.java +4 −0 Original line number Diff line number Diff line Loading @@ -185,6 +185,10 @@ public class SettingsBackupTest { Settings.Global.DATA_ROAMING, Settings.Global.DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS, Settings.Global.DATA_STALL_ALARM_NON_AGGRESSIVE_DELAY_IN_MS, Settings.Global.DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD, Settings.Global.DATA_STALL_EVALUATION_TYPE, Settings.Global.DATA_STALL_MIN_EVALUATE_INTERVAL, Settings.Global.DATA_STALL_VALID_DNS_TIME_THRESHOLD, Settings.Global.DEBUG_APP, Settings.Global.DEBUG_VIEW_ATTRIBUTES, Settings.Global.DEFAULT_DNS_SERVER, Loading
services/core/java/com/android/server/ConnectivityService.java +18 −0 Original line number Diff line number Diff line Loading @@ -1667,6 +1667,24 @@ public class ConnectivityService extends IConnectivityManager.Stub loge("Error parsing ip address in validation event"); } } @Override public void onDnsEvent(int netId, int eventType, int returnCode, String hostname, String[] ipAddresses, int ipAddressesCount, long timestamp, int uid) { NetworkAgentInfo nai = getNetworkAgentInfoForNetId(netId); // Netd event only allow registrants from system. Each NetworkMonitor thread is under // the caller thread of registerNetworkAgent. Thus, it's not allowed to register netd // event callback for certain nai. e.g. cellular. Register here to pass to // NetworkMonitor instead. // TODO: Move the Dns Event to NetworkMonitor. Use Binder.clearCallingIdentity() in // registerNetworkAgent to have NetworkMonitor created with system process as design // expectation. Also, NetdEventListenerService only allow one callback from each // caller type. Need to re-factor NetdEventListenerService to allow multiple // NetworkMonitor registrants. if (nai != null && nai.satisfies(mDefaultRequest)) { nai.networkMonitor.sendMessage(NetworkMonitor.EVENT_DNS_NOTIFICATION, returnCode); } } }; @VisibleForTesting Loading
services/core/java/com/android/server/connectivity/NetworkMonitor.java +187 −1 Original line number Diff line number Diff line Loading @@ -72,6 +72,7 @@ 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; Loading Loading @@ -99,7 +100,7 @@ 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. Loading @@ -116,6 +117,15 @@ public class NetworkMonitor extends StateMachine { 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); static enum EvaluationResult { VALIDATED(true), CAPTIVE_PORTAL(false); Loading Loading @@ -233,6 +243,12 @@ public class NetworkMonitor extends StateMachine { */ 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; Loading Loading @@ -314,6 +330,12 @@ public class NetworkMonitor extends StateMachine { 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) { Loading Loading @@ -359,6 +381,12 @@ public class NetworkMonitor extends StateMachine { 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(); } Loading Loading @@ -507,6 +535,9 @@ public class NetworkMonitor extends StateMachine { sendMessage(CMD_EVALUATE_PRIVATE_DNS); break; } case EVENT_DNS_NOTIFICATION: mDnsStallDetector.accumulateConsecutiveDnsTimeoutCount(message.arg1); break; default: break; } Loading Loading @@ -537,6 +568,13 @@ public class NetworkMonitor extends StateMachine { 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; } Loading Loading @@ -856,6 +894,7 @@ public class NetworkMonitor extends StateMachine { 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). Loading Loading @@ -883,6 +922,7 @@ public class NetworkMonitor extends StateMachine { // Leave the event to EvaluatingState. Defer this message will result in reset // of mReevaluateDelayMs and mEvaluateAttempts. case CMD_NETWORK_DISCONNECTED: case EVENT_DNS_NOTIFICATION: return NOT_HANDLED; default: // TODO: Some events may able to handle in this state, instead of deferring to Loading Loading @@ -947,6 +987,29 @@ public class NetworkMonitor extends StateMachine { 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); Loading Loading @@ -1462,4 +1525,127 @@ public class NetworkMonitor extends StateMachine { 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; } }