Add UDP Encap Socket Support to IpSecManager

-Implement the UdpEncapsulationSocket
-Convert all ManagedResources to use resourceIds
-Rework ManagedResource to track resourceIds

Bug: 30984788
Test: cts - IpSecManagerTest#testUdpEncapsulation()

Change-Id: I7b1099c487051a8d951c1485791c4b6cef2deb1d
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index a7ce95b..ec275cc 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -18,9 +18,10 @@
 
 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 static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static com.android.internal.util.Preconditions.checkNotNull;
 
 import android.content.Context;
 import android.net.IIpSecService;
@@ -28,47 +29,92 @@
 import android.net.IpSecAlgorithm;
 import android.net.IpSecConfig;
 import android.net.IpSecManager;
+import android.net.IpSecSpiResponse;
 import android.net.IpSecTransform;
+import android.net.IpSecTransformResponse;
+import android.net.IpSecUdpEncapResponse;
 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.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
 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.IOException;
 import java.io.PrintWriter;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
 import java.util.concurrent.atomic.AtomicInteger;
+import libcore.io.IoUtils;
 
 /** @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 static final int NETD_FETCH_TIMEOUT = 5000; //ms
+    private static final int MAX_PORT_BIND_ATTEMPTS = 10;
+    private static final InetAddress INADDR_ANY;
+
+    static {
+        try {
+            INADDR_ANY = InetAddress.getByAddress(new byte[] {0, 0, 0, 0});
+        } catch (UnknownHostException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    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
+
+    /* Binder context for this service */
     private final Context mContext;
 
-    private Object mLock = new Object();
+    /** Should be a never-repeating global ID for resources */
+    private static AtomicInteger mNextResourceId = new AtomicInteger(0x00FADED0);
 
-    private static final int NETD_FETCH_TIMEOUT = 5000; //ms
+    @GuardedBy("this")
+    private final ManagedResourceArray<SpiRecord> mSpiRecords = new ManagedResourceArray<>();
 
-    private AtomicInteger mNextResourceId = new AtomicInteger(0x00FADED0);
+    @GuardedBy("this")
+    private final ManagedResourceArray<TransformRecord> mTransformRecords =
+            new ManagedResourceArray<>();
 
