Merge "Add reference counted resources to IpSecService"
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index 1154fbe..92db7cc 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -57,11 +57,23 @@
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import libcore.io.IoUtils;
-/** @hide */
+/**
+ * A service to manage multiple clients that want to access the IpSec API. The service is
+ * responsible for maintaining a list of clients and managing the resources (and related quotas)
+ * that each of them own.
+ *
+ * <p>Synchronization in IpSecService is done on all entrypoints due to potential race conditions at
+ * the kernel/xfrm level. Further, this allows the simplifying assumption to be made that only one
+ * thread is ever running at a time.
+ *
+ * @hide
+ */
public class IpSecService extends IIpSecService.Stub {
private static final String TAG = "IpSecService";
private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
@@ -92,15 +104,15 @@
private static AtomicInteger mNextResourceId = new AtomicInteger(0x00FADED0);
@GuardedBy("this")
- private final ManagedResourceArray<SpiRecord> mSpiRecords = new ManagedResourceArray<>();
+ private final KernelResourceArray<SpiRecord> mSpiRecords = new KernelResourceArray<>();
@GuardedBy("this")
- private final ManagedResourceArray<TransformRecord> mTransformRecords =
- new ManagedResourceArray<>();
+ private final KernelResourceArray<TransformRecord> mTransformRecords =
+ new KernelResourceArray<>();
@GuardedBy("this")
- private final ManagedResourceArray<UdpSocketRecord> mUdpSocketRecords =
- new ManagedResourceArray<>();
+ private final KernelResourceArray<UdpSocketRecord> mUdpSocketRecords =
+ new KernelResourceArray<>();
interface IpSecServiceConfiguration {
INetd getNetdInstance() throws RemoteException;
@@ -120,6 +132,173 @@
private final IpSecServiceConfiguration mSrvConfig;
+ /**
+ * Interface for user-reference and kernel-resource cleanup.
+ *
+ * <p>This interface must be implemented for a resource to be reference counted.
+ */
+ @VisibleForTesting
+ public interface IResource {
+ /**
+ * Invalidates a IResource object, ensuring it is invalid for the purposes of allocating new
+ * objects dependent on it.
+ *
+ * <p>Implementations of this method are expected to remove references to the IResource
+ * object from the IpSecService's tracking arrays. The removal from the arrays ensures that
+ * the resource is considered invalid for user access or allocation or use in other
+ * resources.
+ *
+ * <p>References to the IResource object may be held by other RefcountedResource objects,
+ * and as such, the kernel resources and quota may not be cleaned up.
+ */
+ void invalidate() throws RemoteException;
+
+ /**
+ * Releases underlying resources and related quotas.
+ *
+ * <p>Implementations of this method are expected to remove all system resources that are
+ * tracked by the IResource object. Due to other RefcountedResource objects potentially
+ * having references to the IResource object, releaseKernelResources may not always be
+ * called from releaseIfUnreferencedRecursively().
+ */
+ void freeUnderlyingResources() throws RemoteException;
+ }
+
+ /**
+ * RefcountedResource manages references and dependencies in an exclusively acyclic graph.
+ *
+ * <p>RefcountedResource implements both explicit and implicit resource management. Creating a
+ * RefcountedResource object creates an explicit reference that must be freed by calling
+ * userRelease(). Additionally, adding this object as a child of another RefcountedResource
+ * object will add an implicit reference.
+ *
+ * <p>Resources are cleaned up when all references, both implicit and explicit, are released
+ * (ie, when userRelease() is called and when all parents have called releaseReference() on this
+ * object.)
+ */
+ @VisibleForTesting
+ public class RefcountedResource<T extends IResource> implements IBinder.DeathRecipient {
+ private final T mResource;
+ private final List<RefcountedResource> mChildren;
+ int mRefCount = 1; // starts at 1 for user's reference.
+ IBinder mBinder;
+
+ RefcountedResource(T resource, IBinder binder, RefcountedResource... children) {
+ synchronized (IpSecService.this) {
+ this.mResource = resource;
+ this.mChildren = new ArrayList<>(children.length);
+ this.mBinder = binder;
+
+ for (RefcountedResource child : children) {
+ mChildren.add(child);
+ child.mRefCount++;
+ }
+
+ try {
+ mBinder.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ binderDied();
+ }
+ }
+ }
+
+ /**
+ * If the Binder object dies, this function is called to free the system resources that are
+ * being managed by this record and to subsequently release this record for garbage
+ * collection
+ */
+ @Override
+ public void binderDied() {
+ synchronized (IpSecService.this) {
+ try {
+ userRelease();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to release resource: " + e);
+ }
+ }
+ }
+
+ public T getResource() {
+ return mResource;
+ }
+
+ /**
+ * Unlinks from binder and performs IpSecService resource cleanup (removes from resource
+ * arrays)
+ *
+ * <p>If this method has been previously called, the RefcountedResource's binder field will
+ * be null, and the method will return without performing the cleanup a second time.
+ *
+ * <p>Note that calling this function does not imply that kernel resources will be freed at
+ * this time, or that the related quota will be returned. Such actions will only be
+ * performed upon the reference count reaching zero.
+ */
+ @GuardedBy("IpSecService.this")
+ public void userRelease() throws RemoteException {
+ // Prevent users from putting reference counts into a bad state by calling
+ // userRelease() multiple times.
+ if (mBinder == null) {
+ return;
+ }
+
+ mBinder.unlinkToDeath(this, 0);
+ mBinder = null;
+
+ mResource.invalidate();
+
+ releaseReference();
+ }
+
+ /**
+ * Removes a reference to this resource. If the resultant reference count is zero, the
+ * underlying resources are freed, and references to all child resources are also dropped
+ * recursively (resulting in them freeing their resources and children, etcetera)
+ *
+ * <p>This method also sets the reference count to an invalid value (-1) to signify that it
+ * has been fully released. Any subsequent calls to this method will result in an
+ * IllegalStateException being thrown due to resource already having been previously
+ * released
+ */
+ @VisibleForTesting
+ @GuardedBy("IpSecService.this")
+ public void releaseReference() throws RemoteException {
+ mRefCount--;
+
+ if (mRefCount > 0) {
+ return;
+ } else if (mRefCount < 0) {
+ throw new IllegalStateException(
+ "Invalid operation - resource has already been released.");
+ }
+
+ // Cleanup own resources
+ mResource.freeUnderlyingResources();
+
+ // Cleanup child resources as needed
+ for (RefcountedResource<? extends IResource> child : mChildren) {
+ child.releaseReference();
+ }
+
+ // Enforce that resource cleanup can only be called once
+ // By decrementing the refcount (from 0 to -1), the next call will throw an
+ // IllegalStateException - it has already been released fully.
+ mRefCount--;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("{mResource=")
+ .append(mResource)
+ .append(", mRefCount=")
+ .append(mRefCount)
+ .append(", mChildren=")
+ .append(mChildren)
+ .append("}")
+ .toString();
+ }
+ }
+
/* Very simple counting class that looks much like a counting semaphore */
public static class ResourceTracker {
private final int mMax;
@@ -211,13 +390,13 @@
private final UserQuotaTracker mUserQuotaTracker = new UserQuotaTracker();
/**
- * 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
+ * The KernelResource class provides a facility to cleanly and reliably release system
+ * resources. It relies on two things: an IBinder that allows KernelResource 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 {
+ private abstract class KernelResource implements IBinder.DeathRecipient {
final int pid;
final int uid;
private IBinder mBinder;
@@ -225,7 +404,7 @@
private AtomicInteger mReferenceCount = new AtomicInteger(0);
- ManagedResource(int resourceId, IBinder binder) {
+ KernelResource(int resourceId, IBinder binder) {
super();
if (resourceId == INVALID_RESOURCE_ID) {
throw new IllegalArgumentException("Resource ID must not be INVALID_RESOURCE_ID");
@@ -341,7 +520,7 @@
/**
* Minimal wrapper around SparseArray that performs ownership validation on element accesses.
*/
- private class ManagedResourceArray<T extends ManagedResource> {
+ private class KernelResourceArray<T extends KernelResource> {
SparseArray<T> mArray = new SparseArray<>();
T getAndCheckOwner(int key) {
@@ -369,7 +548,7 @@
}
}
- private final class TransformRecord extends ManagedResource {
+ private final class TransformRecord extends KernelResource {
private final IpSecConfig mConfig;
private final SpiRecord[] mSpis;
private final UdpSocketRecord mSocket;
@@ -456,7 +635,7 @@
}
}
- private final class SpiRecord extends ManagedResource {
+ private final class SpiRecord extends KernelResource {
private final int mDirection;
private final String mLocalAddress;
private final String mRemoteAddress;
@@ -544,7 +723,7 @@
}
}
- private final class UdpSocketRecord extends ManagedResource {
+ private final class UdpSocketRecord extends KernelResource {
private FileDescriptor mSocket;
private final int mPort;
@@ -718,8 +897,8 @@
/* 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)
+ private synchronized <T extends KernelResource> void releaseKernelResource(
+ KernelResourceArray<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.
@@ -737,7 +916,7 @@
/** Release a previously allocated SPI that has been registered with the system server */
@Override
public void releaseSecurityParameterIndex(int resourceId) throws RemoteException {
- releaseManagedResource(mSpiRecords, resourceId, "SecurityParameterIndex");
+ releaseKernelResource(mSpiRecords, resourceId, "SecurityParameterIndex");
}
/**
@@ -827,7 +1006,7 @@
@Override
public void closeUdpEncapsulationSocket(int resourceId) throws RemoteException {
- releaseManagedResource(mUdpSocketRecords, resourceId, "UdpEncapsulationSocket");
+ releaseKernelResource(mUdpSocketRecords, resourceId, "UdpEncapsulationSocket");
}
/**
@@ -974,7 +1153,7 @@
*/
@Override
public void deleteTransportModeTransform(int resourceId) throws RemoteException {
- releaseManagedResource(mTransformRecords, resourceId, "IpSecTransform");
+ releaseKernelResource(mTransformRecords, resourceId, "IpSecTransform");
}
/**
@@ -984,7 +1163,7 @@
@Override
public synchronized void applyTransportModeTransform(
ParcelFileDescriptor socket, int resourceId) throws RemoteException {
- // Synchronize liberally here because we are using ManagedResources in this block
+ // Synchronize liberally here because we are using KernelResources in this block
TransformRecord info;
// FIXME: this code should be factored out into a security check + getter
info = mTransformRecords.getAndCheckOwner(resourceId);