diff --git a/core/java/android/net/IIpSecService.aidl b/core/java/android/net/IIpSecService.aidl index d6774d47b4..933256a3b4 100644 --- a/core/java/android/net/IIpSecService.aidl +++ b/core/java/android/net/IIpSecService.aidl @@ -58,6 +58,9 @@ interface IIpSecService in LinkAddress localAddr, in String callingPackage); + void setNetworkForTunnelInterface( + int tunnelResourceId, in Network underlyingNetwork, in String callingPackage); + void deleteTunnelInterface(int resourceId, in String callingPackage); IpSecTransformResponse createTransform( diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java index 70bca30198..55fdafcea6 100644 --- a/core/java/android/net/IpSecManager.java +++ b/core/java/android/net/IpSecManager.java @@ -782,6 +782,43 @@ public final class IpSecManager { } } + /** + * Update the underlying network for this IpSecTunnelInterface. + * + *

This new underlying network will be used for all transforms applied AFTER this call is + * complete. Before new {@link IpSecTransform}(s) with matching addresses are applied to + * this tunnel interface, traffic will still use the old SA, and be routed on the old + * underlying network. + * + *

To migrate IPsec tunnel mode traffic, a caller should: + * + *

    + *
  1. Update the IpSecTunnelInterface’s underlying network. + *
  2. Apply {@link IpSecTransform}(s) with matching addresses to this + * IpSecTunnelInterface. + *
+ * + * @param underlyingNetwork the new {@link Network} that will carry traffic for this tunnel. + * This network MUST never be the network exposing this IpSecTunnelInterface, otherwise + * this method will throw an {@link IllegalArgumentException}. + * @hide + */ + // TODO: b/169171001 Update the documentation when transform migration is supported. + // The purpose of making updating network and applying transforms separate is to leave open + // the possibility to support lossless migration procedures. To do that, Android platform + // will need to support multiple inbound tunnel mode transforms, just like it can support + // multiple transport mode transforms. + @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) + @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) + public void setUnderlyingNetwork(@NonNull Network underlyingNetwork) throws IOException { + try { + mService.setNetworkForTunnelInterface( + mResourceId, underlyingNetwork, mOpPackageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private IpSecTunnelInterface(@NonNull Context ctx, @NonNull IIpSecService service, @NonNull InetAddress localAddress, @NonNull InetAddress remoteAddress, @NonNull Network underlyingNetwork) diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java index f648c3e146..b48bc900aa 100644 --- a/services/core/java/com/android/server/IpSecService.java +++ b/services/core/java/com/android/server/IpSecService.java @@ -29,6 +29,7 @@ import android.annotation.NonNull; import android.app.AppOpsManager; import android.content.Context; import android.content.pm.PackageManager; +import android.net.ConnectivityManager; import android.net.IIpSecService; import android.net.INetd; import android.net.InetAddresses; @@ -41,6 +42,7 @@ import android.net.IpSecTransformResponse; import android.net.IpSecTunnelInterfaceResponse; import android.net.IpSecUdpEncapResponse; import android.net.LinkAddress; +import android.net.LinkProperties; import android.net.Network; import android.net.TrafficStats; import android.net.util.NetdService; @@ -797,9 +799,15 @@ public class IpSecService extends IIpSecService.Stub { } } - private final class TunnelInterfaceRecord extends OwnedResourceRecord { + /** + * Tracks an tunnel interface, and manages cleanup paths. + * + *

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 + */ + @VisibleForTesting + final class TunnelInterfaceRecord extends OwnedResourceRecord { private final String mInterfaceName; - private final Network mUnderlyingNetwork; // outer addresses private final String mLocalAddress; @@ -810,6 +818,8 @@ public class IpSecService extends IIpSecService.Stub { private final int mIfId; + private Network mUnderlyingNetwork; + TunnelInterfaceRecord( int resourceId, String interfaceName, @@ -870,14 +880,22 @@ public class IpSecService extends IIpSecService.Stub { releaseNetId(mOkey); } - public String getInterfaceName() { - return mInterfaceName; + @GuardedBy("IpSecService.this") + public void setUnderlyingNetwork(Network underlyingNetwork) { + // When #applyTunnelModeTransform is called, this new underlying network will be used to + // update the output mark of the input transform. + mUnderlyingNetwork = underlyingNetwork; } + @GuardedBy("IpSecService.this") public Network getUnderlyingNetwork() { return mUnderlyingNetwork; } + public String getInterfaceName() { + return mInterfaceName; + } + /** Returns the local, outer address for the tunnelInterface */ public String getLocalAddress() { return mLocalAddress; @@ -1429,6 +1447,34 @@ public class IpSecService extends IIpSecService.Stub { } } + /** Set TunnelInterface to use a specific underlying network. */ + @Override + public synchronized void setNetworkForTunnelInterface( + int tunnelResourceId, Network underlyingNetwork, String callingPackage) { + enforceTunnelFeatureAndPermissions(callingPackage); + Objects.requireNonNull(underlyingNetwork, "No underlying network was specified"); + + final UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid()); + + // Get tunnelInterface record; if no such interface is found, will throw + // IllegalArgumentException. userRecord.mTunnelInterfaceRecords is never null + final TunnelInterfaceRecord tunnelInterfaceInfo = + userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelResourceId); + + final ConnectivityManager connectivityManager = + mContext.getSystemService(ConnectivityManager.class); + final LinkProperties lp = connectivityManager.getLinkProperties(underlyingNetwork); + if (tunnelInterfaceInfo.getInterfaceName().equals(lp.getInterfaceName())) { + throw new IllegalArgumentException( + "Underlying network cannot be the network being exposed by this tunnel"); + } + + // It is meaningless to check if the network exists or is valid because the network might + // disconnect at any time after it passes the check. + + tunnelInterfaceInfo.setUnderlyingNetwork(underlyingNetwork); + } + /** * Delete a TunnelInterface that has been been allocated by and registered with the system * server