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

Commit ce1468a2 authored by Yan Yan's avatar Yan Yan
Browse files

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
parent 84889ec8
Loading
Loading
Loading
Loading
+10 −4
Original line number Diff line number Diff line
@@ -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,
+6 −0
Original line number Diff line number Diff line
@@ -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();
+68 −5
Original line number Diff line number Diff line
@@ -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);
        }
    }
}
+227 −2
Original line number Diff line number Diff line
@@ -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);
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -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 =
Loading