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

Commit 8fac2ea3 authored by Benedict Wong's avatar Benedict Wong
Browse files

Dynamically set MTU based on proposed algorithms

This change adds the relevant utilities and plumbing to ensure that MTUs
are set dynamically based on the underlying network's MTU.

Bug: 184697651
Test: atest FrameworksVcnTests
Change-Id: I77e34a92eb4e81e83d20fe6019b38ea5f1af4765
parent 7087a4ac
Loading
Loading
Loading
Loading
+89 −7
Original line number Diff line number Diff line
@@ -85,6 +85,7 @@ import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscription
import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord;
import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback;
import com.android.server.vcn.Vcn.VcnGatewayStatusCallback;
import com.android.server.vcn.util.MtuUtils;

import java.io.IOException;
import java.net.Inet4Address;
@@ -449,6 +450,44 @@ public class VcnGatewayConnection extends StateMachine {
     */
    private static final int EVENT_SAFE_MODE_TIMEOUT_EXCEEDED = 10;

    /**
     * Sent when an IKE has completed migration, and created updated transforms for application.
     *
     * <p>Only relevant in the Connected state.
     *
     * @param arg1 The session token for the IKE Session that completed migration, used to prevent
     *     out-of-date signals from propagating.
     * @param obj @NonNull An EventMigrationCompletedInfo instance with relevant data.
     */
    private static final int EVENT_MIGRATION_COMPLETED = 11;

    private static class EventMigrationCompletedInfo implements EventInfo {
        @NonNull public final IpSecTransform inTransform;
        @NonNull public final IpSecTransform outTransform;

        EventMigrationCompletedInfo(
                @NonNull IpSecTransform inTransform, @NonNull IpSecTransform outTransform) {
            this.inTransform = Objects.requireNonNull(inTransform);
            this.outTransform = Objects.requireNonNull(outTransform);
        }

        @Override
        public int hashCode() {
            return Objects.hash(inTransform, outTransform);
        }

        @Override
        public boolean equals(@Nullable Object other) {
            if (!(other instanceof EventMigrationCompletedInfo)) {
                return false;
            }

            final EventMigrationCompletedInfo rhs = (EventMigrationCompletedInfo) other;
            return Objects.equals(inTransform, rhs.inTransform)
                    && Objects.equals(outTransform, rhs.outTransform);
        }
    }

    @VisibleForTesting(visibility = Visibility.PRIVATE)
    @NonNull
    final DisconnectedState mDisconnectedState = new DisconnectedState();
@@ -1054,6 +1093,14 @@ public class VcnGatewayConnection extends StateMachine {
        sendMessageAndAcquireWakeLock(EVENT_SESSION_CLOSED, token);
    }

    private void migrationCompleted(
            int token, @NonNull IpSecTransform inTransform, @NonNull IpSecTransform outTransform) {
        sendMessageAndAcquireWakeLock(
                EVENT_MIGRATION_COMPLETED,
                token,
                new EventMigrationCompletedInfo(inTransform, outTransform));
    }

    private void childTransformCreated(
            int token, @NonNull IpSecTransform transform, int direction) {
        sendMessageAndAcquireWakeLock(
@@ -1149,7 +1196,9 @@ public class VcnGatewayConnection extends StateMachine {
                case EVENT_SETUP_COMPLETED: // Fallthrough
                case EVENT_DISCONNECT_REQUESTED: // Fallthrough
                case EVENT_TEARDOWN_TIMEOUT_EXPIRED: // Fallthrough
                case EVENT_SUBSCRIPTIONS_CHANGED:
                case EVENT_SUBSCRIPTIONS_CHANGED: // Fallthrough
                case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED: // Fallthrough
                case EVENT_MIGRATION_COMPLETED:
                    logUnexpectedEvent(msg.what);
                    break;
                default:
@@ -1446,7 +1495,8 @@ public class VcnGatewayConnection extends StateMachine {
            final NetworkCapabilities caps =
                    buildNetworkCapabilities(mConnectionConfig, mUnderlying);
            final LinkProperties lp =
                    buildConnectedLinkProperties(mConnectionConfig, tunnelIface, childConfig);
                    buildConnectedLinkProperties(
                            mConnectionConfig, tunnelIface, childConfig, mUnderlying);

            agent.sendNetworkCapabilities(caps);
            agent.sendLinkProperties(lp);
@@ -1458,7 +1508,8 @@ public class VcnGatewayConnection extends StateMachine {
            final NetworkCapabilities caps =
                    buildNetworkCapabilities(mConnectionConfig, mUnderlying);
            final LinkProperties lp =
                    buildConnectedLinkProperties(mConnectionConfig, tunnelIface, childConfig);
                    buildConnectedLinkProperties(
                            mConnectionConfig, tunnelIface, childConfig, mUnderlying);
            final NetworkAgentConfig nac =
                    new NetworkAgentConfig.Builder()
                            .setLegacyType(ConnectivityManager.TYPE_MOBILE)
@@ -1627,12 +1678,36 @@ public class VcnGatewayConnection extends StateMachine {
                case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
                    handleSafeModeTimeoutExceeded();
                    break;
                case EVENT_MIGRATION_COMPLETED:
                    final EventMigrationCompletedInfo migrationCompletedInfo =
                            (EventMigrationCompletedInfo) msg.obj;

                    handleMigrationCompleted(migrationCompletedInfo);
                    break;
                default:
                    logUnhandledMessage(msg);
                    break;
            }
        }

        private void handleMigrationCompleted(EventMigrationCompletedInfo migrationCompletedInfo) {
            applyTransform(
                    mCurrentToken,
                    mTunnelIface,
                    mUnderlying.network,
                    migrationCompletedInfo.inTransform,
                    IpSecManager.DIRECTION_IN);

            applyTransform(
                    mCurrentToken,
                    mTunnelIface,
                    mUnderlying.network,
                    migrationCompletedInfo.outTransform,
                    IpSecManager.DIRECTION_OUT);

            updateNetworkAgent(mTunnelIface, mNetworkAgent, mChildConfig);
        }

        private void handleUnderlyingNetworkChanged(@NonNull Message msg) {
            final UnderlyingNetworkRecord oldUnderlying = mUnderlying;
            mUnderlying = ((EventUnderlyingNetworkChangedInfo) msg.obj).newUnderlying;
@@ -1822,7 +1897,10 @@ public class VcnGatewayConnection extends StateMachine {
    private static LinkProperties buildConnectedLinkProperties(
            @NonNull VcnGatewayConnectionConfig gatewayConnectionConfig,
            @NonNull IpSecTunnelInterface tunnelIface,
            @NonNull VcnChildSessionConfiguration childConfig) {
            @NonNull VcnChildSessionConfiguration childConfig,
            @Nullable UnderlyingNetworkRecord underlying) {
        final VcnControlPlaneIkeConfig controlPlaneConfig =
                (VcnControlPlaneIkeConfig) gatewayConnectionConfig.getControlPlaneConfig();
        final LinkProperties lp = new LinkProperties();

        lp.setInterfaceName(tunnelIface.getInterfaceName());
@@ -1838,7 +1916,12 @@ public class VcnGatewayConnection extends StateMachine {
        lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null /*gateway*/,
                null /*iface*/, RouteInfo.RTN_UNICAST));

        lp.setMtu(gatewayConnectionConfig.getMaxMtu());
        final int underlyingMtu = (underlying == null) ? 0 : underlying.linkProperties.getMtu();
        lp.setMtu(
                MtuUtils.getMtu(
                        controlPlaneConfig.getChildSessionParams().getSaProposals(),
                        gatewayConnectionConfig.getMaxMtu(),
                        underlyingMtu));

        return lp;
    }
@@ -1919,8 +2002,7 @@ public class VcnGatewayConnection extends StateMachine {
                @NonNull IpSecTransform inIpSecTransform,
                @NonNull IpSecTransform outIpSecTransform) {
            Slog.v(TAG, "ChildTransformsMigrated; token " + mToken);
            onIpSecTransformCreated(inIpSecTransform, IpSecManager.DIRECTION_IN);
            onIpSecTransformCreated(outIpSecTransform, IpSecManager.DIRECTION_OUT);
            migrationCompleted(mToken, inIpSecTransform, outIpSecTransform);
        }

        @Override
+155 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.vcn.util;

import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_3DES;
import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_CBC;
import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_CTR;
import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12;
import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16;
import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8;
import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_CHACHA20_POLY1305;
import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_AES_CMAC_96;
import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_AES_XCBC_96;
import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96;
import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_256_128;
import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_384_192;
import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_512_256;
import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_NONE;

import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU;

import static java.lang.Math.max;
import static java.util.Collections.unmodifiableMap;

import android.annotation.NonNull;
import android.net.ipsec.ike.ChildSaProposal;
import android.util.ArrayMap;
import android.util.Pair;
import android.util.Slog;

import java.util.List;
import java.util.Map;

/** @hide */
public class MtuUtils {
    private static final String TAG = MtuUtils.class.getSimpleName();
    /**
     * Max ESP overhead possible
     *
     * <p>60 (Outer IPv4 + options) + 8 (UDP encap) + 4 (SPI) + 4 (Seq) + 2 (Pad + NextHeader)
     */
    private static final int GENERIC_ESP_OVERHEAD_MAX = 78;

    /** Maximum overheads of authentication algorithms, keyed on IANA-defined constants */
    private static final Map<Integer, Integer> AUTH_ALGORITHM_OVERHEAD;

    static {
        final Map<Integer, Integer> map = new ArrayMap<>();
        map.put(INTEGRITY_ALGORITHM_NONE, 0);
        map.put(INTEGRITY_ALGORITHM_HMAC_SHA1_96, 12);
        map.put(INTEGRITY_ALGORITHM_AES_XCBC_96, 12);
        map.put(INTEGRITY_ALGORITHM_HMAC_SHA2_256_128, 32);
        map.put(INTEGRITY_ALGORITHM_HMAC_SHA2_384_192, 48);
        map.put(INTEGRITY_ALGORITHM_HMAC_SHA2_512_256, 64);
        map.put(INTEGRITY_ALGORITHM_AES_CMAC_96, 12);

        AUTH_ALGORITHM_OVERHEAD = unmodifiableMap(map);
    }

    /** Maximum overheads of encryption algorithms, keyed on IANA-defined constants */
    private static final Map<Integer, Integer> CRYPT_ALGORITHM_OVERHEAD;

    static {
        final Map<Integer, Integer> map = new ArrayMap<>();
        map.put(ENCRYPTION_ALGORITHM_3DES, 15); // 8 (IV) + 7 (Max pad)
        map.put(ENCRYPTION_ALGORITHM_AES_CBC, 31); // 16 (IV) + 15 (Max pad)
        map.put(ENCRYPTION_ALGORITHM_AES_CTR, 11); // 8 (IV) + 3 (Max pad)

        CRYPT_ALGORITHM_OVERHEAD = unmodifiableMap(map);
    }

    /** Maximum overheads of combined mode algorithms, keyed on IANA-defined constants */
    private static final Map<Integer, Integer> AUTHCRYPT_ALGORITHM_OVERHEAD;

    static {
        final Map<Integer, Integer> map = new ArrayMap<>();
        map.put(ENCRYPTION_ALGORITHM_AES_GCM_8, 19); // 8 (IV) + 3 (Max pad) + 8 (ICV)
        map.put(ENCRYPTION_ALGORITHM_AES_GCM_12, 23); // 8 (IV) + 3 (Max pad) + 12 (ICV)
        map.put(ENCRYPTION_ALGORITHM_AES_GCM_16, 27); // 8 (IV) + 3 (Max pad) + 16 (ICV)
        map.put(ENCRYPTION_ALGORITHM_CHACHA20_POLY1305, 27); // 8 (IV) + 3 (Max pad) + 16 (ICV)

        AUTHCRYPT_ALGORITHM_OVERHEAD = unmodifiableMap(map);
    }

    /**
     * Calculates the MTU of the inner interface based on the parameters provided
     *
     * <p>The MTU of the inner interface will be the minimum of the following:
     *
     * <ul>
     *   <li>The MTU of the outer interface, minus the greatest ESP overhead (based on proposed
     *       algorithms).
     *   <li>The maximum MTU as provided in the arguments.
     * </ul>
     */
    public static int getMtu(
            @NonNull List<ChildSaProposal> childProposals, int maxMtu, int underlyingMtu) {
        if (underlyingMtu <= 0) {
            return IPV6_MIN_MTU;
        }

        boolean hasUnknownAlgorithm = false;
        int maxAuthOverhead = 0;
        int maxCryptOverhead = 0;
        int maxAuthCryptOverhead = 0;

        for (ChildSaProposal proposal : childProposals) {
            for (Pair<Integer, Integer> encryptionAlgoPair : proposal.getEncryptionAlgorithms()) {
                final int algo = encryptionAlgoPair.first;

                if (AUTHCRYPT_ALGORITHM_OVERHEAD.containsKey(algo)) {
                    maxAuthCryptOverhead =
                            max(maxAuthCryptOverhead, AUTHCRYPT_ALGORITHM_OVERHEAD.get(algo));
                    continue;
                } else if (CRYPT_ALGORITHM_OVERHEAD.containsKey(algo)) {
                    maxCryptOverhead = max(maxCryptOverhead, CRYPT_ALGORITHM_OVERHEAD.get(algo));
                    continue;
                }

                Slog.wtf(TAG, "Unknown encryption algorithm requested: " + algo);
                return IPV6_MIN_MTU;
            }

            for (int algo : proposal.getIntegrityAlgorithms()) {
                if (AUTH_ALGORITHM_OVERHEAD.containsKey(algo)) {
                    maxAuthOverhead = max(maxAuthOverhead, AUTH_ALGORITHM_OVERHEAD.get(algo));
                    continue;
                }

                Slog.wtf(TAG, "Unknown integrity algorithm requested: " + algo);
                return IPV6_MIN_MTU;
            }
        }

        // Return minimum of maxMtu, and the adjusted MTUs based on algorithms.
        final int combinedModeMtu = underlyingMtu - maxAuthCryptOverhead - GENERIC_ESP_OVERHEAD_MAX;
        final int normalModeMtu =
                underlyingMtu - maxCryptOverhead - maxAuthOverhead - GENERIC_ESP_OVERHEAD_MAX;
        return Math.min(Math.min(maxMtu, combinedModeMtu), normalModeMtu);
    }
}
+18 −1
Original line number Diff line number Diff line
@@ -47,15 +47,19 @@ import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkAgent;
import android.net.NetworkCapabilities;
import android.net.ipsec.ike.ChildSaProposal;
import android.net.ipsec.ike.exceptions.AuthenticationFailedException;
import android.net.ipsec.ike.exceptions.IkeException;
import android.net.ipsec.ike.exceptions.IkeInternalException;
import android.net.ipsec.ike.exceptions.TemporaryFailureException;
import android.net.vcn.VcnControlPlaneIkeConfig;
import android.net.vcn.VcnManager.VcnErrorCode;

import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.server.vcn.util.MtuUtils;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -153,7 +157,9 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection
    }

    @Test
    public void testMigratedTransformsAreApplied() throws Exception {
    public void testMigration() throws Exception {
        triggerChildOpened();

        getChildSessionCallback()
                .onIpSecTransformsMigrated(makeDummyIpSecTransform(), makeDummyIpSecTransform());
        mTestLooper.dispatchAll();
@@ -171,6 +177,17 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection
        }

        assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());

        final List<ChildSaProposal> saProposals =
                ((VcnControlPlaneIkeConfig) mConfig.getControlPlaneConfig())
                        .getChildSessionParams()
                        .getSaProposals();
        final int expectedMtu =
                MtuUtils.getMtu(
                        saProposals,
                        mConfig.getMaxMtu(),
                        TEST_UNDERLYING_NETWORK_RECORD_1.linkProperties.getMtu());
        verify(mNetworkAgent).sendLinkProperties(argThat(lp -> expectedMtu == lp.getMtu()));
    }

    private void triggerChildOpened() {
+10 −0
Original line number Diff line number Diff line
@@ -90,12 +90,18 @@ public class VcnGatewayConnectionTestBase {
    protected static final int TEST_SUB_ID = 5;
    protected static final long ELAPSED_REAL_TIME = 123456789L;
    protected static final String TEST_IPSEC_TUNNEL_IFACE = "IPSEC_IFACE";

    protected static final UnderlyingNetworkRecord TEST_UNDERLYING_NETWORK_RECORD_1 =
            new UnderlyingNetworkRecord(
                    new Network(0),
                    new NetworkCapabilities(),
                    new LinkProperties(),
                    false /* blocked */);

    static {
        TEST_UNDERLYING_NETWORK_RECORD_1.linkProperties.setMtu(1500);
    }

    protected static final UnderlyingNetworkRecord TEST_UNDERLYING_NETWORK_RECORD_2 =
            new UnderlyingNetworkRecord(
                    new Network(1),
@@ -103,6 +109,10 @@ public class VcnGatewayConnectionTestBase {
                    new LinkProperties(),
                    false /* blocked */);

    static {
        TEST_UNDERLYING_NETWORK_RECORD_2.linkProperties.setMtu(1460);
    }

    protected static final TelephonySubscriptionSnapshot TEST_SUBSCRIPTION_SNAPSHOT =
            new TelephonySubscriptionSnapshot(
                    Collections.singletonMap(TEST_SUB_ID, TEST_SUB_GRP), Collections.EMPTY_MAP);
+92 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.vcn.util;

import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_CBC;
import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12;
import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16;
import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8;
import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_256_128;
import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_256;

import static com.android.net.module.util.NetworkStackConstants.ETHER_MTU;
import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU;
import static com.android.server.vcn.util.MtuUtils.getMtu;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import static java.util.Collections.emptyList;

import android.net.ipsec.ike.ChildSaProposal;

import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.Arrays;
import java.util.List;

@RunWith(AndroidJUnit4.class)
@SmallTest
public class MtuUtilsTest {
    @Test
    public void testUnderlyingMtuZero() {
        assertEquals(
                IPV6_MIN_MTU, getMtu(emptyList(), ETHER_MTU /* maxMtu */, 0 /* underlyingMtu */));
    }

    @Test
    public void testClampsToMaxMtu() {
        assertEquals(0, getMtu(emptyList(), 0 /* maxMtu */, IPV6_MIN_MTU /* underlyingMtu */));
    }

    @Test
    public void testNormalModeAlgorithmLessThanUnderlyingMtu() {
        final List<ChildSaProposal> saProposals =
                Arrays.asList(
                        new ChildSaProposal.Builder()
                                .addEncryptionAlgorithm(
                                        ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_256)
                                .addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_256_128)
                                .build());

        final int actualMtu =
                getMtu(saProposals, ETHER_MTU /* maxMtu */, ETHER_MTU /* underlyingMtu */);
        assertTrue(ETHER_MTU > actualMtu);
    }

    @Test
    public void testCombinedModeAlgorithmLessThanUnderlyingMtu() {
        final List<ChildSaProposal> saProposals =
                Arrays.asList(
                        new ChildSaProposal.Builder()
                                .addEncryptionAlgorithm(
                                        ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_256)
                                .addEncryptionAlgorithm(
                                        ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_256)
                                .addEncryptionAlgorithm(
                                        ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_256)
                                .build());

        final int actualMtu =
                getMtu(saProposals, ETHER_MTU /* maxMtu */, ETHER_MTU /* underlyingMtu */);
        assertTrue(ETHER_MTU > actualMtu);
    }
}