Enable NetworkMetricMonitor and support penalizing networks

This CL integrates the IpSecPacketLossDetector into VCN
route selection. With this change, when a data stall is
reported, the network candidate will be penalized and
then deprioritized during network selection. A network
candidate will stop being penalized until it hitting a timeout
or passing the validation.

Bug: 282996138
Test: atest FrameworksVcnTests(new tests), CtsVcnTestCases
Change-Id: Ifabd6fdea1d5a4fea40cf929dbab7c26d37274ac
This commit is contained in:
Yan Yan 2023-12-13 23:48:55 +00:00
commit ddcf238d5e
9 changed files with 625 additions and 28 deletions

View file

@ -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
@ -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
@ -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
/**
@ -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,

View file

@ -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();

View file

@ -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;
@ -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;
@ -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
@ -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();
@ -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) {
@ -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();
@ -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)
@ -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);
}
}
}

View file

@ -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
@ -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");
@ -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(
@ -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;
@ -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,
@ -162,6 +334,10 @@ public class UnderlyingNetworkEvaluator {
updatePriorityClass(
underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
for (NetworkMetricMonitor monitor : mMetricMonitors) {
monitor.setIsSelectedUnderlyingNetwork(isSelected);
}
}
/**
@ -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 */
@ -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:");
@ -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);
}
}

View file

@ -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
@ -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 =

View file

@ -223,6 +223,8 @@ public class VcnGatewayConnectionTestBase {
doReturn(mVcnNetworkProvider).when(mVcnContext).getVcnNetworkProvider();
doReturn(mFeatureFlags).when(mVcnContext).getFeatureFlags();
doReturn(true).when(mVcnContext).isFlagSafeModeTimeoutConfigEnabled();
doReturn(true).when(mVcnContext).isFlagIpSecTransformStateEnabled();
doReturn(true).when(mVcnContext).isFlagNetworkMetricMonitorEnabled();
doReturn(mUnderlyingNetworkController)
.when(mDeps)

View file

@ -26,6 +26,8 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.net.IpSecConfig;
import android.net.IpSecTransform;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
@ -141,4 +143,8 @@ public abstract class NetworkEvaluationTestBase {
mock(Handler.class));
setupSystemService(mContext, mPowerManager, Context.POWER_SERVICE, PowerManager.class);
}
protected IpSecTransform makeDummyIpSecTransform() throws Exception {
return new IpSecTransform(mContext, new IpSecConfig());
}
}

View file

