diff options
Diffstat (limited to 'services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java')
-rw-r--r-- | services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java | 221 |
1 files changed, 207 insertions, 14 deletions
diff --git a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java index ed9fa65dee15..474253223628 100644 --- a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java +++ b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java @@ -16,8 +16,10 @@ package com.android.server.vcn.routeselection; +import static com.android.internal.annotations.VisibleForTesting.Visibility; import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.BroadcastReceiver; @@ -38,6 +40,10 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; import com.android.server.vcn.VcnContext; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.util.BitSet; import java.util.Objects; import java.util.concurrent.TimeUnit; @@ -56,8 +62,51 @@ import java.util.concurrent.TimeUnit; public class IpSecPacketLossDetector extends NetworkMetricMonitor { private static final String TAG = IpSecPacketLossDetector.class.getSimpleName(); + 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 PACKET_LOSS_UNAVALAIBLE = -1; + 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 {} + + /** Indicates a valid packet loss rate is available */ + private static final int PACKET_LOSS_RATE_VALID = 0; + + /** + * Indicates that the detector cannot get a valid packet loss rate due to one of the following + * reasons: + * + * <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 @@ -66,10 +115,18 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { // validation failure. private static final int IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DEFAULT = 12; + /** Carriers can disable the detector by setting the threshold to -1 */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final int IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DISABLE_DETECTOR = -1; + 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; @@ -108,6 +165,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(); @@ -172,6 +230,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()) { @@ -199,7 +275,10 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { // When multiple parallel inbound transforms are created, NetworkMetricMonitor will be // enabled on the last one as a sample mInboundTransform = inboundTransform; - start(); + + if (!Flags.allowDisableIpsecLossDetector() || canStart()) { + start(); + } } @Override @@ -207,6 +286,19 @@ 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); + } + + if (Flags.allowDisableIpsecLossDetector() && canStart() != isStarted()) { + if (canStart()) { + start(); + } else { + stop(); + } + } } @Override @@ -221,6 +313,12 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { mHandler.postDelayed(new PollIpSecStateRunnable(), mCancellationToken, 0L); } + private boolean canStart() { + return mInboundTransform != null + && mPacketLossRatePercentThreshold + != IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DISABLE_DETECTOR; + } + @Override protected void start() { super.start(); @@ -307,30 +405,40 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { return; } - final int packetLossRate = + final PacketLossCalculationResult calculateResult = mPacketLossCalculator.getPacketLossRatePercentage( - mLastIpSecTransformState, state, getLogPrefix()); + mLastIpSecTransformState, + state, + mMaxSeqNumIncreasePerSecond, + getLogPrefix()); - if (packetLossRate == PACKET_LOSS_UNAVALAIBLE) { + if (calculateResult.getResultType() == PACKET_LOSS_RATE_INVALID) { return; } final String logMsg = - "packetLossRate: " - + packetLossRate + "calculateResult: " + + calculateResult + "% in the past " + (state.getTimestampMillis() - mLastIpSecTransformState.getTimestampMillis()) + "ms"; mLastIpSecTransformState = state; - if (packetLossRate < mPacketLossRatePercentThreshold) { + 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); - onValidationResultReceivedInternal(true /* isFailed */); + 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. @@ -343,9 +451,10 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { @VisibleForTesting(visibility = Visibility.PRIVATE) public static class PacketLossCalculator { /** Calculate the packet loss rate between two timestamps */ - public int getPacketLossRatePercentage( + public PacketLossCalculationResult getPacketLossRatePercentage( @NonNull IpSecTransformState oldState, @NonNull IpSecTransformState newState, + int maxSeqNumIncreasePerSecond, String logPrefix) { logVIpSecTransform("oldState", oldState, logPrefix); logVIpSecTransform("newState", newState, logPrefix); @@ -359,7 +468,23 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { if (oldSeqHi == newSeqHi || newSeqHi < replayWindowSize) { // The replay window did not proceed and all packets might have been delivered out // of order - return PACKET_LOSS_UNAVALAIBLE; + 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 @@ -381,15 +506,23 @@ 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 || actualPktCntDiff > expectedPktCntDiff) { logWtf(TAG, "Impossible values for expectedPktCntDiff or" + " actualPktCntDiff"); - return PACKET_LOSS_UNAVALAIBLE; + return PacketLossCalculationResult.invalid(); } - return 100 - (int) (actualPktCntDiff * 100 / expectedPktCntDiff); + final int percent = 100 - (int) (actualPktCntDiff * 100 / expectedPktCntDiff); + return isUnusualSeqNumLeap + ? PacketLossCalculationResult.unusualSeqNumLeap(percent) + : PacketLossCalculationResult.valid(percent); } } @@ -409,4 +542,64 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { private static long getPacketCntInReplayWindow(@NonNull IpSecTransformState state) { return BitSet.valueOf(state.getReplayBitmap()).cardinality(); } + + @VisibleForTesting(visibility = Visibility.PRIVATE) + public static class PacketLossCalculationResult { + @PacketLossResultType private final int mResultType; + private final int mPacketLossRatePercent; + + private PacketLossCalculationResult(@PacketLossResultType int type, int percent) { + mResultType = type; + mPacketLossRatePercent = percent; + } + + /** Construct an instance that contains a valid packet loss rate */ + public static PacketLossCalculationResult valid(int percent) { + return new PacketLossCalculationResult(PACKET_LOSS_RATE_VALID, percent); + } + + /** Construct an instance indicating the inability to get a valid packet loss rate */ + public static PacketLossCalculationResult invalid() { + return new PacketLossCalculationResult( + 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; + } + + public int getPacketLossRatePercent() { + return mPacketLossRatePercent; + } + + @Override + public int hashCode() { + return Objects.hash(mResultType, mPacketLossRatePercent); + } + + @Override + public boolean equals(@Nullable Object other) { + if (!(other instanceof PacketLossCalculationResult)) { + return false; + } + + final PacketLossCalculationResult rhs = (PacketLossCalculationResult) other; + return mResultType == rhs.mResultType + && mPacketLossRatePercent == rhs.mPacketLossRatePercent; + } + + @Override + public String toString() { + return "mResultType: " + + mResultType + + " | mPacketLossRatePercent: " + + mPacketLossRatePercent; + } + } } |