Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 17a51311 authored by Yan Yan's avatar Yan Yan Committed by Gerrit Code Review
Browse files

Merge changes Iccc47e83,I163cb274 into main

* changes:
  VCN: Ignore packet loss detection when there is too few traffic
  VCN: Handle sequence number leap in packet loss detector
parents b402f570 32637fbc
Loading
Loading
Loading
Loading
+17 −0
Original line number Diff line number Diff line
@@ -123,6 +123,22 @@ public class VcnManager {
    public static final String VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY =
            "vcn_network_selection_ipsec_packet_loss_percent_threshold";

    /**
     * Key for detecting unusually large increases in IPsec packet sequence numbers.
     *
     * <p>If the sequence number increases by more than this value within a second, it may indicate
     * an intentional leap on the server's downlink. To avoid false positives, the packet loss
     * detector will suppress loss reporting.
     *
     * <p>By default, there's no maximum limit enforced, prioritizing detection of lossy networks.
     * To reduce false positives, consider setting an appropriate maximum threshold.
     *
     * @hide
     */
    @NonNull
    public static final String VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY =
            "vcn_network_selection_max_seq_num_increase_per_second";

    /**
     * Key for the list of timeouts in minute to stop penalizing an underlying network candidate
     *
@@ -180,6 +196,7 @@ public class VcnManager {
                VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY,
                VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY,
                VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY,
                VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY,
                VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY,
                VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY,
                VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY,
+10 −0
Original line number Diff line number Diff line
@@ -35,3 +35,13 @@ flag{
    description: "Re-evaluate IPsec packet loss on LinkProperties or NetworkCapabilities change"
    bug: "323238888"
}

flag{
    name: "handle_seq_num_leap"
    namespace: "vcn"
    description: "Do not report bad network when there is a suspected sequence number leap"
    bug: "332598276"
    metadata {
      purpose: PURPOSE_BUGFIX
    }
}
 No newline at end of file
+90 −4
Original line number Diff line number Diff line
@@ -64,12 +64,19 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor {

    private static final int PACKET_LOSS_PERCENT_UNAVAILABLE = -1;

    // Ignore the packet loss detection result if the expected packet number is smaller than 10.
    // Solarwinds NPM uses 10 ICMP echos to calculate packet loss rate (as per
    // https://thwack.solarwinds.com/products/network-performance-monitor-npm/f/forum/63829/how-is-packet-loss-calculated)
    @VisibleForTesting(visibility = Visibility.PRIVATE)
    static final int MIN_VALID_EXPECTED_RX_PACKET_NUM = 10;

    @Retention(RetentionPolicy.SOURCE)
    @IntDef(
            prefix = {"PACKET_LOSS_"},
            value = {
                PACKET_LOSS_RATE_VALID,
                PACKET_LOSS_RATE_INVALID,
                PACKET_LOSS_UNUSUAL_SEQ_NUM_LEAP,
            })
    @Target({ElementType.TYPE_USE})
    private @interface PacketLossResultType {}
@@ -84,11 +91,23 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor {
     * <ul>
     *   <li>The replay window did not proceed and thus all packets might have been delivered out of
     *       order
     *   <li>The expected received packet number is too small and thus the detection result is not
     *       reliable
     *   <li>There are unexpected errors
     * </ul>
     */
    private static final int PACKET_LOSS_RATE_INVALID = 1;

    /**
     * The sequence number increase is unusually large and might be caused an intentional leap on
     * the server's downlink
     *
     * <p>Inbound sequence number will not always increase consecutively. During load balancing the
     * server might add a big leap on the sequence number intentionally. In such case a high packet
     * loss rate does not always indicate a lossy network
     */
    private static final int PACKET_LOSS_UNUSUAL_SEQ_NUM_LEAP = 2;

    // For VoIP, losses between 5% and 10% of the total packet stream will affect the quality
    // significantly (as per "Computer Networking for LANS to WANS: Hardware, Software and
    // Security"). For audio and video streaming, above 10-12% packet loss is unacceptable (as per
@@ -98,8 +117,12 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor {

    private static final int POLL_IPSEC_STATE_INTERVAL_SECONDS_DEFAULT = 20;

    // By default, there's no maximum limit enforced
    private static final int MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED = -1;

    private long mPollIpSecStateIntervalMs;
    private final int mPacketLossRatePercentThreshold;
    private int mPacketLossRatePercentThreshold;
    private int mMaxSeqNumIncreasePerSecond;

    @NonNull private final Handler mHandler;
    @NonNull private final PowerManager mPowerManager;
@@ -138,6 +161,7 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor {

        mPollIpSecStateIntervalMs = getPollIpSecStateIntervalMs(carrierConfig);
        mPacketLossRatePercentThreshold = getPacketLossRatePercentThreshold(carrierConfig);
        mMaxSeqNumIncreasePerSecond = getMaxSeqNumIncreasePerSecond(carrierConfig);

        // Register for system broadcasts to monitor idle mode change
        final IntentFilter intentFilter = new IntentFilter();
@@ -202,6 +226,24 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor {
        return IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DEFAULT;
    }

    @VisibleForTesting(visibility = Visibility.PRIVATE)
    static int getMaxSeqNumIncreasePerSecond(@Nullable PersistableBundleWrapper carrierConfig) {
        int maxSeqNumIncrease = MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED;
        if (Flags.handleSeqNumLeap() && carrierConfig != null) {
            maxSeqNumIncrease =
                    carrierConfig.getInt(
                            VcnManager.VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY,
                            MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED);
        }

        if (maxSeqNumIncrease < MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED) {
            logE(TAG, "Invalid value of MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY " + maxSeqNumIncrease);
            return MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED;
        }

        return maxSeqNumIncrease;
    }

    @Override
    protected void onSelectedUnderlyingNetworkChanged() {
        if (!isSelectedUnderlyingNetwork()) {
@@ -237,6 +279,11 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor {
        // The already scheduled event will not be affected. The followup events will be scheduled
        // with the new interval
        mPollIpSecStateIntervalMs = getPollIpSecStateIntervalMs(carrierConfig);

        if (Flags.handleSeqNumLeap()) {
            mPacketLossRatePercentThreshold = getPacketLossRatePercentThreshold(carrierConfig);
            mMaxSeqNumIncreasePerSecond = getMaxSeqNumIncreasePerSecond(carrierConfig);
        }
    }

    @Override
@@ -339,7 +386,10 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor {

        final PacketLossCalculationResult calculateResult =
                mPacketLossCalculator.getPacketLossRatePercentage(
                        mLastIpSecTransformState, state, getLogPrefix());
                        mLastIpSecTransformState,
                        state,
                        mMaxSeqNumIncreasePerSecond,
                        getLogPrefix());

        if (calculateResult.getResultType() == PACKET_LOSS_RATE_INVALID) {
            return;
@@ -356,11 +406,18 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor {
        mLastIpSecTransformState = state;
        if (calculateResult.getPacketLossRatePercent() < mPacketLossRatePercentThreshold) {
            logV(logMsg);

            // In both "valid" or "unusual_seq_num_leap" cases, notify that the network has passed
            // the validation
            onValidationResultReceivedInternal(false /* isFailed */);
        } else {
            logInfo(logMsg);

            if (calculateResult.getResultType() == PACKET_LOSS_RATE_VALID) {
                onValidationResultReceivedInternal(true /* isFailed */);
            }

            // In both "valid" or "unusual_seq_num_leap" cases, trigger network validation
            if (Flags.validateNetworkOnIpsecLoss()) {
                // Trigger re-validation of the underlying network; if it fails, the VCN will
                // attempt to migrate away.
@@ -376,6 +433,7 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor {
        public PacketLossCalculationResult getPacketLossRatePercentage(
                @NonNull IpSecTransformState oldState,
                @NonNull IpSecTransformState newState,
                int maxSeqNumIncreasePerSecond,
                String logPrefix) {
            logVIpSecTransform("oldState", oldState, logPrefix);
            logVIpSecTransform("newState", newState, logPrefix);
@@ -392,6 +450,22 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor {
                return PacketLossCalculationResult.invalid();
            }

            boolean isUnusualSeqNumLeap = false;

            // Handle sequence number leap
            if (Flags.handleSeqNumLeap()
                    && maxSeqNumIncreasePerSecond != MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED) {
                final long timeDiffMillis =
                        newState.getTimestampMillis() - oldState.getTimestampMillis();
                final long maxSeqNumIncrease = timeDiffMillis * maxSeqNumIncreasePerSecond / 1000;

                // Sequence numbers are unsigned 32-bit values. If maxSeqNumIncrease overflows,
                // isUnusualSeqNumLeap can never be true.
                if (maxSeqNumIncrease >= 0 && newSeqHi - oldSeqHi >= maxSeqNumIncrease) {
                    isUnusualSeqNumLeap = true;
                }
            }

            // Get the expected packet count by assuming there is no packet loss. In this case, SA
            // should receive all packets whose sequence numbers are smaller than the lower bound of
            // the replay window AND the packets received within the window.
@@ -411,6 +485,11 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor {
                            + " actualPktCntDiff: "
                            + actualPktCntDiff);

            if (Flags.handleSeqNumLeap() && expectedPktCntDiff < MIN_VALID_EXPECTED_RX_PACKET_NUM) {
                // The sample size is too small to ensure a reliable detection result
                return PacketLossCalculationResult.invalid();
            }

            if (expectedPktCntDiff < 0
                    || expectedPktCntDiff == 0
                    || actualPktCntDiff < 0
@@ -420,7 +499,9 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor {
            }

            final int percent = 100 - (int) (actualPktCntDiff * 100 / expectedPktCntDiff);
            return PacketLossCalculationResult.valid(percent);
            return isUnusualSeqNumLeap
                    ? PacketLossCalculationResult.unusualSeqNumLeap(percent)
                    : PacketLossCalculationResult.valid(percent);
        }
    }

@@ -462,6 +543,11 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor {
                    PACKET_LOSS_RATE_INVALID, PACKET_LOSS_PERCENT_UNAVAILABLE);
        }

        /** Construct an instance indicating that there is an unusual sequence number leap */
        public static PacketLossCalculationResult unusualSeqNumLeap(int percent) {
            return new PacketLossCalculationResult(PACKET_LOSS_UNUSUAL_SEQ_NUM_LEAP, percent);
        }

        @PacketLossResultType
        public int getResultType() {
            return mResultType;
+5 −0
Original line number Diff line number Diff line
@@ -272,6 +272,11 @@ public abstract class NetworkMetricMonitor implements AutoCloseable {
        }
    }

    protected static void logE(String className, String msgWithPrefix) {
        Slog.w(className, msgWithPrefix);
        LOCAL_LOG.log("[ERROR ] " + className + msgWithPrefix);
    }

    protected static void logWtf(String className, String msgWithPrefix) {
        Slog.wtf(className, msgWithPrefix);
        LOCAL_LOG.log("[WTF ] " + className + msgWithPrefix);
+121 −6
Original line number Diff line number Diff line
@@ -17,8 +17,11 @@
package com.android.server.vcn.routeselection;

import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY;
import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY;
import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY;

import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.MIN_VALID_EXPECTED_RX_PACKET_NUM;
import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.getMaxSeqNumIncreasePerSecond;
import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;

import static org.junit.Assert.assertEquals;
@@ -65,6 +68,7 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase {
    private static final int REPLAY_BITMAP_LEN_BYTE = 512;
    private static final int REPLAY_BITMAP_LEN_BIT = REPLAY_BITMAP_LEN_BYTE * 8;
    private static final int IPSEC_PACKET_LOSS_PERCENT_THRESHOLD = 5;
    private static final int MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED = -1;
    private static final long POLL_IPSEC_STATE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(30L);

    @Mock private IpSecTransformWrapper mIpSecTransform;
@@ -91,6 +95,9 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase {
                        eq(VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY),
                        anyInt()))
                .thenReturn(IPSEC_PACKET_LOSS_PERCENT_THRESHOLD);
        when(mCarrierConfig.getInt(
                        eq(VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY), anyInt()))
                .thenReturn(MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED);

        when(mDependencies.getPacketLossCalculator()).thenReturn(mPacketLossCalculator);

@@ -112,6 +119,20 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase {
                .build();
    }

    private static IpSecTransformState newNextTransformState(
            IpSecTransformState before,
            long timeDiffMillis,
            long rxSeqNoDiff,
            long packtCountDiff,
            int packetInWin) {
        return new IpSecTransformState.Builder()
                .setTimestampMillis(before.getTimestampMillis() + timeDiffMillis)
                .setRxHighestSequenceNumber(before.getRxHighestSequenceNumber() + rxSeqNoDiff)
                .setPacketCount(before.getPacketCount() + packtCountDiff)
                .setReplayBitmap(newReplayBitmap(packetInWin))
                .build();
    }

    private static byte[] newReplayBitmap(int receivedPktCnt) {
        final BitSet bitSet = new BitSet(REPLAY_BITMAP_LEN_BIT);
        for (int i = 0; i < receivedPktCnt; i++) {
@@ -165,7 +186,7 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase {
        // Verify the first polled state is stored
        assertEquals(mTransformStateInitial, mIpSecPacketLossDetector.getLastTransformState());
        verify(mPacketLossCalculator, never())
                .getPacketLossRatePercentage(any(), any(), anyString());
                .getPacketLossRatePercentage(any(), any(), anyInt(), anyString());

        // Verify next poll is scheduled
        assertNull(mTestLooper.nextMessage());
@@ -278,7 +299,7 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase {

        xfrmStateReceiver.onResult(newTransformState(1, 1, newReplayBitmap(1)));
        verify(mPacketLossCalculator, never())
                .getPacketLossRatePercentage(any(), any(), anyString());
                .getPacketLossRatePercentage(any(), any(), anyInt(), anyString());
    }

    @Test
@@ -289,7 +310,7 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase {

        xfrmStateReceiver.onError(new RuntimeException("Test"));
        verify(mPacketLossCalculator, never())
                .getPacketLossRatePercentage(any(), any(), anyString());
                .getPacketLossRatePercentage(any(), any(), anyInt(), anyString());
    }

    private void checkHandleLossRate(
@@ -301,7 +322,7 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase {
                startMonitorAndCaptureStateReceiver();
        doReturn(mockPacketLossRate)
                .when(mPacketLossCalculator)
                .getPacketLossRatePercentage(any(), any(), anyString());
                .getPacketLossRatePercentage(any(), any(), anyInt(), anyString());

        // Mock receiving two states with mTransformStateInitial and an arbitrary transformNew
        final IpSecTransformState transformNew = newTransformState(1, 1, newReplayBitmap(1));
@@ -311,7 +332,10 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase {
        // Verifications
        verify(mPacketLossCalculator)
                .getPacketLossRatePercentage(
                        eq(mTransformStateInitial), eq(transformNew), anyString());
                        eq(mTransformStateInitial),
                        eq(transformNew),
                        eq(MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED),
                        anyString());

        if (isLastStateExpectedToUpdate) {
            assertEquals(transformNew, mIpSecPacketLossDetector.getLastTransformState());
@@ -351,6 +375,22 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase {
                false /* isCallbackExpected */);
    }

    @Test
    public void testHandleLossRate_unusualSeqNumLeap_highLossRate() throws Exception {
        checkHandleLossRate(
                PacketLossCalculationResult.unusualSeqNumLeap(22),
                true /* isLastStateExpectedToUpdate */,
                false /* isCallbackExpected */);
    }

    @Test
    public void testHandleLossRate_unusualSeqNumLeap_lowLossRate() throws Exception {
        checkHandleLossRate(
                PacketLossCalculationResult.unusualSeqNumLeap(2),
                true /* isLastStateExpectedToUpdate */,
                true /* isCallbackExpected */);
    }

    private void checkGetPacketLossRate(
            IpSecTransformState oldState,
            IpSecTransformState newState,
@@ -358,7 +398,8 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase {
            throws Exception {
        assertEquals(
                expectedLossRate,
                mPacketLossCalculator.getPacketLossRatePercentage(oldState, newState, TAG));
                mPacketLossCalculator.getPacketLossRatePercentage(
                        oldState, newState, MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED, TAG));
    }

    private void checkGetPacketLossRate(
@@ -396,6 +437,21 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase {
                mTransformStateInitial, 3000, 2000, 2000, PacketLossCalculationResult.invalid());
    }

    @Test
    public void testGetPacketLossRate_expectedPacketNumTooFew() throws Exception {
        final int oldRxNo = 4096;
        final int oldPktCnt = 4096;
        final int pktCntDiff = MIN_VALID_EXPECTED_RX_PACKET_NUM - 1;
        final byte[] bitmapReceiveAll = newReplayBitmap(4096);

        final IpSecTransformState oldState =
                newTransformState(oldRxNo, oldPktCnt, bitmapReceiveAll);
        final IpSecTransformState newState =
                newTransformState(oldRxNo + pktCntDiff, oldPktCnt + pktCntDiff, bitmapReceiveAll);

        checkGetPacketLossRate(oldState, newState, PacketLossCalculationResult.invalid());
    }

    @Test
    public void testGetPacketLossRate_againstInitialState() throws Exception {
        checkGetPacketLossRate(mTransformStateInitial, 7000, 7001, 4096, 0);
@@ -443,6 +499,45 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase {
        checkGetPacketLossRate(oldState, 20000, 14000, 3000, 10);
    }

    private void checkGetPktLossRate_unusualSeqNumLeap(
            int maxSeqNumIncreasePerSecond,
            int timeDiffMillis,
            int rxSeqNoDiff,
            PacketLossCalculationResult expected)
            throws Exception {
        final IpSecTransformState oldState = mTransformStateInitial;
        final IpSecTransformState newState =
                newNextTransformState(
                        oldState,
                        timeDiffMillis,
                        rxSeqNoDiff,
                        1 /* packtCountDiff */,
                        1 /* packetInWin */);

        assertEquals(
                expected,
                mPacketLossCalculator.getPacketLossRatePercentage(
                        oldState, newState, maxSeqNumIncreasePerSecond, TAG));
    }

    @Test
    public void testGetPktLossRate_unusualSeqNumLeap() throws Exception {
        checkGetPktLossRate_unusualSeqNumLeap(
                10000 /* maxSeqNumIncreasePerSecond */,
                (int) TimeUnit.SECONDS.toMillis(2L),
                30000 /* rxSeqNoDiff */,
                PacketLossCalculationResult.unusualSeqNumLeap(100));
    }

    @Test
    public void testGetPktLossRate_unusualSeqNumLeap_smallSeqNumDiff() throws Exception {
        checkGetPktLossRate_unusualSeqNumLeap(
                10000 /* maxSeqNumIncreasePerSecond */,
                (int) TimeUnit.SECONDS.toMillis(2L),
                5000 /* rxSeqNoDiff */,
                PacketLossCalculationResult.valid(100));
    }

    // Verify the polling event is scheduled with expected delays
    private void verifyPollEventDelayAndScheduleNext(long expectedDelayMs) {
        if (expectedDelayMs > 0) {
@@ -469,4 +564,24 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase {
        // Verify the 3rd poll is scheduled with configured delay
        verifyPollEventDelayAndScheduleNext(POLL_IPSEC_STATE_INTERVAL_MS);
    }

    @Test
    public void testGetMaxSeqNumIncreasePerSecond() throws Exception {
        final int seqNumLeapNegative = 500_000;
        when(mCarrierConfig.getInt(
                        eq(VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY), anyInt()))
                .thenReturn(seqNumLeapNegative);
        assertEquals(seqNumLeapNegative, getMaxSeqNumIncreasePerSecond(mCarrierConfig));
    }

    @Test
    public void testGetMaxSeqNumIncreasePerSecond_negativeValue() throws Exception {
        final int seqNumLeapNegative = -10;
        when(mCarrierConfig.getInt(
                        eq(VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY), anyInt()))
                .thenReturn(seqNumLeapNegative);
        assertEquals(
                MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED,
                getMaxSeqNumIncreasePerSecond(mCarrierConfig));
    }
}
Loading