+    @GuardedBy("this")
+    private final ManagedResourceArray<UdpSocketRecord> mUdpSocketRecords =
+            new ManagedResourceArray<>();
+
+    /**
+     * The ManagedResource class provides a facility to cleanly and reliably release system
+     * resources. It relies on two things: an IBinder that allows ManagedResource to automatically
+     * clean up in the event that the Binder dies and a user-provided resourceId that should
+     * uniquely identify the managed resource. To use this class, the user should implement the
+     * releaseResources() method that is responsible for releasing system resources when invoked.
+     */
     private abstract class ManagedResource implements IBinder.DeathRecipient {
         final int pid;
         final int uid;
         private IBinder mBinder;
+        protected int mResourceId;
 
-        ManagedResource(IBinder binder) {
+        private AtomicInteger mReferenceCount = new AtomicInteger(0);
+
+        ManagedResource(int resourceId, IBinder binder) {
             super();
             mBinder = binder;
+            mResourceId = resourceId;
             pid = Binder.getCallingPid();
             uid = Binder.getCallingUid();
 
@@ -79,21 +125,53 @@
             }
         }
 
+        public void addReference() {
+            mReferenceCount.incrementAndGet();
+        }
+
+        public void removeReference() {
+            if (mReferenceCount.decrementAndGet() < 0) {
+                Log.wtf(TAG, "Programming error: negative reference count");
+            }
+        }
+
+        public boolean isReferenced() {
+            return (mReferenceCount.get() > 0);
+        }
+
+        public void checkOwnerOrSystemAndThrow() {
+            if (uid != Binder.getCallingUid()
+                    && android.os.Process.SYSTEM_UID != Binder.getCallingUid()) {
+                throw new SecurityException("Only the owner may access managed resources!");
+            }
+        }
+
         /**
          * 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.
+         * clean up all system resources and nullify the record. This function shall perform all
+         * necessary cleanup of the resources managed by this record.
+         *
+         * <p>NOTE: this function verifies ownership before allowing resources to be freed.
          */
-        public final void release() {
-            //Release all the underlying system resources first
-            releaseResources();
+        public final void release() throws RemoteException {
+            synchronized (IpSecService.this) {
+                if (isReferenced()) {
+                    throw new IllegalStateException(
+                            "Cannot release a resource that has active references!");
+                }
 
-            if (mBinder != null) {
-                mBinder.unlinkToDeath(this, 0);
+                if (mResourceId == INVALID_RESOURCE_ID) {
+                    return;
+                }
+
+                releaseResources();
+                if (mBinder != null) {
+                    mBinder.unlinkToDeath(this, 0);
+                }
+                mBinder = null;
+
+                mResourceId = INVALID_RESOURCE_ID;
             }
-            mBinder = null;
-
-            //remove this record so that it can be cleaned up
-            nullifyRecord();
         }
 
         /**
@@ -102,41 +180,85 @@
          * collection
          */
         public final void binderDied() {
-            release();
+            try {
+                release();
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to release resource: " + e);
+            }
         }
 
         /**
-         * 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()
+         * used by calling release(). This should NEVER be called directly.
+         *
+         * <p>Calls to this are always guarded by IpSecService#this
          */
-        protected abstract void releaseResources();
+        protected abstract void releaseResources() throws RemoteException;
     };
 
-    private final class TransformRecord extends ManagedResource {
-        private IpSecConfig mConfig;
-        private int mResourceId;
+    /**
+     * Minimal wrapper around SparseArray that performs ownership
+     * validation on element accesses.
+     */
+    private class ManagedResourceArray<T extends ManagedResource> {
+        SparseArray<T> mArray = new SparseArray<>();
 
-        TransformRecord(IpSecConfig config, int resourceId, IBinder binder) {
-            super(binder);
+        T get(int key) {
+            T val = mArray.get(key);
+            val.checkOwnerOrSystemAndThrow();
+            return val;
+        }
+
+        void put(int key, T obj) {
+            checkNotNull(obj, "Null resources cannot be added");
+            mArray.put(key, obj);
+        }
+
+        void remove(int key) {
+            mArray.remove(key);
+        }
+    }
+
+    private final class TransformRecord extends ManagedResource {
+        private final IpSecConfig mConfig;
+        private final SpiRecord[] mSpis;
+        private final UdpSocketRecord mSocket;
+
+        TransformRecord(
+                int resourceId,
+                IBinder binder,
+                IpSecConfig config,
+                SpiRecord[] spis,
+                UdpSocketRecord socket) {
+            super(resourceId, binder);
             mConfig = config;
-            mResourceId = resourceId;
+            mSpis = spis;
+            mSocket = socket;
+
+            for (int direction : DIRECTIONS) {
+                mSpis[direction].addReference();
+                mSpis[direction].setOwnedByTransform();
+            }
+
+            if (mSocket != null) {
+                mSocket.addReference();
+            }
         }
 
         public IpSecConfig getConfig() {
             return mConfig;
         }
 
+        public SpiRecord getSpiRecord(int direction) {
+            return mSpis[direction];
+        }
+
+        /** always guarded by IpSecService#this */
         @Override
         protected void releaseResources() {
             for (int direction : DIRECTIONS) {
+                int spi = mSpis[direction].getSpi();
                 try {
                     getNetdInstance()
                             .ipSecDeleteSecurityAssociation(
@@ -148,19 +270,21 @@
                                     (mConfig.getRemoteAddress() != null)
                                             ? mConfig.getRemoteAddress().getHostAddress()
                                             : "",
-                                    mConfig.getSpi(direction));
+                                    spi);
                 } 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;
+            for (int direction : DIRECTIONS) {
+                mSpis[direction].removeReference();
+            }
+
+            if (mSocket != null) {
+                mSocket.removeReference();
+            }
         }
     }
 
@@ -168,27 +292,37 @@
         private final int mDirection;
         private final String mLocalAddress;
         private final String mRemoteAddress;
-        private final IBinder mBinder;
         private int mSpi;
-        private int mResourceId;
+
+        private boolean mOwnedByTransform = false;
 
         SpiRecord(
                 int resourceId,
+                IBinder binder,
                 int direction,
                 String localAddress,
                 String remoteAddress,
-                int spi,
-                IBinder binder) {
-            super(binder);
-            mResourceId = resourceId;
+                int spi) {
+            super(resourceId, binder);
             mDirection = direction;
             mLocalAddress = localAddress;
             mRemoteAddress = remoteAddress;
             mSpi = spi;
-            mBinder = binder;
         }
 
+        /** always guarded by IpSecService#this */
+        @Override
         protected void releaseResources() {
+            if (mOwnedByTransform) {
+                Log.d(TAG, "Cannot release Spi " + mSpi + ": Currently locked by a Transform");
+                // Because SPIs are "handed off" to transform, objects, they should never be
+                // freed from the SpiRecord once used in a transform. (They refer to the same SA,
+                // thus ownership and responsibility for freeing these resources passes to the
+                // Transform object). Thus, we should let the user free them without penalty once
+                // they are applied in a Transform object.
+                return;
+            }
+
             try {
                 getNetdInstance()
                         .ipSecDeleteSecurityAssociation(
@@ -198,19 +332,50 @@
             } catch (RemoteException e) {
                 Log.e(TAG, "Failed to delete SPI reservation with ID: " + mResourceId);
             }
+
+            mSpi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
         }
 
-        protected void nullifyRecord() {
-            mSpi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
-            mResourceId = INVALID_RESOURCE_ID;
+        public int getSpi() {
+            return mSpi;
+        }
+
+        public void setOwnedByTransform() {
+            if (mOwnedByTransform) {
+                // Programming error
+                new IllegalStateException("Cannot own an SPI twice!");
+            }
+
+            mOwnedByTransform = true;
         }
     }
 
-    @GuardedBy("mSpiRecords")
-    private final SparseArray<SpiRecord> mSpiRecords = new SparseArray<>();
+    private final class UdpSocketRecord extends ManagedResource {
+        private FileDescriptor mSocket;
+        private final int mPort;
 
-    @GuardedBy("mTransformRecords")
-    private final SparseArray<TransformRecord> mTransformRecords = new SparseArray<>();
+        UdpSocketRecord(int resourceId, IBinder binder, FileDescriptor socket, int port) {
+            super(resourceId, binder);
+            mSocket = socket;
+            mPort = port;
+        }
+
+        /** always guarded by IpSecService#this */
+        @Override
+        protected void releaseResources() {
+            Log.d(TAG, "Closing port " + mPort);
+            IoUtils.closeQuietly(mSocket);
+            mSocket = null;
+        }
+
+        public int getPort() {
+            return mPort;
+        }
+
+        public FileDescriptor getSocket() {
+            return mSocket;
+        }
+    }
 
     /**
      * Constructs a new IpSecService instance
@@ -242,7 +407,7 @@
                         new Runnable() {
                             @Override
                             public void run() {
-                                synchronized (mLock) {
+                                synchronized (IpSecService.this) {
                                     NetdService.get(NETD_FETCH_TIMEOUT);
                                 }
                             }
@@ -258,30 +423,27 @@
         return netd;
     }
 
-    boolean isNetdAlive() {
-        synchronized (mLock) {
-            try {
-                final INetd netd = getNetdInstance();
-                if (netd == null) {
-                    return false;
-                }
-                return netd.isAlive();
-            } catch (RemoteException re) {
+    synchronized boolean isNetdAlive() {
+        try {
+            final INetd netd = getNetdInstance();
+            if (netd == null) {
                 return false;
             }
+            return netd.isAlive();
+        } catch (RemoteException re) {
+            return false;
         }
     }
 
     @Override
     /** Get a new SPI and maintain the reservation in the system server */
-    public Bundle reserveSecurityParameterIndex(
+    public synchronized IpSecSpiResponse 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()
@@ -292,29 +454,78 @@
                                     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));
-            }
+            mSpiRecords.put(
+                    resourceId,
+                    new SpiRecord(resourceId, binder, direction, localAddress, remoteAddress, spi));
         } 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);
+            return new IpSecSpiResponse(
+                    IpSecManager.Status.SPI_UNAVAILABLE, IpSecManager.INVALID_RESOURCE_ID, spi);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
-        return retBundle;
+        return new IpSecSpiResponse(IpSecManager.Status.OK, resourceId, spi);
+    }
+
+    /* 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.
+     */
+    private synchronized <T extends ManagedResource> void releaseManagedResource(
+            ManagedResourceArray<T> resArray, int resourceId, String typeName)
+            throws RemoteException {
+        // We want to non-destructively get so that we can check credentials before removing
+        // this from the records.
+        T record = resArray.get(resourceId);
+
+        if (record == null) {
+            throw new IllegalArgumentException(
+                    typeName + " " + resourceId + " is not available to be deleted");
+        }
+
+        record.release();
+        resArray.remove(resourceId);
     }
 
     /** Release a previously allocated SPI that has been registered with the system server */
     @Override
