Merge changes I3dd45b29,Ic177015f am: 67c7cd81a7

Original change: https://android-review.googlesource.com/c/platform/packages/modules/Connectivity/+/2264261

Change-Id: Ie32a8ef9886cd832f4a22ccd6c30c93dfbf5967c
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Yan Yan
2022-12-22 20:05:54 +00:00
committed by Automerger Merge Worker
5 changed files with 403 additions and 11 deletions

View File

@@ -66,6 +66,12 @@ interface IIpSecService
IpSecTransformResponse createTransform(
in IpSecConfig c, in IBinder binder, in String callingPackage);
void migrateTransform(
int transformId,
in String newSourceAddress,
in String newDestinationAddress,
in String callingPackage);
void deleteTransform(int transformId);
void applyTransportModeTransform(

View File

@@ -37,6 +37,7 @@ import android.util.AndroidException;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.build.SdkLevel;
import dalvik.system.CloseGuard;
@@ -64,6 +65,24 @@ import java.util.Objects;
public class IpSecManager {
private static final String TAG = "IpSecManager";
/**
* Feature flag to declare the kernel support of updating IPsec SAs.
*
* <p>Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device
* has the requisite kernel support for migrating IPsec tunnels to new source/destination
* addresses.
*
* <p>This feature implies that the device supports XFRM Migration (CONFIG_XFRM_MIGRATE) and has
* the kernel fixes to allow XFRM Migration correctly
*
* @see android.content.pm.PackageManager#FEATURE_IPSEC_TUNNEL_MIGRATION
* @hide
*/
// Redefine this flag here so that IPsec code shipped in a mainline module can build on old
// platforms before FEATURE_IPSEC_TUNNEL_MIGRATION API is released.
public static final String FEATURE_IPSEC_TUNNEL_MIGRATION =
"android.software.ipsec_tunnel_migration";
/**
* Used when applying a transform to direct traffic through an {@link IpSecTransform}
* towards the host.
@@ -987,6 +1006,59 @@ public class IpSecManager {
}
}
/**
* Migrate an active Tunnel Mode IPsec Transform to new source/destination addresses.
*
* <p>Begins the process of migrating a transform and cache the new addresses. To complete the
* migration once started, callers MUST apply the same transform to the appropriate tunnel using
* {@link IpSecManager#applyTunnelModeTransform}. Otherwise, the address update will not be
* committed and the transform will still only process traffic between the current source and
* destination address. One common use case is that the control plane will start the migration
* process and then hand off the transform to the IPsec caller to perform the actual migration
* when the tunnel is ready.
*
* <p>If this method is called multiple times before {@link
* IpSecManager#applyTunnelModeTransform} is called, when the transform is applied, it will be
* migrated to the addresses from the last call.
*
* <p>The provided source and destination addresses MUST share the same address family, but they
* can have a different family from the current addresses.
*
* <p>Transform migration is only supported for tunnel mode transforms. Calling this method on
* other types of transforms will throw an {@code UnsupportedOperationException}.
*
* @see IpSecTunnelInterface#setUnderlyingNetwork
* @param transform a tunnel mode {@link IpSecTransform}
* @param newSourceAddress the new source address
* @param newDestinationAddress the new destination address
* @hide
*/
@RequiresFeature(FEATURE_IPSEC_TUNNEL_MIGRATION)
@RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS)
public void startMigration(
@NonNull IpSecTransform transform,
@NonNull InetAddress newSourceAddress,
@NonNull InetAddress newDestinationAddress) {
if (!SdkLevel.isAtLeastU()) {
throw new UnsupportedOperationException(
"Transform migration only supported for Android 14+");
}
Objects.requireNonNull(transform, "transform was null");
Objects.requireNonNull(newSourceAddress, "newSourceAddress was null");
Objects.requireNonNull(newDestinationAddress, "newDestinationAddress was null");
try {
mService.migrateTransform(
transform.getResourceId(),
newSourceAddress.getHostAddress(),
newDestinationAddress.getHostAddress(),
mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* @hide
*/

View File

@@ -17,6 +17,7 @@
package com.android.server;
import static android.Manifest.permission.DUMP;
import static android.net.IpSecManager.FEATURE_IPSEC_TUNNEL_MIGRATION;
import static android.net.IpSecManager.INVALID_RESOURCE_ID;
import static android.system.OsConstants.AF_INET;
import static android.system.OsConstants.AF_INET6;
@@ -36,6 +37,7 @@ import android.net.InetAddresses;
import android.net.IpSecAlgorithm;
import android.net.IpSecConfig;
import android.net.IpSecManager;
import android.net.IpSecMigrateInfoParcel;
import android.net.IpSecSpiResponse;
import android.net.IpSecTransform;
import android.net.IpSecTransformResponse;
@@ -590,14 +592,19 @@ public class IpSecService extends IIpSecService.Stub {
}
/**
* Tracks an SA in the kernel, and manages cleanup paths. Once a TransformRecord is
* created, the SpiRecord that originally tracked the SAs will reliquish the
* responsibility of freeing the underlying SA to this class via the mOwnedByTransform flag.
* Tracks an SA in the kernel, and manages cleanup paths. Once a TransformRecord is created, the
* SpiRecord that originally tracked the SAs will reliquish the responsibility of freeing the
* underlying SA to this class via the mOwnedByTransform flag.
*
* <p>This class is not thread-safe, and expects that that users of this class will ensure
* synchronization and thread safety by holding the IpSecService.this instance lock
*/
private final class TransformRecord extends OwnedResourceRecord {
private final IpSecConfig mConfig;
private final SpiRecord mSpi;
private final EncapSocketRecord mSocket;
private String mNewSourceAddress = null;
private String mNewDestinationAddress = null;
TransformRecord(
int resourceId, IpSecConfig config, SpiRecord spi, EncapSocketRecord socket) {
@@ -621,6 +628,51 @@ public class IpSecService extends IIpSecService.Stub {
return mSocket;
}
@GuardedBy("IpSecService.this")
public String getNewSourceAddress() {
return mNewSourceAddress;
}
@GuardedBy("IpSecService.this")
public String getNewDestinationAddress() {
return mNewDestinationAddress;
}
private void verifyTunnelModeOrThrow() {
if (mConfig.getMode() != IpSecTransform.MODE_TUNNEL) {
throw new UnsupportedOperationException(
"Migration requested/called on non-tunnel-mode transform");
}
}
/** Start migrating this transform to new source and destination addresses */
@GuardedBy("IpSecService.this")
public void startMigration(String newSourceAddress, String newDestinationAddress) {
verifyTunnelModeOrThrow();
Objects.requireNonNull(newSourceAddress, "newSourceAddress was null");
Objects.requireNonNull(newDestinationAddress, "newDestinationAddress was null");
mNewSourceAddress = newSourceAddress;
mNewDestinationAddress = newDestinationAddress;
}
/** Finish migration and update addresses. */
@GuardedBy("IpSecService.this")
public void finishMigration() {
verifyTunnelModeOrThrow();
mConfig.setSourceAddress(mNewSourceAddress);
mConfig.setDestinationAddress(mNewDestinationAddress);
mNewSourceAddress = null;
mNewDestinationAddress = null;
}
/** Return if this transform is going to be migrated. */
@GuardedBy("IpSecService.this")
public boolean isMigrating() {
verifyTunnelModeOrThrow();
return mNewSourceAddress != null;
}
/** always guarded by IpSecService#this */
@Override
public void freeUnderlyingResources() {
@@ -1630,6 +1682,14 @@ public class IpSecService extends IIpSecService.Stub {
android.Manifest.permission.MANAGE_IPSEC_TUNNELS, "IpSecService");
}
private void enforceMigrateFeature() {
if (!mContext.getPackageManager().hasSystemFeature(FEATURE_IPSEC_TUNNEL_MIGRATION)) {
throw new UnsupportedOperationException(
"IPsec Tunnel migration requires"
+ " PackageManager.FEATURE_IPSEC_TUNNEL_MIGRATION");
}
}
private void createOrUpdateTransform(
IpSecConfig c, int resourceId, SpiRecord spiRecord, EncapSocketRecord socketRecord)
throws RemoteException {
@@ -1725,6 +1785,45 @@ public class IpSecService extends IIpSecService.Stub {
return new IpSecTransformResponse(IpSecManager.Status.OK, resourceId);
}
/**
* Migrate an active Tunnel Mode IPsec Transform to new source/destination addresses.
*
* <p>Begins the process of migrating a transform and cache the new addresses. To complete the
* migration once started, callers MUST apply the same transform to the appropriate tunnel using
* {@link #applyTunnelModeTransform}. Otherwise, the address update will not be committed and
* the transform will still only process traffic between the current source and destination
* address. One common use case is that the control plane will start the migration process and
* then hand off the transform to the IPsec caller to perform the actual migration when the
* tunnel is ready.
*
* <p>If this method is called multiple times before {@link #applyTunnelModeTransform} is
* called, when the transform is applied, it will be migrated to the addresses from the last
* call.
*
* <p>The provided source and destination addresses MUST share the same address family, but they
* can have a different family from the current addresses.
*
* <p>Transform migration is only supported for tunnel mode transforms. Calling this method on
* other types of transforms will throw an {@code UnsupportedOperationException}.
*/
@Override
public synchronized void migrateTransform(
int transformId,
String newSourceAddress,
String newDestinationAddress,
String callingPackage) {
Objects.requireNonNull(newSourceAddress, "newSourceAddress was null");
Objects.requireNonNull(newDestinationAddress, "newDestinationAddress was null");
enforceTunnelFeatureAndPermissions(callingPackage);
enforceMigrateFeature();
UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
TransformRecord transformInfo =
userRecord.mTransformRecords.getResourceOrThrow(transformId);
transformInfo.startMigration(newSourceAddress, newDestinationAddress);
}
/**
* Delete a transport mode transform that was previously allocated by + registered with the
* system server. If this is called on an inactive (or non-existent) transform, it will not
@@ -1784,12 +1883,15 @@ public class IpSecService extends IIpSecService.Stub {
/**
* Apply an active tunnel mode transform to a TunnelInterface, which will apply the IPsec
* security association as a correspondent policy to the provided interface
* security association as a correspondent policy to the provided interface.
*
* <p>If the transform is migrating, migrate the IPsec security association to new
* source/destination addresses, and mark the migration as finished.
*/
@Override
public synchronized void applyTunnelModeTransform(
int tunnelResourceId, int direction,
int transformResourceId, String callingPackage) throws RemoteException {
int tunnelResourceId, int direction, int transformResourceId, String callingPackage)
throws RemoteException {
enforceTunnelFeatureAndPermissions(callingPackage);
checkDirection(direction);
@@ -1868,6 +1970,32 @@ public class IpSecService extends IIpSecService.Stub {
// Update SA with tunnel mark (ikey or okey based on direction)
createOrUpdateTransform(c, transformResourceId, spiRecord, socketRecord);
if (transformInfo.isMigrating()) {
if (!mContext.getPackageManager()
.hasSystemFeature(FEATURE_IPSEC_TUNNEL_MIGRATION)) {
Log.wtf(
TAG,
"Attempted to migrate a transform without"
+ " FEATURE_IPSEC_TUNNEL_MIGRATION");
}
for (int selAddrFamily : ADDRESS_FAMILIES) {
final IpSecMigrateInfoParcel migrateInfo =
new IpSecMigrateInfoParcel(
Binder.getCallingUid(),
selAddrFamily,
direction,
c.getSourceAddress(),
c.getDestinationAddress(),
transformInfo.getNewSourceAddress(),
transformInfo.getNewDestinationAddress(),
c.getXfrmInterfaceId());
mNetd.ipSecMigrate(migrateInfo);
}
transformInfo.finishMigration();
}
} catch (ServiceSpecificException e) {
if (e.errorCode == EINVAL) {
throw new IllegalArgumentException(e.toString());

View File

@@ -18,22 +18,92 @@ package android.net;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.os.Build;
import android.test.mock.MockContext;
import androidx.test.filters.SmallTest;
import com.android.server.IpSecService;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.net.InetAddress;
/** Unit tests for {@link IpSecTransform}. */
@SmallTest
@RunWith(DevSdkIgnoreRunner.class)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class IpSecTransformTest {
@Rule public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
private static final int DROID_SPI = 0xD1201D;
private static final int TEST_RESOURCE_ID = 0x1234;
private static final InetAddress SRC_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.200");
private static final InetAddress DST_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.201");
private static final InetAddress SRC_ADDRESS_V6 =
InetAddresses.parseNumericAddress("2001:db8::200");
private static final InetAddress DST_ADDRESS_V6 =
InetAddresses.parseNumericAddress("2001:db8::201");
private MockContext mMockContext;
private IpSecService mMockIpSecService;
private IpSecManager mIpSecManager;
@Before
public void setUp() throws Exception {
mMockIpSecService = mock(IpSecService.class);
mIpSecManager = new IpSecManager(mock(Context.class) /* unused */, mMockIpSecService);
// Set up mMockContext since IpSecTransform needs an IpSecManager instance and a non-null
// package name to create transform
mMockContext =
new MockContext() {
@Override
public String getSystemServiceName(Class<?> serviceClass) {
if (serviceClass.equals(IpSecManager.class)) {
return Context.IPSEC_SERVICE;
}
throw new UnsupportedOperationException();
}
@Override
public Object getSystemService(String name) {
if (name.equals(Context.IPSEC_SERVICE)) {
return mIpSecManager;
}
throw new UnsupportedOperationException();
}
@Override
public String getOpPackageName() {
return "fooPackage";
}
};
final IpSecSpiResponse spiResp =
new IpSecSpiResponse(IpSecManager.Status.OK, TEST_RESOURCE_ID, DROID_SPI);
when(mMockIpSecService.allocateSecurityParameterIndex(any(), anyInt(), any()))
.thenReturn(spiResp);
final IpSecTransformResponse transformResp =
new IpSecTransformResponse(IpSecManager.Status.OK, TEST_RESOURCE_ID);
when(mMockIpSecService.createTransform(any(), any(), any())).thenReturn(transformResp);
}
@Test
public void testCreateTransformCopiesConfig() {
@@ -64,4 +134,32 @@ public class IpSecTransformTest {
assertEquals(config1, config2);
}
private IpSecTransform buildTestTransform() throws Exception {
final IpSecManager.SecurityParameterIndex spi =
mIpSecManager.allocateSecurityParameterIndex(DST_ADDRESS);
return new IpSecTransform.Builder(mMockContext).buildTunnelModeTransform(SRC_ADDRESS, spi);
}
@Test
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
public void testStartMigration() throws Exception {
mIpSecManager.startMigration(buildTestTransform(), SRC_ADDRESS_V6, DST_ADDRESS_V6);
verify(mMockIpSecService)
.migrateTransform(
anyInt(),
eq(SRC_ADDRESS_V6.getHostAddress()),
eq(DST_ADDRESS_V6.getHostAddress()),
any());
}
@Test
@DevSdkIgnoreRule.IgnoreAfter(Build.VERSION_CODES.TIRAMISU)
public void testStartMigrationOnSdkBeforeU() throws Exception {
try {
mIpSecManager.startMigration(buildTestTransform(), SRC_ADDRESS_V6, DST_ADDRESS_V6);
fail("Expect to fail since migration is not supported before U");
} catch (UnsupportedOperationException expected) {
}
}
}

View File

@@ -23,6 +23,7 @@ import static android.net.INetd.IF_STATE_UP;
import static android.net.IpSecManager.DIRECTION_FWD;
import static android.net.IpSecManager.DIRECTION_IN;
import static android.net.IpSecManager.DIRECTION_OUT;
import static android.net.IpSecManager.FEATURE_IPSEC_TUNNEL_MIGRATION;
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import static android.system.OsConstants.AF_INET;
import static android.system.OsConstants.AF_INET6;
@@ -30,11 +31,16 @@ import static android.system.OsConstants.AF_INET6;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -49,6 +55,7 @@ import android.net.InterfaceConfigurationParcel;
import android.net.IpSecAlgorithm;
import android.net.IpSecConfig;
import android.net.IpSecManager;
import android.net.IpSecMigrateInfoParcel;
import android.net.IpSecSpiResponse;
import android.net.IpSecTransform;
import android.net.IpSecTransformResponse;
@@ -130,6 +137,9 @@ public class IpSecServiceParameterizedTest {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F
};
private static final String NEW_SRC_ADDRESS = "2001:db8:2::1";
private static final String NEW_DST_ADDRESS = "2001:db8:2::2";
AppOpsManager mMockAppOps = mock(AppOpsManager.class);
ConnectivityManager mMockConnectivityMgr = mock(ConnectivityManager.class);
@@ -369,8 +379,8 @@ public class IpSecServiceParameterizedTest {
.ipSecAddSecurityAssociation(
eq(mUid),
eq(config.getMode()),
eq(config.getSourceAddress()),
eq(config.getDestinationAddress()),
eq(mSourceAddr),
eq(mDestinationAddr),
eq((config.getNetwork() != null) ? config.getNetwork().netId : 0),
eq(TEST_SPI),
eq(0),
@@ -910,9 +920,60 @@ public class IpSecServiceParameterizedTest {
}
}
@Test
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
public void testApplyAndMigrateTunnelModeTransformOutbound() throws Exception {
verifyApplyAndMigrateTunnelModeTransformCommon(false, DIRECTION_OUT);
}
@Test
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
public void testApplyAndMigrateTunnelModeTransformOutboundReleasedSpi() throws Exception {
verifyApplyAndMigrateTunnelModeTransformCommon(true, DIRECTION_OUT);
}
@Test
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
public void testApplyAndMigrateTunnelModeTransformInbound() throws Exception {
verifyApplyAndMigrateTunnelModeTransformCommon(false, DIRECTION_IN);
}
@Test
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
public void testApplyAndMigrateTunnelModeTransformInboundReleasedSpi() throws Exception {
verifyApplyAndMigrateTunnelModeTransformCommon(true, DIRECTION_IN);
}
@Test
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
public void testApplyAndMigrateTunnelModeTransformForward() throws Exception {
verifyApplyAndMigrateTunnelModeTransformCommon(false, DIRECTION_FWD);
}
@Test
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
public void testApplyAndMigrateTunnelModeTransformForwardReleasedSpi() throws Exception {
verifyApplyAndMigrateTunnelModeTransformCommon(true, DIRECTION_FWD);
}
public void verifyApplyTunnelModeTransformCommon(boolean closeSpiBeforeApply, int direction)
throws Exception {
IpSecConfig ipSecConfig = new IpSecConfig();
verifyApplyTunnelModeTransformCommon(
new IpSecConfig(), closeSpiBeforeApply, false /* isMigrating */, direction);
}
public void verifyApplyAndMigrateTunnelModeTransformCommon(
boolean closeSpiBeforeApply, int direction) throws Exception {
verifyApplyTunnelModeTransformCommon(
new IpSecConfig(), closeSpiBeforeApply, true /* isMigrating */, direction);
}
public int verifyApplyTunnelModeTransformCommon(
IpSecConfig ipSecConfig,
boolean closeSpiBeforeApply,
boolean isMigrating,
int direction)
throws Exception {
ipSecConfig.setMode(IpSecTransform.MODE_TUNNEL);
addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
addAuthAndCryptToIpSecConfig(ipSecConfig);
@@ -928,6 +989,12 @@ public class IpSecServiceParameterizedTest {
int transformResourceId = createTransformResp.resourceId;
int tunnelResourceId = createTunnelResp.resourceId;
if (isMigrating) {
mIpSecService.migrateTransform(
transformResourceId, NEW_SRC_ADDRESS, NEW_DST_ADDRESS, BLESSED_PACKAGE);
}
mIpSecService.applyTunnelModeTransform(
tunnelResourceId, direction, transformResourceId, BLESSED_PACKAGE);
@@ -947,8 +1014,16 @@ public class IpSecServiceParameterizedTest {
ipSecConfig.setXfrmInterfaceId(tunnelResourceId);
verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp);
}
if (isMigrating) {
verify(mMockNetd, times(ADDRESS_FAMILIES.length))
.ipSecMigrate(any(IpSecMigrateInfoParcel.class));
} else {
verify(mMockNetd, never()).ipSecMigrate(any());
}
return tunnelResourceId;
}
@Test
public void testApplyTunnelModeTransformWithClosedSpi() throws Exception {
@@ -1023,7 +1098,7 @@ public class IpSecServiceParameterizedTest {
}
@Test
public void testFeatureFlagVerification() throws Exception {
public void testFeatureFlagIpSecTunnelsVerification() throws Exception {
when(mMockPkgMgr.hasSystemFeature(eq(PackageManager.FEATURE_IPSEC_TUNNELS)))
.thenReturn(false);
@@ -1035,4 +1110,17 @@ public class IpSecServiceParameterizedTest {
} catch (UnsupportedOperationException expected) {
}
}
@Test
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
public void testFeatureFlagIpSecTunnelMigrationVerification() throws Exception {
when(mMockPkgMgr.hasSystemFeature(eq(FEATURE_IPSEC_TUNNEL_MIGRATION))).thenReturn(false);
try {
mIpSecService.migrateTransform(
1 /* transformId */, NEW_SRC_ADDRESS, NEW_DST_ADDRESS, BLESSED_PACKAGE);
fail("Expected UnsupportedOperationException for disabled feature");
} catch (UnsupportedOperationException expected) {
}
}
}