@ -47,6 +47,8 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.IpSecConfig;
import android.net.IpSecTransform;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
@ -70,6 +72,7 @@ import com.android.server.vcn.routeselection.UnderlyingNetworkController.Depende
import com.android.server.vcn.routeselection.UnderlyingNetworkController.NetworkBringupCallback;
import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkControllerCallback;
import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkListener;
import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.NetworkEvaluatorCallback;
import org.junit.Before;
import org.junit.Test;
@ -153,11 +156,13 @@ public class UnderlyingNetworkControllerTest {
@Mock private CarrierConfigManager mCarrierConfigManager;
@Mock private TelephonySubscriptionSnapshot mSubscriptionSnapshot;
@Mock private UnderlyingNetworkControllerCallback mNetworkControllerCb;
@Mock private NetworkEvaluatorCallback mEvaluatorCallback;
@Mock private Network mNetwork;
@Spy private Dependencies mDependencies = new Dependencies();
@Captor private ArgumentCaptor<UnderlyingNetworkListener> mUnderlyingNetworkListenerCaptor;
@Captor private ArgumentCaptor<NetworkEvaluatorCallback> mEvaluatorCallbackCaptor;
private TestLooper mTestLooper;
private VcnContext mVcnContext;
@ -176,7 +181,7 @@ public class UnderlyingNetworkControllerTest {
mTestLooper.getLooper(),
mVcnNetworkProvider,
false /* isInTestMode */));
resetVcnContext();
resetVcnContext(mVcnContext);
setupSystemService(
mContext,
@ -202,10 +207,11 @@ public class UnderlyingNetworkControllerTest {
.getVcnUnderlyingNetworkPriorities(),
SUB_GROUP,
mSubscriptionSnapshot,
null));
null,
mEvaluatorCallback));
doReturn(mNetworkEvaluator)
.when(mDependencies)
.newUnderlyingNetworkEvaluator(any(), any(), any(), any(), any(), any());
.newUnderlyingNetworkEvaluator(any(), any(), any(), any(), any(), any(), any());
mUnderlyingNetworkController =
new UnderlyingNetworkController(
@ -217,9 +223,11 @@ public class UnderlyingNetworkControllerTest {
mDependencies);
}
private void resetVcnContext() {
reset(mVcnContext);
doNothing().when(mVcnContext).ensureRunningOnLooperThread();
private void resetVcnContext(VcnContext vcnContext) {
reset(vcnContext);
doNothing().when(vcnContext).ensureRunningOnLooperThread();
doReturn(true).when(vcnContext).isFlagNetworkMetricMonitorEnabled();
doReturn(true).when(vcnContext).isFlagIpSecTransformStateEnabled();
}
// Package private for use in NetworkPriorityClassifierTest
@ -245,11 +253,13 @@ public class UnderlyingNetworkControllerTest {
final ConnectivityManager cm = mock(ConnectivityManager.class);
setupSystemService(mContext, cm, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class);
final VcnContext vcnContext =
new VcnContext(
mContext,
mTestLooper.getLooper(),
mVcnNetworkProvider,
true /* isInTestMode */);
spy(
new VcnContext(
mContext,
mTestLooper.getLooper(),
mVcnNetworkProvider,
true /* isInTestMode */));
resetVcnContext(vcnContext);
new UnderlyingNetworkController(
vcnContext,
@ -554,6 +564,45 @@ public class UnderlyingNetworkControllerTest {
verify(mNetworkEvaluator).reevaluate(any(), any(), any(), any());
}
@Test
public void testUpdateIpSecTransform() {
verifyRegistrationOnAvailableAndGetCallback();
final UnderlyingNetworkRecord expectedRecord =
getTestNetworkRecord(
mNetwork,
INITIAL_NETWORK_CAPABILITIES,
INITIAL_LINK_PROPERTIES,
false /* isBlocked */);
final IpSecTransform expectedTransform = new IpSecTransform(mContext, new IpSecConfig());
mUnderlyingNetworkController.updateInboundTransform(expectedRecord, expectedTransform);
verify(mNetworkEvaluator).setInboundTransform(expectedTransform);
}
@Test
public void testOnEvaluationResultChanged() {
verifyRegistrationOnAvailableAndGetCallback();
// Verify #reevaluateNetworks is called by checking #getNetworkRecord
verify(mNetworkEvaluator).getNetworkRecord();
// Trigger the callback
verify(mDependencies)
.newUnderlyingNetworkEvaluator(
any(),
any(),
any(),
any(),
any(),
any(),
mEvaluatorCallbackCaptor.capture());
mEvaluatorCallbackCaptor.getValue().onEvaluationResultChanged();
// Verify #reevaluateNetworks is called again
verify(mNetworkEvaluator, times(2)).getNetworkRecord();
}
private UnderlyingNetworkListener verifyRegistrationOnAvailableAndGetCallback() {
return verifyRegistrationOnAvailableAndGetCallback(INITIAL_NETWORK_CAPABILITIES);
}
@ -682,7 +731,7 @@ public class UnderlyingNetworkControllerTest {
cb.onBlockedStatusChanged(mNetwork, true /* isBlocked */);
verifyOnSelectedUnderlyingNetworkChanged(null);
verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(null);
}
@Test
@ -690,6 +739,7 @@ public class UnderlyingNetworkControllerTest {
UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback();
cb.onLost(mNetwork);
verify(mNetworkEvaluator).close();
verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(null);
}
@ -755,10 +805,11 @@ public class UnderlyingNetworkControllerTest {
underlyingNetworkTemplates,
SUB_GROUP,
mSubscriptionSnapshot,
null));
null,
mEvaluatorCallback));
doReturn(evaluator)
.when(mDependencies)
.newUnderlyingNetworkEvaluator(any(), any(), any(), any(), any(), any());
.newUnderlyingNetworkEvaluator(any(), any(), any(), any(), any(), any(), any());
cb.onAvailable(network);
cb.onCapabilitiesChanged(network, responseNetworkCaps);

View file