-    public void releaseSecurityParameterIndex(int resourceId) throws RemoteException {}
+    public void releaseSecurityParameterIndex(int resourceId) throws RemoteException {
+        releaseManagedResource(mSpiRecords, resourceId, "SecurityParameterIndex");
+    }
+
+    /**
+     * This function finds and forcibly binds to a random system port, ensuring that the port cannot
+     * be unbound.
+     *
+     * <p>A socket cannot be un-bound from a port if it was bound to that port by number. To select
+     * a random open port and then bind by number, this function creates a temp socket, binds to a
+     * random port (specifying 0), gets that port number, and then uses is to bind the user's UDP
+     * Encapsulation Socket forcibly, so that it cannot be un-bound by the user with the returned
+     * FileHandle.
+     *
+     * <p>The loop in this function handles the inherent race window between un-binding to a port
+     * and re-binding, during which the system could *technically* hand that port out to someone
+     * else.
+     */
+    private void bindToRandomPort(FileDescriptor sockFd) throws IOException {
+        for (int i = MAX_PORT_BIND_ATTEMPTS; i > 0; i--) {
+            try {
+                FileDescriptor probeSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+                Os.bind(probeSocket, INADDR_ANY, 0);
+                int port = ((InetSocketAddress) Os.getsockname(probeSocket)).getPort();
+                Os.close(probeSocket);
+                Log.v(TAG, "Binding to port " + port);
+                Os.bind(sockFd, INADDR_ANY, port);
+                return;
+            } catch (ErrnoException e) {
+                // Someone miraculously claimed the port just after we closed probeSocket.
+                if (e.errno == OsConstants.EADDRINUSE) {
+                    continue;
+                }
+                throw e.rethrowAsIOException();
+            }
+        }
+        throw new IOException("Failed " + MAX_PORT_BIND_ATTEMPTS + " attempts to bind to a port");
+    }
 
     /**
      * Open a socket via the system server and bind it to the specified port (random if port=0).
@@ -323,13 +534,47 @@
      * needed.
      */
     @Override
