Merge "Use consistent naming for allocating SPI."
diff --git a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
index 3d57fff..963e042 100644
--- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -22,7 +22,6 @@
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -39,6 +38,7 @@
import android.os.Binder;
import android.os.ParcelFileDescriptor;
import android.support.test.filters.SmallTest;
+import android.system.Os;
import java.net.Socket;
import java.util.Arrays;
@@ -154,6 +154,56 @@
anyString(),
anyString(),
eq(TEST_SPI_OUT));
+
+ // Verify quota and RefcountedResource objects cleaned up
+ IpSecService.UserRecord userRecord =
+ mIpSecService.mUserResourceTracker.getUserRecord(Os.getuid());
+ assertEquals(0, userRecord.mSpiQuotaTracker.mCurrent);
+ try {
+ userRecord.mSpiRecords.getRefcountedResourceOrThrow(spiResp.resourceId);
+ fail("Expected IllegalArgumentException on attempt to access deleted resource");
+ } catch (IllegalArgumentException expected) {
+
+ }
+ }
+
+ @Test
+ public void testSecurityParameterIndexBinderDeath() throws Exception {
+ when(mMockNetd.ipSecAllocateSpi(
+ anyInt(),
+ eq(IpSecTransform.DIRECTION_OUT),
+ anyString(),
+ eq(mRemoteAddr),
+ eq(TEST_SPI_OUT)))
+ .thenReturn(TEST_SPI_OUT);
+
+ IpSecSpiResponse spiResp =
+ mIpSecService.reserveSecurityParameterIndex(
+ IpSecTransform.DIRECTION_OUT, mRemoteAddr, TEST_SPI_OUT, new Binder());
+
+ IpSecService.UserRecord userRecord =
+ mIpSecService.mUserResourceTracker.getUserRecord(Os.getuid());
+ IpSecService.RefcountedResource refcountedRecord =
+ userRecord.mSpiRecords.getRefcountedResourceOrThrow(spiResp.resourceId);
+
+ refcountedRecord.binderDied();
+
+ verify(mMockNetd)
+ .ipSecDeleteSecurityAssociation(
+ eq(spiResp.resourceId),
+ anyInt(),
+ anyString(),
+ anyString(),
+ eq(TEST_SPI_OUT));
+
+ // Verify quota and RefcountedResource objects cleaned up
+ assertEquals(0, userRecord.mSpiQuotaTracker.mCurrent);
+ try {
+ userRecord.mSpiRecords.getRefcountedResourceOrThrow(spiResp.resourceId);
+ fail("Expected IllegalArgumentException on attempt to access deleted resource");
+ } catch (IllegalArgumentException expected) {
+
+ }
}
private int getNewSpiResourceId(int direction, String remoteAddress, int returnSpi)
@@ -379,6 +429,61 @@
anyString(),
anyString(),
eq(TEST_SPI_IN));
+
+ // Verify quota and RefcountedResource objects cleaned up
+ IpSecService.UserRecord userRecord =
+ mIpSecService.mUserResourceTracker.getUserRecord(Os.getuid());
+ assertEquals(0, userRecord.mTransformQuotaTracker.mCurrent);
+ try {
+ userRecord.mTransformRecords.getRefcountedResourceOrThrow(
+ createTransformResp.resourceId);
+ fail("Expected IllegalArgumentException on attempt to access deleted resource");
+ } catch (IllegalArgumentException expected) {
+
+ }
+ }
+
+ @Test
+ public void testTransportModeTransformBinderDeath() throws Exception {
+ IpSecConfig ipSecConfig = new IpSecConfig();
+ addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
+ addAuthAndCryptToIpSecConfig(ipSecConfig);
+
+ IpSecTransformResponse createTransformResp =
+ mIpSecService.createTransportModeTransform(ipSecConfig, new Binder());
+
+ IpSecService.UserRecord userRecord =
+ mIpSecService.mUserResourceTracker.getUserRecord(Os.getuid());
+ IpSecService.RefcountedResource refcountedRecord =
+ userRecord.mTransformRecords.getRefcountedResourceOrThrow(
+ createTransformResp.resourceId);
+
+ refcountedRecord.binderDied();
+
+ verify(mMockNetd)
+ .ipSecDeleteSecurityAssociation(
+ eq(createTransformResp.resourceId),
+ eq(IpSecTransform.DIRECTION_OUT),
+ anyString(),
+ anyString(),
+ eq(TEST_SPI_OUT));
+ verify(mMockNetd)
+ .ipSecDeleteSecurityAssociation(
+ eq(createTransformResp.resourceId),
+ eq(IpSecTransform.DIRECTION_IN),
+ anyString(),
+ anyString(),
+ eq(TEST_SPI_IN));
+
+ // Verify quota and RefcountedResource objects cleaned up
+ assertEquals(0, userRecord.mTransformQuotaTracker.mCurrent);
+ try {
+ userRecord.mTransformRecords.getRefcountedResourceOrThrow(
+ createTransformResp.resourceId);
+ fail("Expected IllegalArgumentException on attempt to access deleted resource");
+ } catch (IllegalArgumentException expected) {
+
+ }
}
@Test
diff --git a/tests/net/java/com/android/server/IpSecServiceRefcountedResourceTest.java b/tests/net/java/com/android/server/IpSecServiceRefcountedResourceTest.java
new file mode 100644
index 0000000..cf8f715
--- /dev/null
+++ b/tests/net/java/com/android/server/IpSecServiceRefcountedResourceTest.java
@@ -0,0 +1,356 @@
+/*
+ * 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 com.android.server;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.server.IpSecService.IResource;
+import com.android.server.IpSecService.RefcountedResource;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ThreadLocalRandom;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit tests for {@link IpSecService.RefcountedResource}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class IpSecServiceRefcountedResourceTest {
+ Context mMockContext;
+ IpSecService.IpSecServiceConfiguration mMockIpSecSrvConfig;
+ IpSecService mIpSecService;
+
+ @Before
+ public void setUp() throws Exception {
+ mMockContext = mock(Context.class);
+ mMockIpSecSrvConfig = mock(IpSecService.IpSecServiceConfiguration.class);
+ mIpSecService = new IpSecService(mMockContext, mMockIpSecSrvConfig);
+ }
+
+ private void assertResourceState(
+ RefcountedResource<IResource> resource,
+ int refCount,
+ int userReleaseCallCount,
+ int releaseReferenceCallCount,
+ int invalidateCallCount,
+ int freeUnderlyingResourcesCallCount)
+ throws RemoteException {
+ // Check refcount on RefcountedResource
+ assertEquals(refCount, resource.mRefCount);
+
+ // Check call count of RefcountedResource
+ verify(resource, times(userReleaseCallCount)).userRelease();
+ verify(resource, times(releaseReferenceCallCount)).releaseReference();
+
+ // Check call count of IResource
+ verify(resource.getResource(), times(invalidateCallCount)).invalidate();
+ verify(resource.getResource(), times(freeUnderlyingResourcesCallCount))
+ .freeUnderlyingResources();
+ }
+
+ /** Adds mockito instrumentation */
+ private RefcountedResource<IResource> getTestRefcountedResource(
+ RefcountedResource... children) {
+ return getTestRefcountedResource(new Binder(), children);
+ }
+
+ /** Adds mockito instrumentation with provided binder */
+ private RefcountedResource<IResource> getTestRefcountedResource(
+ IBinder binder, RefcountedResource... children) {
+ return spy(
+ mIpSecService
+ .new RefcountedResource<IResource>(mock(IResource.class), binder, children));
+ }
+
+ @Test
+ public void testConstructor() throws RemoteException {
+ IBinder binderMock = mock(IBinder.class);
+ RefcountedResource<IResource> resource = getTestRefcountedResource(binderMock);
+
+ // Verify resource's refcount starts at 1 (for user-reference)
+ assertResourceState(resource, 1, 0, 0, 0, 0);
+
+ // Verify linking to binder death
+ verify(binderMock).linkToDeath(anyObject(), anyInt());
+ }
+
+ @Test
+ public void testConstructorWithChildren() throws RemoteException {
+ IBinder binderMockChild = mock(IBinder.class);
+ IBinder binderMockParent = mock(IBinder.class);
+ RefcountedResource<IResource> childResource = getTestRefcountedResource(binderMockChild);
+ RefcountedResource<IResource> parentResource =
+ getTestRefcountedResource(binderMockParent, childResource);
+
+ // Verify parent's refcount starts at 1 (for user-reference)
+ assertResourceState(parentResource, 1, 0, 0, 0, 0);
+
+ // Verify child's refcounts were incremented
+ assertResourceState(childResource, 2, 0, 0, 0, 0);
+
+ // Verify linking to binder death
+ verify(binderMockChild).linkToDeath(anyObject(), anyInt());
+ verify(binderMockParent).linkToDeath(anyObject(), anyInt());
+ }
+
+ @Test
+ public void testFailLinkToDeath() throws RemoteException {
+ IBinder binderMock = mock(IBinder.class);
+ doThrow(new RemoteException()).when(binderMock).linkToDeath(anyObject(), anyInt());
+
+ RefcountedResource<IResource> refcountedResource = getTestRefcountedResource(binderMock);
+
+ // Verify that cleanup is performed (Spy limitations prevent verification of method calls
+ // for binder death scenario; check refcount to determine if cleanup was performed.)
+ assertEquals(-1, refcountedResource.mRefCount);
+ }
+
+ @Test
+ public void testCleanupAndRelease() throws RemoteException {
+ IBinder binderMock = mock(IBinder.class);
+ RefcountedResource<IResource> refcountedResource = getTestRefcountedResource(binderMock);
+
+ // Verify user-initiated cleanup path decrements refcount and calls full cleanup flow
+ refcountedResource.userRelease();
+ assertResourceState(refcountedResource, -1, 1, 1, 1, 1);
+
+ // Verify user-initated cleanup path unlinks from binder
+ verify(binderMock).unlinkToDeath(eq(refcountedResource), eq(0));
+ assertNull(refcountedResource.mBinder);
+ }
+
+ @Test
+ public void testMultipleCallsToCleanupAndRelease() throws RemoteException {
+ RefcountedResource<IResource> refcountedResource = getTestRefcountedResource();
+
+ // Verify calling userRelease multiple times does not trigger any other cleanup
+ // methods
+ refcountedResource.userRelease();
+ assertResourceState(refcountedResource, -1, 1, 1, 1, 1);
+
+ refcountedResource.userRelease();
+ refcountedResource.userRelease();
+ assertResourceState(refcountedResource, -1, 3, 1, 1, 1);
+ }
+
+ @Test
+ public void testBinderDeathAfterCleanupAndReleaseDoesNothing() throws RemoteException {
+ RefcountedResource<IResource> refcountedResource = getTestRefcountedResource();
+
+ refcountedResource.userRelease();
+ assertResourceState(refcountedResource, -1, 1, 1, 1, 1);
+
+ // Verify binder death call does not trigger any other cleanup methods if called after
+ // userRelease()
+ refcountedResource.binderDied();
+ assertResourceState(refcountedResource, -1, 2, 1, 1, 1);
+ }
+
+ @Test
+ public void testBinderDeath() throws RemoteException {
+ RefcountedResource<IResource> refcountedResource = getTestRefcountedResource();
+
+ // Verify binder death caused cleanup
+ refcountedResource.binderDied();
+ verify(refcountedResource, times(1)).binderDied();
+ assertResourceState(refcountedResource, -1, 1, 1, 1, 1);
+ assertNull(refcountedResource.mBinder);
+ }
+
+ @Test
+ public void testCleanupParentDecrementsChildRefcount() throws RemoteException {
+ RefcountedResource<IResource> childResource = getTestRefcountedResource();
+ RefcountedResource<IResource> parentResource = getTestRefcountedResource(childResource);
+
+ parentResource.userRelease();
+
+ // Verify parent gets cleaned up properly, and triggers releaseReference on
+ // child
+ assertResourceState(childResource, 1, 0, 1, 0, 0);
+ assertResourceState(parentResource, -1, 1, 1, 1, 1);
+ }
+
+ @Test
+ public void testCleanupReferencedChildDoesNotTriggerRelease() throws RemoteException {
+ RefcountedResource<IResource> childResource = getTestRefcountedResource();
+ RefcountedResource<IResource> parentResource = getTestRefcountedResource(childResource);
+
+ childResource.userRelease();
+
+ // Verify that child does not clean up kernel resources and quota.
+ assertResourceState(childResource, 1, 1, 1, 1, 0);
+ assertResourceState(parentResource, 1, 0, 0, 0, 0);
+ }
+
+ @Test
+ public void testTwoParents() throws RemoteException {
+ RefcountedResource<IResource> childResource = getTestRefcountedResource();
+ RefcountedResource<IResource> parentResource1 = getTestRefcountedResource(childResource);
+ RefcountedResource<IResource> parentResource2 = getTestRefcountedResource(childResource);
+
+ // Verify that child does not cleanup kernel resources and quota until all references
+ // have been released. Assumption: parents release correctly based on
+ // testCleanupParentDecrementsChildRefcount()
+ childResource.userRelease();
+ assertResourceState(childResource, 2, 1, 1, 1, 0);
+
+ parentResource1.userRelease();
+ assertResourceState(childResource, 1, 1, 2, 1, 0);
+
+ parentResource2.userRelease();
+ assertResourceState(childResource, -1, 1, 3, 1, 1);
+ }
+
+ @Test
+ public void testTwoChildren() throws RemoteException {
+ RefcountedResource<IResource> childResource1 = getTestRefcountedResource();
+ RefcountedResource<IResource> childResource2 = getTestRefcountedResource();
+ RefcountedResource<IResource> parentResource =
+ getTestRefcountedResource(childResource1, childResource2);
+
+ childResource1.userRelease();
+ assertResourceState(childResource1, 1, 1, 1, 1, 0);
+ assertResourceState(childResource2, 2, 0, 0, 0, 0);
+
+ parentResource.userRelease();
+ assertResourceState(childResource1, -1, 1, 2, 1, 1);
+ assertResourceState(childResource2, 1, 0, 1, 0, 0);
+
+ childResource2.userRelease();
+ assertResourceState(childResource1, -1, 1, 2, 1, 1);
+ assertResourceState(childResource2, -1, 1, 2, 1, 1);
+ }
+
+ @Test
+ public void testSampleUdpEncapTranform() throws RemoteException {
+ RefcountedResource<IResource> spi1 = getTestRefcountedResource();
+ RefcountedResource<IResource> spi2 = getTestRefcountedResource();
+ RefcountedResource<IResource> udpEncapSocket = getTestRefcountedResource();
+ RefcountedResource<IResource> transform =
+ getTestRefcountedResource(spi1, spi2, udpEncapSocket);
+
+ // Pretend one SPI goes out of reference (releaseManagedResource -> userRelease)
+ spi1.userRelease();
+
+ // User called releaseManagedResource on udpEncap socket
+ udpEncapSocket.userRelease();
+
+ // User dies, and binder kills the rest
+ spi2.binderDied();
+ transform.binderDied();
+
+ // Check resource states
+ assertResourceState(spi1, -1, 1, 2, 1, 1);
+ assertResourceState(spi2, -1, 1, 2, 1, 1);
+ assertResourceState(udpEncapSocket, -1, 1, 2, 1, 1);
+ assertResourceState(transform, -1, 1, 1, 1, 1);
+ }
+
+ @Test
+ public void testSampleDualTransformEncapSocket() throws RemoteException {
+ RefcountedResource<IResource> spi1 = getTestRefcountedResource();
+ RefcountedResource<IResource> spi2 = getTestRefcountedResource();
+ RefcountedResource<IResource> spi3 = getTestRefcountedResource();
+ RefcountedResource<IResource> spi4 = getTestRefcountedResource();
+ RefcountedResource<IResource> udpEncapSocket = getTestRefcountedResource();
+ RefcountedResource<IResource> transform1 =
+ getTestRefcountedResource(spi1, spi2, udpEncapSocket);
+ RefcountedResource<IResource> transform2 =
+ getTestRefcountedResource(spi3, spi4, udpEncapSocket);
+
+ // Pretend one SPIs goes out of reference (releaseManagedResource -> userRelease)
+ spi1.userRelease();
+
+ // User called releaseManagedResource on udpEncap socket and spi4
+ udpEncapSocket.userRelease();
+ spi4.userRelease();
+
+ // User dies, and binder kills the rest
+ spi2.binderDied();
+ spi3.binderDied();
+ transform2.binderDied();
+ transform1.binderDied();
+
+ // Check resource states
+ assertResourceState(spi1, -1, 1, 2, 1, 1);
+ assertResourceState(spi2, -1, 1, 2, 1, 1);
+ assertResourceState(spi3, -1, 1, 2, 1, 1);
+ assertResourceState(spi4, -1, 1, 2, 1, 1);
+ assertResourceState(udpEncapSocket, -1, 1, 3, 1, 1);
+ assertResourceState(transform1, -1, 1, 1, 1, 1);
+ assertResourceState(transform2, -1, 1, 1, 1, 1);
+ }
+
+ @Test
+ public void fuzzTest() throws RemoteException {
+ List<RefcountedResource<IResource>> resources = new ArrayList<>();
+
+ // Build a tree of resources
+ for (int i = 0; i < 100; i++) {
+ // Choose a random number of children from the existing list
+ int numChildren = ThreadLocalRandom.current().nextInt(0, resources.size() + 1);
+
+ // Build a (random) list of children
+ Set<RefcountedResource<IResource>> children = new HashSet<>();
+ for (int j = 0; j < numChildren; j++) {
+ int childIndex = ThreadLocalRandom.current().nextInt(0, resources.size());
+ children.add(resources.get(childIndex));
+ }
+
+ RefcountedResource<IResource> newRefcountedResource =
+ getTestRefcountedResource(
+ children.toArray(new RefcountedResource[children.size()]));
+ resources.add(newRefcountedResource);
+ }
+
+ // Cleanup all resources in a random order
+ List<RefcountedResource<IResource>> clonedResources =
+ new ArrayList<>(resources); // shallow copy
+ while (!clonedResources.isEmpty()) {
+ int index = ThreadLocalRandom.current().nextInt(0, clonedResources.size());
+ RefcountedResource<IResource> refcountedResource = clonedResources.get(index);
+ refcountedResource.userRelease();
+ clonedResources.remove(index);
+ }
+
+ // Verify all resources were cleaned up properly
+ for (RefcountedResource<IResource> refcountedResource : resources) {
+ assertEquals(-1, refcountedResource.mRefCount);
+ }
+ }
+}
diff --git a/tests/net/java/com/android/server/IpSecServiceTest.java b/tests/net/java/com/android/server/IpSecServiceTest.java
index 6cea9a8..8683c12 100644
--- a/tests/net/java/com/android/server/IpSecServiceTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceTest.java
@@ -131,7 +131,39 @@
mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
udpEncapResp.fileDescriptor.close();
- // TODO: Added check for the resource tracker
+ IpSecService.UserRecord userRecord =
+ mIpSecService.mUserResourceTracker.getUserRecord(Os.getuid());
+ assertEquals(0, userRecord.mSocketQuotaTracker.mCurrent);
+ try {
+ userRecord.mEncapSocketRecords.getRefcountedResourceOrThrow(udpEncapResp.resourceId);
+ fail("Expected IllegalArgumentException on attempt to access deleted resource");
+ } catch (IllegalArgumentException expected) {
+
+ }
+ }
+
+ @Test
+ public void testUdpEncapsulationSocketBinderDeath() throws Exception {
+ int localport = findUnusedPort();
+
+ IpSecUdpEncapResponse udpEncapResp =
+ mIpSecService.openUdpEncapsulationSocket(localport, new Binder());
+
+ IpSecService.UserRecord userRecord =
+ mIpSecService.mUserResourceTracker.getUserRecord(Os.getuid());
+ IpSecService.RefcountedResource refcountedRecord =
+ userRecord.mEncapSocketRecords.getRefcountedResourceOrThrow(
+ udpEncapResp.resourceId);
+
+ refcountedRecord.binderDied();
+
+ assertEquals(0, userRecord.mSocketQuotaTracker.mCurrent);
+ try {
+ userRecord.mEncapSocketRecords.getRefcountedResourceOrThrow(udpEncapResp.resourceId);
+ fail("Expected IllegalArgumentException on attempt to access deleted resource");
+ } catch (IllegalArgumentException expected) {
+
+ }
}
@Test