diff options
| author | 2017-03-07 13:23:36 -0800 | |
|---|---|---|
| committer | 2017-04-05 10:24:45 -0700 | |
| commit | f1dad26972dceac86edfc42bc87753b7ad8ad54f (patch) | |
| tree | 7db25e18fb2057dc14a3eb0c9ddfd6a0cf00fa8d | |
| parent | 127f4558003a12e7bdebc74defc04f413090296c (diff) | |
Add Initial IPsec APIs to IpSecService
-Plumb IpSecManager APIs to NetD
-Add Resource Management to IpSecService
Bug: 30984788
Test: b/34812052, b/34811227
Change-Id: Ic43965c6158f28cac53810adbf5cf50d2c54f920
(cherry picked from commit 93962f34ce21f5aac825afbcebf2f3e8c7a30910)
| -rw-r--r-- | core/java/android/net/IIpSecService.aidl | 22 | ||||
| -rw-r--r-- | core/java/android/net/IpSecAlgorithm.java | 2 | ||||
| -rw-r--r-- | core/java/android/net/IpSecConfig.java | 70 | ||||
| -rw-r--r-- | core/java/android/net/IpSecManager.java | 112 | ||||
| -rw-r--r-- | core/java/android/net/IpSecTransform.java | 143 | ||||
| -rw-r--r-- | services/core/java/com/android/server/IpSecService.java | 396 |
6 files changed, 620 insertions, 125 deletions
diff --git a/core/java/android/net/IIpSecService.aidl b/core/java/android/net/IIpSecService.aidl index b8737fef72e2..0aa3ce66eb29 100644 --- a/core/java/android/net/IIpSecService.aidl +++ b/core/java/android/net/IIpSecService.aidl @@ -16,9 +16,31 @@ package android.net; +import android.net.Network; +import android.net.IpSecConfig; +import android.os.Bundle; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; + /** * @hide */ interface IIpSecService { + Bundle reserveSecurityParameterIndex( + int direction, in String remoteAddress, int requestedSpi, in IBinder binder); + + void releaseSecurityParameterIndex(int resourceId); + + Bundle openUdpEncapsulationSocket(int port, in IBinder binder); + + void closeUdpEncapsulationSocket(in ParcelFileDescriptor socket); + + Bundle createTransportModeTransform(in IpSecConfig c, in IBinder binder); + + void deleteTransportModeTransform(int transformId); + + void applyTransportModeTransform(in ParcelFileDescriptor socket, int transformId); + + void removeTransportModeTransform(in ParcelFileDescriptor socket, int transformId); } diff --git a/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java index da5cb37961ab..7fea4a25cab7 100644 --- a/core/java/android/net/IpSecAlgorithm.java +++ b/core/java/android/net/IpSecAlgorithm.java @@ -164,6 +164,8 @@ public final class IpSecAlgorithm implements Parcelable { private static boolean isTruncationLengthValid(String algo, int truncLenBits) { switch (algo) { + case ALGO_CRYPT_AES_CBC: + return (truncLenBits == 128 || truncLenBits == 192 || truncLenBits == 256); case ALGO_AUTH_HMAC_MD5: return (truncLenBits >= 96 && truncLenBits <= 128); case ALGO_AUTH_HMAC_SHA1: diff --git a/core/java/android/net/IpSecConfig.java b/core/java/android/net/IpSecConfig.java index b58bf421a86a..13dc19f68577 100644 --- a/core/java/android/net/IpSecConfig.java +++ b/core/java/android/net/IpSecConfig.java @@ -23,7 +23,7 @@ import java.net.UnknownHostException; /** @hide */ public final class IpSecConfig implements Parcelable { - private static final String TAG = IpSecConfig.class.getSimpleName(); + private static final String TAG = "IpSecConfig"; //MODE_TRANSPORT or MODE_TUNNEL int mode; @@ -43,13 +43,13 @@ public final class IpSecConfig implements Parcelable { int spi; // Encryption Algorithm - IpSecAlgorithm encryptionAlgo; + IpSecAlgorithm encryption; // Authentication Algorithm - IpSecAlgorithm authenticationAlgo; + IpSecAlgorithm authentication; } - Flow[] flow = new Flow[2]; + Flow[] flow = new Flow[] {new Flow(), new Flow()}; // For tunnel mode IPv4 UDP Encapsulation // IpSecTransform#ENCAP_ESP_*, such as ENCAP_ESP_OVER_UDP_IKE @@ -57,17 +57,15 @@ public final class IpSecConfig implements Parcelable { int encapLocalPort; int encapRemotePort; - // An optional protocol to match with the selector - int selectorProto; - - // A bitmask of FEATURE_* indicating which of the fields - // of this class are valid. - long features; - // An interval, in seconds between the NattKeepalive packets int nattKeepaliveInterval; - public InetAddress getLocalIp() { + // Transport or Tunnel + public int getMode() { + return mode; + } + + public InetAddress getLocalAddress() { return localAddress; } @@ -75,19 +73,19 @@ public final class IpSecConfig implements Parcelable { return flow[direction].spi; } - public InetAddress getRemoteIp() { + public InetAddress getRemoteAddress() { return remoteAddress; } - public IpSecAlgorithm getEncryptionAlgo(int direction) { - return flow[direction].encryptionAlgo; + public IpSecAlgorithm getEncryption(int direction) { + return flow[direction].encryption; } - public IpSecAlgorithm getAuthenticationAlgo(int direction) { - return flow[direction].authenticationAlgo; + public IpSecAlgorithm getAuthentication(int direction) { + return flow[direction].authentication; } - Network getNetwork() { + public Network getNetwork() { return network; } @@ -103,18 +101,10 @@ public final class IpSecConfig implements Parcelable { return encapRemotePort; } - public int getSelectorProto() { - return selectorProto; - } - - int getNattKeepaliveInterval() { + public int getNattKeepaliveInterval() { return nattKeepaliveInterval; } - public boolean hasProperty(int featureBits) { - return (features & featureBits) == featureBits; - } - // Parcelable Methods @Override @@ -124,31 +114,25 @@ public final class IpSecConfig implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { - out.writeLong(features); // TODO: Use a byte array or other better method for storing IPs that can also include scope out.writeString((localAddress != null) ? localAddress.getHostAddress() : null); // TODO: Use a byte array or other better method for storing IPs that can also include scope out.writeString((remoteAddress != null) ? remoteAddress.getHostAddress() : null); out.writeParcelable(network, flags); out.writeInt(flow[IpSecTransform.DIRECTION_IN].spi); - out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].encryptionAlgo, flags); - out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].authenticationAlgo, flags); + out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].encryption, flags); + out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].authentication, flags); out.writeInt(flow[IpSecTransform.DIRECTION_OUT].spi); - out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].encryptionAlgo, flags); - out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].authenticationAlgo, flags); + out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].encryption, flags); + out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].authentication, flags); out.writeInt(encapType); out.writeInt(encapLocalPort); out.writeInt(encapRemotePort); - out.writeInt(selectorProto); } // Package Private: Used by the IpSecTransform.Builder; // there should be no public constructor for this object - IpSecConfig() { - flow[IpSecTransform.DIRECTION_IN].spi = 0; - flow[IpSecTransform.DIRECTION_OUT].spi = 0; - nattKeepaliveInterval = 0; //FIXME constant - } + IpSecConfig() {} private static InetAddress readInetAddressFromParcel(Parcel in) { String addrString = in.readString(); @@ -164,24 +148,22 @@ public final class IpSecConfig implements Parcelable { } private IpSecConfig(Parcel in) { - features = in.readLong(); localAddress = readInetAddressFromParcel(in); remoteAddress = readInetAddressFromParcel(in); network = (Network) in.readParcelable(Network.class.getClassLoader()); flow[IpSecTransform.DIRECTION_IN].spi = in.readInt(); - flow[IpSecTransform.DIRECTION_IN].encryptionAlgo = + flow[IpSecTransform.DIRECTION_IN].encryption = (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); - flow[IpSecTransform.DIRECTION_IN].authenticationAlgo = + flow[IpSecTransform.DIRECTION_IN].authentication = (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); flow[IpSecTransform.DIRECTION_OUT].spi = in.readInt(); - flow[IpSecTransform.DIRECTION_OUT].encryptionAlgo = + flow[IpSecTransform.DIRECTION_OUT].encryption = (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); - flow[IpSecTransform.DIRECTION_OUT].authenticationAlgo = + flow[IpSecTransform.DIRECTION_OUT].authentication = (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); encapType = in.readInt(); encapLocalPort = in.readInt(); encapRemotePort = in.readInt(); - selectorProto = in.readInt(); } public static final Parcelable.Creator<IpSecConfig> CREATOR = diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java index 3fcdb7e28cf7..6852beb06529 100644 --- a/core/java/android/net/IpSecManager.java +++ b/core/java/android/net/IpSecManager.java @@ -17,8 +17,11 @@ package android.net; import static com.android.internal.util.Preconditions.checkNotNull; -import android.annotation.SystemApi; +import android.annotation.NonNull; +import android.os.Binder; +import android.os.Bundle; import android.os.ParcelFileDescriptor; +import android.os.RemoteException; import android.util.AndroidException; import dalvik.system.CloseGuard; import java.io.FileDescriptor; @@ -39,6 +42,29 @@ public final class IpSecManager { private static final String TAG = "IpSecManager"; /** + * The Security Parameter Index, SPI, 0 indicates an unknown or invalid index. + * + * <p>No IPsec packet may contain an SPI of 0. + */ + public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; + + /** @hide */ + public interface Status { + public static final int OK = 0; + public static final int RESOURCE_UNAVAILABLE = 1; + public static final int SPI_UNAVAILABLE = 2; + } + + /** @hide */ + public static final String KEY_STATUS = "status"; + /** @hide */ + public static final String KEY_RESOURCE_ID = "resourceId"; + /** @hide */ + public static final String KEY_SPI = "spi"; + /** @hide */ + public static final int INVALID_RESOURCE_ID = 0; + + /** * Indicates that the combination of remote InetAddress and SPI was non-unique for a given * request. If encountered, selection of a new SPI is required before a transform may be * created. Note, this should happen very rarely if the SPI is chosen to be sufficiently random @@ -83,22 +109,14 @@ public final class IpSecManager { private final IIpSecService mService; private final InetAddress mRemoteAddress; private final CloseGuard mCloseGuard = CloseGuard.get(); - private int mSpi; + private int mSpi = INVALID_SECURITY_PARAMETER_INDEX; + private int mResourceId; /** Return the underlying SPI held by this object */ public int getSpi() { return mSpi; } - private SecurityParameterIndex( - IIpSecService service, int direction, InetAddress remoteAddress, int spi) - throws ResourceUnavailableException, SpiUnavailableException { - mService = service; - mRemoteAddress = remoteAddress; - mSpi = spi; - mCloseGuard.open("open"); - } - /** * Release an SPI that was previously reserved. * @@ -108,7 +126,7 @@ public final class IpSecManager { */ @Override public void close() { - mSpi = INVALID_SECURITY_PARAMETER_INDEX; // TODO: Invalid SPI + mSpi = INVALID_SECURITY_PARAMETER_INDEX; mCloseGuard.close(); } @@ -120,14 +138,52 @@ public final class IpSecManager { close(); } - } - /** - * The Security Parameter Index, SPI, 0 indicates an unknown or invalid index. - * - * <p>No IPsec packet may contain an SPI of 0. - */ - public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; + private SecurityParameterIndex( + @NonNull IIpSecService service, int direction, InetAddress remoteAddress, int spi) + throws ResourceUnavailableException, SpiUnavailableException { + mService = service; + mRemoteAddress = remoteAddress; + try { + Bundle result = + mService.reserveSecurityParameterIndex( + direction, remoteAddress.getHostAddress(), spi, new Binder()); + + if (result == null) { + throw new NullPointerException("Received null response from IpSecService"); + } + + int status = result.getInt(KEY_STATUS); + switch (status) { + case Status.OK: + break; + case Status.RESOURCE_UNAVAILABLE: + throw new ResourceUnavailableException( + "No more SPIs may be allocated by this requester."); + case Status.SPI_UNAVAILABLE: + throw new SpiUnavailableException("Requested SPI is unavailable", spi); + default: + throw new RuntimeException( + "Unknown status returned by IpSecService: " + status); + } + mSpi = result.getInt(KEY_SPI); + mResourceId = result.getInt(KEY_RESOURCE_ID); + + if (mSpi == INVALID_SECURITY_PARAMETER_INDEX) { + throw new RuntimeException("Invalid SPI returned by IpSecService: " + status); + } + + if (mResourceId == INVALID_RESOURCE_ID) { + throw new RuntimeException( + "Invalid Resource ID returned by IpSecService: " + status); + } + + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + mCloseGuard.open("open"); + } + } /** * Reserve an SPI for traffic bound towards the specified remote address. @@ -184,7 +240,13 @@ public final class IpSecManager { } /* Call down to activate a transform */ - private void applyTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {} + private void applyTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) { + try { + mService.applyTransportModeTransform(pfd, transform.getResourceId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } /** * Apply an active Tunnel Mode IPsec Transform to a network, which will tunnel all traffic to @@ -228,7 +290,13 @@ public final class IpSecManager { } /* Call down to activate a transform */ - private void removeTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {} + private void removeTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) { + try { + mService.removeTransportModeTransform(pfd, transform.getResourceId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } /** * Remove a Tunnel Mode IPsec Transform from a {@link Network}. This must be used as part of @@ -255,7 +323,7 @@ public final class IpSecManager { private final IIpSecService mService; private final CloseGuard mCloseGuard = CloseGuard.get(); - private UdpEncapsulationSocket(IIpSecService service, int port) + private UdpEncapsulationSocket(@NonNull IIpSecService service, int port) throws ResourceUnavailableException { mService = service; mCloseGuard.open("constructor"); diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java index 74d60106c083..801e98c7b138 100644 --- a/core/java/android/net/IpSecTransform.java +++ b/core/java/android/net/IpSecTransform.java @@ -15,11 +15,21 @@ */ package android.net; +import static android.net.IpSecManager.INVALID_RESOURCE_ID; +import static android.net.IpSecManager.KEY_RESOURCE_ID; +import static android.net.IpSecManager.KEY_STATUS; + import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.SystemApi; import android.content.Context; -import android.system.ErrnoException; +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; import android.util.Log; +import com.android.internal.util.Preconditions; import dalvik.system.CloseGuard; import java.io.IOException; import java.lang.annotation.Retention; @@ -86,39 +96,64 @@ public final class IpSecTransform implements AutoCloseable { @Retention(RetentionPolicy.SOURCE) public @interface EncapType {} - /** - * Sentinel for an invalid transform (means that this transform is inactive). - * - * @hide - */ - public static final int INVALID_TRANSFORM_ID = -1; - private IpSecTransform(Context context, IpSecConfig config) { mContext = context; mConfig = config; - mTransformId = INVALID_TRANSFORM_ID; + mResourceId = INVALID_RESOURCE_ID; + } + + private IIpSecService getIpSecService() { + IBinder b = ServiceManager.getService(android.content.Context.IPSEC_SERVICE); + if (b == null) { + throw new RemoteException("Failed to connect to IpSecService") + .rethrowAsRuntimeException(); + } + + return IIpSecService.Stub.asInterface(b); + } + + private void checkResultStatusAndThrow(int status) + throws IOException, IpSecManager.ResourceUnavailableException, + IpSecManager.SpiUnavailableException { + switch (status) { + case IpSecManager.Status.OK: + return; + // TODO: Pass Error string back from bundle so that errors can be more specific + case IpSecManager.Status.RESOURCE_UNAVAILABLE: + throw new IpSecManager.ResourceUnavailableException( + "Failed to allocate a new IpSecTransform"); + case IpSecManager.Status.SPI_UNAVAILABLE: + Log.wtf(TAG, "Attempting to use an SPI that was somehow not reserved"); + // Fall through + default: + throw new IllegalStateException( + "Failed to Create a Transform with status code " + status); + } } private IpSecTransform activate() throws IOException, IpSecManager.ResourceUnavailableException, IpSecManager.SpiUnavailableException { - int transformId; synchronized (this) { - //try { - transformId = INVALID_TRANSFORM_ID; - //} catch (RemoteException e) { - // throw e.rethrowFromSystemServer(); - //} - - if (transformId < 0) { - throw new ErrnoException("addTransform", -transformId).rethrowAsIOException(); + try { + IIpSecService svc = getIpSecService(); + Bundle result = svc.createTransportModeTransform(mConfig, new Binder()); + int status = result.getInt(KEY_STATUS); + checkResultStatusAndThrow(status); + mResourceId = result.getInt(KEY_RESOURCE_ID, INVALID_RESOURCE_ID); + + /* Keepalive will silently fail if not needed by the config; but, if needed and + * it fails to start, we need to bail because a transform will not be reliable + * to use if keepalive is expected to offload and fails. + */ + // FIXME: if keepalive fails, we need to fail spectacularly + startKeepalive(mContext); + Log.d(TAG, "Added Transform with Id " + mResourceId); + mCloseGuard.open("build"); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); } - - startKeepalive(mContext); // Will silently fail if not required - mTransformId = transformId; - Log.d(TAG, "Added Transform with Id " + transformId); } - mCloseGuard.open("build"); return this; } @@ -133,21 +168,27 @@ public final class IpSecTransform implements AutoCloseable { * transform is no longer needed. */ public void close() { - Log.d(TAG, "Removing Transform with Id " + mTransformId); + Log.d(TAG, "Removing Transform with Id " + mResourceId); // Always safe to attempt cleanup - if (mTransformId == INVALID_TRANSFORM_ID) { + if (mResourceId == INVALID_RESOURCE_ID) { + mCloseGuard.close(); return; } - //try { - stopKeepalive(); - //} catch (RemoteException e) { - // transform.setTransformId(transformId); - // throw e.rethrowFromSystemServer(); - //} finally { - mTransformId = INVALID_TRANSFORM_ID; - //} - mCloseGuard.close(); + try { + /* Order matters here because the keepalive is best-effort but could fail in some + * horrible way to be removed if the wifi (or cell) subsystem has crashed, and we + * still want to clear out the transform. + */ + IIpSecService svc = getIpSecService(); + svc.deleteTransportModeTransform(mResourceId); + stopKeepalive(); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } finally { + mResourceId = INVALID_RESOURCE_ID; + mCloseGuard.close(); + } } @Override @@ -164,7 +205,7 @@ public final class IpSecTransform implements AutoCloseable { } private final IpSecConfig mConfig; - private int mTransformId; + private int mResourceId; private final Context mContext; private final CloseGuard mCloseGuard = CloseGuard.get(); private ConnectivityManager.PacketKeepalive mKeepalive; @@ -200,6 +241,7 @@ public final class IpSecTransform implements AutoCloseable { /* Package */ void startKeepalive(Context c) { + // FIXME: NO_KEEPALIVE needs to be a constant if (mConfig.getNattKeepaliveInterval() == 0) { return; } @@ -208,7 +250,7 @@ public final class IpSecTransform implements AutoCloseable { (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE); if (mKeepalive != null) { - Log.e(TAG, "Keepalive already started for this IpSecTransform."); + Log.wtf(TAG, "Keepalive already started for this IpSecTransform."); return; } @@ -218,10 +260,11 @@ public final class IpSecTransform implements AutoCloseable { mConfig.getNetwork(), mConfig.getNattKeepaliveInterval(), mKeepaliveCallback, - mConfig.getLocalIp(), + mConfig.getLocalAddress(), mConfig.getEncapLocalPort(), - mConfig.getRemoteIp()); + mConfig.getRemoteAddress()); try { + // FIXME: this is still a horrible way to fudge the synchronous callback mKeepaliveSyncLock.wait(2000); } catch (InterruptedException e) { } @@ -232,6 +275,11 @@ public final class IpSecTransform implements AutoCloseable { } /* Package */ + int getResourceId() { + return mResourceId; + } + + /* Package */ void stopKeepalive() { if (mKeepalive == null) { return; @@ -247,16 +295,6 @@ public final class IpSecTransform implements AutoCloseable { } } - /* Package */ - void setTransformId(int transformId) { - mTransformId = transformId; - } - - /* Package */ - int getTransformId() { - return mTransformId; - } - /** * Builder object to facilitate the creation of IpSecTransform objects. * @@ -280,7 +318,7 @@ public final class IpSecTransform implements AutoCloseable { */ public IpSecTransform.Builder setEncryption( @TransformDirection int direction, IpSecAlgorithm algo) { - mConfig.flow[direction].encryptionAlgo = algo; + mConfig.flow[direction].encryption = algo; return this; } @@ -295,7 +333,7 @@ public final class IpSecTransform implements AutoCloseable { */ public IpSecTransform.Builder setAuthentication( @TransformDirection int direction, IpSecAlgorithm algo) { - mConfig.flow[direction].authenticationAlgo = algo; + mConfig.flow[direction].authentication = algo; return this; } @@ -318,6 +356,8 @@ public final class IpSecTransform implements AutoCloseable { */ public IpSecTransform.Builder setSpi( @TransformDirection int direction, IpSecManager.SecurityParameterIndex spi) { + // TODO: convert to using the resource Id of the SPI. Then build() can validate + // the owner in the IpSecService mConfig.flow[direction].spi = spi.getSpi(); return this; } @@ -439,7 +479,8 @@ public final class IpSecTransform implements AutoCloseable { * * @param context current Context */ - public Builder(Context context) { + public Builder(@NonNull Context context) { + Preconditions.checkNotNull(context); mContext = context; mConfig = new IpSecConfig(); } diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java index 994adc480fb5..a7ce95ba0c88 100644 --- a/services/core/java/com/android/server/IpSecService.java +++ b/services/core/java/com/android/server/IpSecService.java @@ -17,23 +17,40 @@ package com.android.server; import static android.Manifest.permission.DUMP; +import static android.net.IpSecManager.INVALID_RESOURCE_ID; +import static android.net.IpSecManager.KEY_RESOURCE_ID; +import static android.net.IpSecManager.KEY_SPI; +import static android.net.IpSecManager.KEY_STATUS; import android.content.Context; import android.net.IIpSecService; import android.net.INetd; +import android.net.IpSecAlgorithm; +import android.net.IpSecConfig; +import android.net.IpSecManager; +import android.net.IpSecTransform; import android.net.util.NetdService; +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.os.ServiceSpecificException; import android.util.Log; import android.util.Slog; - +import android.util.SparseArray; +import com.android.internal.annotations.GuardedBy; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.concurrent.atomic.AtomicInteger; /** @hide */ public class IpSecService extends IIpSecService.Stub { private static final String TAG = "IpSecService"; private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); private static final String NETD_SERVICE_NAME = "netd"; + private static final int[] DIRECTIONS = + new int[] {IpSecTransform.DIRECTION_OUT, IpSecTransform.DIRECTION_IN}; /** Binder context for this service */ private final Context mContext; @@ -42,6 +59,159 @@ public class IpSecService extends IIpSecService.Stub { private static final int NETD_FETCH_TIMEOUT = 5000; //ms + private AtomicInteger mNextResourceId = new AtomicInteger(0x00FADED0); + + private abstract class ManagedResource implements IBinder.DeathRecipient { + final int pid; + final int uid; + private IBinder mBinder; + + ManagedResource(IBinder binder) { + super(); + mBinder = binder; + pid = Binder.getCallingPid(); + uid = Binder.getCallingUid(); + + try { + mBinder.linkToDeath(this, 0); + } catch (RemoteException e) { + binderDied(); + } + } + + /** + * When this record is no longer needed for managing system resources this function should + * unlink all references held by the record to allow efficient garbage collection. + */ + public final void release() { + //Release all the underlying system resources first + releaseResources(); + + if (mBinder != null) { + mBinder.unlinkToDeath(this, 0); + } + mBinder = null; + + //remove this record so that it can be cleaned up + nullifyRecord(); + } + + /** + * If the Binder object dies, this function is called to free the system resources that are + * being managed by this record and to subsequently release this record for garbage + * collection + */ + public final void binderDied() { + release(); + } + + /** + * Implement this method to release all object references contained in the subclass to allow + * efficient garbage collection of the record. This should remove any references to the + * record from all other locations that hold a reference as the record is no longer valid. + */ + protected abstract void nullifyRecord(); + + /** + * Implement this method to release all system resources that are being protected by this + * record. Once the resources are released, the record should be invalidated and no longer + * used by calling releaseRecord() + */ + protected abstract void releaseResources(); + }; + + private final class TransformRecord extends ManagedResource { + private IpSecConfig mConfig; + private int mResourceId; + + TransformRecord(IpSecConfig config, int resourceId, IBinder binder) { + super(binder); + mConfig = config; + mResourceId = resourceId; + } + + public IpSecConfig getConfig() { + return mConfig; + } + + @Override + protected void releaseResources() { + for (int direction : DIRECTIONS) { + try { + getNetdInstance() + .ipSecDeleteSecurityAssociation( + mResourceId, + direction, + (mConfig.getLocalAddress() != null) + ? mConfig.getLocalAddress().getHostAddress() + : "", + (mConfig.getRemoteAddress() != null) + ? mConfig.getRemoteAddress().getHostAddress() + : "", + mConfig.getSpi(direction)); + } catch (ServiceSpecificException e) { + // FIXME: get the error code and throw is at an IOException from Errno Exception + } catch (RemoteException e) { + Log.e(TAG, "Failed to delete SA with ID: " + mResourceId); + } + } + } + + @Override + protected void nullifyRecord() { + mConfig = null; + mResourceId = INVALID_RESOURCE_ID; + } + } + + private final class SpiRecord extends ManagedResource { + private final int mDirection; + private final String mLocalAddress; + private final String mRemoteAddress; + private final IBinder mBinder; + private int mSpi; + private int mResourceId; + + SpiRecord( + int resourceId, + int direction, + String localAddress, + String remoteAddress, + int spi, + IBinder binder) { + super(binder); + mResourceId = resourceId; + mDirection = direction; + mLocalAddress = localAddress; + mRemoteAddress = remoteAddress; + mSpi = spi; + mBinder = binder; + } + + protected void releaseResources() { + try { + getNetdInstance() + .ipSecDeleteSecurityAssociation( + mResourceId, mDirection, mLocalAddress, mRemoteAddress, mSpi); + } catch (ServiceSpecificException e) { + // FIXME: get the error code and throw is at an IOException from Errno Exception + } catch (RemoteException e) { + Log.e(TAG, "Failed to delete SPI reservation with ID: " + mResourceId); + } + } + + protected void nullifyRecord() { + mSpi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX; + mResourceId = INVALID_RESOURCE_ID; + } + } + + @GuardedBy("mSpiRecords") + private final SparseArray<SpiRecord> mSpiRecords = new SparseArray<>(); + + @GuardedBy("mTransformRecords") + private final SparseArray<TransformRecord> mTransformRecords = new SparseArray<>(); + /** * Constructs a new IpSecService instance * @@ -80,22 +250,21 @@ public class IpSecService extends IIpSecService.Stub { t.run(); } - INetd getNetdInstance() { + INetd getNetdInstance() throws RemoteException { final INetd netd = NetdService.getInstance(); if (netd == null) { - throw new RemoteException("Failed to Get Netd Instance").rethrowFromSystemServer(); + throw new RemoteException("Failed to Get Netd Instance"); } return netd; } boolean isNetdAlive() { synchronized (mLock) { - final INetd netd = getNetdInstance(); - if (netd == null) { - return false; - } - try { + final INetd netd = getNetdInstance(); + if (netd == null) { + return false; + } return netd.isAlive(); } catch (RemoteException re) { return false; @@ -104,6 +273,217 @@ public class IpSecService extends IIpSecService.Stub { } @Override + /** Get a new SPI and maintain the reservation in the system server */ + public Bundle reserveSecurityParameterIndex( + int direction, String remoteAddress, int requestedSpi, IBinder binder) + throws RemoteException { + int resourceId = mNextResourceId.getAndIncrement(); + + int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX; + String localAddress = ""; + Bundle retBundle = new Bundle(3); + try { + spi = + getNetdInstance() + .ipSecAllocateSpi( + resourceId, + direction, + localAddress, + remoteAddress, + requestedSpi); + Log.d(TAG, "Allocated SPI " + spi); + retBundle.putInt(KEY_STATUS, IpSecManager.Status.OK); + retBundle.putInt(KEY_RESOURCE_ID, resourceId); + retBundle.putInt(KEY_SPI, spi); + synchronized (mSpiRecords) { + mSpiRecords.put( + resourceId, + new SpiRecord( + resourceId, direction, localAddress, remoteAddress, spi, binder)); + } + } catch (ServiceSpecificException e) { + // TODO: Add appropriate checks when other ServiceSpecificException types are supported + retBundle.putInt(KEY_STATUS, IpSecManager.Status.SPI_UNAVAILABLE); + retBundle.putInt(KEY_RESOURCE_ID, resourceId); + retBundle.putInt(KEY_SPI, spi); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return retBundle; + } + + /** Release a previously allocated SPI that has been registered with the system server */ + @Override + public void releaseSecurityParameterIndex(int resourceId) throws RemoteException {} + + /** + * Open a socket via the system server and bind it to the specified port (random if port=0). + * This will return a PFD to the user that represent a bound UDP socket. The system server will + * cache the socket and a record of its owner so that it can and must be freed when no longer + * needed. + */ + @Override + public Bundle openUdpEncapsulationSocket(int port, IBinder binder) throws RemoteException { + return null; + } + + /** close a socket that has been been allocated by and registered with the system server */ + @Override + public void closeUdpEncapsulationSocket(ParcelFileDescriptor socket) {} + + /** + * Create a transport mode transform, which represent two security associations (one in each + * direction) in the kernel. The transform will be cached by the system server and must be freed + * when no longer needed. It is possible to free one, deleting the SA from underneath sockets + * that are using it, which will result in all of those sockets becoming unable to send or + * receive data. + */ + @Override + public Bundle createTransportModeTransform(IpSecConfig c, IBinder binder) + throws RemoteException { + // TODO: Basic input validation here since it's coming over the Binder + int resourceId = mNextResourceId.getAndIncrement(); + for (int direction : DIRECTIONS) { + IpSecAlgorithm auth = c.getAuthentication(direction); + IpSecAlgorithm crypt = c.getEncryption(direction); + try { + int result = + getNetdInstance() + .ipSecAddSecurityAssociation( + resourceId, + c.getMode(), + direction, + (c.getLocalAddress() != null) + ? c.getLocalAddress().getHostAddress() + : "", + (c.getRemoteAddress() != null) + ? c.getRemoteAddress().getHostAddress() + : "", + (c.getNetwork() != null) + ? c.getNetwork().getNetworkHandle() + : 0, + c.getSpi(direction), + (auth != null) ? auth.getName() : "", + (auth != null) ? auth.getKey() : null, + (auth != null) ? auth.getTruncationLengthBits() : 0, + (crypt != null) ? crypt.getName() : "", + (crypt != null) ? crypt.getKey() : null, + (crypt != null) ? crypt.getTruncationLengthBits() : 0, + c.getEncapType(), + c.getEncapLocalPort(), + c.getEncapRemotePort()); + if (result != c.getSpi(direction)) { + // TODO: cleanup the first SA if creation of second SA fails + Bundle retBundle = new Bundle(2); + retBundle.putInt(KEY_STATUS, IpSecManager.Status.SPI_UNAVAILABLE); + retBundle.putInt(KEY_RESOURCE_ID, INVALID_RESOURCE_ID); + return retBundle; + } + } catch (ServiceSpecificException e) { + // FIXME: get the error code and throw is at an IOException from Errno Exception + } + } + synchronized (mTransformRecords) { + mTransformRecords.put(resourceId, new TransformRecord(c, resourceId, binder)); + } + + Bundle retBundle = new Bundle(2); + retBundle.putInt(KEY_STATUS, IpSecManager.Status.OK); + retBundle.putInt(KEY_RESOURCE_ID, resourceId); + return retBundle; + } + + /** + * 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 + * return an error. It's safe to de-allocate transforms that may have already been deleted for + * other reasons. + */ + @Override + public void deleteTransportModeTransform(int resourceId) throws RemoteException { + synchronized (mTransformRecords) { + TransformRecord record; + // We want to non-destructively get so that we can check credentials before removing + // this from the records. + record = mTransformRecords.get(resourceId); + + if (record == null) { + throw new IllegalArgumentException( + "Transform " + resourceId + " is not available to be deleted"); + } + + if (record.pid != Binder.getCallingPid() || record.uid != Binder.getCallingUid()) { + throw new SecurityException("Only the owner of an IpSec Transform may delete it!"); + } + + // TODO: if releaseResources() throws RemoteException, we can try again to clean up on + // binder death. Need to make sure that path is actually functional. + record.releaseResources(); + mTransformRecords.remove(resourceId); + record.nullifyRecord(); + } + } + + /** + * Apply an active transport mode transform to a socket, which will apply the IPsec security + * association as a correspondent policy to the provided socket + */ + @Override + public void applyTransportModeTransform(ParcelFileDescriptor socket, int resourceId) + throws RemoteException { + + synchronized (mTransformRecords) { + TransformRecord info; + // FIXME: this code should be factored out into a security check + getter + info = mTransformRecords.get(resourceId); + + if (info == null) { + throw new IllegalArgumentException("Transform " + resourceId + " is not active"); + } + + // TODO: make this a function. + if (info.pid != getCallingPid() || info.uid != getCallingUid()) { + throw new SecurityException("Only the owner of an IpSec Transform may apply it!"); + } + + IpSecConfig c = info.getConfig(); + try { + for (int direction : DIRECTIONS) { + getNetdInstance() + .ipSecApplyTransportModeTransform( + socket.getFileDescriptor(), + resourceId, + direction, + (c.getLocalAddress() != null) + ? c.getLocalAddress().getHostAddress() + : "", + (c.getRemoteAddress() != null) + ? c.getRemoteAddress().getHostAddress() + : "", + c.getSpi(direction)); + } + } catch (ServiceSpecificException e) { + // FIXME: get the error code and throw is at an IOException from Errno Exception + } + } + } + /** + * Remove a transport mode transform from a socket, applying the default (empty) policy. This + * will ensure that NO IPsec policy is applied to the socket (would be the equivalent of + * applying a policy that performs no IPsec). Today the resourceId parameter is passed but not + * used: reserved for future improved input validation. + */ + @Override + public void removeTransportModeTransform(ParcelFileDescriptor socket, int resourceId) + throws RemoteException { + try { + getNetdInstance().ipSecRemoveTransportModeTransform(socket.getFileDescriptor()); + } catch (ServiceSpecificException e) { + // FIXME: get the error code and throw is at an IOException from Errno Exception + } + } + + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { mContext.enforceCallingOrSelfPermission(DUMP, TAG); |