-    public Bundle openUdpEncapsulationSocket(int port, IBinder binder) throws RemoteException {
-        return null;
+    public synchronized IpSecUdpEncapResponse openUdpEncapsulationSocket(int port, IBinder binder)
+            throws RemoteException {
+        if (port != 0 && (port < FREE_PORT_MIN || port > PORT_MAX)) {
+            throw new IllegalArgumentException(
+                    "Specified port number must be a valid non-reserved UDP port");
+        }
+        int resourceId = mNextResourceId.getAndIncrement();
+        FileDescriptor sockFd = null;
+        try {
+            sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+
+            if (port != 0) {
+                Log.v(TAG, "Binding to port " + port);
+                Os.bind(sockFd, INADDR_ANY, port);
+            } else {
+                bindToRandomPort(sockFd);
+            }
+            // This code is common to both the unspecified and specified port cases
+            Os.setsockoptInt(
+                    sockFd,
+                    OsConstants.IPPROTO_UDP,
+                    OsConstants.UDP_ENCAP,
+                    OsConstants.UDP_ENCAP_ESPINUDP);
+
+            mUdpSocketRecords.put(
+                    resourceId, new UdpSocketRecord(resourceId, binder, sockFd, port));
+            return new IpSecUdpEncapResponse(IpSecManager.Status.OK, resourceId, port, sockFd);
+        } catch (IOException | ErrnoException e) {
+            IoUtils.closeQuietly(sockFd);
+        }
+        // If we make it to here, then something has gone wrong and we couldn't open a socket.
+        // The only reasonable condition that would cause that is resource unavailable.
+        return new IpSecUdpEncapResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
     }
 
     /** close a socket that has been been allocated by and registered with the system server */
     @Override
-    public void closeUdpEncapsulationSocket(ParcelFileDescriptor socket) {}
+    public void closeUdpEncapsulationSocket(int resourceId) throws RemoteException {
+
+        releaseManagedResource(mUdpSocketRecords, resourceId, "UdpEncapsulationSocket");
+    }
 
     /**
      * Create a transport mode transform, which represent two security associations (one in each
@@ -339,13 +584,26 @@
      * receive data.
      */
     @Override
-    public Bundle createTransportModeTransform(IpSecConfig c, IBinder binder)
-            throws RemoteException {
-        // TODO: Basic input validation here since it's coming over the Binder
+    public synchronized IpSecTransformResponse createTransportModeTransform(
+            IpSecConfig c, IBinder binder) throws RemoteException {
         int resourceId = mNextResourceId.getAndIncrement();
+        SpiRecord[] spis = new SpiRecord[DIRECTIONS.length];
+        // TODO: Basic input validation here since it's coming over the Binder
+        int encapType, encapLocalPort = 0, encapRemotePort = 0;
+        UdpSocketRecord socketRecord = null;
+        encapType = c.getEncapType();
+        if (encapType != IpSecTransform.ENCAP_NONE) {
+            socketRecord = mUdpSocketRecords.get(c.getEncapLocalResourceId());
+            encapLocalPort = socketRecord.getPort();
+            encapRemotePort = c.getEncapRemotePort();
+        }
+
         for (int direction : DIRECTIONS) {
             IpSecAlgorithm auth = c.getAuthentication(direction);
             IpSecAlgorithm crypt = c.getEncryption(direction);
+
+            spis[direction] = mSpiRecords.get(c.getSpiResourceId(direction));
+            int spi = spis[direction].getSpi();
             try {
                 int result =
                         getNetdInstance()
@@ -362,35 +620,29 @@
                                         (c.getNetwork() != null)
                                                 ? c.getNetwork().getNetworkHandle()
                                                 : 0,
-                                        c.getSpi(direction),
+                                        spi,
                                         (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)) {
+                                        encapType,
+                                        encapLocalPort,
+                                        encapRemotePort);
+                if (result != spi) {
                     // 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;
+                    return new IpSecTransformResponse(
+                            IpSecManager.Status.SPI_UNAVAILABLE, INVALID_RESOURCE_ID);
                 }
             } 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;
+        // Both SAs were created successfully, time to construct a record and lock it away
+        mTransformRecords.put(
+                resourceId, new TransformRecord(resourceId, binder, c, spis, socketRecord));
+        return new IpSecTransformResponse(IpSecManager.Status.OK, resourceId);
     }
 
     /**
@@ -401,27 +653,7 @@
      */
     @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();
-        }
+        releaseManagedResource(mTransformRecords, resourceId, "IpSecTransform");
     }
 
     /**
@@ -429,44 +661,43 @@
      * association as a correspondent policy to the provided socket
      */
     @Override
-    public void applyTransportModeTransform(ParcelFileDescriptor socket, int resourceId)
-            throws RemoteException {
+    public synchronized void applyTransportModeTransform(
+            ParcelFileDescriptor socket, int resourceId) throws RemoteException {
+        // Synchronize liberally here because we are using ManagedResources in this block
+        TransformRecord info;
+        // FIXME: this code should be factored out into a security check + getter
+        info = mTransformRecords.get(resourceId);
 
-        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");
+        }
 
-            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()
+                                        : "",
+                                info.getSpiRecord(direction).getSpi());
             }
-
-            // 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
-            }
+        } 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
@@ -486,7 +717,7 @@
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         mContext.enforceCallingOrSelfPermission(DUMP, TAG);
-
+        // TODO: Add dump code to print out a log of all the resources being tracked
         pw.println("IpSecService Log:");
         pw.println("NetdNativeService Connection: " + (isNetdAlive() ? "alive" : "dead"));
         pw.println();