@ -16,27 +16,65 @@
package com.android.server.vcn.routeselection;
import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY;
import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_INVALID;
import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyObject;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.net.IpSecTransform;
import android.net.vcn.VcnGatewayConnectionConfig;
import android.os.PersistableBundle;
import com.android.server.vcn.routeselection.NetworkMetricMonitor.NetworkMetricMonitorCallback;
import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.Dependencies;
import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.NetworkEvaluatorCallback;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import java.util.concurrent.TimeUnit;
public class UnderlyingNetworkEvaluatorTest extends NetworkEvaluationTestBase {
private PersistableBundleWrapper mCarrierConfig;
private static final int PENALTY_TIMEOUT_MIN = 10;
private static final long PENALTY_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(PENALTY_TIMEOUT_MIN);
@Mock private PersistableBundleWrapper mCarrierConfig;
@Mock private IpSecPacketLossDetector mIpSecPacketLossDetector;
@Mock private Dependencies mDependencies;
@Mock private NetworkEvaluatorCallback mEvaluatorCallback;
@Captor private ArgumentCaptor<NetworkMetricMonitorCallback> mMetricMonitorCbCaptor;
private UnderlyingNetworkEvaluator mNetworkEvaluator;
@Before
public void setUp() throws Exception {
super.setUp();
mCarrierConfig = new PersistableBundleWrapper(new PersistableBundle());
when(mDependencies.newIpSecPacketLossDetector(any(), any(), any(), any()))
.thenReturn(mIpSecPacketLossDetector);
when(mCarrierConfig.getIntArray(
eq(VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY), anyObject()))
.thenReturn(new int[] {PENALTY_TIMEOUT_MIN});
mNetworkEvaluator = newValidUnderlyingNetworkEvaluator();
}
private UnderlyingNetworkEvaluator newUnderlyingNetworkEvaluator() {
@ -46,7 +84,34 @@ public class UnderlyingNetworkEvaluatorTest extends NetworkEvaluationTestBase {
VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
SUB_GROUP,
mSubscriptionSnapshot,
mCarrierConfig,
mEvaluatorCallback,
mDependencies);
}
private UnderlyingNetworkEvaluator newValidUnderlyingNetworkEvaluator() {
final UnderlyingNetworkEvaluator evaluator = newUnderlyingNetworkEvaluator();
evaluator.setNetworkCapabilities(
CELL_NETWORK_CAPABILITIES,
VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
SUB_GROUP,
mSubscriptionSnapshot,
mCarrierConfig);
evaluator.setLinkProperties(
LINK_PROPERTIES,
VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
SUB_GROUP,
mSubscriptionSnapshot,
mCarrierConfig);
evaluator.setIsBlocked(
false /* isBlocked */,
VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
SUB_GROUP,
mSubscriptionSnapshot,
mCarrierConfig);
return evaluator;
}
@Test
@ -98,4 +163,174 @@ public class UnderlyingNetworkEvaluatorTest extends NetworkEvaluationTestBase {
assertEquals(2, evaluator.getPriorityClass());
assertEquals(expectedRecord, evaluator.getNetworkRecord());
}
private void checkSetSelectedNetwork(boolean isSelected) {
mNetworkEvaluator.setIsSelected(
isSelected,
VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
SUB_GROUP,
mSubscriptionSnapshot,
mCarrierConfig);
verify(mIpSecPacketLossDetector).setIsSelectedUnderlyingNetwork(isSelected);
}
@Test
public void testSetIsSelected_selected() throws Exception {
checkSetSelectedNetwork(true /* isSelectedExpected */);
}
@Test
public void testSetIsSelected_unselected() throws Exception {
checkSetSelectedNetwork(false /* isSelectedExpected */);
}
@Test
public void testSetIpSecTransform_onSelectedNetwork() throws Exception {
final IpSecTransform transform = makeDummyIpSecTransform();
// Make the network selected
mNetworkEvaluator.setIsSelected(
true,
VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
SUB_GROUP,
mSubscriptionSnapshot,
mCarrierConfig);
mNetworkEvaluator.setInboundTransform(transform);
verify(mIpSecPacketLossDetector).setInboundTransform(transform);
}
@Test
public void testSetIpSecTransform_onUnSelectedNetwork() throws Exception {
mNetworkEvaluator.setIsSelected(
false,
VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
SUB_GROUP,
mSubscriptionSnapshot,
mCarrierConfig);
mNetworkEvaluator.setInboundTransform(makeDummyIpSecTransform());
verify(mIpSecPacketLossDetector, never()).setInboundTransform(any());
}
@Test
public void close() throws Exception {
mNetworkEvaluator.close();
verify(mIpSecPacketLossDetector).close();
mTestLooper.moveTimeForward(PENALTY_TIMEOUT_MS);
assertNull(mTestLooper.nextMessage());
}
private NetworkMetricMonitorCallback getMetricMonitorCbCaptor() throws Exception {
verify(mDependencies)
.newIpSecPacketLossDetector(any(), any(), any(), mMetricMonitorCbCaptor.capture());
return mMetricMonitorCbCaptor.getValue();
}
private void checkPenalizeNetwork() throws Exception {
assertFalse(mNetworkEvaluator.isPenalized());
// Validation failed
when(mIpSecPacketLossDetector.isValidationFailed()).thenReturn(true);
getMetricMonitorCbCaptor().onValidationResultReceived();
// Verify the evaluator is penalized
assertTrue(mNetworkEvaluator.isPenalized());
verify(mEvaluatorCallback).onEvaluationResultChanged();
}
@Test
public void testRcvValidationResult_penalizeNetwork_penaltyTimeout() throws Exception {
checkPenalizeNetwork();
// Penalty timeout
mTestLooper.moveTimeForward(PENALTY_TIMEOUT_MS);
mTestLooper.dispatchAll();
// Verify the evaluator is not penalized
assertFalse(mNetworkEvaluator.isPenalized());
verify(mEvaluatorCallback, times(2)).onEvaluationResultChanged();
}
@Test
public void testRcvValidationResult_penalizeNetwork_passValidation() throws Exception {
checkPenalizeNetwork();
// Validation passed
when(mIpSecPacketLossDetector.isValidationFailed()).thenReturn(false);
getMetricMonitorCbCaptor().onValidationResultReceived();
// Verify the evaluator is not penalized and penalty timeout is canceled
assertFalse(mNetworkEvaluator.isPenalized());
verify(mEvaluatorCallback, times(2)).onEvaluationResultChanged();
mTestLooper.moveTimeForward(PENALTY_TIMEOUT_MS);
assertNull(mTestLooper.nextMessage());
}
@Test
public void testRcvValidationResult_penalizeNetwork_closeEvaluator() throws Exception {
checkPenalizeNetwork();
mNetworkEvaluator.close();
// Verify penalty timeout is canceled
mTestLooper.moveTimeForward(PENALTY_TIMEOUT_MS);
assertNull(mTestLooper.nextMessage());
}
@Test
public void testRcvValidationResult_PenaltyStateUnchanged() throws Exception {
assertFalse(mNetworkEvaluator.isPenalized());
// Validation passed
when(mIpSecPacketLossDetector.isValidationFailed()).thenReturn(false);
getMetricMonitorCbCaptor().onValidationResultReceived();
// Verifications
assertFalse(mNetworkEvaluator.isPenalized());
verify(mEvaluatorCallback, never()).onEvaluationResultChanged();
}
@Test
public void testSetCarrierConfig() throws Exception {
final int additionalTimeoutMin = 10;
when(mCarrierConfig.getIntArray(
eq(VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY), anyObject()))
.thenReturn(new int[] {PENALTY_TIMEOUT_MIN + additionalTimeoutMin});
// Update evaluator and penalize the network
mNetworkEvaluator.reevaluate(
VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
SUB_GROUP,
mSubscriptionSnapshot,
mCarrierConfig);
checkPenalizeNetwork();
// Verify penalty timeout is changed
mTestLooper.moveTimeForward(PENALTY_TIMEOUT_MS);
assertNull(mTestLooper.nextMessage());
mTestLooper.moveTimeForward(TimeUnit.MINUTES.toMillis(additionalTimeoutMin));
assertNotNull(mTestLooper.nextMessage());
// Verify NetworkMetricMonitor is notified
verify(mIpSecPacketLossDetector).setCarrierConfig(any());
}
@Test
public void testCompare() throws Exception {
when(mIpSecPacketLossDetector.isValidationFailed()).thenReturn(true);
getMetricMonitorCbCaptor().onValidationResultReceived();
final UnderlyingNetworkEvaluator penalized = mNetworkEvaluator;
final UnderlyingNetworkEvaluator notPenalized = newValidUnderlyingNetworkEvaluator();
assertEquals(penalized.getPriorityClass(), notPenalized.getPriorityClass());
final int result =
UnderlyingNetworkEvaluator.getComparator(mVcnContext)
.compare(penalized, notPenalized);
assertEquals(1, result);
}
}