Merge "Make DNS cache lifecycle management explicit"
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/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 56d4e02..63fd2fd 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -27,6 +27,7 @@
import static android.net.ConnectivityManager.isNetworkTypeValid;
import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY;
import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID;
+import static android.net.IpSecManager.INVALID_RESOURCE_ID;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
@@ -736,19 +737,18 @@
}
}
- final DetailedState state = DetailedState.DISCONNECTED;
-
if (wasFirstNetwork || wasDefault) {
- maybeLogBroadcast(nai, state, type, wasDefault);
- mService.sendLegacyNetworkBroadcast(nai, state, type);
+ maybeLogBroadcast(nai, DetailedState.DISCONNECTED, type, wasDefault);
+ mService.sendLegacyNetworkBroadcast(nai, DetailedState.DISCONNECTED, type);
}
if (!list.isEmpty() && wasFirstNetwork) {
if (DBG) log("Other network available for type " + type +
", sending connected broadcast");
final NetworkAgentInfo replacement = list.get(0);
- maybeLogBroadcast(replacement, state, type, mService.isDefaultNetwork(replacement));
- mService.sendLegacyNetworkBroadcast(replacement, state, type);
+ maybeLogBroadcast(replacement, DetailedState.CONNECTED, type,
+ mService.isDefaultNetwork(replacement));
+ mService.sendLegacyNetworkBroadcast(replacement, DetailedState.CONNECTED, type);
}
}
@@ -2818,6 +2818,11 @@
EVENT_PROVISIONING_NOTIFICATION, PROVISIONING_NOTIFICATION_HIDE,
mNai.network.netId));
}
+
+ @Override
+ public int getInterfaceVersion() {
+ return this.VERSION;
+ }
}
private boolean networkRequiresValidation(NetworkAgentInfo nai) {
@@ -2856,7 +2861,7 @@
try {
nai.networkMonitor().notifyPrivateDnsChanged(cfg.toParcel());
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ e.rethrowAsRuntimeException();
}
// With Private DNS bypass support, we can proceed to update the
@@ -3026,7 +3031,7 @@
try {
nai.networkMonitor().notifyNetworkDisconnected();
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ e.rethrowAsRuntimeException();
}
mNetworkAgentInfos.remove(nai.messenger);
nai.clatd.update();
@@ -3440,7 +3445,7 @@
try {
nai.networkMonitor().setAcceptPartialConnectivity();
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ e.rethrowAsRuntimeException();
}
}
}
@@ -3476,7 +3481,7 @@
try {
nai.networkMonitor().launchCaptivePortalApp();
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ e.rethrowAsRuntimeException();
}
});
}
@@ -4104,7 +4109,7 @@
try {
nai.networkMonitor().forceReevaluation(uid);
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ e.rethrowAsRuntimeException();
}
}
@@ -5484,7 +5489,7 @@
try {
networkMonitor.start();
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ e.rethrowAsRuntimeException();
}
nai.asyncChannel.connect(mContext, mTrackerHandler, nai.messenger);
NetworkInfo networkInfo = nai.networkInfo;
@@ -5541,7 +5546,7 @@
try {
networkAgent.networkMonitor().notifyLinkPropertiesChanged(newLp);
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ e.rethrowAsRuntimeException();
}
if (networkAgent.everConnected) {
notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_IP_CHANGED);
@@ -6533,7 +6538,7 @@
networkAgent.networkMonitor().notifyNetworkConnected(
networkAgent.linkProperties, networkAgent.networkCapabilities);
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ e.rethrowAsRuntimeException();
}
scheduleUnvalidatedPrompt(networkAgent);
@@ -6854,6 +6859,7 @@
enforceKeepalivePermission();
mKeepaliveTracker.startNattKeepalive(
getNetworkAgentInfoForNetwork(network), null /* fd */,
+ INVALID_RESOURCE_ID /* Unused */,
intervalSeconds, cb,
srcAddr, srcPort, dstAddr, NattSocketKeepalive.NATT_PORT);
}
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/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
index 77a18e2..bde430c 100644
--- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
+++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
@@ -17,6 +17,7 @@
package com.android.server.connectivity;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.net.IpSecManager.INVALID_RESOURCE_ID;
import static android.net.NattSocketKeepalive.NATT_PORT;
import static android.net.NetworkAgent.CMD_ADD_KEEPALIVE_PACKET_FILTER;
import static android.net.NetworkAgent.CMD_REMOVE_KEEPALIVE_PACKET_FILTER;
@@ -37,6 +38,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.net.IIpSecService;
import android.net.ISocketKeepaliveCallback;
import android.net.KeepalivePacketData;
import android.net.NattKeepalivePacketData;
@@ -52,6 +54,7 @@
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;
@@ -89,11 +92,14 @@
private final TcpKeepaliveController mTcpController;
@NonNull
private final Context mContext;
+ @NonNull
+ private final IIpSecService mIpSec;
public KeepaliveTracker(Context context, Handler handler) {
mConnectivityServiceHandler = handler;
mTcpController = new TcpKeepaliveController(handler);
mContext = context;
+ mIpSec = IIpSecService.Stub.asInterface(ServiceManager.getService(Context.IPSEC_SERVICE));
}
/**
@@ -112,6 +118,10 @@
private final int mType;
private final FileDescriptor mFd;
+ private final int mEncapSocketResourceId;
+ // Stores the NATT keepalive resource ID returned by IpSecService.
+ private int mNattIpsecResourceId = INVALID_RESOURCE_ID;
+
public static final int TYPE_NATT = 1;
public static final int TYPE_TCP = 2;
@@ -140,7 +150,8 @@
@NonNull KeepalivePacketData packet,
int interval,
int type,
- @Nullable FileDescriptor fd) throws InvalidSocketException {
+ @Nullable FileDescriptor fd,
+ int encapSocketResourceId) throws InvalidSocketException {
mCallback = callback;
mPid = Binder.getCallingPid();
mUid = Binder.getCallingUid();
@@ -151,6 +162,9 @@
mInterval = interval;
mType = type;
+ mEncapSocketResourceId = encapSocketResourceId;
+ mNattIpsecResourceId = INVALID_RESOURCE_ID;
+
// For SocketKeepalive, a dup of fd is kept in mFd so the source port from which the
// keepalives are sent cannot be reused by another app even if the fd gets closed by
// the user. A null is acceptable here for backward compatibility of PacketKeepalive
@@ -158,7 +172,7 @@
try {
if (fd != null) {
mFd = Os.dup(fd);
- } else {
+ } else {
Log.d(TAG, toString() + " calls with null fd");
if (!mPrivileged) {
throw new SecurityException(
@@ -206,6 +220,8 @@
+ IpUtils.addressAndPortToString(mPacket.dstAddress, mPacket.dstPort)
+ " interval=" + mInterval
+ " uid=" + mUid + " pid=" + mPid + " privileged=" + mPrivileged
+ + " nattIpsecRId=" + mNattIpsecResourceId
+ + " encapSocketRId=" + mEncapSocketResourceId
+ " packetData=" + HexDump.toHexString(mPacket.getPacket())
+ " ]";
}
@@ -262,6 +278,51 @@
return SUCCESS;
}
+ private int checkAndLockNattKeepaliveResource() {
+ // Check that apps should be either privileged or fill in an ipsec encapsulated socket
+ // resource id.
+ if (mEncapSocketResourceId == INVALID_RESOURCE_ID) {
+ if (mPrivileged) {
+ return SUCCESS;
+ } else {
+ // Invalid access.
+ return ERROR_INVALID_SOCKET;
+ }
+ }
+
+ // Check if the ipsec encapsulated socket resource id is registered.
+ final HashMap<Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(mNai);
+ if (networkKeepalives == null) {
+ return ERROR_INVALID_NETWORK;
+ }
+ for (KeepaliveInfo ki : networkKeepalives.values()) {
+ if (ki.mEncapSocketResourceId == mEncapSocketResourceId
+ && ki.mNattIpsecResourceId != INVALID_RESOURCE_ID) {
+ Log.d(TAG, "Registered resource found on keepalive " + mSlot
+ + " when verify NATT socket with uid=" + mUid + " rid="
+ + mEncapSocketResourceId);
+ return ERROR_INVALID_SOCKET;
+ }
+ }
+
+ // Ensure that the socket is created by IpSecService, and lock the resource that is
+ // preserved by IpSecService. If succeed, a resource id is stored to keep tracking
+ // the resource preserved by IpSecServce and must be released when stopping keepalive.
+ try {
+ mNattIpsecResourceId =
+ mIpSec.lockEncapSocketForNattKeepalive(mEncapSocketResourceId, mUid);
+ return SUCCESS;
+ } catch (IllegalArgumentException e) {
+ // The UID specified does not own the specified UDP encapsulation socket.
+ Log.d(TAG, "Failed to verify NATT socket with uid=" + mUid + " rid="
+ + mEncapSocketResourceId + ": " + e);
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Error calling lockEncapSocketForNattKeepalive with "
+ + this.toString(), e);
+ }
+ return ERROR_INVALID_SOCKET;
+ }
+
private int isValid() {
synchronized (mNai) {
int error = checkInterval();
@@ -275,6 +336,13 @@
void start(int slot) {
mSlot = slot;
int error = isValid();
+
+ // Check and lock ipsec resource needed by natt kepalive. This should be only called
+ // once per keepalive.
+ if (error == SUCCESS && mType == TYPE_NATT) {
+ error = checkAndLockNattKeepaliveResource();
+ }
+
if (error == SUCCESS) {
Log.d(TAG, "Starting keepalive " + mSlot + " on " + mNai.name());
switch (mType) {
@@ -350,6 +418,20 @@
}
}
+ // Release the resource held by keepalive in IpSecService.
+ if (mNattIpsecResourceId != INVALID_RESOURCE_ID) {
+ if (mType != TYPE_NATT) {
+ Log.wtf(TAG, "natt ipsec resource held by incorrect keepalive "
+ + this.toString());
+ }
+ try {
+ mIpSec.releaseNattKeepalive(mNattIpsecResourceId, mUid);
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "error calling releaseNattKeepalive with " + this.toString(), e);
+ }
+ mNattIpsecResourceId = INVALID_RESOURCE_ID;
+ }
+
if (reason == SUCCESS) {
try {
mCallback.onStopped();
@@ -538,6 +620,7 @@
**/
public void startNattKeepalive(@Nullable NetworkAgentInfo nai,
@Nullable FileDescriptor fd,
+ int encapSocketResourceId,
int intervalSeconds,
@NonNull ISocketKeepaliveCallback cb,
@NonNull String srcAddrString,
@@ -569,7 +652,7 @@
KeepaliveInfo ki = null;
try {
ki = new KeepaliveInfo(cb, nai, packet, intervalSeconds,
- KeepaliveInfo.TYPE_NATT, fd);
+ KeepaliveInfo.TYPE_NATT, fd, encapSocketResourceId);
} catch (InvalidSocketException | IllegalArgumentException | SecurityException e) {
Log.e(TAG, "Fail to construct keepalive", e);
notifyErrorCallback(cb, ERROR_INVALID_SOCKET);
@@ -609,7 +692,7 @@
KeepaliveInfo ki = null;
try {
ki = new KeepaliveInfo(cb, nai, packet, intervalSeconds,
- KeepaliveInfo.TYPE_TCP, fd);
+ KeepaliveInfo.TYPE_TCP, fd, INVALID_RESOURCE_ID /* Unused */);
} catch (InvalidSocketException | IllegalArgumentException | SecurityException e) {
Log.e(TAG, "Fail to construct keepalive e=" + e);
notifyErrorCallback(cb, ERROR_INVALID_SOCKET);
@@ -628,17 +711,12 @@
**/
public void startNattKeepalive(@Nullable NetworkAgentInfo nai,
@Nullable FileDescriptor fd,
- int resourceId,
+ int encapSocketResourceId,
int intervalSeconds,
@NonNull ISocketKeepaliveCallback cb,
@NonNull String srcAddrString,
@NonNull String dstAddrString,
int dstPort) {
- // Ensure that the socket is created by IpSecService.
- if (!isNattKeepaliveSocketValid(fd, resourceId)) {
- notifyErrorCallback(cb, ERROR_INVALID_SOCKET);
- }
-
// Get src port to adopt old API.
int srcPort = 0;
try {
@@ -649,23 +727,8 @@
}
// Forward request to old API.
- startNattKeepalive(nai, fd, intervalSeconds, cb, srcAddrString, srcPort,
- dstAddrString, dstPort);
- }
-
- /**
- * Verify if the IPsec NAT-T file descriptor and resource Id hold for IPsec keepalive is valid.
- **/
- public static boolean isNattKeepaliveSocketValid(@Nullable FileDescriptor fd, int resourceId) {
- // TODO: 1. confirm whether the fd is called from system api or created by IpSecService.
- // 2. If the fd is created from the system api, check that it's bounded. And
- // call dup to keep the fd open.
- // 3. If the fd is created from IpSecService, check if the resource ID is valid. And
- // hold the resource needed in IpSecService.
- if (null == fd) {
- return false;
- }
- return true;
+ startNattKeepalive(nai, fd, encapSocketResourceId, intervalSeconds, cb, srcAddrString,
+ srcPort, dstAddrString, dstPort);
}
public void dump(IndentingPrintWriter pw) {
diff --git a/core/java/android/net/NattKeepalivePacketData.java b/services/net/java/android/net/NattKeepalivePacketData.java
similarity index 100%
rename from core/java/android/net/NattKeepalivePacketData.java
rename to services/net/java/android/net/NattKeepalivePacketData.java
diff --git a/tests/net/Android.bp b/tests/net/Android.bp
index 689abed..1fbb658 100644
--- a/tests/net/Android.bp
+++ b/tests/net/Android.bp
@@ -6,6 +6,7 @@
static_libs: [
"FrameworksNetCommonTests",
"frameworks-base-testutils",
+ "frameworks-net-testutils",
"framework-protos",
"androidx.test.rules",
"mockito-target-minus-junit4",
diff --git a/tests/net/common/Android.bp b/tests/net/common/Android.bp
index 0a1ac75..3b2e34a 100644
--- a/tests/net/common/Android.bp
+++ b/tests/net/common/Android.bp
@@ -18,9 +18,10 @@
// They must be fast and stable, and exercise public or test APIs.
java_library {
name: "FrameworksNetCommonTests",
- srcs: ["java/**/*.java"],
+ srcs: ["java/**/*.java", "java/**/*.kt"],
static_libs: [
"androidx.test.rules",
+ "frameworks-net-testutils",
"junit",
],
libs: [
diff --git a/tests/net/java/android/net/LinkAddressTest.java b/tests/net/common/java/android/net/LinkAddressTest.java
similarity index 100%
rename from tests/net/java/android/net/LinkAddressTest.java
rename to tests/net/common/java/android/net/LinkAddressTest.java
diff --git a/tests/net/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java
similarity index 99%
rename from tests/net/java/android/net/LinkPropertiesTest.java
rename to tests/net/common/java/android/net/LinkPropertiesTest.java
index 4177291..709f5f6 100644
--- a/tests/net/java/android/net/LinkPropertiesTest.java
+++ b/tests/net/common/java/android/net/LinkPropertiesTest.java
@@ -868,12 +868,12 @@
source.setNat64Prefix(new IpPrefix("2001:db8:1:2:64:64::/96"));
- TestUtils.assertParcelingIsLossless(source, LinkProperties.CREATOR);
+ TestUtils.assertParcelingIsLossless(source);
}
@Test
public void testParcelUninitialized() throws Exception {
LinkProperties empty = new LinkProperties();
- TestUtils.assertParcelingIsLossless(empty, LinkProperties.CREATOR);
+ TestUtils.assertParcelingIsLossless(empty);
}
}
diff --git a/tests/net/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
similarity index 100%
rename from tests/net/java/android/net/NetworkCapabilitiesTest.java
rename to tests/net/common/java/android/net/NetworkCapabilitiesTest.java
diff --git a/tests/net/java/android/net/NetworkTest.java b/tests/net/common/java/android/net/NetworkTest.java
similarity index 100%
rename from tests/net/java/android/net/NetworkTest.java
rename to tests/net/common/java/android/net/NetworkTest.java
diff --git a/tests/net/java/android/net/RouteInfoTest.java b/tests/net/common/java/android/net/RouteInfoTest.java
similarity index 100%
rename from tests/net/java/android/net/RouteInfoTest.java
rename to tests/net/common/java/android/net/RouteInfoTest.java
diff --git a/tests/net/java/android/net/StaticIpConfigurationTest.java b/tests/net/common/java/android/net/StaticIpConfigurationTest.java
similarity index 100%
rename from tests/net/java/android/net/StaticIpConfigurationTest.java
rename to tests/net/common/java/android/net/StaticIpConfigurationTest.java
diff --git a/tests/net/java/android/net/apf/ApfCapabilitiesTest.java b/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java
similarity index 92%
rename from tests/net/java/android/net/apf/ApfCapabilitiesTest.java
rename to tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java
index 75752c3..3ed8a86 100644
--- a/tests/net/java/android/net/apf/ApfCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java
@@ -19,11 +19,10 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
-import android.net.shared.ParcelableTestUtil;
-
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.util.ParcelableTestUtil;
import com.android.internal.util.TestUtils;
import org.junit.Test;
@@ -37,7 +36,7 @@
final ApfCapabilities caps = new ApfCapabilities(123, 456, 789);
ParcelableTestUtil.assertFieldCountEquals(3, ApfCapabilities.class);
- TestUtils.assertParcelingIsLossless(caps, ApfCapabilities.CREATOR);
+ TestUtils.assertParcelingIsLossless(caps);
}
@Test
diff --git a/tests/net/common/java/android/net/metrics/DhcpErrorEventTest.kt b/tests/net/common/java/android/net/metrics/DhcpErrorEventTest.kt
new file mode 100644
index 0000000..e191953
--- /dev/null
+++ b/tests/net/common/java/android/net/metrics/DhcpErrorEventTest.kt
@@ -0,0 +1,67 @@
+package android.net.metrics
+
+import android.net.metrics.DhcpErrorEvent.errorCodeWithOption
+import android.net.metrics.DhcpErrorEvent.DHCP_INVALID_OPTION_LENGTH
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.internal.util.TestUtils.parcelingRoundTrip
+import java.lang.reflect.Modifier
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val TEST_ERROR_CODE = 12345
+/**
+ * DHCP Optional Type: DHCP Subnet Mask (Copy from DhcpPacket.java)
+ */
+private const val DHCP_SUBNET_MASK = 1
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class DhcpErrorEventTest {
+
+ @Test
+ fun testConstructor() {
+ val event = DhcpErrorEvent(TEST_ERROR_CODE)
+ assertEquals(TEST_ERROR_CODE, event.errorCode)
+ }
+
+ @Test
+ fun testParcelUnparcel() {
+ val event = DhcpErrorEvent(TEST_ERROR_CODE)
+ val parceled = parcelingRoundTrip(event)
+ assertEquals(TEST_ERROR_CODE, parceled.errorCode)
+ }
+
+ @Test
+ fun testErrorCodeWithOption() {
+ val errorCode = errorCodeWithOption(DHCP_INVALID_OPTION_LENGTH, DHCP_SUBNET_MASK);
+ assertTrue((DHCP_INVALID_OPTION_LENGTH and errorCode) == DHCP_INVALID_OPTION_LENGTH);
+ assertTrue((DHCP_SUBNET_MASK and errorCode) == DHCP_SUBNET_MASK);
+ }
+
+ @Test
+ fun testToString() {
+ val names = listOf("L2_ERROR", "L3_ERROR", "L4_ERROR", "DHCP_ERROR", "MISC_ERROR")
+ val errorFields = DhcpErrorEvent::class.java.declaredFields.filter {
+ it.type == Int::class.javaPrimitiveType
+ && Modifier.isPublic(it.modifiers) && Modifier.isStatic(it.modifiers)
+ && it.name !in names
+ }
+
+ errorFields.forEach {
+ val intValue = it.getInt(null)
+ val stringValue = DhcpErrorEvent(intValue).toString()
+ assertTrue("Invalid string for error 0x%08X (field %s): %s".format(intValue, it.name,
+ stringValue),
+ stringValue.contains(it.name))
+ }
+ }
+
+ @Test
+ fun testToString_InvalidErrorCode() {
+ assertNotNull(DhcpErrorEvent(TEST_ERROR_CODE).toString())
+ }
+}
\ No newline at end of file
diff --git a/tests/net/java/android/net/TcpKeepalivePacketDataTest.java b/tests/net/java/android/net/TcpKeepalivePacketDataTest.java
index e0b7227..583d3fd 100644
--- a/tests/net/java/android/net/TcpKeepalivePacketDataTest.java
+++ b/tests/net/java/android/net/TcpKeepalivePacketDataTest.java
@@ -79,7 +79,7 @@
assertEquals(testInfo.tos, resultData.ipTos);
assertEquals(testInfo.ttl, resultData.ipTtl);
- TestUtils.assertParcelingIsLossless(resultData, TcpKeepalivePacketData.CREATOR);
+ TestUtils.assertParcelingIsLossless(resultData);
final byte[] packet = resultData.getPacket();
// IP version and IHL
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 77dc3ac..ca1add8 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -16,6 +16,8 @@
package com.android.server;
+import static android.content.pm.PackageManager.GET_PERMISSIONS;
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.NETID_UNSET;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
@@ -103,6 +105,7 @@
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.net.ConnectivityManager;
@@ -146,6 +149,7 @@
import android.net.shared.NetworkMonitorUtils;
import android.net.shared.PrivateDnsConfig;
import android.net.util.MultinetworkPolicyTracker;
+import android.os.Binder;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
@@ -272,6 +276,7 @@
@Mock IDnsResolver mMockDnsResolver;
@Mock INetd mMockNetd;
@Mock NetworkStackClient mNetworkStack;
+ @Mock PackageManager mPackageManager;
@Mock UserManager mUserManager;
private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor =
@@ -357,7 +362,12 @@
public Resources getResources() {
return mResources;
}
- }
+
+ @Override
+ public PackageManager getPackageManager() {
+ return mPackageManager;
+ }
+ }
public void waitForIdle(int timeoutMsAsInt) {
long timeoutMs = timeoutMsAsInt;
@@ -1232,6 +1242,7 @@
if (Looper.myLooper() == null) {
Looper.prepare();
}
+ mockDefaultPackages();
FakeSettingsProvider.clearSettingsProvider();
mServiceContext = new MockContext(InstrumentationRegistry.getContext(),
@@ -1284,7 +1295,24 @@
FakeSettingsProvider.clearSettingsProvider();
}
- private static int transportToLegacyType(int transport) {
+ private void mockDefaultPackages() throws Exception {
+ final String testPackageName = mContext.getPackageName();
+ final PackageInfo testPackageInfo = mContext.getPackageManager().getPackageInfo(
+ testPackageName, PackageManager.GET_PERMISSIONS);
+ when(mPackageManager.getPackagesForUid(Binder.getCallingUid())).thenReturn(
+ new String[] {testPackageName});
+ when(mPackageManager.getPackageInfoAsUser(eq(testPackageName), anyInt(),
+ eq(UserHandle.getCallingUserId()))).thenReturn(testPackageInfo);
+
+ when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
+ Arrays.asList(new PackageInfo[] {
+ buildPackageInfo(/* SYSTEM */ false, APP1_UID),
+ buildPackageInfo(/* SYSTEM */ false, APP2_UID),
+ buildPackageInfo(/* SYSTEM */ false, VPN_UID)
+ }));
+ }
+
+ private static int transportToLegacyType(int transport) {
switch (transport) {
case TRANSPORT_ETHERNET:
return TYPE_ETHERNET;
@@ -4228,6 +4256,25 @@
callback.expectStarted();
ka.stop();
callback.expectStopped();
+
+ // Check that the same NATT socket cannot be used by 2 keepalives.
+ try (SocketKeepalive ka2 = mCm.createSocketKeepalive(
+ myNet, testSocket, myIPv4, dstIPv4, executor, callback)) {
+ // Check that second keepalive cannot be started if the first one is running.
+ ka.start(validKaInterval);
+ callback.expectStarted();
+ ka2.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_SOCKET);
+ ka.stop();
+ callback.expectStopped();
+
+ // Check that second keepalive can be started/stopped normally if the first one is
+ // stopped.
+ ka2.start(validKaInterval);
+ callback.expectStarted();
+ ka2.stop();
+ callback.expectStopped();
+ }
}
// Check that deleting the IP address stops the keepalive.
@@ -4291,6 +4338,10 @@
testSocket.close();
testSocket2.close();
}
+
+ // Check that the closed socket cannot be used to start keepalive.
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_SOCKET);
}
// Check that there is no port leaked after all keepalives and sockets are closed.
@@ -6156,7 +6207,6 @@
}
@Test
- @Ignore
public void testFullyRoutedVpnResultsInInterfaceFilteringRules() throws Exception {
LinkProperties lp = new LinkProperties();
lp.setInterfaceName("tun0");
@@ -6183,7 +6233,6 @@
}
@Test
- @Ignore
public void testLegacyVpnDoesNotResultInInterfaceFilteringRule() throws Exception {
LinkProperties lp = new LinkProperties();
lp.setInterfaceName("tun0");
@@ -6197,7 +6246,6 @@
}
@Test
- @Ignore
public void testLocalIpv4OnlyVpnDoesNotResultInInterfaceFilteringRule()
throws Exception {
LinkProperties lp = new LinkProperties();
@@ -6213,7 +6261,6 @@
}
@Test
- @Ignore
public void testVpnHandoverChangesInterfaceFilteringRule() throws Exception {
LinkProperties lp = new LinkProperties();
lp.setInterfaceName("tun0");
@@ -6263,7 +6310,6 @@
}
@Test
- @Ignore
public void testUidUpdateChangesInterfaceFilteringRule() throws Exception {
LinkProperties lp = new LinkProperties();
lp.setInterfaceName("tun0");
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);
+ }
}
diff --git a/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt b/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt
index d983b65..f045369 100644
--- a/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt
+++ b/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt
@@ -20,6 +20,8 @@
import android.net.ConnectivityManager.TYPE_MOBILE
import android.net.ConnectivityManager.TYPE_WIFI
import android.net.ConnectivityManager.TYPE_WIMAX
+import android.net.NetworkInfo.DetailedState.CONNECTED
+import android.net.NetworkInfo.DetailedState.DISCONNECTED
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.server.ConnectivityService.LegacyTypeTracker
@@ -32,8 +34,12 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
const val UNSUPPORTED_TYPE = TYPE_WIMAX
@@ -89,4 +95,20 @@
mTracker.add(UNSUPPORTED_TYPE, mobileNai)
assertNull(mTracker.getNetworkForType(UNSUPPORTED_TYPE))
}
+
+ @Test
+ fun testBroadcastOnDisconnect() {
+ val mobileNai1 = mock(NetworkAgentInfo::class.java)
+ val mobileNai2 = mock(NetworkAgentInfo::class.java)
+ doReturn(false).`when`(mMockService).isDefaultNetwork(mobileNai1)
+ mTracker.add(TYPE_MOBILE, mobileNai1)
+ verify(mMockService).sendLegacyNetworkBroadcast(mobileNai1, CONNECTED, TYPE_MOBILE)
+ reset(mMockService)
+ doReturn(false).`when`(mMockService).isDefaultNetwork(mobileNai2)
+ mTracker.add(TYPE_MOBILE, mobileNai2)
+ verify(mMockService, never()).sendLegacyNetworkBroadcast(any(), any(), anyInt())
+ mTracker.remove(TYPE_MOBILE, mobileNai1, false /* wasDefault */)
+ verify(mMockService).sendLegacyNetworkBroadcast(mobileNai1, DISCONNECTED, TYPE_MOBILE)
+ verify(mMockService).sendLegacyNetworkBroadcast(mobileNai2, CONNECTED, TYPE_MOBILE)
+ }
}