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/core/java/android/net/IIpSecService.aidl b/core/java/android/net/IIpSecService.aidl
index 0aa3ce6..0b1ea98 100644
--- a/core/java/android/net/IIpSecService.aidl
+++ b/core/java/android/net/IIpSecService.aidl
@@ -18,6 +18,9 @@
 
 import android.net.Network;
 import android.net.IpSecConfig;
+import android.net.IpSecUdpEncapResponse;
+import android.net.IpSecSpiResponse;
+import android.net.IpSecTransformResponse;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
@@ -27,16 +30,16 @@
  */
 interface IIpSecService
 {
-    Bundle reserveSecurityParameterIndex(
+    IpSecSpiResponse reserveSecurityParameterIndex(
             int direction, in String remoteAddress, int requestedSpi, in IBinder binder);
 
     void releaseSecurityParameterIndex(int resourceId);
 
-    Bundle openUdpEncapsulationSocket(int port, in IBinder binder);
+    IpSecUdpEncapResponse openUdpEncapsulationSocket(int port, in IBinder binder);
 
-    void closeUdpEncapsulationSocket(in ParcelFileDescriptor socket);
+    void closeUdpEncapsulationSocket(int resourceId);
 
-    Bundle createTransportModeTransform(in IpSecConfig c, in IBinder binder);
+    IpSecTransformResponse createTransportModeTransform(in IpSecConfig c, in IBinder binder);
 
     void deleteTransportModeTransform(int transformId);
 
diff --git a/core/java/android/net/IpSecConfig.java b/core/java/android/net/IpSecConfig.java
index 13dc19f..8b80f2b 100644
--- a/core/java/android/net/IpSecConfig.java
+++ b/core/java/android/net/IpSecConfig.java
@@ -40,7 +40,7 @@
         // Minimum requirements for identifying a transform
         // SPI identifying the IPsec flow in packet processing
         // and a remote IP address
-        int spi;
+        int spiResourceId;
 
         // Encryption Algorithm
         IpSecAlgorithm encryption;
@@ -54,7 +54,7 @@
     // For tunnel mode IPv4 UDP Encapsulation
     // IpSecTransform#ENCAP_ESP_*, such as ENCAP_ESP_OVER_UDP_IKE
     int encapType;
-    int encapLocalPort;
+    int encapLocalPortResourceId;
     int encapRemotePort;
 
     // An interval, in seconds between the NattKeepalive packets
@@ -69,8 +69,8 @@
         return localAddress;
     }
 
-    public int getSpi(int direction) {
-        return flow[direction].spi;
+    public int getSpiResourceId(int direction) {
+        return flow[direction].spiResourceId;
     }
 
     public InetAddress getRemoteAddress() {
@@ -93,8 +93,8 @@
         return encapType;
     }
 
-    public int getEncapLocalPort() {
-        return encapLocalPort;
+    public int getEncapLocalResourceId() {
+        return encapLocalPortResourceId;
     }
 
     public int getEncapRemotePort() {
@@ -119,14 +119,14 @@
         // 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.writeInt(flow[IpSecTransform.DIRECTION_IN].spiResourceId);
         out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].encryption, flags);
         out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].authentication, flags);
-        out.writeInt(flow[IpSecTransform.DIRECTION_OUT].spi);
+        out.writeInt(flow[IpSecTransform.DIRECTION_OUT].spiResourceId);
         out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].encryption, flags);
         out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].authentication, flags);
         out.writeInt(encapType);
-        out.writeInt(encapLocalPort);
+        out.writeInt(encapLocalPortResourceId);
         out.writeInt(encapRemotePort);
     }
 
@@ -151,18 +151,18 @@
         localAddress = readInetAddressFromParcel(in);
         remoteAddress = readInetAddressFromParcel(in);
         network = (Network) in.readParcelable(Network.class.getClassLoader());
