From 8bc907311b16ae7c2c5f87e5cd5130224160a5d9 Mon Sep 17 00:00:00 2001 From: Benedict Wong Date: Thu, 18 Jan 2018 18:31:45 -0800 Subject: [PATCH] Add TunnelInterface API and KernelResourceRecords This change adds one KernelResourceRecord type (TunnelInterfaceRecord), and adds methods for the creation of TunnelInterfaces, as well as the application of Transforms to the given TunnelInterfaces As part of the generation of ikeys/okeys, a ReserveKeyTracker manages a java bitset to avoid collisions and reserve/release keys. Bug: 63588681 Test: Compiles, CTS, unit tests all pass on AOSP_marlin Change-Id: I9e9b6455e27073acd4491eae666aa966b3b10e0f --- core/java/android/net/IIpSecService.aidl | 19 ++ core/java/android/net/IpSecManager.java | 53 +++- .../net/IpSecTunnelInterfaceResponse.aidl | 20 ++ .../net/IpSecTunnelInterfaceResponse.java | 78 +++++ .../java/com/android/server/IpSecService.java | 284 ++++++++++++++++++ 5 files changed, 446 insertions(+), 8 deletions(-) create mode 100644 core/java/android/net/IpSecTunnelInterfaceResponse.aidl create mode 100644 core/java/android/net/IpSecTunnelInterfaceResponse.java diff --git a/core/java/android/net/IIpSecService.aidl b/core/java/android/net/IIpSecService.aidl index eeb30e23d0..3ce0283d7f 100644 --- a/core/java/android/net/IIpSecService.aidl +++ b/core/java/android/net/IIpSecService.aidl @@ -21,6 +21,7 @@ import android.net.IpSecConfig; import android.net.IpSecUdpEncapResponse; import android.net.IpSecSpiResponse; import android.net.IpSecTransformResponse; +import android.net.IpSecTunnelInterfaceResponse; import android.os.Bundle; import android.os.IBinder; import android.os.ParcelFileDescriptor; @@ -39,11 +40,29 @@ interface IIpSecService void closeUdpEncapsulationSocket(int resourceId); + IpSecTunnelInterfaceResponse createTunnelInterface( + in String localAddr, + in String remoteAddr, + in Network underlyingNetwork, + in IBinder binder); + + void addAddressToTunnelInterface( + int tunnelResourceId, + String localAddr); + + void removeAddressFromTunnelInterface( + int tunnelResourceId, + String localAddr); + + void deleteTunnelInterface(int resourceId); + IpSecTransformResponse createTransform(in IpSecConfig c, in IBinder binder); void deleteTransform(int transformId); void applyTransportModeTransform(in ParcelFileDescriptor socket, int direction, int transformId); + void applyTunnelModeTransform(int tunnelResourceId, int direction, int transformResourceId); + void removeTransportModeTransforms(in ParcelFileDescriptor socket); } diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java index 6125394ab5..24a078fccc 100644 --- a/core/java/android/net/IpSecManager.java +++ b/core/java/android/net/IpSecManager.java @@ -685,7 +685,30 @@ public final class IpSecManager { mLocalAddress = localAddress; mRemoteAddress = remoteAddress; mUnderlyingNetwork = underlyingNetwork; - // TODO: Call IpSecService + + try { + IpSecTunnelInterfaceResponse result = + mService.createTunnelInterface( + localAddress.getHostAddress(), + remoteAddress.getHostAddress(), + underlyingNetwork, + new Binder()); + switch (result.status) { + case Status.OK: + break; + case Status.RESOURCE_UNAVAILABLE: + throw new ResourceUnavailableException( + "No more tunnel interfaces may be allocated by this requester."); + default: + throw new RuntimeException( + "Unknown status returned by IpSecService: " + result.status); + } + mResourceId = result.resourceId; + mInterfaceName = result.interfaceName; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + mCloseGuard.open("constructor"); } /** @@ -697,12 +720,12 @@ public final class IpSecManager { */ @Override public void close() { - // try { - // TODO: Call IpSecService - mResourceId = INVALID_RESOURCE_ID; - // } catch (RemoteException e) { - // throw e.rethrowFromSystemServer(); - // } + try { + mService.deleteTunnelInterface(mResourceId); + mResourceId = INVALID_RESOURCE_ID; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } mCloseGuard.close(); } @@ -714,11 +737,20 @@ public final class IpSecManager { } close(); } + + /** @hide */ + @VisibleForTesting + public int getResourceId() { + return mResourceId; + } } /** * Create a new IpSecTunnelInterface as a local endpoint for tunneled IPsec traffic. * + *

An application that creates tunnels is responsible for cleaning up the tunnel when the + * underlying network goes away, and the onLost() callback is received. + * * @param localAddress The local addres of the tunnel * @param remoteAddress The local addres of the tunnel * @param underlyingNetwork the {@link Network} that will carry traffic for this tunnel. @@ -750,7 +782,12 @@ public final class IpSecManager { @SystemApi public void applyTunnelModeTransform(IpSecTunnelInterface tunnel, int direction, IpSecTransform transform) throws IOException { - // TODO: call IpSecService + try { + mService.applyTunnelModeTransform( + tunnel.getResourceId(), direction, transform.getResourceId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** * Construct an instance of IpSecManager within an application context. diff --git a/core/java/android/net/IpSecTunnelInterfaceResponse.aidl b/core/java/android/net/IpSecTunnelInterfaceResponse.aidl new file mode 100644 index 0000000000..7239221415 --- /dev/null +++ b/core/java/android/net/IpSecTunnelInterfaceResponse.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2018 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 android.net; + +/** @hide */ +parcelable IpSecTunnelInterfaceResponse; diff --git a/core/java/android/net/IpSecTunnelInterfaceResponse.java b/core/java/android/net/IpSecTunnelInterfaceResponse.java new file mode 100644 index 0000000000..c23d831a44 --- /dev/null +++ b/core/java/android/net/IpSecTunnelInterfaceResponse.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2018 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 android.net; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * This class is used to return an IpSecTunnelInterface resource Id and and corresponding status + * from the IpSecService to an IpSecTunnelInterface object. + * + * @hide + */ +public final class IpSecTunnelInterfaceResponse implements Parcelable { + private static final String TAG = "IpSecTunnelInterfaceResponse"; + + public final int resourceId; + public final String interfaceName; + public final int status; + // Parcelable Methods + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(status); + out.writeInt(resourceId); + out.writeString(interfaceName); + } + + public IpSecTunnelInterfaceResponse(int inStatus) { + if (inStatus == IpSecManager.Status.OK) { + throw new IllegalArgumentException("Valid status implies other args must be provided"); + } + status = inStatus; + resourceId = IpSecManager.INVALID_RESOURCE_ID; + interfaceName = ""; + } + + public IpSecTunnelInterfaceResponse(int inStatus, int inResourceId, String inInterfaceName) { + status = inStatus; + resourceId = inResourceId; + interfaceName = inInterfaceName; + } + + private IpSecTunnelInterfaceResponse(Parcel in) { + status = in.readInt(); + resourceId = in.readInt(); + interfaceName = in.readString(); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public IpSecTunnelInterfaceResponse createFromParcel(Parcel in) { + return new IpSecTunnelInterfaceResponse(in); + } + + public IpSecTunnelInterfaceResponse[] newArray(int size) { + return new IpSecTunnelInterfaceResponse[size]; + } + }; +} diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java index a9a81089fc..c2adbfaeb7 100644 --- a/services/core/java/com/android/server/IpSecService.java +++ b/services/core/java/com/android/server/IpSecService.java @@ -34,7 +34,9 @@ import android.net.IpSecManager; import android.net.IpSecSpiResponse; import android.net.IpSecTransform; import android.net.IpSecTransformResponse; +import android.net.IpSecTunnelInterfaceResponse; import android.net.IpSecUdpEncapResponse; +import android.net.Network; import android.net.NetworkUtils; import android.net.TrafficStats; import android.net.util.NetdService; @@ -50,6 +52,7 @@ import android.text.TextUtils; import android.util.Log; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -62,6 +65,7 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.ArrayList; +import java.util.BitSet; import java.util.List; import libcore.io.IoUtils; @@ -99,6 +103,7 @@ public class IpSecService extends IIpSecService.Stub { static final int FREE_PORT_MIN = 1024; // ports 1-1023 are reserved static final int PORT_MAX = 0xFFFF; // ports are an unsigned 16-bit integer + static final String TUNNEL_INTERFACE_PREFIX = "ipsec"; /* Binder context for this service */ private final Context mContext; @@ -347,6 +352,7 @@ public class IpSecService extends IIpSecService.Stub { @VisibleForTesting static final class UserRecord { /* Maximum number of each type of resource that a single UID may possess */ + public static final int MAX_NUM_TUNNEL_INTERFACES = 2; public static final int MAX_NUM_ENCAP_SOCKETS = 2; public static final int MAX_NUM_TRANSFORMS = 4; public static final int MAX_NUM_SPIS = 8; @@ -366,6 +372,8 @@ public class IpSecService extends IIpSecService.Stub { new RefcountedResourceArray<>(TransformRecord.class.getSimpleName()); final RefcountedResourceArray mEncapSocketRecords = new RefcountedResourceArray<>(EncapSocketRecord.class.getSimpleName()); + final RefcountedResourceArray mTunnelInterfaceRecords = + new RefcountedResourceArray<>(TunnelInterfaceRecord.class.getSimpleName()); /** * Trackers for quotas for each of the OwnedResource types. @@ -379,6 +387,7 @@ public class IpSecService extends IIpSecService.Stub { final ResourceTracker mSpiQuotaTracker = new ResourceTracker(MAX_NUM_SPIS); final ResourceTracker mTransformQuotaTracker = new ResourceTracker(MAX_NUM_TRANSFORMS); final ResourceTracker mSocketQuotaTracker = new ResourceTracker(MAX_NUM_ENCAP_SOCKETS); + final ResourceTracker mTunnelQuotaTracker = new ResourceTracker(MAX_NUM_TUNNEL_INTERFACES); void removeSpiRecord(int resourceId) { mSpiRecords.remove(resourceId); @@ -388,6 +397,10 @@ public class IpSecService extends IIpSecService.Stub { mTransformRecords.remove(resourceId); } + void removeTunnelInterfaceRecord(int resourceId) { + mTunnelInterfaceRecords.remove(resourceId); + } + void removeEncapSocketRecord(int resourceId) { mEncapSocketRecords.remove(resourceId); } @@ -721,6 +734,145 @@ public class IpSecService extends IIpSecService.Stub { } } + // These values have been reserved in ConnectivityService + @VisibleForTesting static final int TUN_INTF_NETID_START = 0xFC00; + + @VisibleForTesting static final int TUN_INTF_NETID_RANGE = 0x0400; + + private final SparseBooleanArray mTunnelNetIds = new SparseBooleanArray(); + private int mNextTunnelNetIdIndex = 0; + + /** + * Reserves a netId within the range of netIds allocated for IPsec tunnel interfaces + * + *

This method should only be called from Binder threads. Do not call this from within the + * system server as it will crash the system on failure. + * + * @return an integer key within the netId range, if successful + * @throws IllegalStateException if unsuccessful (all netId are currently reserved) + */ + @VisibleForTesting + int reserveNetId() { + synchronized (mTunnelNetIds) { + for (int i = 0; i < TUN_INTF_NETID_RANGE; i++) { + int index = mNextTunnelNetIdIndex; + int netId = index + TUN_INTF_NETID_START; + if (++mNextTunnelNetIdIndex >= TUN_INTF_NETID_RANGE) mNextTunnelNetIdIndex = 0; + if (!mTunnelNetIds.get(netId)) { + mTunnelNetIds.put(netId, true); + return netId; + } + } + } + throw new IllegalStateException("No free netIds to allocate"); + } + + @VisibleForTesting + void releaseNetId(int netId) { + synchronized (mTunnelNetIds) { + mTunnelNetIds.delete(netId); + } + } + + private final class TunnelInterfaceRecord extends OwnedResourceRecord { + private final String mInterfaceName; + private final Network mUnderlyingNetwork; + + // outer addresses + private final String mLocalAddress; + private final String mRemoteAddress; + + private final int mIkey; + private final int mOkey; + + TunnelInterfaceRecord( + int resourceId, + String interfaceName, + Network underlyingNetwork, + String localAddr, + String remoteAddr, + int ikey, + int okey) { + super(resourceId); + + mInterfaceName = interfaceName; + mUnderlyingNetwork = underlyingNetwork; + mLocalAddress = localAddr; + mRemoteAddress = remoteAddr; + mIkey = ikey; + mOkey = okey; + } + + /** always guarded by IpSecService#this */ + @Override + public void freeUnderlyingResources() { + // TODO: Add calls to netd + // Teardown VTI + // Delete global policies + + getResourceTracker().give(); + releaseNetId(mIkey); + releaseNetId(mOkey); + } + + public String getInterfaceName() { + return mInterfaceName; + } + + public Network getUnderlyingNetwork() { + return mUnderlyingNetwork; + } + + /** Returns the local, outer address for the tunnelInterface */ + public String getLocalAddress() { + return mLocalAddress; + } + + /** Returns the remote, outer address for the tunnelInterface */ + public String getRemoteAddress() { + return mRemoteAddress; + } + + public int getIkey() { + return mIkey; + } + + public int getOkey() { + return mOkey; + } + + @Override + protected ResourceTracker getResourceTracker() { + return getUserRecord().mTunnelQuotaTracker; + } + + @Override + public void invalidate() { + getUserRecord().removeTunnelInterfaceRecord(mResourceId); + } + + @Override + public String toString() { + return new StringBuilder() + .append("{super=") + .append(super.toString()) + .append(", mInterfaceName=") + .append(mInterfaceName) + .append(", mUnderlyingNetwork=") + .append(mUnderlyingNetwork) + .append(", mLocalAddress=") + .append(mLocalAddress) + .append(", mRemoteAddress=") + .append(mRemoteAddress) + .append(", mIkey=") + .append(mIkey) + .append(", mOkey=") + .append(mOkey) + .append("}") + .toString(); + } + } + /** * Tracks a UDP encap socket, and manages cleanup paths * @@ -1051,6 +1203,97 @@ public class IpSecService extends IIpSecService.Stub { releaseResource(userRecord.mEncapSocketRecords, resourceId); } + /** + * Create a tunnel interface for use in IPSec tunnel mode. The system server will cache the + * tunnel interface and a record of its owner so that it can and must be freed when no longer + * needed. + */ + @Override + public synchronized IpSecTunnelInterfaceResponse createTunnelInterface( + String localAddr, String remoteAddr, Network underlyingNetwork, IBinder binder) { + checkNotNull(binder, "Null Binder passed to createTunnelInterface"); + checkNotNull(underlyingNetwork, "No underlying network was specified"); + checkInetAddress(localAddr); + checkInetAddress(remoteAddr); + + // TODO: Check that underlying network exists, and IP addresses not assigned to a different + // network (b/72316676). + + UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid()); + if (!userRecord.mTunnelQuotaTracker.isAvailable()) { + return new IpSecTunnelInterfaceResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE); + } + + final int resourceId = mNextResourceId++; + final int ikey = reserveNetId(); + final int okey = reserveNetId(); + String intfName = String.format("%s%d", TUNNEL_INTERFACE_PREFIX, resourceId); + + // TODO: Add calls to netd: + // Create VTI + // Add inbound/outbound global policies + // (use reqid = 0) + + userRecord.mTunnelInterfaceRecords.put( + resourceId, + new RefcountedResource( + new TunnelInterfaceRecord( + resourceId, + intfName, + underlyingNetwork, + localAddr, + remoteAddr, + ikey, + okey), + binder)); + return new IpSecTunnelInterfaceResponse(IpSecManager.Status.OK, resourceId, intfName); + } + + /** + * Adds a new local address to the tunnel interface. This allows packets to be sent and received + * from multiple local IP addresses over the same tunnel. + */ + @Override + public synchronized void addAddressToTunnelInterface(int tunnelResourceId, String localAddr) { + UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid()); + + // Get tunnelInterface record; if no such interface is found, will throw + // IllegalArgumentException + TunnelInterfaceRecord tunnelInterfaceInfo = + userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelResourceId); + + // TODO: Add calls to netd: + // Add address to TunnelInterface + } + + /** + * Remove a new local address from the tunnel interface. After removal, the address will no + * longer be available to send from, or receive on. + */ + @Override + public synchronized void removeAddressFromTunnelInterface( + int tunnelResourceId, String localAddr) { + UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid()); + + // Get tunnelInterface record; if no such interface is found, will throw + // IllegalArgumentException + TunnelInterfaceRecord tunnelInterfaceInfo = + userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelResourceId); + + // TODO: Add calls to netd: + // Remove address from TunnelInterface + } + + /** + * Delete a TunnelInterface that has been been allocated by and registered with the system + * server + */ + @Override + public synchronized void deleteTunnelInterface(int resourceId) throws RemoteException { + UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid()); + releaseResource(userRecord.mTunnelInterfaceRecords, resourceId); + } + @VisibleForTesting void validateAlgorithms(IpSecConfig config) throws IllegalArgumentException { IpSecAlgorithm auth = config.getAuthentication(); @@ -1249,7 +1492,12 @@ public class IpSecService extends IIpSecService.Stub { throw new SecurityException("Only the owner of an IpSec Transform may apply it!"); } + // Get config and check that to-be-applied transform has the correct mode IpSecConfig c = info.getConfig(); + Preconditions.checkArgument( + c.getMode() == IpSecTransform.MODE_TRANSPORT, + "Transform mode was not Transport mode; cannot be applied to a socket"); + try { mSrvConfig .getNetdInstance() @@ -1287,6 +1535,42 @@ 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 + */ + @Override + public synchronized void applyTunnelModeTransform( + int tunnelResourceId, int direction, int transformResourceId) throws RemoteException { + checkDirection(direction); + + UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid()); + + // Get transform record; if no transform is found, will throw IllegalArgumentException + TransformRecord transformInfo = + userRecord.mTransformRecords.getResourceOrThrow(transformResourceId); + + // Get tunnelInterface record; if no such interface is found, will throw + // IllegalArgumentException + TunnelInterfaceRecord tunnelInterfaceInfo = + userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelResourceId); + + // Get config and check that to-be-applied transform has the correct mode + IpSecConfig c = transformInfo.getConfig(); + Preconditions.checkArgument( + c.getMode() == IpSecTransform.MODE_TUNNEL, + "Transform mode was not Tunnel mode; cannot be applied to a tunnel interface"); + + int mark = + (direction == IpSecManager.DIRECTION_IN) + ? tunnelInterfaceInfo.getIkey() + : tunnelInterfaceInfo.getOkey(); + + // TODO: Add calls to netd: + // Update SA with tunnel mark (ikey or okey based on direction) + // If outbound, add SPI to policy + } + @Override protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) { mContext.enforceCallingOrSelfPermission(DUMP, TAG);