Loading src/android/net/captiveportal/CapportApiProbeResult.java +3 −2 Original line number Diff line number Diff line Loading @@ -25,11 +25,12 @@ import com.android.networkstack.apishim.common.CaptivePortalDataShim; * @hide */ public class CapportApiProbeResult extends CaptivePortalProbeResult { @NonNull // CaptivePortalData may be null if the capport API does not send any valid reply. @Nullable private final CaptivePortalDataShim mCapportData; public CapportApiProbeResult(@NonNull CaptivePortalProbeResult result, @NonNull CaptivePortalDataShim capportData) { @Nullable CaptivePortalDataShim capportData) { this(result.mHttpResponseCode, result.redirectUrl, result.detectUrl, capportData, result.probeType); } Loading src/android/net/util/DataStallUtils.java +7 −6 Original line number Diff line number Diff line Loading @@ -31,11 +31,12 @@ public class DataStallUtils { /** Detect data stall using tcp connection fail rate. */ public static final int DATA_STALL_EVALUATION_TYPE_TCP = 1 << 1; @IntDef(prefix = { "DATA_STALL_EVALUATION_TYPE_" }, value = { @IntDef(prefix = { "DATA_STALL_EVALUATION_TYPE_" }, flag = true, value = { DATA_STALL_EVALUATION_TYPE_NONE, DATA_STALL_EVALUATION_TYPE_DNS, DATA_STALL_EVALUATION_TYPE_TCP, }) DATA_STALL_EVALUATION_TYPE_TCP, }) @Retention(RetentionPolicy.SOURCE) public @interface EvaluationType { } Loading Loading @@ -96,7 +97,7 @@ public class DataStallUtils { /** * Default polling interval to observe the tcp health. */ public static int DEFAULT_TCP_POLLING_INTERVAL_MS = 10_000; public static int DEFAULT_TCP_POLLING_INTERVAL_MS = 20_000; /** * Default tcp packets fail rate to suspect as a data stall. Loading src/com/android/server/connectivity/NetworkMonitor.java +37 −28 Original line number Diff line number Diff line Loading @@ -493,7 +493,8 @@ public class NetworkMonitor extends StateMachine { @Nullable private final DnsStallDetector mDnsStallDetector; private long mLastProbeTime; // The signal causing a data stall to be suspected. Reset to 0 after metrics are sent to statsd. // A bitmask of signals causing a data stall to be suspected. Reset to // {@link DataStallUtils#DATA_STALL_EVALUATION_TYPE_NONE} after metrics are sent to statsd. private @EvaluationType int mDataStallTypeToCollect; private boolean mAcceptPartialConnectivity = false; private final EvaluationState mEvaluationState = new EvaluationState(); Loading Loading @@ -2626,7 +2627,7 @@ public class NetworkMonitor extends StateMachine { validationLog("Missing user-portal-url from capport response"); return new CapportApiProbeResult( sendDnsAndHttpProbes(mProxy, mUrl, ValidationProbeEvent.PROBE_HTTP), capportData); null /* capportData */); } final String loginUrlString = capportData.getUserPortalUrl().toString(); // Starting from R (where CaptivePortalData was introduced), the captive portal app Loading Loading @@ -3206,7 +3207,8 @@ public class NetworkMonitor extends StateMachine { return false; } Boolean result = null; int typeToCollect = 0; final int notStall = -1; final StringJoiner msg = (DBG || VDBG_STALL) ? new StringJoiner(", ") : null; // Reevaluation will generate traffic. Thus, set a minimal reevaluation timer to limit the // possible traffic cost in metered network. Loading @@ -3221,18 +3223,9 @@ public class NetworkMonitor extends StateMachine { final TcpSocketTracker tst = getTcpSocketTracker(); if (dataStallEvaluateTypeEnabled(DATA_STALL_EVALUATION_TYPE_TCP) && tst != null) { if (tst.getLatestReceivedCount() > 0) { result = false; typeToCollect = notStall; } else if (tst.isDataStallSuspected()) { result = true; mDataStallTypeToCollect = DATA_STALL_EVALUATION_TYPE_TCP; final DataStallReportParcelable p = new DataStallReportParcelable(); p.detectionMethod = DETECTION_METHOD_TCP_METRICS; p.timestampMillis = SystemClock.elapsedRealtime(); p.tcpPacketFailRate = tst.getLatestPacketFailPercentage(); p.tcpMetricsCollectionPeriodMillis = getTcpPollingInterval(); notifyDataStallSuspected(p); typeToCollect |= DATA_STALL_EVALUATION_TYPE_TCP; } if (DBG || VDBG_STALL) { msg.add("tcp packets received=" + tst.getLatestReceivedCount()) Loading @@ -3244,32 +3237,48 @@ public class NetworkMonitor extends StateMachine { // 1. The number of consecutive DNS query timeouts >= mConsecutiveDnsTimeoutThreshold. // 2. Those consecutive DNS queries happened in the last mValidDataStallDnsTimeThreshold ms. final DnsStallDetector dsd = getDnsStallDetector(); if ((result == null) && (dsd != null) if ((typeToCollect != notStall) && (dsd != null) && dataStallEvaluateTypeEnabled(DATA_STALL_EVALUATION_TYPE_DNS)) { if (dsd.isDataStallSuspected(mConsecutiveDnsTimeoutThreshold, mDataStallValidDnsTimeThreshold)) { result = true; mDataStallTypeToCollect = DATA_STALL_EVALUATION_TYPE_DNS; if (dsd.isDataStallSuspected( mConsecutiveDnsTimeoutThreshold, mDataStallValidDnsTimeThreshold)) { typeToCollect |= DATA_STALL_EVALUATION_TYPE_DNS; logNetworkEvent(NetworkEvent.NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND); } if (DBG || VDBG_STALL) { msg.add("consecutive dns timeout count=" + dsd.getConsecutiveTimeoutCount()); } } if (typeToCollect > 0) { mDataStallTypeToCollect = typeToCollect; final DataStallReportParcelable p = new DataStallReportParcelable(); p.detectionMethod = DETECTION_METHOD_DNS_EVENTS; int detectionMethod = 0; p.timestampMillis = SystemClock.elapsedRealtime(); if (isDataStallTypeDetected(typeToCollect, DATA_STALL_EVALUATION_TYPE_DNS)) { detectionMethod |= DETECTION_METHOD_DNS_EVENTS; p.dnsConsecutiveTimeouts = mDnsStallDetector.getConsecutiveTimeoutCount(); notifyDataStallSuspected(p); } if (DBG || VDBG_STALL) { msg.add("consecutive dns timeout count=" + dsd.getConsecutiveTimeoutCount()); if (isDataStallTypeDetected(typeToCollect, DATA_STALL_EVALUATION_TYPE_TCP)) { detectionMethod |= DETECTION_METHOD_TCP_METRICS; p.tcpPacketFailRate = tst.getLatestPacketFailPercentage(); p.tcpMetricsCollectionPeriodMillis = getTcpPollingInterval(); } p.detectionMethod = detectionMethod; notifyDataStallSuspected(p); } // log only data stall suspected. if ((DBG && Boolean.TRUE.equals(result)) || VDBG_STALL) { log("isDataStall: result=" + result + ", " + msg); if ((DBG && (typeToCollect > 0)) || VDBG_STALL) { log("isDataStall: result=" + typeToCollect + ", " + msg); } return (result == null) ? false : result; return typeToCollect > 0; } private static boolean isDataStallTypeDetected(int typeToCollect, int evaluationType) { return (typeToCollect & evaluationType) != 0; } // Class to keep state of evaluation results and probe results. // // The main purpose was to ensure NetworkMonitor can notify ConnectivityService of probe results Loading tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java +51 −3 Original line number Diff line number Diff line Loading @@ -1153,7 +1153,29 @@ public class NetworkMonitorTest { } @Test public void testIsCaptivePortal_CapportApiIsPortal() throws Exception { public void testIsCaptivePortal_CapportApiIsPortalWithNullPortalUrl() throws Exception { assumeTrue(CaptivePortalDataShimImpl.isSupported()); setSslException(mHttpsConnection); final long bytesRemaining = 10_000L; final long secondsRemaining = 500L; // Set content without partal url. setApiContent(mCapportApiConnection, "{'captive': true," + "'venue-info-url': '" + TEST_VENUE_INFO_URL + "'," + "'bytes-remaining': " + bytesRemaining + "," + "'seconds-remaining': " + secondsRemaining + "}"); setPortal302(mHttpConnection); runNetworkTest(makeCapportLPs(), CELL_METERED_CAPABILITIES, VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded*/, TEST_LOGIN_URL); verify(mCapportApiConnection).getResponseCode(); verify(mHttpConnection, times(1)).getResponseCode(); verify(mCallbacks, never()).notifyCaptivePortalDataChanged(any()); } @Test public void testIsCaptivePortal_CapportApiIsPortalWithValidPortalUrl() throws Exception { assumeTrue(CaptivePortalDataShimImpl.isSupported()); setSslException(mHttpsConnection); final long bytesRemaining = 10_000L; Loading Loading @@ -1596,6 +1618,25 @@ public class NetworkMonitorTest { verify(mCallbacks).notifyDataStallSuspected(matchTcpDataStallParcelable()); } @Test public void testIsDataStall_EvaluationDnsAndTcp() throws Exception { setDataStallEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS | DATA_STALL_EVALUATION_TYPE_TCP); setupTcpDataStall(); final WrappedNetworkMonitor nm = makeMonitor(CELL_METERED_CAPABILITIES); nm.setLastProbeTime(SystemClock.elapsedRealtime() - 1000); makeDnsTimeoutEvent(nm, DEFAULT_DNS_TIMEOUT_THRESHOLD); assertTrue(nm.isDataStall()); verify(mCallbacks).notifyDataStallSuspected( matchDnsAndTcpDataStallParcelable(DEFAULT_DNS_TIMEOUT_THRESHOLD)); when(mTst.getLatestReceivedCount()).thenReturn(5); // Trigger a tcp event immediately. setTcpPollingInterval(0); nm.sendTcpPollingEvent(); HandlerUtilsKt.waitForIdle(nm.getHandler(), HANDLER_TIMEOUT_MS); assertFalse(nm.isDataStall()); } @Test public void testIsDataStall_DisableTcp() { // Disable tcp detection with only DNS detect. keep the tcp signal but set to no DNS signal. Loading Loading @@ -2635,13 +2676,20 @@ public class NetworkMonitorTest { && Objects.equals(p.redirectUrl, redirectUrl)); } private DataStallReportParcelable matchDnsAndTcpDataStallParcelable(final int timeoutCount) { return argThat(p -> (p.detectionMethod & ConstantsShim.DETECTION_METHOD_DNS_EVENTS) != 0 && (p.detectionMethod & ConstantsShim.DETECTION_METHOD_TCP_METRICS) != 0 && p.dnsConsecutiveTimeouts == timeoutCount); } private DataStallReportParcelable matchDnsDataStallParcelable(final int timeoutCount) { return argThat(p -> p.detectionMethod == ConstantsShim.DETECTION_METHOD_DNS_EVENTS return argThat(p -> (p.detectionMethod & ConstantsShim.DETECTION_METHOD_DNS_EVENTS) != 0 && p.dnsConsecutiveTimeouts == timeoutCount); } private DataStallReportParcelable matchTcpDataStallParcelable() { return argThat(p -> p.detectionMethod == ConstantsShim.DETECTION_METHOD_TCP_METRICS); return argThat(p -> (p.detectionMethod & ConstantsShim.DETECTION_METHOD_TCP_METRICS) != 0); } } Loading
src/android/net/captiveportal/CapportApiProbeResult.java +3 −2 Original line number Diff line number Diff line Loading @@ -25,11 +25,12 @@ import com.android.networkstack.apishim.common.CaptivePortalDataShim; * @hide */ public class CapportApiProbeResult extends CaptivePortalProbeResult { @NonNull // CaptivePortalData may be null if the capport API does not send any valid reply. @Nullable private final CaptivePortalDataShim mCapportData; public CapportApiProbeResult(@NonNull CaptivePortalProbeResult result, @NonNull CaptivePortalDataShim capportData) { @Nullable CaptivePortalDataShim capportData) { this(result.mHttpResponseCode, result.redirectUrl, result.detectUrl, capportData, result.probeType); } Loading
src/android/net/util/DataStallUtils.java +7 −6 Original line number Diff line number Diff line Loading @@ -31,11 +31,12 @@ public class DataStallUtils { /** Detect data stall using tcp connection fail rate. */ public static final int DATA_STALL_EVALUATION_TYPE_TCP = 1 << 1; @IntDef(prefix = { "DATA_STALL_EVALUATION_TYPE_" }, value = { @IntDef(prefix = { "DATA_STALL_EVALUATION_TYPE_" }, flag = true, value = { DATA_STALL_EVALUATION_TYPE_NONE, DATA_STALL_EVALUATION_TYPE_DNS, DATA_STALL_EVALUATION_TYPE_TCP, }) DATA_STALL_EVALUATION_TYPE_TCP, }) @Retention(RetentionPolicy.SOURCE) public @interface EvaluationType { } Loading Loading @@ -96,7 +97,7 @@ public class DataStallUtils { /** * Default polling interval to observe the tcp health. */ public static int DEFAULT_TCP_POLLING_INTERVAL_MS = 10_000; public static int DEFAULT_TCP_POLLING_INTERVAL_MS = 20_000; /** * Default tcp packets fail rate to suspect as a data stall. Loading
src/com/android/server/connectivity/NetworkMonitor.java +37 −28 Original line number Diff line number Diff line Loading @@ -493,7 +493,8 @@ public class NetworkMonitor extends StateMachine { @Nullable private final DnsStallDetector mDnsStallDetector; private long mLastProbeTime; // The signal causing a data stall to be suspected. Reset to 0 after metrics are sent to statsd. // A bitmask of signals causing a data stall to be suspected. Reset to // {@link DataStallUtils#DATA_STALL_EVALUATION_TYPE_NONE} after metrics are sent to statsd. private @EvaluationType int mDataStallTypeToCollect; private boolean mAcceptPartialConnectivity = false; private final EvaluationState mEvaluationState = new EvaluationState(); Loading Loading @@ -2626,7 +2627,7 @@ public class NetworkMonitor extends StateMachine { validationLog("Missing user-portal-url from capport response"); return new CapportApiProbeResult( sendDnsAndHttpProbes(mProxy, mUrl, ValidationProbeEvent.PROBE_HTTP), capportData); null /* capportData */); } final String loginUrlString = capportData.getUserPortalUrl().toString(); // Starting from R (where CaptivePortalData was introduced), the captive portal app Loading Loading @@ -3206,7 +3207,8 @@ public class NetworkMonitor extends StateMachine { return false; } Boolean result = null; int typeToCollect = 0; final int notStall = -1; final StringJoiner msg = (DBG || VDBG_STALL) ? new StringJoiner(", ") : null; // Reevaluation will generate traffic. Thus, set a minimal reevaluation timer to limit the // possible traffic cost in metered network. Loading @@ -3221,18 +3223,9 @@ public class NetworkMonitor extends StateMachine { final TcpSocketTracker tst = getTcpSocketTracker(); if (dataStallEvaluateTypeEnabled(DATA_STALL_EVALUATION_TYPE_TCP) && tst != null) { if (tst.getLatestReceivedCount() > 0) { result = false; typeToCollect = notStall; } else if (tst.isDataStallSuspected()) { result = true; mDataStallTypeToCollect = DATA_STALL_EVALUATION_TYPE_TCP; final DataStallReportParcelable p = new DataStallReportParcelable(); p.detectionMethod = DETECTION_METHOD_TCP_METRICS; p.timestampMillis = SystemClock.elapsedRealtime(); p.tcpPacketFailRate = tst.getLatestPacketFailPercentage(); p.tcpMetricsCollectionPeriodMillis = getTcpPollingInterval(); notifyDataStallSuspected(p); typeToCollect |= DATA_STALL_EVALUATION_TYPE_TCP; } if (DBG || VDBG_STALL) { msg.add("tcp packets received=" + tst.getLatestReceivedCount()) Loading @@ -3244,32 +3237,48 @@ public class NetworkMonitor extends StateMachine { // 1. The number of consecutive DNS query timeouts >= mConsecutiveDnsTimeoutThreshold. // 2. Those consecutive DNS queries happened in the last mValidDataStallDnsTimeThreshold ms. final DnsStallDetector dsd = getDnsStallDetector(); if ((result == null) && (dsd != null) if ((typeToCollect != notStall) && (dsd != null) && dataStallEvaluateTypeEnabled(DATA_STALL_EVALUATION_TYPE_DNS)) { if (dsd.isDataStallSuspected(mConsecutiveDnsTimeoutThreshold, mDataStallValidDnsTimeThreshold)) { result = true; mDataStallTypeToCollect = DATA_STALL_EVALUATION_TYPE_DNS; if (dsd.isDataStallSuspected( mConsecutiveDnsTimeoutThreshold, mDataStallValidDnsTimeThreshold)) { typeToCollect |= DATA_STALL_EVALUATION_TYPE_DNS; logNetworkEvent(NetworkEvent.NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND); } if (DBG || VDBG_STALL) { msg.add("consecutive dns timeout count=" + dsd.getConsecutiveTimeoutCount()); } } if (typeToCollect > 0) { mDataStallTypeToCollect = typeToCollect; final DataStallReportParcelable p = new DataStallReportParcelable(); p.detectionMethod = DETECTION_METHOD_DNS_EVENTS; int detectionMethod = 0; p.timestampMillis = SystemClock.elapsedRealtime(); if (isDataStallTypeDetected(typeToCollect, DATA_STALL_EVALUATION_TYPE_DNS)) { detectionMethod |= DETECTION_METHOD_DNS_EVENTS; p.dnsConsecutiveTimeouts = mDnsStallDetector.getConsecutiveTimeoutCount(); notifyDataStallSuspected(p); } if (DBG || VDBG_STALL) { msg.add("consecutive dns timeout count=" + dsd.getConsecutiveTimeoutCount()); if (isDataStallTypeDetected(typeToCollect, DATA_STALL_EVALUATION_TYPE_TCP)) { detectionMethod |= DETECTION_METHOD_TCP_METRICS; p.tcpPacketFailRate = tst.getLatestPacketFailPercentage(); p.tcpMetricsCollectionPeriodMillis = getTcpPollingInterval(); } p.detectionMethod = detectionMethod; notifyDataStallSuspected(p); } // log only data stall suspected. if ((DBG && Boolean.TRUE.equals(result)) || VDBG_STALL) { log("isDataStall: result=" + result + ", " + msg); if ((DBG && (typeToCollect > 0)) || VDBG_STALL) { log("isDataStall: result=" + typeToCollect + ", " + msg); } return (result == null) ? false : result; return typeToCollect > 0; } private static boolean isDataStallTypeDetected(int typeToCollect, int evaluationType) { return (typeToCollect & evaluationType) != 0; } // Class to keep state of evaluation results and probe results. // // The main purpose was to ensure NetworkMonitor can notify ConnectivityService of probe results Loading
tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java +51 −3 Original line number Diff line number Diff line Loading @@ -1153,7 +1153,29 @@ public class NetworkMonitorTest { } @Test public void testIsCaptivePortal_CapportApiIsPortal() throws Exception { public void testIsCaptivePortal_CapportApiIsPortalWithNullPortalUrl() throws Exception { assumeTrue(CaptivePortalDataShimImpl.isSupported()); setSslException(mHttpsConnection); final long bytesRemaining = 10_000L; final long secondsRemaining = 500L; // Set content without partal url. setApiContent(mCapportApiConnection, "{'captive': true," + "'venue-info-url': '" + TEST_VENUE_INFO_URL + "'," + "'bytes-remaining': " + bytesRemaining + "," + "'seconds-remaining': " + secondsRemaining + "}"); setPortal302(mHttpConnection); runNetworkTest(makeCapportLPs(), CELL_METERED_CAPABILITIES, VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded*/, TEST_LOGIN_URL); verify(mCapportApiConnection).getResponseCode(); verify(mHttpConnection, times(1)).getResponseCode(); verify(mCallbacks, never()).notifyCaptivePortalDataChanged(any()); } @Test public void testIsCaptivePortal_CapportApiIsPortalWithValidPortalUrl() throws Exception { assumeTrue(CaptivePortalDataShimImpl.isSupported()); setSslException(mHttpsConnection); final long bytesRemaining = 10_000L; Loading Loading @@ -1596,6 +1618,25 @@ public class NetworkMonitorTest { verify(mCallbacks).notifyDataStallSuspected(matchTcpDataStallParcelable()); } @Test public void testIsDataStall_EvaluationDnsAndTcp() throws Exception { setDataStallEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS | DATA_STALL_EVALUATION_TYPE_TCP); setupTcpDataStall(); final WrappedNetworkMonitor nm = makeMonitor(CELL_METERED_CAPABILITIES); nm.setLastProbeTime(SystemClock.elapsedRealtime() - 1000); makeDnsTimeoutEvent(nm, DEFAULT_DNS_TIMEOUT_THRESHOLD); assertTrue(nm.isDataStall()); verify(mCallbacks).notifyDataStallSuspected( matchDnsAndTcpDataStallParcelable(DEFAULT_DNS_TIMEOUT_THRESHOLD)); when(mTst.getLatestReceivedCount()).thenReturn(5); // Trigger a tcp event immediately. setTcpPollingInterval(0); nm.sendTcpPollingEvent(); HandlerUtilsKt.waitForIdle(nm.getHandler(), HANDLER_TIMEOUT_MS); assertFalse(nm.isDataStall()); } @Test public void testIsDataStall_DisableTcp() { // Disable tcp detection with only DNS detect. keep the tcp signal but set to no DNS signal. Loading Loading @@ -2635,13 +2676,20 @@ public class NetworkMonitorTest { && Objects.equals(p.redirectUrl, redirectUrl)); } private DataStallReportParcelable matchDnsAndTcpDataStallParcelable(final int timeoutCount) { return argThat(p -> (p.detectionMethod & ConstantsShim.DETECTION_METHOD_DNS_EVENTS) != 0 && (p.detectionMethod & ConstantsShim.DETECTION_METHOD_TCP_METRICS) != 0 && p.dnsConsecutiveTimeouts == timeoutCount); } private DataStallReportParcelable matchDnsDataStallParcelable(final int timeoutCount) { return argThat(p -> p.detectionMethod == ConstantsShim.DETECTION_METHOD_DNS_EVENTS return argThat(p -> (p.detectionMethod & ConstantsShim.DETECTION_METHOD_DNS_EVENTS) != 0 && p.dnsConsecutiveTimeouts == timeoutCount); } private DataStallReportParcelable matchTcpDataStallParcelable() { return argThat(p -> p.detectionMethod == ConstantsShim.DETECTION_METHOD_TCP_METRICS); return argThat(p -> (p.detectionMethod & ConstantsShim.DETECTION_METHOD_TCP_METRICS) != 0); } }