Loading core/java/android/net/vcn/VcnManager.java +10 −4 Original line number Diff line number Diff line Loading @@ -80,8 +80,6 @@ public class VcnManager { * <p>The VCN will only migrate to a Carrier WiFi network that has a signal strength greater * than, or equal to this threshold. * * <p>WARNING: The VCN does not listen for changes to this key made after VCN startup. * * @hide */ @NonNull Loading @@ -94,8 +92,6 @@ public class VcnManager { * <p>If the VCN's selected Carrier WiFi network has a signal strength less than this threshold, * the VCN will attempt to migrate away from the Carrier WiFi network. * * <p>WARNING: The VCN does not listen for changes to this key made after VCN startup. * * @hide */ @NonNull Loading @@ -120,6 +116,15 @@ 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 the list of timeouts in minute to stop penalizing an underlying network candidate * * @hide */ @NonNull public static final String VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY = "vcn_network_selection_penalty_timeout_minutes_list"; // TODO: Add separate signal strength thresholds for 2.4 GHz and 5GHz /** Loading Loading @@ -168,6 +173,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_PENALTY_TIMEOUT_MINUTES_LIST_KEY, VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY, VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY, VCN_TUNNEL_AGGREGATION_SA_COUNT_MAX_KEY, Loading services/core/java/com/android/server/vcn/VcnGatewayConnection.java +6 −0 Original line number Diff line number Diff line Loading @@ -1910,6 +1910,12 @@ public class VcnGatewayConnection extends StateMachine { // Transforms do not need to be persisted; the IkeSession will keep them alive mIpSecManager.applyTunnelModeTransform(tunnelIface, direction, transform); if (direction == IpSecManager.DIRECTION_IN && mVcnContext.isFlagNetworkMetricMonitorEnabled() && mVcnContext.isFlagIpSecTransformStateEnabled()) { mUnderlyingNetworkController.updateInboundTransform(mUnderlying, transform); } // For inbound transforms, additionally allow forwarded traffic to bridge to DUN (as // needed) final Set<Integer> exposedCaps = mConnectionConfig.getAllExposedCapabilities(); Loading services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java +68 −5 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.IpSecTransform; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; Loading @@ -52,6 +53,7 @@ import com.android.internal.annotations.VisibleForTesting.Visibility; import com.android.internal.util.IndentingPrintWriter; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import com.android.server.vcn.VcnContext; import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.NetworkEvaluatorCallback; import com.android.server.vcn.util.LogUtils; import java.util.ArrayList; Loading Loading @@ -201,6 +203,14 @@ public class UnderlyingNetworkController { NetworkCallback oldWifiExitRssiThresholdCallback = mWifiExitRssiThresholdCallback; List<NetworkCallback> oldCellCallbacks = new ArrayList<>(mCellBringupCallbacks); mCellBringupCallbacks.clear(); if (mVcnContext.isFlagNetworkMetricMonitorEnabled() && mVcnContext.isFlagIpSecTransformStateEnabled()) { for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) { evaluator.close(); } } mUnderlyingNetworkRecords.clear(); // Register new callbacks. Make-before-break; always register new callbacks before removal Loading Loading @@ -417,11 +427,42 @@ public class UnderlyingNetworkController { if (oldSnapshot .getAllSubIdsInGroup(mSubscriptionGroup) .equals(newSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))) { if (mVcnContext.isFlagNetworkMetricMonitorEnabled() && mVcnContext.isFlagIpSecTransformStateEnabled()) { reevaluateNetworks(); } return; } registerOrUpdateNetworkRequests(); } /** * Pass the IpSecTransform of the VCN to UnderlyingNetworkController for metric monitoring * * <p>Caller MUST call it when IpSecTransforms have been created for VCN creation or migration */ public void updateInboundTransform( @NonNull UnderlyingNetworkRecord currentNetwork, @NonNull IpSecTransform transform) { if (!mVcnContext.isFlagNetworkMetricMonitorEnabled() || !mVcnContext.isFlagIpSecTransformStateEnabled()) { logWtf("#updateInboundTransform: unexpected call; flags missing"); return; } Objects.requireNonNull(currentNetwork, "currentNetwork is null"); Objects.requireNonNull(transform, "transform is null"); if (mCurrentRecord == null || mRouteSelectionCallback == null || !Objects.equals(currentNetwork.network, mCurrentRecord.network)) { // The caller (VcnGatewayConnection) is out-of-dated. Ignore this call. return; } mUnderlyingNetworkRecords.get(mCurrentRecord.network).setInboundTransform(transform); } /** Tears down this Tracker, and releases all underlying network requests. */ public void teardown() { mVcnContext.ensureRunningOnLooperThread(); Loading @@ -438,7 +479,7 @@ public class UnderlyingNetworkController { private TreeSet<UnderlyingNetworkEvaluator> getSortedUnderlyingNetworks() { TreeSet<UnderlyingNetworkEvaluator> sorted = new TreeSet<>(UnderlyingNetworkEvaluator.getComparator()); new TreeSet<>(UnderlyingNetworkEvaluator.getComparator(mVcnContext)); for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) { if (evaluator.getPriorityClass() != NetworkPriorityClassifier.PRIORITY_INVALID) { Loading Loading @@ -525,11 +566,17 @@ public class UnderlyingNetworkController { mConnectionConfig.getVcnUnderlyingNetworkPriorities(), mSubscriptionGroup, mLastSnapshot, mCarrierConfig)); mCarrierConfig, new NetworkEvaluatorCallbackImpl())); } @Override public void onLost(@NonNull Network network) { if (mVcnContext.isFlagNetworkMetricMonitorEnabled() && mVcnContext.isFlagIpSecTransformStateEnabled()) { mUnderlyingNetworkRecords.get(network).close(); } mUnderlyingNetworkRecords.remove(network); reevaluateNetworks(); Loading Loading @@ -598,6 +645,21 @@ public class UnderlyingNetworkController { } } @VisibleForTesting class NetworkEvaluatorCallbackImpl implements NetworkEvaluatorCallback { @Override public void onEvaluationResultChanged() { if (!mVcnContext.isFlagNetworkMetricMonitorEnabled() || !mVcnContext.isFlagIpSecTransformStateEnabled()) { logWtf("#onEvaluationResultChanged: unexpected call; flags missing"); return; } mVcnContext.ensureRunningOnLooperThread(); reevaluateNetworks(); } } private String getLogPrefix() { return "(" + LogUtils.getHashedSubscriptionGroup(mSubscriptionGroup) Loading Loading @@ -690,21 +752,22 @@ public class UnderlyingNetworkController { @VisibleForTesting(visibility = Visibility.PRIVATE) public static class Dependencies { /** Construct a new UnderlyingNetworkEvaluator */ public UnderlyingNetworkEvaluator newUnderlyingNetworkEvaluator( @NonNull VcnContext vcnContext, @NonNull Network network, @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates, @NonNull ParcelUuid subscriptionGroup, @NonNull TelephonySubscriptionSnapshot lastSnapshot, @Nullable PersistableBundleWrapper carrierConfig) { @Nullable PersistableBundleWrapper carrierConfig, @NonNull NetworkEvaluatorCallback evaluatorCallback) { return new UnderlyingNetworkEvaluator( vcnContext, network, underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig); carrierConfig, evaluatorCallback); } } } services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java +227 −2 Original line number Diff line number Diff line Loading @@ -16,23 +16,32 @@ package com.android.server.vcn.routeselection; import static com.android.server.VcnManagementService.LOCAL_LOG; import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import android.annotation.NonNull; import android.annotation.Nullable; import android.net.IpSecTransform; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.vcn.VcnManager; import android.net.vcn.VcnUnderlyingNetworkTemplate; import android.os.Handler; import android.os.ParcelUuid; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; import com.android.internal.util.IndentingPrintWriter; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import com.android.server.vcn.VcnContext; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; /** * UnderlyingNetworkEvaluator evaluates the quality and priority class of a network candidate for Loading @@ -43,20 +52,41 @@ import java.util.Objects; public class UnderlyingNetworkEvaluator { private static final String TAG = UnderlyingNetworkEvaluator.class.getSimpleName(); private static final int[] PENALTY_TIMEOUT_MINUTES_DEFAULT = new int[] {5}; @NonNull private final VcnContext mVcnContext; @NonNull private final Handler mHandler; @NonNull private final Object mCancellationToken = new Object(); @NonNull private final UnderlyingNetworkRecord.Builder mNetworkRecordBuilder; @NonNull private final NetworkEvaluatorCallback mEvaluatorCallback; @NonNull private final List<NetworkMetricMonitor> mMetricMonitors = new ArrayList<>(); @NonNull private final Dependencies mDependencies; // TODO: Support back-off timeouts private long mPenalizedTimeoutMs; private boolean mIsSelected; private boolean mIsPenalized; private int mPriorityClass = NetworkPriorityClassifier.PRIORITY_INVALID; @VisibleForTesting(visibility = Visibility.PRIVATE) public UnderlyingNetworkEvaluator( @NonNull VcnContext vcnContext, @NonNull Network network, @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates, @NonNull ParcelUuid subscriptionGroup, @NonNull TelephonySubscriptionSnapshot lastSnapshot, @Nullable PersistableBundleWrapper carrierConfig) { @Nullable PersistableBundleWrapper carrierConfig, @NonNull NetworkEvaluatorCallback evaluatorCallback, @NonNull Dependencies dependencies) { mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext"); mHandler = new Handler(mVcnContext.getLooper()); mDependencies = Objects.requireNonNull(dependencies, "Missing dependencies"); mEvaluatorCallback = Objects.requireNonNull(evaluatorCallback, "Missing deps"); Objects.requireNonNull(underlyingNetworkTemplates, "Missing underlyingNetworkTemplates"); Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup"); Loading @@ -66,9 +96,76 @@ public class UnderlyingNetworkEvaluator { new UnderlyingNetworkRecord.Builder( Objects.requireNonNull(network, "Missing network")); mIsSelected = false; mIsPenalized = false; mPenalizedTimeoutMs = getPenaltyTimeoutMs(carrierConfig); updatePriorityClass( underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig); if (isIpSecPacketLossDetectorEnabled()) { try { mMetricMonitors.add( mDependencies.newIpSecPacketLossDetector( mVcnContext, mNetworkRecordBuilder.getNetwork(), carrierConfig, new MetricMonitorCallbackImpl())); } catch (IllegalAccessException e) { // No action. Do not add anything to mMetricMonitors } } } public UnderlyingNetworkEvaluator( @NonNull VcnContext vcnContext, @NonNull Network network, @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates, @NonNull ParcelUuid subscriptionGroup, @NonNull TelephonySubscriptionSnapshot lastSnapshot, @Nullable PersistableBundleWrapper carrierConfig, @NonNull NetworkEvaluatorCallback evaluatorCallback) { this( vcnContext, network, underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig, evaluatorCallback, new Dependencies()); } @VisibleForTesting(visibility = Visibility.PRIVATE) public static class Dependencies { /** Get an IpSecPacketLossDetector instance */ public IpSecPacketLossDetector newIpSecPacketLossDetector( @NonNull VcnContext vcnContext, @NonNull Network network, @Nullable PersistableBundleWrapper carrierConfig, @NonNull NetworkMetricMonitor.NetworkMetricMonitorCallback callback) throws IllegalAccessException { return new IpSecPacketLossDetector(vcnContext, network, carrierConfig, callback); } } /** Callback to notify caller to reevaluate network selection */ public interface NetworkEvaluatorCallback { /** * Called when mIsPenalized changed * * <p>When receiving this call, UnderlyingNetworkController should reevaluate all network * candidates for VCN underlying network selection */ void onEvaluationResultChanged(); } private class MetricMonitorCallbackImpl implements NetworkMetricMonitor.NetworkMetricMonitorCallback { public void onValidationResultReceived() { mVcnContext.ensureRunningOnLooperThread(); handleValidationResult(); } } private void updatePriorityClass( Loading @@ -91,8 +188,25 @@ public class UnderlyingNetworkEvaluator { } } public static Comparator<UnderlyingNetworkEvaluator> getComparator() { private boolean isIpSecPacketLossDetectorEnabled() { return isIpSecPacketLossDetectorEnabled(mVcnContext); } private static boolean isIpSecPacketLossDetectorEnabled(VcnContext vcnContext) { return vcnContext.isFlagIpSecTransformStateEnabled() && vcnContext.isFlagNetworkMetricMonitorEnabled(); } /** Get the comparator for UnderlyingNetworkEvaluator */ public static Comparator<UnderlyingNetworkEvaluator> getComparator(VcnContext vcnContext) { return (left, right) -> { if (isIpSecPacketLossDetectorEnabled(vcnContext)) { if (left.mIsPenalized != right.mIsPenalized) { // A penalized network should have lower priority which means a larger index return left.mIsPenalized ? 1 : -1; } } final int leftIndex = left.mPriorityClass; final int rightIndex = right.mPriorityClass; Loading @@ -112,6 +226,64 @@ public class UnderlyingNetworkEvaluator { }; } private static long getPenaltyTimeoutMs(@Nullable PersistableBundleWrapper carrierConfig) { final int[] timeoutMinuteList; if (carrierConfig != null) { timeoutMinuteList = carrierConfig.getIntArray( VcnManager.VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY, PENALTY_TIMEOUT_MINUTES_DEFAULT); } else { timeoutMinuteList = PENALTY_TIMEOUT_MINUTES_DEFAULT; } // TODO: Add the support of back-off timeouts and return the full list return TimeUnit.MINUTES.toMillis(timeoutMinuteList[0]); } private void handleValidationResult() { final boolean wasPenalized = mIsPenalized; mIsPenalized = false; for (NetworkMetricMonitor monitor : mMetricMonitors) { mIsPenalized |= monitor.isValidationFailed(); } if (wasPenalized == mIsPenalized) { return; } logInfo( "#handleValidationResult: wasPenalized " + wasPenalized + " mIsPenalized " + mIsPenalized); if (mIsPenalized) { mHandler.postDelayed( new ExitPenaltyBoxRunnable(), mCancellationToken, mPenalizedTimeoutMs); } else { // Exit the penalty box mHandler.removeCallbacksAndEqualMessages(mCancellationToken); } mEvaluatorCallback.onEvaluationResultChanged(); } public class ExitPenaltyBoxRunnable implements Runnable { @Override public void run() { if (!mIsPenalized) { logWtf("Evaluator not being penalized but ExitPenaltyBoxRunnable was scheduled"); return; } // TODO: There might be a future metric monitor (e.g. ping) that will require the // validation to pass before exiting the penalty box. mIsPenalized = false; mEvaluatorCallback.onEvaluationResultChanged(); } } /** Set the NetworkCapabilities */ public void setNetworkCapabilities( @NonNull NetworkCapabilities nc, Loading Loading @@ -162,6 +334,10 @@ public class UnderlyingNetworkEvaluator { updatePriorityClass( underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig); for (NetworkMetricMonitor monitor : mMetricMonitors) { monitor.setIsSelectedUnderlyingNetwork(isSelected); } } /** Loading @@ -174,6 +350,35 @@ public class UnderlyingNetworkEvaluator { @Nullable PersistableBundleWrapper carrierConfig) { updatePriorityClass( underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig); // The already scheduled event will not be affected. The followup events will be scheduled // with the new timeout mPenalizedTimeoutMs = getPenaltyTimeoutMs(carrierConfig); for (NetworkMetricMonitor monitor : mMetricMonitors) { monitor.setCarrierConfig(carrierConfig); } } /** Update the inbound IpSecTransform applied to the network */ public void setInboundTransform(@NonNull IpSecTransform transform) { if (!mIsSelected) { logWtf("setInboundTransform on an unselected evaluator"); return; } for (NetworkMetricMonitor monitor : mMetricMonitors) { monitor.setInboundTransform(transform); } } /** Close the evaluator and stop all the underlying network metric monitors */ public void close() { mHandler.removeCallbacksAndEqualMessages(mCancellationToken); for (NetworkMetricMonitor monitor : mMetricMonitors) { monitor.close(); } } /** Return whether this network evaluator is valid */ Loading @@ -196,6 +401,11 @@ public class UnderlyingNetworkEvaluator { return mPriorityClass; } /** Return whether the network is being penalized */ public boolean isPenalized() { return mIsPenalized; } /** Dump the information of this instance */ public void dump(IndentingPrintWriter pw) { pw.println("UnderlyingNetworkEvaluator:"); Loading @@ -211,7 +421,22 @@ public class UnderlyingNetworkEvaluator { pw.println("mIsSelected: " + mIsSelected); pw.println("mPriorityClass: " + mPriorityClass); pw.println("mIsPenalized: " + mIsPenalized); pw.decreaseIndent(); } private String getLogPrefix() { return "[Network " + mNetworkRecordBuilder.getNetwork() + "] "; } private void logInfo(String msg) { Slog.i(TAG, getLogPrefix() + msg); LOCAL_LOG.log("[INFO ] " + TAG + getLogPrefix() + msg); } private void logWtf(String msg) { Slog.wtf(TAG, getLogPrefix() + msg); LOCAL_LOG.log("[WTF ] " + TAG + getLogPrefix() + msg); } } tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java +3 −0 Original line number Diff line number Diff line Loading @@ -269,6 +269,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection @Test public void testCreatedTransformsAreApplied() throws Exception { verifyVcnTransformsApplied(mGatewayConnection, false /* expectForwardTransform */); verify(mUnderlyingNetworkController).updateInboundTransform(any(), any()); } @Test Loading Loading @@ -327,6 +328,8 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(direction), anyInt(), any()); } verify(mUnderlyingNetworkController).updateInboundTransform(any(), any()); assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState()); final List<ChildSaProposal> saProposals = Loading Loading
core/java/android/net/vcn/VcnManager.java +10 −4 Original line number Diff line number Diff line Loading @@ -80,8 +80,6 @@ public class VcnManager { * <p>The VCN will only migrate to a Carrier WiFi network that has a signal strength greater * than, or equal to this threshold. * * <p>WARNING: The VCN does not listen for changes to this key made after VCN startup. * * @hide */ @NonNull Loading @@ -94,8 +92,6 @@ public class VcnManager { * <p>If the VCN's selected Carrier WiFi network has a signal strength less than this threshold, * the VCN will attempt to migrate away from the Carrier WiFi network. * * <p>WARNING: The VCN does not listen for changes to this key made after VCN startup. * * @hide */ @NonNull Loading @@ -120,6 +116,15 @@ 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 the list of timeouts in minute to stop penalizing an underlying network candidate * * @hide */ @NonNull public static final String VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY = "vcn_network_selection_penalty_timeout_minutes_list"; // TODO: Add separate signal strength thresholds for 2.4 GHz and 5GHz /** Loading Loading @@ -168,6 +173,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_PENALTY_TIMEOUT_MINUTES_LIST_KEY, VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY, VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY, VCN_TUNNEL_AGGREGATION_SA_COUNT_MAX_KEY, Loading
services/core/java/com/android/server/vcn/VcnGatewayConnection.java +6 −0 Original line number Diff line number Diff line Loading @@ -1910,6 +1910,12 @@ public class VcnGatewayConnection extends StateMachine { // Transforms do not need to be persisted; the IkeSession will keep them alive mIpSecManager.applyTunnelModeTransform(tunnelIface, direction, transform); if (direction == IpSecManager.DIRECTION_IN && mVcnContext.isFlagNetworkMetricMonitorEnabled() && mVcnContext.isFlagIpSecTransformStateEnabled()) { mUnderlyingNetworkController.updateInboundTransform(mUnderlying, transform); } // For inbound transforms, additionally allow forwarded traffic to bridge to DUN (as // needed) final Set<Integer> exposedCaps = mConnectionConfig.getAllExposedCapabilities(); Loading
services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java +68 −5 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.IpSecTransform; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; Loading @@ -52,6 +53,7 @@ import com.android.internal.annotations.VisibleForTesting.Visibility; import com.android.internal.util.IndentingPrintWriter; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import com.android.server.vcn.VcnContext; import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.NetworkEvaluatorCallback; import com.android.server.vcn.util.LogUtils; import java.util.ArrayList; Loading Loading @@ -201,6 +203,14 @@ public class UnderlyingNetworkController { NetworkCallback oldWifiExitRssiThresholdCallback = mWifiExitRssiThresholdCallback; List<NetworkCallback> oldCellCallbacks = new ArrayList<>(mCellBringupCallbacks); mCellBringupCallbacks.clear(); if (mVcnContext.isFlagNetworkMetricMonitorEnabled() && mVcnContext.isFlagIpSecTransformStateEnabled()) { for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) { evaluator.close(); } } mUnderlyingNetworkRecords.clear(); // Register new callbacks. Make-before-break; always register new callbacks before removal Loading Loading @@ -417,11 +427,42 @@ public class UnderlyingNetworkController { if (oldSnapshot .getAllSubIdsInGroup(mSubscriptionGroup) .equals(newSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))) { if (mVcnContext.isFlagNetworkMetricMonitorEnabled() && mVcnContext.isFlagIpSecTransformStateEnabled()) { reevaluateNetworks(); } return; } registerOrUpdateNetworkRequests(); } /** * Pass the IpSecTransform of the VCN to UnderlyingNetworkController for metric monitoring * * <p>Caller MUST call it when IpSecTransforms have been created for VCN creation or migration */ public void updateInboundTransform( @NonNull UnderlyingNetworkRecord currentNetwork, @NonNull IpSecTransform transform) { if (!mVcnContext.isFlagNetworkMetricMonitorEnabled() || !mVcnContext.isFlagIpSecTransformStateEnabled()) { logWtf("#updateInboundTransform: unexpected call; flags missing"); return; } Objects.requireNonNull(currentNetwork, "currentNetwork is null"); Objects.requireNonNull(transform, "transform is null"); if (mCurrentRecord == null || mRouteSelectionCallback == null || !Objects.equals(currentNetwork.network, mCurrentRecord.network)) { // The caller (VcnGatewayConnection) is out-of-dated. Ignore this call. return; } mUnderlyingNetworkRecords.get(mCurrentRecord.network).setInboundTransform(transform); } /** Tears down this Tracker, and releases all underlying network requests. */ public void teardown() { mVcnContext.ensureRunningOnLooperThread(); Loading @@ -438,7 +479,7 @@ public class UnderlyingNetworkController { private TreeSet<UnderlyingNetworkEvaluator> getSortedUnderlyingNetworks() { TreeSet<UnderlyingNetworkEvaluator> sorted = new TreeSet<>(UnderlyingNetworkEvaluator.getComparator()); new TreeSet<>(UnderlyingNetworkEvaluator.getComparator(mVcnContext)); for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) { if (evaluator.getPriorityClass() != NetworkPriorityClassifier.PRIORITY_INVALID) { Loading Loading @@ -525,11 +566,17 @@ public class UnderlyingNetworkController { mConnectionConfig.getVcnUnderlyingNetworkPriorities(), mSubscriptionGroup, mLastSnapshot, mCarrierConfig)); mCarrierConfig, new NetworkEvaluatorCallbackImpl())); } @Override public void onLost(@NonNull Network network) { if (mVcnContext.isFlagNetworkMetricMonitorEnabled() && mVcnContext.isFlagIpSecTransformStateEnabled()) { mUnderlyingNetworkRecords.get(network).close(); } mUnderlyingNetworkRecords.remove(network); reevaluateNetworks(); Loading Loading @@ -598,6 +645,21 @@ public class UnderlyingNetworkController { } } @VisibleForTesting class NetworkEvaluatorCallbackImpl implements NetworkEvaluatorCallback { @Override public void onEvaluationResultChanged() { if (!mVcnContext.isFlagNetworkMetricMonitorEnabled() || !mVcnContext.isFlagIpSecTransformStateEnabled()) { logWtf("#onEvaluationResultChanged: unexpected call; flags missing"); return; } mVcnContext.ensureRunningOnLooperThread(); reevaluateNetworks(); } } private String getLogPrefix() { return "(" + LogUtils.getHashedSubscriptionGroup(mSubscriptionGroup) Loading Loading @@ -690,21 +752,22 @@ public class UnderlyingNetworkController { @VisibleForTesting(visibility = Visibility.PRIVATE) public static class Dependencies { /** Construct a new UnderlyingNetworkEvaluator */ public UnderlyingNetworkEvaluator newUnderlyingNetworkEvaluator( @NonNull VcnContext vcnContext, @NonNull Network network, @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates, @NonNull ParcelUuid subscriptionGroup, @NonNull TelephonySubscriptionSnapshot lastSnapshot, @Nullable PersistableBundleWrapper carrierConfig) { @Nullable PersistableBundleWrapper carrierConfig, @NonNull NetworkEvaluatorCallback evaluatorCallback) { return new UnderlyingNetworkEvaluator( vcnContext, network, underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig); carrierConfig, evaluatorCallback); } } }
services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java +227 −2 Original line number Diff line number Diff line Loading @@ -16,23 +16,32 @@ package com.android.server.vcn.routeselection; import static com.android.server.VcnManagementService.LOCAL_LOG; import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import android.annotation.NonNull; import android.annotation.Nullable; import android.net.IpSecTransform; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.vcn.VcnManager; import android.net.vcn.VcnUnderlyingNetworkTemplate; import android.os.Handler; import android.os.ParcelUuid; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; import com.android.internal.util.IndentingPrintWriter; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import com.android.server.vcn.VcnContext; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; /** * UnderlyingNetworkEvaluator evaluates the quality and priority class of a network candidate for Loading @@ -43,20 +52,41 @@ import java.util.Objects; public class UnderlyingNetworkEvaluator { private static final String TAG = UnderlyingNetworkEvaluator.class.getSimpleName(); private static final int[] PENALTY_TIMEOUT_MINUTES_DEFAULT = new int[] {5}; @NonNull private final VcnContext mVcnContext; @NonNull private final Handler mHandler; @NonNull private final Object mCancellationToken = new Object(); @NonNull private final UnderlyingNetworkRecord.Builder mNetworkRecordBuilder; @NonNull private final NetworkEvaluatorCallback mEvaluatorCallback; @NonNull private final List<NetworkMetricMonitor> mMetricMonitors = new ArrayList<>(); @NonNull private final Dependencies mDependencies; // TODO: Support back-off timeouts private long mPenalizedTimeoutMs; private boolean mIsSelected; private boolean mIsPenalized; private int mPriorityClass = NetworkPriorityClassifier.PRIORITY_INVALID; @VisibleForTesting(visibility = Visibility.PRIVATE) public UnderlyingNetworkEvaluator( @NonNull VcnContext vcnContext, @NonNull Network network, @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates, @NonNull ParcelUuid subscriptionGroup, @NonNull TelephonySubscriptionSnapshot lastSnapshot, @Nullable PersistableBundleWrapper carrierConfig) { @Nullable PersistableBundleWrapper carrierConfig, @NonNull NetworkEvaluatorCallback evaluatorCallback, @NonNull Dependencies dependencies) { mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext"); mHandler = new Handler(mVcnContext.getLooper()); mDependencies = Objects.requireNonNull(dependencies, "Missing dependencies"); mEvaluatorCallback = Objects.requireNonNull(evaluatorCallback, "Missing deps"); Objects.requireNonNull(underlyingNetworkTemplates, "Missing underlyingNetworkTemplates"); Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup"); Loading @@ -66,9 +96,76 @@ public class UnderlyingNetworkEvaluator { new UnderlyingNetworkRecord.Builder( Objects.requireNonNull(network, "Missing network")); mIsSelected = false; mIsPenalized = false; mPenalizedTimeoutMs = getPenaltyTimeoutMs(carrierConfig); updatePriorityClass( underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig); if (isIpSecPacketLossDetectorEnabled()) { try { mMetricMonitors.add( mDependencies.newIpSecPacketLossDetector( mVcnContext, mNetworkRecordBuilder.getNetwork(), carrierConfig, new MetricMonitorCallbackImpl())); } catch (IllegalAccessException e) { // No action. Do not add anything to mMetricMonitors } } } public UnderlyingNetworkEvaluator( @NonNull VcnContext vcnContext, @NonNull Network network, @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates, @NonNull ParcelUuid subscriptionGroup, @NonNull TelephonySubscriptionSnapshot lastSnapshot, @Nullable PersistableBundleWrapper carrierConfig, @NonNull NetworkEvaluatorCallback evaluatorCallback) { this( vcnContext, network, underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig, evaluatorCallback, new Dependencies()); } @VisibleForTesting(visibility = Visibility.PRIVATE) public static class Dependencies { /** Get an IpSecPacketLossDetector instance */ public IpSecPacketLossDetector newIpSecPacketLossDetector( @NonNull VcnContext vcnContext, @NonNull Network network, @Nullable PersistableBundleWrapper carrierConfig, @NonNull NetworkMetricMonitor.NetworkMetricMonitorCallback callback) throws IllegalAccessException { return new IpSecPacketLossDetector(vcnContext, network, carrierConfig, callback); } } /** Callback to notify caller to reevaluate network selection */ public interface NetworkEvaluatorCallback { /** * Called when mIsPenalized changed * * <p>When receiving this call, UnderlyingNetworkController should reevaluate all network * candidates for VCN underlying network selection */ void onEvaluationResultChanged(); } private class MetricMonitorCallbackImpl implements NetworkMetricMonitor.NetworkMetricMonitorCallback { public void onValidationResultReceived() { mVcnContext.ensureRunningOnLooperThread(); handleValidationResult(); } } private void updatePriorityClass( Loading @@ -91,8 +188,25 @@ public class UnderlyingNetworkEvaluator { } } public static Comparator<UnderlyingNetworkEvaluator> getComparator() { private boolean isIpSecPacketLossDetectorEnabled() { return isIpSecPacketLossDetectorEnabled(mVcnContext); } private static boolean isIpSecPacketLossDetectorEnabled(VcnContext vcnContext) { return vcnContext.isFlagIpSecTransformStateEnabled() && vcnContext.isFlagNetworkMetricMonitorEnabled(); } /** Get the comparator for UnderlyingNetworkEvaluator */ public static Comparator<UnderlyingNetworkEvaluator> getComparator(VcnContext vcnContext) { return (left, right) -> { if (isIpSecPacketLossDetectorEnabled(vcnContext)) { if (left.mIsPenalized != right.mIsPenalized) { // A penalized network should have lower priority which means a larger index return left.mIsPenalized ? 1 : -1; } } final int leftIndex = left.mPriorityClass; final int rightIndex = right.mPriorityClass; Loading @@ -112,6 +226,64 @@ public class UnderlyingNetworkEvaluator { }; } private static long getPenaltyTimeoutMs(@Nullable PersistableBundleWrapper carrierConfig) { final int[] timeoutMinuteList; if (carrierConfig != null) { timeoutMinuteList = carrierConfig.getIntArray( VcnManager.VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY, PENALTY_TIMEOUT_MINUTES_DEFAULT); } else { timeoutMinuteList = PENALTY_TIMEOUT_MINUTES_DEFAULT; } // TODO: Add the support of back-off timeouts and return the full list return TimeUnit.MINUTES.toMillis(timeoutMinuteList[0]); } private void handleValidationResult() { final boolean wasPenalized = mIsPenalized; mIsPenalized = false; for (NetworkMetricMonitor monitor : mMetricMonitors) { mIsPenalized |= monitor.isValidationFailed(); } if (wasPenalized == mIsPenalized) { return; } logInfo( "#handleValidationResult: wasPenalized " + wasPenalized + " mIsPenalized " + mIsPenalized); if (mIsPenalized) { mHandler.postDelayed( new ExitPenaltyBoxRunnable(), mCancellationToken, mPenalizedTimeoutMs); } else { // Exit the penalty box mHandler.removeCallbacksAndEqualMessages(mCancellationToken); } mEvaluatorCallback.onEvaluationResultChanged(); } public class ExitPenaltyBoxRunnable implements Runnable { @Override public void run() { if (!mIsPenalized) { logWtf("Evaluator not being penalized but ExitPenaltyBoxRunnable was scheduled"); return; } // TODO: There might be a future metric monitor (e.g. ping) that will require the // validation to pass before exiting the penalty box. mIsPenalized = false; mEvaluatorCallback.onEvaluationResultChanged(); } } /** Set the NetworkCapabilities */ public void setNetworkCapabilities( @NonNull NetworkCapabilities nc, Loading Loading @@ -162,6 +334,10 @@ public class UnderlyingNetworkEvaluator { updatePriorityClass( underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig); for (NetworkMetricMonitor monitor : mMetricMonitors) { monitor.setIsSelectedUnderlyingNetwork(isSelected); } } /** Loading @@ -174,6 +350,35 @@ public class UnderlyingNetworkEvaluator { @Nullable PersistableBundleWrapper carrierConfig) { updatePriorityClass( underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig); // The already scheduled event will not be affected. The followup events will be scheduled // with the new timeout mPenalizedTimeoutMs = getPenaltyTimeoutMs(carrierConfig); for (NetworkMetricMonitor monitor : mMetricMonitors) { monitor.setCarrierConfig(carrierConfig); } } /** Update the inbound IpSecTransform applied to the network */ public void setInboundTransform(@NonNull IpSecTransform transform) { if (!mIsSelected) { logWtf("setInboundTransform on an unselected evaluator"); return; } for (NetworkMetricMonitor monitor : mMetricMonitors) { monitor.setInboundTransform(transform); } } /** Close the evaluator and stop all the underlying network metric monitors */ public void close() { mHandler.removeCallbacksAndEqualMessages(mCancellationToken); for (NetworkMetricMonitor monitor : mMetricMonitors) { monitor.close(); } } /** Return whether this network evaluator is valid */ Loading @@ -196,6 +401,11 @@ public class UnderlyingNetworkEvaluator { return mPriorityClass; } /** Return whether the network is being penalized */ public boolean isPenalized() { return mIsPenalized; } /** Dump the information of this instance */ public void dump(IndentingPrintWriter pw) { pw.println("UnderlyingNetworkEvaluator:"); Loading @@ -211,7 +421,22 @@ public class UnderlyingNetworkEvaluator { pw.println("mIsSelected: " + mIsSelected); pw.println("mPriorityClass: " + mPriorityClass); pw.println("mIsPenalized: " + mIsPenalized); pw.decreaseIndent(); } private String getLogPrefix() { return "[Network " + mNetworkRecordBuilder.getNetwork() + "] "; } private void logInfo(String msg) { Slog.i(TAG, getLogPrefix() + msg); LOCAL_LOG.log("[INFO ] " + TAG + getLogPrefix() + msg); } private void logWtf(String msg) { Slog.wtf(TAG, getLogPrefix() + msg); LOCAL_LOG.log("[WTF ] " + TAG + getLogPrefix() + msg); } }
tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java +3 −0 Original line number Diff line number Diff line Loading @@ -269,6 +269,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection @Test public void testCreatedTransformsAreApplied() throws Exception { verifyVcnTransformsApplied(mGatewayConnection, false /* expectForwardTransform */); verify(mUnderlyingNetworkController).updateInboundTransform(any(), any()); } @Test Loading Loading @@ -327,6 +328,8 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(direction), anyInt(), any()); } verify(mUnderlyingNetworkController).updateInboundTransform(any(), any()); assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState()); final List<ChildSaProposal> saProposals = Loading