-        flow[IpSecTransform.DIRECTION_IN].spi = in.readInt();
+        flow[IpSecTransform.DIRECTION_IN].spiResourceId = in.readInt();
         flow[IpSecTransform.DIRECTION_IN].encryption =
                 (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
         flow[IpSecTransform.DIRECTION_IN].authentication =
                 (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
-        flow[IpSecTransform.DIRECTION_OUT].spi = in.readInt();
+        flow[IpSecTransform.DIRECTION_OUT].spiResourceId = in.readInt();
         flow[IpSecTransform.DIRECTION_OUT].encryption =
                 (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
         flow[IpSecTransform.DIRECTION_OUT].authentication =
                 (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
         encapType = in.readInt();
-        encapLocalPort = in.readInt();
+        encapLocalPortResourceId = in.readInt();
         encapRemotePort = in.readInt();
     }
 
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index 114e46e..0240cf1 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -19,10 +19,10 @@
 
 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 android.util.Log;
 import dalvik.system.CloseGuard;
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -58,12 +58,6 @@
     }
 
     /** @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;
 
     /**
@@ -128,7 +122,11 @@
          */
         @Override
         public void close() {
-            mSpi = INVALID_SECURITY_PARAMETER_INDEX;
+            try {
+                mService.releaseSecurityParameterIndex(mResourceId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
             mCloseGuard.close();
         }
 
@@ -147,7 +145,7 @@
             mService = service;
             mRemoteAddress = remoteAddress;
             try {
-                Bundle result =
+                IpSecSpiResponse result =
                         mService.reserveSecurityParameterIndex(
                                 direction, remoteAddress.getHostAddress(), spi, new Binder());
 
@@ -155,7 +153,7 @@
                     throw new NullPointerException("Received null response from IpSecService");
                 }
 
-                int status = result.getInt(KEY_STATUS);
+                int status = result.status;
                 switch (status) {
                     case Status.OK:
                         break;
@@ -168,8 +166,8 @@
                         throw new RuntimeException(
                                 "Unknown status returned by IpSecService: " + status);
                 }
-                mSpi = result.getInt(KEY_SPI);
-                mResourceId = result.getInt(KEY_RESOURCE_ID);
+                mSpi = result.spi;
+                mResourceId = result.resourceId;
 
                 if (mSpi == INVALID_SECURITY_PARAMETER_INDEX) {
                     throw new RuntimeException("Invalid SPI returned by IpSecService: " + status);
@@ -185,6 +183,11 @@
             }
             mCloseGuard.open("open");
         }
+
+        /** @hide */
+        int getResourceId() {
+            return mResourceId;
+        }
     }
 
     /**
@@ -201,8 +204,7 @@
      * @throws SpiUnavailableException indicating that a particular SPI cannot be reserved
      */
     public SecurityParameterIndex reserveSecurityParameterIndex(
-            int direction, InetAddress remoteAddress)
-            throws ResourceUnavailableException {
+            int direction, InetAddress remoteAddress) throws ResourceUnavailableException {
         try {
             return new SecurityParameterIndex(
                     mService,
@@ -251,7 +253,9 @@
      */
     public void applyTransportModeTransform(Socket socket, IpSecTransform transform)
             throws IOException {
-        applyTransportModeTransform(ParcelFileDescriptor.fromSocket(socket), transform);
+        try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket)) {
+            applyTransportModeTransform(pfd, transform);
+        }
     }
 
     /**
@@ -269,15 +273,8 @@
      */
     public void applyTransportModeTransform(DatagramSocket socket, IpSecTransform transform)
             throws IOException {
-        applyTransportModeTransform(ParcelFileDescriptor.fromDatagramSocket(socket), transform);
-    }
-
-    /* Call down to activate a transform */
-    private void applyTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {
-        try {
-            mService.applyTransportModeTransform(pfd, transform.getResourceId());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
+        try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(socket)) {
+            applyTransportModeTransform(pfd, transform);
         }
     }
 
@@ -295,7 +292,22 @@
      */
     public void applyTransportModeTransform(FileDescriptor socket, IpSecTransform transform)
             throws IOException {
-        applyTransportModeTransform(new ParcelFileDescriptor(socket), transform);
+        // We dup() the FileDescriptor here because if we don't, then the ParcelFileDescriptor()
+        // constructor takes control and closes the user's FD when we exit the method
+        // This is behaviorally the same as the other versions, but the PFD constructor does not
+        // dup() automatically, whereas PFD.fromSocket() and PDF.fromDatagramSocket() do dup().
+        try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(socket)) {
+            applyTransportModeTransform(pfd, transform);
+        }
+    }
+
+    /* Call down to activate a transform */
+    private void applyTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {
+        try {
+            mService.applyTransportModeTransform(pfd, transform.getResourceId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -324,7 +336,9 @@
      */
     public void removeTransportModeTransform(Socket socket, IpSecTransform transform)
             throws IOException {
-        removeTransportModeTransform(ParcelFileDescriptor.fromSocket(socket), transform);
+        try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket)) {
+            removeTransportModeTransform(pfd, transform);
+        }
     }
 
     /**
@@ -340,7 +354,9 @@
      */
     public void removeTransportModeTransform(DatagramSocket socket, IpSecTransform transform)
             throws IOException {
-        removeTransportModeTransform(ParcelFileDescriptor.fromDatagramSocket(socket), transform);
+        try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(socket)) {
+            removeTransportModeTransform(pfd, transform);
+        }
     }
 
     /**
@@ -355,7 +371,9 @@
      */
     public void removeTransportModeTransform(FileDescriptor socket, IpSecTransform transform)
             throws IOException {
-        removeTransportModeTransform(new ParcelFileDescriptor(socket), transform);
+        try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(socket)) {
+            removeTransportModeTransform(pfd, transform);
+        }
     }
 
     /* Call down to activate a transform */
@@ -388,33 +406,48 @@
      * FileDescriptor. Instead, disposing of this socket requires a call to close().
      */
     public static final class UdpEncapsulationSocket implements AutoCloseable {
-        private final FileDescriptor mFd;
+        private final ParcelFileDescriptor mPfd;
         private final IIpSecService mService;
+        private final int mResourceId;
+        private final int mPort;
         private final CloseGuard mCloseGuard = CloseGuard.get();
 
         private UdpEncapsulationSocket(@NonNull IIpSecService service, int port)
-                throws ResourceUnavailableException {
+                throws ResourceUnavailableException, IOException {
             mService = service;
+            try {
+                IpSecUdpEncapResponse result =
+                        mService.openUdpEncapsulationSocket(port, new Binder());
+                switch (result.status) {
+                    case Status.OK:
+                        break;
+                    case Status.RESOURCE_UNAVAILABLE:
+                        throw new ResourceUnavailableException(
+                                "No more Sockets may be allocated by this requester.");
+                    default:
+                        throw new RuntimeException(
+                                "Unknown status returned by IpSecService: " + result.status);
+                }
+                mResourceId = result.resourceId;
+                mPort = result.port;
+                mPfd = result.fileDescriptor;
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
             mCloseGuard.open("constructor");
-            // TODO: go down to the kernel and get a socket on the specified
-            mFd = new FileDescriptor();
-        }
-
-        private UdpEncapsulationSocket(IIpSecService service) throws ResourceUnavailableException {
-            mService = service;
-            mCloseGuard.open("constructor");
-            // TODO: go get a random socket on a random port
-            mFd = new FileDescriptor();
         }
 
         /** Access the inner UDP Encapsulation Socket */
         public FileDescriptor getSocket() {
-            return mFd;
+            if (mPfd == null) {
+                return null;
+            }
+            return mPfd.getFileDescriptor();
         }
 
         /** Retrieve the port number of the inner encapsulation socket */
         public int getPort() {
-            return 0; // TODO get the port number from the Socket;
+            return mPort;
         }
 
         @Override
@@ -429,7 +462,18 @@
          * @param fd a file descriptor previously returned as a UDP Encapsulation socket.
          */
         public void close() throws IOException {
-            // TODO: Go close the socket
+            try {
+                mService.closeUdpEncapsulationSocket(mResourceId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+
+            try {
+                mPfd.close();
+            } catch (IOException e) {
+                Log.e(TAG, "Failed to close UDP Encapsulation Socket with Port= " + mPort);
+                throw e;
+            }
             mCloseGuard.close();
         }
 
@@ -438,9 +482,13 @@
             if (mCloseGuard != null) {
                 mCloseGuard.warnIfOpen();
             }
-
             close();
         }
+
+        /** @hide */
+        int getResourceId() {
+            return mResourceId;
+        }
     };
 
     /**
@@ -467,7 +515,13 @@
     // socket.
     public UdpEncapsulationSocket openUdpEncapsulationSocket(int port)
             throws IOException, ResourceUnavailableException {
-        // Temporary code
+        /*
+         * Most range checking is done in the service, but this version of the constructor expects
+         * a valid port number, and zero cannot be checked after being passed to the service.
+         */
+        if (port == 0) {
+            throw new IllegalArgumentException("Specified port must be a valid port number!");
+        }
         return new UdpEncapsulationSocket(mService, port);
     }
 
@@ -491,8 +545,7 @@
     // socket.
     public UdpEncapsulationSocket openUdpEncapsulationSocket()
             throws IOException, ResourceUnavailableException {
-        // Temporary code
-        return new UdpEncapsulationSocket(mService);
+        return new UdpEncapsulationSocket(mService, 0);
     }
 
     /**
diff --git a/core/java/android/net/IpSecSpiResponse.aidl b/core/java/android/net/IpSecSpiResponse.aidl
new file mode 100644
index 0000000..6484a00
--- /dev/null
+++ b/core/java/android/net/IpSecSpiResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2017 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 IpSecSpiResponse;
diff --git a/core/java/android/net/IpSecSpiResponse.java b/core/java/android/net/IpSecSpiResponse.java
new file mode 100644
index 0000000..71dfa03
--- /dev/null
+++ b/core/java/android/net/IpSecSpiResponse.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 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 SPI and corresponding status from the IpSecService to an
+ * IpSecManager.SecurityParameterIndex.
+ *
+ * @hide
+ */
+public final class IpSecSpiResponse implements Parcelable {
+    private static final String TAG = "IpSecSpiResponse";
+
+    public final int resourceId;
+    public final int status;
+    public final int spi;
+    // Parcelable Methods
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(status);
+        out.writeInt(resourceId);
+        out.writeInt(spi);
+    }
+
+    public IpSecSpiResponse(int inStatus, int inResourceId, int inSpi) {
+        status = inStatus;
+        resourceId = inResourceId;
+        spi = inSpi;
+    }
+
+    public IpSecSpiResponse(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;
+        spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
+    }
+
+    private IpSecSpiResponse(Parcel in) {
+        status = in.readInt();
+        resourceId = in.readInt();
+        spi = in.readInt();
+    }
+
+    public static final Parcelable.Creator<IpSecSpiResponse> CREATOR =
+            new Parcelable.Creator<IpSecSpiResponse>() {
+                public IpSecSpiResponse createFromParcel(Parcel in) {
+                    return new IpSecSpiResponse(in);
+                }
+
+                public IpSecSpiResponse[] newArray(int size) {
+                    return new IpSecSpiResponse[size];
+                }
+            };
+}
diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java
index 639d1f2..e65f534 100644
--- a/core/java/android/net/IpSecTransform.java
+++ b/core/java/android/net/IpSecTransform.java
@@ -16,15 +16,12 @@
 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.os.Binder;
-import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -78,23 +75,23 @@
     public static final int ENCAP_NONE = 0;
 
     /**
-     * IpSec traffic will be encapsulated within UDP as per <a
-     * href="https://tools.ietf.org/html/rfc3948">RFC3498</a>.
-     *
-     * @hide
-     */
-    public static final int ENCAP_ESPINUDP = 1;
-
-    /**
      * IpSec traffic will be encapsulated within a UDP header with an additional 8-byte header pad
      * (of '0'-value bytes) that prevents traffic from being interpreted as IKE or as ESP over UDP.
      *
      * @hide
      */
-    public static final int ENCAP_ESPINUDP_NONIKE = 2;
+    public static final int ENCAP_ESPINUDP_NON_IKE = 1;
+
+    /**
+     * IpSec traffic will be encapsulated within UDP as per <a
+     * href="https://tools.ietf.org/html/rfc3948">RFC3498</a>.
+     *
+     * @hide
+     */
+    public static final int ENCAP_ESPINUDP = 2;
 
     /** @hide */
-    @IntDef(value = {ENCAP_NONE, ENCAP_ESPINUDP, ENCAP_ESPINUDP_NONIKE})
+    @IntDef(value = {ENCAP_NONE, ENCAP_ESPINUDP, ENCAP_ESPINUDP_NON_IKE})
     @Retention(RetentionPolicy.SOURCE)
     public @interface EncapType {}
 
@@ -139,10 +136,11 @@
         synchronized (this) {
             try {
                 IIpSecService svc = getIpSecService();
-                Bundle result = svc.createTransportModeTransform(mConfig, new Binder());
-                int status = result.getInt(KEY_STATUS);
+                IpSecTransformResponse result =
+                        svc.createTransportModeTransform(mConfig, new Binder());
+                int status = result.status;
                 checkResultStatusAndThrow(status);
-                mResourceId = result.getInt(KEY_RESOURCE_ID, INVALID_RESOURCE_ID);
+                mResourceId = result.resourceId;
 
                 /* 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
@@ -263,7 +261,10 @@
                             mConfig.getNattKeepaliveInterval(),
                             mKeepaliveCallback,
                             mConfig.getLocalAddress(),
-                            mConfig.getEncapLocalPort(),
+                            0x1234, /* FIXME: get the real port number again,
+                                    which we need to retrieve from the provided
+                                    EncapsulationSocket, and which isn't currently
+                                    stashed in IpSecConfig */
                             mConfig.getRemoteAddress());
             try {
                 // FIXME: this is still a horrible way to fudge the synchronous callback
@@ -360,7 +361,7 @@
                 @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();
+            mConfig.flow[direction].spiResourceId = spi.getResourceId();
             return this;
         }
 
@@ -394,7 +395,7 @@
                 IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) {
             // TODO: check encap type is valid.
             mConfig.encapType = ENCAP_ESPINUDP;
-            mConfig.encapLocalPort = localSocket.getPort(); // TODO: plug in the encap socket
+            mConfig.encapLocalPortResourceId = localSocket.getResourceId();
             mConfig.encapRemotePort = remotePort;
             return this;
         }
diff --git a/core/java/android/net/IpSecTransformResponse.aidl b/core/java/android/net/IpSecTransformResponse.aidl
new file mode 100644
index 0000000..546230d
--- /dev/null
+++ b/core/java/android/net/IpSecTransformResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2017 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 IpSecTransformResponse;
diff --git a/core/java/android/net/IpSecTransformResponse.java b/core/java/android/net/IpSecTransformResponse.java
new file mode 100644
index 0000000..cfc1762
--- /dev/null
+++ b/core/java/android/net/IpSecTransformResponse.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 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 IpSecTransform resource Id and and corresponding status from the
+ * IpSecService to an IpSecTransform object.
+ *
+ * @hide
+ */
+public final class IpSecTransformResponse implements Parcelable {
+    private static final String TAG = "IpSecTransformResponse";
+
+    public final int resourceId;
+    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);
+    }
+
+    public IpSecTransformResponse(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;
+    }
+
+    public IpSecTransformResponse(int inStatus, int inResourceId) {
+        status = inStatus;
+        resourceId = inResourceId;
+    }
+
+    private IpSecTransformResponse(Parcel in) {
+        status = in.readInt();
+        resourceId = in.readInt();
+    }
+
+    public static final Parcelable.Creator<IpSecTransformResponse> CREATOR =
+            new Parcelable.Creator<IpSecTransformResponse>() {
+                public IpSecTransformResponse createFromParcel(Parcel in) {
+                    return new IpSecTransformResponse(in);
+                }
+
+                public IpSecTransformResponse[] newArray(int size) {
+                    return new IpSecTransformResponse[size];
+                }
+            };
+}
diff --git a/core/java/android/net/IpSecUdpEncapResponse.aidl b/core/java/android/net/IpSecUdpEncapResponse.aidl
new file mode 100644
index 0000000..5e451f3
--- /dev/null
+++ b/core/java/android/net/IpSecUdpEncapResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2017 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 IpSecUdpEncapResponse;
diff --git a/core/java/android/net/IpSecUdpEncapResponse.java b/core/java/android/net/IpSecUdpEncapResponse.java
new file mode 100644
index 0000000..4679267
--- /dev/null
+++ b/core/java/android/net/IpSecUdpEncapResponse.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2017 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.ParcelFileDescriptor;
+import android.os.Parcelable;
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+/**
+ * This class is used to return a UDP Socket and corresponding status from the IpSecService to an
+ * IpSecManager.UdpEncapsulationSocket.
+ *
+ * @hide
+ */
+public final class IpSecUdpEncapResponse implements Parcelable {
+    private static final String TAG = "IpSecUdpEncapResponse";
+
+    public final int resourceId;
+    public final int port;
+    public final int status;
+    // There is a weird asymmetry with FileDescriptor: you can write a FileDescriptor
+    // but you read a ParcelFileDescriptor. To circumvent this, when we receive a FD
+    // from the user, we immediately create a ParcelFileDescriptor DUP, which we invalidate
+    // on writeParcel() by setting the flag to do close-on-write.
+    // TODO: tests to ensure this doesn't leak
+    public final ParcelFileDescriptor fileDescriptor;
+
+    // Parcelable Methods
+
+    @Override
+    public int describeContents() {
+        return (fileDescriptor != null) ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(status);
+        out.writeInt(resourceId);
+        out.writeInt(port);
+        out.writeParcelable(fileDescriptor, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+    }
+
+    public IpSecUdpEncapResponse(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;
+        port = -1;
+        fileDescriptor = null; // yes I know it's redundant, but readability
+    }
+
+    public IpSecUdpEncapResponse(int inStatus, int inResourceId, int inPort, FileDescriptor inFd)
+            throws IOException {
+        if (inStatus == IpSecManager.Status.OK && inFd == null) {
+            throw new IllegalArgumentException("Valid status implies FD must be non-null");
+        }
+        status = inStatus;
+        resourceId = inResourceId;
+        port = inPort;
+        fileDescriptor = (status == IpSecManager.Status.OK) ? ParcelFileDescriptor.dup(inFd) : null;
+    }
+
+    private IpSecUdpEncapResponse(Parcel in) {
+        status = in.readInt();
+        resourceId = in.readInt();
+        port = in.readInt();
+        fileDescriptor = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
+    }
+
+    public static final Parcelable.Creator<IpSecUdpEncapResponse> CREATOR =
+            new Parcelable.Creator<IpSecUdpEncapResponse>() {
+                public IpSecUdpEncapResponse createFromParcel(Parcel in) {
+                    return new IpSecUdpEncapResponse(in);
+                }
+
+                public IpSecUdpEncapResponse[] newArray(int size) {
+                    return new IpSecUdpEncapResponse[size];
+                }
+            };
+}
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();