Merge "Disable iorap test mapping"
diff --git a/core/java/android/net/IIpSecService.aidl b/core/java/android/net/IIpSecService.aidl
index d6774d4..b5a2a16 100644
--- a/core/java/android/net/IIpSecService.aidl
+++ b/core/java/android/net/IIpSecService.aidl
@@ -72,4 +72,8 @@
int tunnelResourceId, int direction, int transformResourceId, in String callingPackage);
void removeTransportModeTransforms(in ParcelFileDescriptor socket);
+
+ int lockEncapSocketForNattKeepalive(int encapSocketResourceId, int requesterUid);
+
+ void releaseNattKeepalive(int nattKeepaliveResourceId, int ownerUid);
}
diff --git a/core/java/android/net/ITestNetworkManager.aidl b/core/java/android/net/ITestNetworkManager.aidl
index bab6ae8..d586038 100644
--- a/core/java/android/net/ITestNetworkManager.aidl
+++ b/core/java/android/net/ITestNetworkManager.aidl
@@ -17,6 +17,7 @@
package android.net;
import android.net.LinkAddress;
+import android.net.LinkProperties;
import android.net.TestNetworkInterface;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
@@ -31,7 +32,8 @@
TestNetworkInterface createTunInterface(in LinkAddress[] linkAddrs);
TestNetworkInterface createTapInterface();
- void setupTestNetwork(in String iface, in IBinder binder);
+ void setupTestNetwork(in String iface, in LinkProperties lp, in boolean isMetered,
+ in IBinder binder);
void teardownTestNetwork(int netId);
}
diff --git a/core/java/android/net/TestNetworkManager.java b/core/java/android/net/TestNetworkManager.java
index e274005..4ac4a69 100644
--- a/core/java/android/net/TestNetworkManager.java
+++ b/core/java/android/net/TestNetworkManager.java
@@ -56,6 +56,26 @@
/**
* Sets up a capability-limited, testing-only network for a given interface
*
+ * @param lp The LinkProperties for the TestNetworkService to use for this test network. Note
+ * that the interface name and link addresses will be overwritten, and the passed-in values
+ * discarded.
+ * @param isMetered Whether or not the network should be considered metered.
+ * @param binder A binder object guarding the lifecycle of this test network.
+ * @hide
+ */
+ public void setupTestNetwork(
+ @NonNull LinkProperties lp, boolean isMetered, @NonNull IBinder binder) {
+ Preconditions.checkNotNull(lp, "Invalid LinkProperties");
+ try {
+ mService.setupTestNetwork(lp.getInterfaceName(), lp, isMetered, binder);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sets up a capability-limited, testing-only network for a given interface
+ *
* @param iface the name of the interface to be used for the Network LinkProperties.
* @param binder A binder object guarding the lifecycle of this test network.
* @hide
@@ -63,7 +83,7 @@
@TestApi
public void setupTestNetwork(@NonNull String iface, @NonNull IBinder binder) {
try {
- mService.setupTestNetwork(iface, binder);
+ mService.setupTestNetwork(iface, null, true, binder);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index 2055b64..c1f5255 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -28,8 +28,10 @@
import static com.android.internal.util.Preconditions.checkNotNull;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.net.IIpSecService;
import android.net.INetd;
import android.net.IpSecAlgorithm;
@@ -42,6 +44,7 @@
import android.net.IpSecUdpEncapResponse;
import android.net.LinkAddress;
import android.net.Network;
+import android.net.NetworkStack;
import android.net.NetworkUtils;
import android.net.TrafficStats;
import android.net.util.NetdService;
@@ -74,6 +77,8 @@
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
/**
@@ -175,6 +180,14 @@
}
/**
+ * Sentinel value placeholder for a real binder in RefcountedResources.
+ *
+ * <p>Used for cases where there the allocating party is a system service, and thus is expected
+ * to track the resource lifecycles instead of IpSecService.
+ */
+ private static final Binder DUMMY_BINDER = new Binder();
+
+ /**
* RefcountedResource manages references and dependencies in an exclusively acyclic graph.
*
* <p>RefcountedResource implements both explicit and implicit resource management. Creating a
@@ -188,24 +201,42 @@
*/
@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) {
+ @NonNull private final T mResource;
+ @NonNull private final List<RefcountedResource> mChildren;
+ int mRefCount = 1; // starts at 1 for user's reference.
+
+ /*
+ * This field can take one of three states:
+ * 1. null, when the object has been released by the user (or the user's binder dies)
+ * 2. DUMMY_BINDER, when the refcounted resource is allocated from another system service
+ * and the other system service manages the lifecycle of the resource instead of
+ * IpSecService.
+ * 3. an actual binder, to ensure IpSecService can cleanup after users that forget to close
+ * their resources.
+ */
+ @Nullable IBinder mBinder;
+
+ RefcountedResource(@NonNull T resource, @NonNull RefcountedResource... children) {
+ this(resource, DUMMY_BINDER, children);
+ }
+
+ RefcountedResource(
+ @NonNull T resource,
+ @NonNull IBinder binder,
+ @NonNull RefcountedResource... children) {
synchronized (IpSecService.this) {
this.mResource = resource;
- this.mChildren = new ArrayList<>(children.length);
this.mBinder = binder;
-
+ this.mChildren = Collections.unmodifiableList(Arrays.asList(children));
for (RefcountedResource child : children) {
- mChildren.add(child);
child.mRefCount++;
}
try {
- mBinder.linkToDeath(this, 0);
+ if (mBinder != DUMMY_BINDER) {
+ mBinder.linkToDeath(this, 0);
+ }
} catch (RemoteException e) {
binderDied();
e.rethrowFromSystemServer();
@@ -252,11 +283,12 @@
return;
}
- mBinder.unlinkToDeath(this, 0);
+ if (mBinder != DUMMY_BINDER) {
+ mBinder.unlinkToDeath(this, 0);
+ }
mBinder = null;
mResource.invalidate();
-
releaseReference();
}
@@ -381,6 +413,8 @@
new RefcountedResourceArray<>(EncapSocketRecord.class.getSimpleName());
final RefcountedResourceArray<TunnelInterfaceRecord> mTunnelInterfaceRecords =
new RefcountedResourceArray<>(TunnelInterfaceRecord.class.getSimpleName());
+ final RefcountedResourceArray<NattKeepaliveRecord> mNattKeepaliveRecords =
+ new RefcountedResourceArray<>(NattKeepaliveRecord.class.getSimpleName());
/**
* Trackers for quotas for each of the OwnedResource types.
@@ -394,6 +428,8 @@
final ResourceTracker mSpiQuotaTracker = new ResourceTracker(MAX_NUM_SPIS);
final ResourceTracker mTransformQuotaTracker = new ResourceTracker(MAX_NUM_TRANSFORMS);
final ResourceTracker mSocketQuotaTracker = new ResourceTracker(MAX_NUM_ENCAP_SOCKETS);
+ final ResourceTracker mNattKeepaliveQuotaTracker =
+ new ResourceTracker(MAX_NUM_ENCAP_SOCKETS); // Max 1 NATT keepalive per encap socket
final ResourceTracker mTunnelQuotaTracker = new ResourceTracker(MAX_NUM_TUNNEL_INTERFACES);
void removeSpiRecord(int resourceId) {
@@ -412,6 +448,10 @@
mEncapSocketRecords.remove(resourceId);
}
+ void removeNattKeepaliveRecord(int resourceId) {
+ mNattKeepaliveRecords.remove(resourceId);
+ }
+
@Override
public String toString() {
return new StringBuilder()
@@ -421,6 +461,8 @@
.append(mTransformQuotaTracker)
.append(", mSocketQuotaTracker=")
.append(mSocketQuotaTracker)
+ .append(", mNattKeepaliveQuotaTracker=")
+ .append(mNattKeepaliveQuotaTracker)
.append(", mTunnelQuotaTracker=")
.append(mTunnelQuotaTracker)
.append(", mSpiRecords=")
@@ -429,6 +471,8 @@
.append(mTransformRecords)
.append(", mEncapSocketRecords=")
.append(mEncapSocketRecords)
+ .append(", mNattKeepaliveRecords=")
+ .append(mNattKeepaliveRecords)
.append(", mTunnelInterfaceRecords=")
.append(mTunnelInterfaceRecords)
.append("}")
@@ -573,6 +617,11 @@
mArray.remove(key);
}
+ @VisibleForTesting
+ int size() {
+ return mArray.size();
+ }
+
@Override
public String toString() {
return mArray.toString();
@@ -984,6 +1033,50 @@
}
/**
+ * Tracks a NATT-keepalive instance
+ *
+ * <p>This class ensures that while a NATT-keepalive is active, the UDP encap socket that it is
+ * supporting will stay open until the NATT-keepalive is finished. NATT-keepalive offload
+ * lifecycles will be managed by ConnectivityService, which will validate that the UDP Encap
+ * socket is owned by the requester, and take a reference to it via this NattKeepaliveRecord
+ *
+ * <p>It shall be the responsibility of the caller to ensure that instances of an EncapSocket do
+ * not spawn multiple instances of NATT keepalives (and thereby register duplicate records)
+ */
+ private final class NattKeepaliveRecord extends OwnedResourceRecord {
+ NattKeepaliveRecord(int resourceId) {
+ super(resourceId);
+ }
+
+ @Override
+ @GuardedBy("IpSecService.this")
+ public void freeUnderlyingResources() {
+ Log.d(TAG, "Natt Keepalive released: " + mResourceId);
+
+ getResourceTracker().give();
+ }
+
+ @Override
+ protected ResourceTracker getResourceTracker() {
+ return getUserRecord().mNattKeepaliveQuotaTracker;
+ }
+
+ @Override
+ public void invalidate() {
+ getUserRecord().removeNattKeepaliveRecord(mResourceId);
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("{super=")
+ .append(super.toString())
+ .append("}")
+ .toString();
+ }
+ }
+
+ /**
* Constructs a new IpSecService instance
*
* @param context Binder context for this service
@@ -1818,6 +1911,57 @@
}
}
+ private void verifyNetworkStackCaller() {
+ if (mContext.checkCallingOrSelfPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
+ != PackageManager.PERMISSION_GRANTED
+ && mContext.checkCallingOrSelfPermission(android.Manifest.permission.NETWORK_STACK)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException(
+ "Requires permission NETWORK_STACK or MAINLINE_NETWORK_STACK");
+ }
+ }
+
+ /**
+ * Validates that a provided UID owns the encapSocket, and creates a NATT keepalive record
+ *
+ * <p>For system server use only. Caller must have NETWORK_STACK permission
+ *
+ * @param encapSocketResourceId resource identifier of the encap socket record
+ * @param ownerUid the UID of the caller. Used to verify ownership.
+ * @return
+ */
+ public synchronized int lockEncapSocketForNattKeepalive(
+ int encapSocketResourceId, int ownerUid) {
+ verifyNetworkStackCaller();
+
+ // Verify ownership. Will throw IllegalArgumentException if the UID specified does not
+ // own the specified UDP encapsulation socket
+ UserRecord userRecord = mUserResourceTracker.getUserRecord(ownerUid);
+ RefcountedResource<EncapSocketRecord> refcountedSocketRecord =
+ userRecord.mEncapSocketRecords.getRefcountedResourceOrThrow(encapSocketResourceId);
+
+ // Build NattKeepaliveRecord
+ final int resourceId = mNextResourceId++;
+ userRecord.mNattKeepaliveRecords.put(
+ resourceId,
+ new RefcountedResource<NattKeepaliveRecord>(
+ new NattKeepaliveRecord(resourceId), refcountedSocketRecord));
+
+ return resourceId;
+ }
+
+ /**
+ * Release a previously allocated NattKeepalive that has been registered with the system server
+ */
+ @Override
+ public synchronized void releaseNattKeepalive(int nattKeepaliveResourceId, int ownerUid)
+ throws RemoteException {
+ verifyNetworkStackCaller();
+
+ UserRecord userRecord = mUserResourceTracker.getUserRecord(ownerUid);
+ releaseResource(userRecord.mNattKeepaliveRecords, nattKeepaliveResourceId);
+ }
+
@Override
protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
mContext.enforceCallingOrSelfPermission(DUMP, TAG);
diff --git a/services/core/java/com/android/server/TestNetworkService.java b/services/core/java/com/android/server/TestNetworkService.java
index 40bf7bc..d19d2dd 100644
--- a/services/core/java/com/android/server/TestNetworkService.java
+++ b/services/core/java/com/android/server/TestNetworkService.java
@@ -19,6 +19,7 @@
import static com.android.internal.util.Preconditions.checkNotNull;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.INetd;
@@ -53,6 +54,7 @@
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
+import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;
/** @hide */
@@ -226,6 +228,8 @@
@NonNull Looper looper,
@NonNull Context context,
@NonNull String iface,
+ @Nullable LinkProperties lp,
+ boolean isMetered,
int callingUid,
@NonNull IBinder binder)
throws RemoteException, SocketException {
@@ -245,9 +249,19 @@
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
nc.setNetworkSpecifier(new StringNetworkSpecifier(iface));
+ if (!isMetered) {
+ nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+ }
// Build LinkProperties
- LinkProperties lp = new LinkProperties();
+ if (lp == null) {
+ lp = new LinkProperties();
+ } else {
+ lp = new LinkProperties(lp);
+ // Use LinkAddress(es) from the interface itself to minimize how much the caller
+ // is trusted.
+ lp.setLinkAddresses(new ArrayList<>());
+ }
lp.setInterfaceName(iface);
// Find the currently assigned addresses, and add them to LinkProperties
@@ -284,7 +298,11 @@
* <p>This method provides a Network that is useful only for testing.
*/
@Override
- public void setupTestNetwork(@NonNull String iface, @NonNull IBinder binder) {
+ public void setupTestNetwork(
+ @NonNull String iface,
+ @Nullable LinkProperties lp,
+ boolean isMetered,
+ @NonNull IBinder binder) {
enforceTestNetworkPermissions(mContext);
checkNotNull(iface, "missing Iface");
@@ -315,6 +333,8 @@
mHandler.getLooper(),
mContext,
iface,
+ lp,
+ isMetered,
callingUid,
binder);
diff --git a/services/core/java/com/android/server/pm/OWNERS b/services/core/java/com/android/server/pm/OWNERS
index 4baf70b..62ea95b 100644
--- a/services/core/java/com/android/server/pm/OWNERS
+++ b/services/core/java/com/android/server/pm/OWNERS
@@ -34,10 +34,10 @@
per-file UserRestrictionsUtils.java = omakoto@google.com, rubinxu@google.com, sandness@google.com, yamasani@google.com
# security
-per-file KeySetHandle.java = cbrubaker@google.com
-per-file KeySetManagerService.java = cbrubaker@google.com
-per-file PackageKeySetData.java = cbrubaker@google.com
-per-file PackageSignatures.java = cbrubaker@google.com
+per-file KeySetHandle.java = cbrubaker@google.com, nnk@google.com
+per-file KeySetManagerService.java = cbrubaker@google.com, nnk@google.com
+per-file PackageKeySetData.java = cbrubaker@google.com, nnk@google.com
+per-file PackageSignatures.java = cbrubaker@google.com, nnk@google.com
per-file SELinuxMMAC.java = cbrubaker@google.com, jeffv@google.com, jgalenson@google.com, nnk@google.com
# shortcuts
diff --git a/tests/net/java/com/android/server/IpSecServiceTest.java b/tests/net/java/com/android/server/IpSecServiceTest.java
index 4a35015..6b5a220 100644
--- a/tests/net/java/com/android/server/IpSecServiceTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceTest.java
@@ -118,6 +118,7 @@
INetd mMockNetd;
IpSecService.IpSecServiceConfiguration mMockIpSecSrvConfig;
IpSecService mIpSecService;
+ int mUid = Os.getuid();
@Before
public void setUp() throws Exception {
@@ -665,4 +666,99 @@
mIpSecService.releaseNetId(releasedNetId);
assertEquals(releasedNetId, mIpSecService.reserveNetId());
}
+
+ @Test
+ public void testLockEncapSocketForNattKeepalive() throws Exception {
+ IpSecUdpEncapResponse udpEncapResp =
+ mIpSecService.openUdpEncapsulationSocket(0, new Binder());
+ assertNotNull(udpEncapResp);
+ assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
+
+ // Verify no NATT keepalive records upon startup
+ IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
+ assertEquals(0, userRecord.mNattKeepaliveRecords.size());
+
+ int nattKeepaliveResourceId =
+ mIpSecService.lockEncapSocketForNattKeepalive(udpEncapResp.resourceId, mUid);
+
+ // Validate response, and record was added
+ assertNotEquals(IpSecManager.INVALID_RESOURCE_ID, nattKeepaliveResourceId);
+ assertEquals(1, userRecord.mNattKeepaliveRecords.size());
+
+ // Validate keepalive can be released and removed.
+ mIpSecService.releaseNattKeepalive(nattKeepaliveResourceId, mUid);
+ assertEquals(0, userRecord.mNattKeepaliveRecords.size());
+
+ mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
+ }
+
+ @Test
+ public void testLockEncapSocketForNattKeepaliveInvalidUid() throws Exception {
+ IpSecUdpEncapResponse udpEncapResp =
+ mIpSecService.openUdpEncapsulationSocket(0, new Binder());
+ assertNotNull(udpEncapResp);
+ assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
+
+ // Verify no NATT keepalive records upon startup
+ IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
+ assertEquals(0, userRecord.mNattKeepaliveRecords.size());
+
+ try {
+ int nattKeepaliveResourceId =
+ mIpSecService.lockEncapSocketForNattKeepalive(
+ udpEncapResp.resourceId, mUid + 1);
+ fail("Expected SecurityException for invalid user");
+ } catch (SecurityException expected) {
+ }
+
+ // Validate keepalive was not added to lists
+ assertEquals(0, userRecord.mNattKeepaliveRecords.size());
+ }
+
+ @Test
+ public void testLockEncapSocketForNattKeepaliveInvalidResourceId() throws Exception {
+ // Verify no NATT keepalive records upon startup
+ IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
+ assertEquals(0, userRecord.mNattKeepaliveRecords.size());
+
+ try {
+ int nattKeepaliveResourceId =
+ mIpSecService.lockEncapSocketForNattKeepalive(12345, mUid);
+ fail("Expected IllegalArgumentException for invalid resource ID");
+ } catch (IllegalArgumentException expected) {
+ }
+
+ // Validate keepalive was not added to lists
+ assertEquals(0, userRecord.mNattKeepaliveRecords.size());
+ }
+
+ @Test
+ public void testEncapSocketReleasedBeforeKeepaliveReleased() throws Exception {
+ IpSecUdpEncapResponse udpEncapResp =
+ mIpSecService.openUdpEncapsulationSocket(0, new Binder());
+ assertNotNull(udpEncapResp);
+ assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
+
+ // Get encap socket record, verify initial starting refcount.
+ IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
+ IpSecService.RefcountedResource encapSocketRefcountedRecord =
+ userRecord.mEncapSocketRecords.getRefcountedResourceOrThrow(
+ udpEncapResp.resourceId);
+ assertEquals(1, encapSocketRefcountedRecord.mRefCount);
+
+ // Verify that the reference was added
+ int nattKeepaliveResourceId =
+ mIpSecService.lockEncapSocketForNattKeepalive(udpEncapResp.resourceId, mUid);
+ assertNotEquals(IpSecManager.INVALID_RESOURCE_ID, nattKeepaliveResourceId);
+ assertEquals(2, encapSocketRefcountedRecord.mRefCount);
+
+ // Close UDP encap socket, but expect the refcountedRecord to still have a reference.
+ mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
+ assertEquals(1, encapSocketRefcountedRecord.mRefCount);
+
+ // Verify UDP encap socket cleaned up once reference is removed. Expect -1 if cleanup
+ // was properly completed.
+ mIpSecService.releaseNattKeepalive(nattKeepaliveResourceId, mUid);
+ assertEquals(-1, encapSocketRefcountedRecord.mRefCount);
+ }
}