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

Commit e1cfaca6 authored by Benedict Wong's avatar Benedict Wong Committed by Automerger Merge Worker
Browse files

Merge "Add experimental support for IPsec tunnel aggregation" am: e9b6b197...

Merge "Add experimental support for IPsec tunnel aggregation" am: e9b6b197 am: f5d9b010 am: 912ecdc4

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/2338914



Change-Id: Ib99d74abe57380c4139d684e95fffceec91cc1d3
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents 3d060aca 912ecdc4
Loading
Loading
Loading
Loading
+16 −1
Original line number Diff line number Diff line
@@ -114,13 +114,28 @@ public class VcnManager {
    public static final String VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY =
            "vcn_restricted_transports";

    /**
     * Key for maximum number of parallel SAs for tunnel aggregation
     *
     * <p>If set to a value > 1, multiple tunnels will be set up, and inbound traffic will be
     * aggregated over the various tunnels.
     *
     * <p>Defaults to 1, unless overridden by carrier config
     *
     * @hide
     */
    @NonNull
    public static final String VCN_TUNNEL_AGGREGATION_SA_COUNT_MAX_KEY =
            "vcn_tunnel_aggregation_sa_count_max";

    /** List of Carrier Config options to extract from Carrier Config bundles. @hide */
    @NonNull
    public static final String[] VCN_RELATED_CARRIER_CONFIG_KEYS =
            new String[] {
                VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY,
                VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY,
                VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY
                VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY,
                VCN_TUNNEL_AGGREGATION_SA_COUNT_MAX_KEY,
            };

    private static final Map<
+106 −0
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import static android.net.vcn.VcnManager.VCN_ERROR_CODE_NETWORK_ERROR;

import static com.android.server.VcnManagementService.LOCAL_LOG;
import static com.android.server.VcnManagementService.VDBG;
import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;

import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -59,6 +60,7 @@ import android.net.RouteInfo;
import android.net.TelephonyNetworkSpecifier;
import android.net.Uri;
import android.net.annotations.PolicyDirection;
import android.net.ipsec.ike.ChildSaProposal;
import android.net.ipsec.ike.ChildSessionCallback;
import android.net.ipsec.ike.ChildSessionConfiguration;
import android.net.ipsec.ike.ChildSessionParams;
@@ -67,11 +69,14 @@ import android.net.ipsec.ike.IkeSessionCallback;
import android.net.ipsec.ike.IkeSessionConfiguration;
import android.net.ipsec.ike.IkeSessionConnectionInfo;
import android.net.ipsec.ike.IkeSessionParams;
import android.net.ipsec.ike.IkeTrafficSelector;
import android.net.ipsec.ike.IkeTunnelConnectionParams;
import android.net.ipsec.ike.TunnelModeChildSessionParams;
import android.net.ipsec.ike.exceptions.IkeException;
import android.net.ipsec.ike.exceptions.IkeInternalException;
import android.net.ipsec.ike.exceptions.IkeProtocolException;
import android.net.vcn.VcnGatewayConnectionConfig;
import android.net.vcn.VcnManager;
import android.net.vcn.VcnTransportInfo;
import android.net.wifi.WifiInfo;
import android.os.Handler;
@@ -169,6 +174,9 @@ import java.util.function.Consumer;
public class VcnGatewayConnection extends StateMachine {
    private static final String TAG = VcnGatewayConnection.class.getSimpleName();

    /** Default number of parallel SAs requested */
    static final int TUNNEL_AGGREGATION_SA_COUNT_MAX_DEFAULT = 1;

    // Matches DataConnection.NETWORK_TYPE private constant, and magic string from
    // ConnectivityManager#getNetworkTypeName()
    @VisibleForTesting(visibility = Visibility.PRIVATE)
@@ -1980,6 +1988,22 @@ public class VcnGatewayConnection extends StateMachine {
                            mChildConfig,
                            oldChildConfig,
                            mIkeConnectionInfo);

                    // Create opportunistic child SAs; this allows SA aggregation in the downlink,
                    // reducing lock/atomic contention in high throughput scenarios. All SAs will
                    // share the same UDP encap socket (and keepalives) as necessary, and are
                    // effectively free.
                    final int parallelTunnelCount =
                            mDeps.getParallelTunnelCount(mLastSnapshot, mSubscriptionGroup);
                    logInfo("Parallel tunnel count: " + parallelTunnelCount);

                    for (int i = 0; i < parallelTunnelCount - 1; i++) {
                        mIkeSession.openChildSession(
                                buildOpportunisticChildParams(),
                                new VcnChildSessionCallback(
                                        mCurrentToken, true /* isOpportunistic */));
                    }

                    break;
                case EVENT_DISCONNECT_REQUESTED:
                    handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj);
@@ -2350,15 +2374,44 @@ public class VcnGatewayConnection extends StateMachine {
    @VisibleForTesting(visibility = Visibility.PRIVATE)
    public class VcnChildSessionCallback implements ChildSessionCallback {
        private final int mToken;
        private final boolean mIsOpportunistic;

        private boolean mIsChildOpened = false;

        VcnChildSessionCallback(int token) {
            this(token, false /* isOpportunistic */);
        }

        /**
         * Creates a ChildSessionCallback
         *
         * <p>If configured as opportunistic, transforms will not report initial startup, or
         * associated startup failures. This serves the dual purposes of ensuring that if the server
         * does not support connection multiplexing, new child SA negotiations will be ignored, and
         * at the same time, will notify the VCN session if a successfully negotiated opportunistic
         * child SA is subsequently torn down, which could impact uplink traffic if the SA in use
         * for outbound/uplink traffic is this opportunistic SA.
         *
         * <p>While inbound SAs can be used in parallel, the IPsec stack explicitly selects the last
         * applied outbound transform for outbound traffic. This means that unlike inbound traffic,
         * outbound does not benefit from these parallel SAs in the same manner.
         */
        VcnChildSessionCallback(int token, boolean isOpportunistic) {
            mToken = token;
            mIsOpportunistic = isOpportunistic;
        }

        /** Internal proxy method for injecting of mocked ChildSessionConfiguration */
        @VisibleForTesting(visibility = Visibility.PRIVATE)
        void onOpened(@NonNull VcnChildSessionConfiguration childConfig) {
            logDbg("ChildOpened for token " + mToken);

            if (mIsOpportunistic) {
                logDbg("ChildOpened for opportunistic child; suppressing event message");
                mIsChildOpened = true;
                return;
            }

            childOpened(mToken, childConfig);
        }

@@ -2370,12 +2423,24 @@ public class VcnGatewayConnection extends StateMachine {
        @Override
        public void onClosed() {
            logDbg("ChildClosed for token " + mToken);

            if (mIsOpportunistic && !mIsChildOpened) {
                logDbg("ChildClosed for unopened opportunistic child; ignoring");
                return;
            }

            sessionLost(mToken, null);
        }

        @Override
        public void onClosedExceptionally(@NonNull IkeException exception) {
            logInfo("ChildClosedExceptionally for token " + mToken, exception);

            if (mIsOpportunistic && !mIsChildOpened) {
                logInfo("ChildClosedExceptionally for unopened opportunistic child; ignoring");
                return;
            }

            sessionLost(mToken, exception);
        }

@@ -2580,6 +2645,30 @@ public class VcnGatewayConnection extends StateMachine {
        return mConnectionConfig.getTunnelConnectionParams().getTunnelModeChildSessionParams();
    }

    private ChildSessionParams buildOpportunisticChildParams() {
        final ChildSessionParams baseParams =
                mConnectionConfig.getTunnelConnectionParams().getTunnelModeChildSessionParams();

        final TunnelModeChildSessionParams.Builder builder =
                new TunnelModeChildSessionParams.Builder();
        for (ChildSaProposal proposal : baseParams.getChildSaProposals()) {
            builder.addChildSaProposal(proposal);
        }

        for (IkeTrafficSelector inboundSelector : baseParams.getInboundTrafficSelectors()) {
            builder.addInboundTrafficSelectors(inboundSelector);
        }

        for (IkeTrafficSelector outboundSelector : baseParams.getOutboundTrafficSelectors()) {
            builder.addOutboundTrafficSelectors(outboundSelector);
        }

        builder.setLifetimeSeconds(
                baseParams.getHardLifetimeSeconds(), baseParams.getSoftLifetimeSeconds());

        return builder.build();
    }

    @VisibleForTesting(visibility = Visibility.PRIVATE)
    VcnIkeSession buildIkeSession(@NonNull Network network) {
        final int token = ++mCurrentToken;
@@ -2680,6 +2769,23 @@ public class VcnGatewayConnection extends StateMachine {
                return 0;
            }
        }

        /** Gets the max number of parallel tunnels allowed for tunnel aggregation. */
        public int getParallelTunnelCount(
                TelephonySubscriptionSnapshot snapshot, ParcelUuid subGrp) {
            PersistableBundleWrapper carrierConfig = snapshot.getCarrierConfigForSubGrp(subGrp);
            int result = TUNNEL_AGGREGATION_SA_COUNT_MAX_DEFAULT;

            if (carrierConfig != null) {
                result =
                        carrierConfig.getInt(
                                VcnManager.VCN_TUNNEL_AGGREGATION_SA_COUNT_MAX_KEY,
                                TUNNEL_AGGREGATION_SA_COUNT_MAX_DEFAULT);
            }

            // Guard against tunnel count < 1
            return Math.max(1, result);
        }
    }

    /**
+5 −0
Original line number Diff line number Diff line
@@ -573,5 +573,10 @@ public class PersistableBundleUtils {

            return isEqual(mBundle, other.mBundle);
        }

        @Override
        public String toString() {
            return mBundle.toString();
        }
    }
}
+103 −3
Original line number Diff line number Diff line
@@ -59,6 +59,7 @@ import android.net.NetworkAgent;
import android.net.NetworkCapabilities;
import android.net.ipsec.ike.ChildSaProposal;
import android.net.ipsec.ike.IkeSessionConnectionInfo;
import android.net.ipsec.ike.TunnelModeChildSessionParams;
import android.net.ipsec.ike.exceptions.IkeException;
import android.net.ipsec.ike.exceptions.IkeInternalException;
import android.net.ipsec.ike.exceptions.IkeProtocolException;
@@ -70,6 +71,7 @@ import android.os.PersistableBundle;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.server.vcn.VcnGatewayConnection.VcnChildSessionCallback;
import com.android.server.vcn.routeselection.UnderlyingNetworkRecord;
import com.android.server.vcn.util.MtuUtils;

@@ -90,6 +92,8 @@ import java.util.function.Consumer;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnectionTestBase {
    private static final int PARALLEL_SA_COUNT = 4;

    private VcnIkeSession mIkeSession;
    private VcnNetworkAgent mNetworkAgent;
    private Network mVcnNetwork;
@@ -227,16 +231,29 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection
    private void verifyVcnTransformsApplied(
            VcnGatewayConnection vcnGatewayConnection, boolean expectForwardTransform)
            throws Exception {
        verifyVcnTransformsApplied(
                vcnGatewayConnection,
                expectForwardTransform,
                Collections.singletonList(getChildSessionCallback()));
    }

    private void verifyVcnTransformsApplied(
            VcnGatewayConnection vcnGatewayConnection,
            boolean expectForwardTransform,
            List<VcnChildSessionCallback> callbacks)
            throws Exception {
        for (int direction : new int[] {DIRECTION_IN, DIRECTION_OUT}) {
            getChildSessionCallback().onIpSecTransformCreated(makeDummyIpSecTransform(), direction);
            for (VcnChildSessionCallback cb : callbacks) {
                cb.onIpSecTransformCreated(makeDummyIpSecTransform(), direction);
            }
            mTestLooper.dispatchAll();

            verify(mIpSecSvc)
            verify(mIpSecSvc, times(callbacks.size()))
                    .applyTunnelModeTransform(
                            eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(direction), anyInt(), any());
        }

        verify(mIpSecSvc, expectForwardTransform ? times(1) : never())
        verify(mIpSecSvc, expectForwardTransform ? times(callbacks.size()) : never())
                .applyTunnelModeTransform(
                        eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(DIRECTION_FWD), anyInt(), any());

@@ -416,6 +433,89 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection
        verifySafeModeStateAndCallbackFired(1 /* invocationCount */, false /* isInSafeMode */);
    }

    private List<VcnChildSessionCallback> openChildAndVerifyParallelSasRequested()
            throws Exception {
        doReturn(PARALLEL_SA_COUNT)
                .when(mDeps)
                .getParallelTunnelCount(eq(TEST_SUBSCRIPTION_SNAPSHOT), eq(TEST_SUB_GRP));

        // Verify scheduled but not canceled when entering ConnectedState
        verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
        triggerChildOpened();
        mTestLooper.dispatchAll();

        // Verify new child sessions requested
        final ArgumentCaptor<VcnChildSessionCallback> captor =
                ArgumentCaptor.forClass(VcnChildSessionCallback.class);
        verify(mIkeSession, times(PARALLEL_SA_COUNT - 1))
                .openChildSession(any(TunnelModeChildSessionParams.class), captor.capture());

        return captor.getAllValues();
    }

    private List<VcnChildSessionCallback> verifyChildOpenedRequestsAndAppliesParallelSas()
            throws Exception {
        List<VcnChildSessionCallback> callbacks = openChildAndVerifyParallelSasRequested();

        verifyVcnTransformsApplied(mGatewayConnection, false, callbacks);

        // Mock IKE calling of onOpened()
        for (VcnChildSessionCallback cb : callbacks) {
            cb.onOpened(mock(VcnChildSessionConfiguration.class));
        }
        mTestLooper.dispatchAll();

        assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
        return callbacks;
    }

    @Test
    public void testChildOpenedWithParallelSas() throws Exception {
        verifyChildOpenedRequestsAndAppliesParallelSas();
    }

    @Test
    public void testOpportunisticSa_ignoresPreOpenFailures() throws Exception {
        List<VcnChildSessionCallback> callbacks = openChildAndVerifyParallelSasRequested();

        for (VcnChildSessionCallback cb : callbacks) {
            cb.onClosed();
            cb.onClosedExceptionally(mock(IkeException.class));
        }
        mTestLooper.dispatchAll();

        assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
        assertEquals(mIkeConnectionInfo, mGatewayConnection.getIkeConnectionInfo());
    }

    private void verifyPostOpenFailuresCloseSession(boolean shouldCloseWithException)
            throws Exception {
        List<VcnChildSessionCallback> callbacks = verifyChildOpenedRequestsAndAppliesParallelSas();

        for (VcnChildSessionCallback cb : callbacks) {
            if (shouldCloseWithException) {
                cb.onClosed();
            } else {
                cb.onClosedExceptionally(mock(IkeException.class));
            }
        }
        mTestLooper.dispatchAll();

        assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
        verify(mIkeSession).close();
    }

    @Test
    public void testOpportunisticSa_handlesPostOpenFailures_onClosed() throws Exception {
        verifyPostOpenFailuresCloseSession(false /* shouldCloseWithException */);
    }

    @Test
    public void testOpportunisticSa_handlesPostOpenFailures_onClosedExceptionally()
            throws Exception {
        verifyPostOpenFailuresCloseSession(true /* shouldCloseWithException */);
    }

    @Test
    public void testInternalAndDnsAddressesChanged() throws Exception {
        final List<LinkAddress> startingInternalAddrs =
+3 −0
Original line number Diff line number Diff line
@@ -223,6 +223,9 @@ public class VcnGatewayConnectionTestBase {
        doReturn(mWakeLock)
                .when(mDeps)
                .newWakeLock(eq(mContext), eq(PowerManager.PARTIAL_WAKE_LOCK), any());
        doReturn(1)
                .when(mDeps)
                .getParallelTunnelCount(eq(TEST_SUBSCRIPTION_SNAPSHOT), eq(TEST_SUB_GRP));

        setUpWakeupMessage(mTeardownTimeoutAlarm, VcnGatewayConnection.TEARDOWN_TIMEOUT_ALARM);
        setUpWakeupMessage(mDisconnectRequestAlarm, VcnGatewayConnection.DISCONNECT_REQUEST_ALARM);