Merge changes I8e806b3b,I5e8e4673,Id2a078da
* changes:
Add CTS tests for EthernetNetworkProvider changes
Move EthernetNetworkFactory to using the NetworkProvider API
Improve waiting for interface added or removed
diff --git a/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
index 75c2ad1..68c1c57 100644
--- a/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
+++ b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
@@ -352,15 +352,6 @@
assertFalse(mTestMap.isEmpty());
mTestMap.clear();
assertTrue(mTestMap.isEmpty());
-
- // Clearing an already-closed map throws.
- mTestMap.close();
- try {
- mTestMap.clear();
- fail("clearing already-closed map should throw");
- } catch (IllegalStateException expected) {
- // ParcelFileDescriptor.getFd throws IllegalStateException: Already closed.
- }
}
@Test
diff --git a/framework-t/src/android/net/IpSecManager.java b/framework-t/src/android/net/IpSecManager.java
index 9cb0947..9cceac2 100644
--- a/framework-t/src/android/net/IpSecManager.java
+++ b/framework-t/src/android/net/IpSecManager.java
@@ -817,10 +817,10 @@
* </ol>
*
* @param underlyingNetwork the new {@link Network} that will carry traffic for this tunnel.
- * This network MUST never be the network exposing this IpSecTunnelInterface, otherwise
- * this method will throw an {@link IllegalArgumentException}. If the
- * IpSecTunnelInterface is later added to this network, all outbound traffic will be
- * blackholed.
+ * This network MUST be a functional {@link Network} with valid {@link LinkProperties},
+ * and MUST never be the network exposing this IpSecTunnelInterface, otherwise this
+ * method will throw an {@link IllegalArgumentException}. If the IpSecTunnelInterface is
+ * later added to this network, all outbound traffic will be blackholed.
*/
// TODO: b/169171001 Update the documentation when transform migration is supported.
// The purpose of making updating network and applying transforms separate is to leave open
@@ -962,7 +962,6 @@
* IP header and IPsec Header on all inbound traffic).
* <p>Applications should probably not use this API directly.
*
- *
* @param tunnel The {@link IpSecManager#IpSecTunnelInterface} that will use the supplied
* transform.
* @param direction the direction, {@link DIRECTION_OUT} or {@link #DIRECTION_IN} in which
diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java
index 4f9d845..6245e07 100644
--- a/framework/src/android/net/NetworkRequest.java
+++ b/framework/src/android/net/NetworkRequest.java
@@ -220,7 +220,7 @@
*/
public Builder(@NonNull final NetworkRequest request) {
Objects.requireNonNull(request);
- mNetworkCapabilities = request.networkCapabilities;
+ mNetworkCapabilities = new NetworkCapabilities(request.networkCapabilities);
// If the caller constructed the builder from a request, it means the user
// might explicitly want the capabilities from the request. Thus, the NOT_VCN_MANAGED
// capabilities should not be touched later.
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
index 08e66d9..42d0de5 100644
--- a/netd/BpfHandler.cpp
+++ b/netd/BpfHandler.cpp
@@ -104,6 +104,7 @@
RETURN_IF_NOT_OK(mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY, SELECT_MAP_A,
BPF_ANY));
RETURN_IF_NOT_OK(mUidPermissionMap.init(UID_PERMISSION_MAP_PATH));
+ ALOGI("%s successfully", __func__);
return netdutils::status::ok;
}
@@ -233,7 +234,7 @@
if (sock_cookie == NONEXISTENT_COOKIE) return -errno;
base::Result<void> res = mCookieTagMap.deleteValue(sock_cookie);
if (!res.ok()) {
- ALOGE("Failed to untag socket: %s\n", strerror(res.error().code()));
+ ALOGE("Failed to untag socket: %s", strerror(res.error().code()));
return -res.error().code();
}
return 0;
diff --git a/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
index 4d605ce..6c7a15e 100644
--- a/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
+++ b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
@@ -205,6 +205,10 @@
configuration.error().message().c_str());
return -configuration.error().code();
}
+ if (configuration.value() != SELECT_MAP_A && configuration.value() != SELECT_MAP_B) {
+ ALOGE("%s unknown configuration value: %d", __func__, configuration.value());
+ return -EINVAL;
+ }
const char* statsMapPath = STATS_MAP_PATH[configuration.value()];
BpfMap<StatsKey, StatsValue> statsMap(statsMapPath);
if (!statsMap.isValid()) {
diff --git a/service-t/src/com/android/server/IpSecService.java b/service-t/src/com/android/server/IpSecService.java
index 4bc40ea..16b9f1e 100644
--- a/service-t/src/com/android/server/IpSecService.java
+++ b/service-t/src/com/android/server/IpSecService.java
@@ -1452,6 +1452,11 @@
final ConnectivityManager connectivityManager =
mContext.getSystemService(ConnectivityManager.class);
final LinkProperties lp = connectivityManager.getLinkProperties(underlyingNetwork);
+ if (lp == null) {
+ throw new IllegalArgumentException(
+ "LinkProperties is null. The underlyingNetwork may not be functional");
+ }
+
if (tunnelInterfaceInfo.getInterfaceName().equals(lp.getInterfaceName())) {
throw new IllegalArgumentException(
"Underlying network cannot be the network being exposed by this tunnel");
diff --git a/service-t/src/com/android/server/ethernet/EthernetConfigStore.java b/service-t/src/com/android/server/ethernet/EthernetConfigStore.java
index 6b623f4..6006539 100644
--- a/service-t/src/com/android/server/ethernet/EthernetConfigStore.java
+++ b/service-t/src/com/android/server/ethernet/EthernetConfigStore.java
@@ -16,23 +16,37 @@
package com.android.server.ethernet;
+import static com.android.net.module.util.DeviceConfigUtils.TETHERING_MODULE_NAME;
+
import android.annotation.Nullable;
+import android.content.ApexEnvironment;
import android.net.IpConfiguration;
import android.os.Environment;
import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.net.IpConfigStore;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
/**
* This class provides an API to store and manage Ethernet network configuration.
*/
public class EthernetConfigStore {
- private static final String ipConfigFile = Environment.getDataDirectory() +
- "/misc/ethernet/ipconfig.txt";
+ private static final String TAG = EthernetConfigStore.class.getSimpleName();
+ private static final String CONFIG_FILE = "ipconfig.txt";
+ private static final String FILE_PATH = "/misc/ethernet/";
+ private static final String LEGACY_IP_CONFIG_FILE_PATH = Environment.getDataDirectory()
+ + FILE_PATH;
+ private static final String APEX_IP_CONFIG_FILE_PATH = ApexEnvironment.getApexEnvironment(
+ TETHERING_MODULE_NAME).getDeviceProtectedDataDir() + FILE_PATH;
private IpConfigStore mStore = new IpConfigStore();
- private ArrayMap<String, IpConfiguration> mIpConfigurations;
+ private final ArrayMap<String, IpConfiguration> mIpConfigurations;
private IpConfiguration mIpConfigurationForDefaultInterface;
private final Object mSync = new Object();
@@ -40,22 +54,70 @@
mIpConfigurations = new ArrayMap<>(0);
}
- public void read() {
- synchronized (mSync) {
- ArrayMap<String, IpConfiguration> configs =
- IpConfigStore.readIpConfigurations(ipConfigFile);
+ private static boolean doesConfigFileExist(final String filepath) {
+ return new File(filepath).exists();
+ }
- // This configuration may exist in old file versions when there was only a single active
- // Ethernet interface.
- if (configs.containsKey("0")) {
- mIpConfigurationForDefaultInterface = configs.remove("0");
+ private void writeLegacyIpConfigToApexPath(final String newFilePath, final String oldFilePath,
+ final String filename) {
+ final File directory = new File(newFilePath);
+ if (!directory.exists()) {
+ directory.mkdirs();
+ }
+
+ // Write the legacy IP config to the apex file path.
+ FileOutputStream fos = null;
+ final AtomicFile dst = new AtomicFile(new File(newFilePath + filename));
+ final AtomicFile src = new AtomicFile(new File(oldFilePath + filename));
+ try {
+ final byte[] raw = src.readFully();
+ if (raw.length > 0) {
+ fos = dst.startWrite();
+ fos.write(raw);
+ fos.flush();
+ dst.finishWrite(fos);
}
-
- mIpConfigurations = configs;
+ } catch (IOException e) {
+ Log.e(TAG, "Fail to sync the legacy IP config to the apex file path.");
+ dst.failWrite(fos);
}
}
+ public void read() {
+ read(APEX_IP_CONFIG_FILE_PATH, LEGACY_IP_CONFIG_FILE_PATH, CONFIG_FILE);
+ }
+
+ @VisibleForTesting
+ void read(final String newFilePath, final String oldFilePath, final String filename) {
+ synchronized (mSync) {
+ // Attempt to read the IP configuration from apex file path first.
+ if (doesConfigFileExist(newFilePath + filename)) {
+ loadConfigFileLocked(newFilePath + filename);
+ return;
+ }
+
+ // If the config file doesn't exist in the apex file path, attempt to read it from
+ // the legacy file path, if config file exists, write the legacy IP configuration to
+ // apex config file path, this should just happen on the first boot. New or updated
+ // config entries are only written to the apex config file later.
+ if (!doesConfigFileExist(oldFilePath + filename)) return;
+ loadConfigFileLocked(oldFilePath + filename);
+ writeLegacyIpConfigToApexPath(newFilePath, oldFilePath, filename);
+ }
+ }
+
+ private void loadConfigFileLocked(final String filepath) {
+ final ArrayMap<String, IpConfiguration> configs =
+ IpConfigStore.readIpConfigurations(filepath);
+ mIpConfigurations.putAll(configs);
+ }
+
public void write(String iface, IpConfiguration config) {
+ write(iface, config, APEX_IP_CONFIG_FILE_PATH + CONFIG_FILE);
+ }
+
+ @VisibleForTesting
+ void write(String iface, IpConfiguration config, String filepath) {
boolean modified;
synchronized (mSync) {
@@ -67,7 +129,7 @@
}
if (modified) {
- mStore.writeIpConfigurations(ipConfigFile, mIpConfigurations);
+ mStore.writeIpConfigurations(filepath, mIpConfigurations);
}
}
}
@@ -80,9 +142,6 @@
@Nullable
public IpConfiguration getIpConfigurationForDefaultInterface() {
- synchronized (mSync) {
- return mIpConfigurationForDefaultInterface == null
- ? null : new IpConfiguration(mIpConfigurationForDefaultInterface);
- }
+ return null;
}
}
diff --git a/service/native/TrafficController.cpp b/service/native/TrafficController.cpp
index 5581c40..a9ede6a 100644
--- a/service/native/TrafficController.cpp
+++ b/service/native/TrafficController.cpp
@@ -184,6 +184,7 @@
RETURN_IF_NOT_OK(mUidOwnerMap.init(UID_OWNER_MAP_PATH));
RETURN_IF_NOT_OK(mUidOwnerMap.clear());
RETURN_IF_NOT_OK(mUidPermissionMap.init(UID_PERMISSION_MAP_PATH));
+ ALOGI("%s successfully", __func__);
return netdutils::status::ok;
}
diff --git a/service/native/TrafficControllerTest.cpp b/service/native/TrafficControllerTest.cpp
index ad53cb8..3f13532 100644
--- a/service/native/TrafficControllerTest.cpp
+++ b/service/native/TrafficControllerTest.cpp
@@ -685,7 +685,7 @@
if (res.ok() || (res.error().code() == ENOENT)) {
return Result<void>();
}
- ALOGE("Failed to delete data(cookie = %" PRIu64 "): %s\n", key,
+ ALOGE("Failed to delete data(cookie = %" PRIu64 "): %s", key,
strerror(res.error().code()));
}
// Move forward to next cookie in the map.
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index 524bd65..93e9dcd 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -217,7 +217,10 @@
Log.d(TAG, "Expecting count " + expectedCount + " but actual is " + count + " after "
+ attempts + " attempts; sleeping "
+ SLEEP_TIME_SEC + " seconds before trying again");
- SystemClock.sleep(SLEEP_TIME_SEC * SECOND_IN_MS);
+ // No sleep after the last turn
+ if (attempts <= maxAttempts) {
+ SystemClock.sleep(SLEEP_TIME_SEC * SECOND_IN_MS);
+ }
} while (attempts <= maxAttempts);
assertEquals("Number of expected broadcasts for " + receiverName + " not reached after "
+ maxAttempts * SLEEP_TIME_SEC + " seconds", expectedCount, count);
@@ -328,7 +331,10 @@
}
Log.d(TAG, "App not on background state (" + state + ") on attempt #" + i
+ "; sleeping 1s before trying again");
- SystemClock.sleep(SECOND_IN_MS);
+ // No sleep after the last turn
+ if (i < maxTries) {
+ SystemClock.sleep(SECOND_IN_MS);
+ }
}
fail("App2 (" + mUid + ") is not on background state after "
+ maxTries + " attempts: " + state);
@@ -347,7 +353,10 @@
Log.d(TAG, "App not on foreground state on attempt #" + i
+ "; sleeping 1s before trying again");
turnScreenOn();
- SystemClock.sleep(SECOND_IN_MS);
+ // No sleep after the last turn
+ if (i < maxTries) {
+ SystemClock.sleep(SECOND_IN_MS);
+ }
}
fail("App2 (" + mUid + ") is not on foreground state after "
+ maxTries + " attempts: " + state);
@@ -365,7 +374,10 @@
}
Log.d(TAG, "App not on foreground service state on attempt #" + i
+ "; sleeping 1s before trying again");
- SystemClock.sleep(SECOND_IN_MS);
+ // No sleep after the last turn
+ if (i < maxTries) {
+ SystemClock.sleep(SECOND_IN_MS);
+ }
}
fail("App2 (" + mUid + ") is not on foreground service state after "
+ maxTries + " attempts: " + state);
@@ -506,7 +518,10 @@
Log.v(TAG, "Command '" + command + "' returned '" + result + " instead of '"
+ checker.getExpected() + "' on attempt #" + i
+ "; sleeping " + napTimeSeconds + "s before trying again");
- SystemClock.sleep(napTimeSeconds * SECOND_IN_MS);
+ // No sleep after the last turn
+ if (i < maxTries) {
+ SystemClock.sleep(napTimeSeconds * SECOND_IN_MS);
+ }
}
fail("Command '" + command + "' did not return '" + checker.getExpected() + "' after "
+ maxTries
@@ -578,7 +593,10 @@
}
Log.v(TAG, list + " check for uid " + uid + " doesn't match yet (expected "
+ expected + ", got " + actual + "); sleeping 1s before polling again");
- SystemClock.sleep(SECOND_IN_MS);
+ // No sleep after the last turn
+ if (i < maxTries) {
+ SystemClock.sleep(SECOND_IN_MS);
+ }
}
fail(list + " check for uid " + uid + " failed: expected " + expected + ", got " + actual
+ ". Full list: " + uids);
@@ -738,7 +756,8 @@
protected void assertAppIdle(boolean enabled) throws Exception {
try {
- assertDelayedShellCommand("am get-inactive " + TEST_APP2_PKG, 15, 2, "Idle=" + enabled);
+ assertDelayedShellCommand("am get-inactive " + TEST_APP2_PKG,
+ 30 /* maxTries */, 1 /* napTimeSeconds */, "Idle=" + enabled);
} catch (Throwable e) {
throw e;
}
@@ -765,7 +784,10 @@
return;
}
Log.v(TAG, "app2 receiver is not ready yet; sleeping 1s before polling again");
- SystemClock.sleep(SECOND_IN_MS);
+ // No sleep after the last turn
+ if (i < maxTries) {
+ SystemClock.sleep(SECOND_IN_MS);
+ }
}
fail("app2 receiver is not ready in " + mUid);
}
@@ -814,8 +836,6 @@
return;
} else if (type == TYPE_COMPONENT_ACTIVTIY) {
turnScreenOn();
- // Wait for screen-on state to propagate through the system.
- SystemClock.sleep(2000);
final CountDownLatch latch = new CountDownLatch(1);
final Intent launchIntent = getIntentForComponent(type);
final Bundle extras = new Bundle();
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
index 51acfdf..82f13ae 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
@@ -29,6 +29,7 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
+import android.view.WindowManager;
import androidx.annotation.GuardedBy;
@@ -46,6 +47,8 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "MyActivity.onCreate()");
+
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
@Override
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 8f6786f..3b88189 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -3235,14 +3235,16 @@
// TODD: Have a significant signal to know the uids has been sent to netd.
assertBindSocketToNetworkSuccess(network);
- // Uid is in allowed list. Try file network request again.
- requestNetwork(restrictedRequest, restrictedNetworkCb);
- // Verify that the network is restricted.
- restrictedNetworkCb.eventuallyExpect(CallbackEntry.NETWORK_CAPS_UPDATED,
- NETWORK_CALLBACK_TIMEOUT_MS,
- entry -> network.equals(entry.getNetwork())
- && (!((CallbackEntry.CapabilitiesChanged) entry).getCaps()
- .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)));
+ if (TestUtils.shouldTestTApis()) {
+ // Uid is in allowed list. Try file network request again.
+ requestNetwork(restrictedRequest, restrictedNetworkCb);
+ // Verify that the network is restricted.
+ restrictedNetworkCb.eventuallyExpect(CallbackEntry.NETWORK_CAPS_UPDATED,
+ NETWORK_CALLBACK_TIMEOUT_MS,
+ entry -> network.equals(entry.getNetwork())
+ && (!((CallbackEntry.CapabilitiesChanged) entry).getCaps()
+ .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)));
+ }
} finally {
agent.unregister();
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 088d202..33a0a83 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -52,9 +52,7 @@
import androidx.test.runner.AndroidJUnit4
import com.android.net.module.util.ArrayTrackRecord
import com.android.net.module.util.TrackRecord
-import com.android.networkstack.apishim.ConstantsShim
import com.android.networkstack.apishim.NsdShimImpl
-import com.android.testutils.SC_V2
import com.android.testutils.TestableNetworkAgent
import com.android.testutils.TestableNetworkCallback
import com.android.testutils.runAsShell
@@ -249,9 +247,11 @@
fun setUp() {
handlerThread.start()
- runAsShell(MANAGE_TEST_NETWORKS) {
- testNetwork1 = createTestNetwork()
- testNetwork2 = createTestNetwork()
+ if (TestUtils.shouldTestTApis()) {
+ runAsShell(MANAGE_TEST_NETWORKS) {
+ testNetwork1 = createTestNetwork()
+ testNetwork2 = createTestNetwork()
+ }
}
}
@@ -290,9 +290,11 @@
@After
fun tearDown() {
- runAsShell(MANAGE_TEST_NETWORKS) {
- testNetwork1.close(cm)
- testNetwork2.close(cm)
+ if (TestUtils.shouldTestTApis()) {
+ runAsShell(MANAGE_TEST_NETWORKS) {
+ testNetwork1.close(cm)
+ testNetwork2.close(cm)
+ }
}
handlerThread.quitSafely()
}
@@ -419,7 +421,7 @@
@Test
fun testNsdManager_DiscoverOnNetwork() {
// This test requires shims supporting T+ APIs (discovering on specific network)
- assumeTrue(ConstantsShim.VERSION > SC_V2)
+ assumeTrue(TestUtils.shouldTestTApis())
val si = NsdServiceInfo()
si.serviceType = SERVICE_TYPE
@@ -453,7 +455,7 @@
@Test
fun testNsdManager_DiscoverWithNetworkRequest() {
// This test requires shims supporting T+ APIs (discovering on network request)
- assumeTrue(ConstantsShim.VERSION > SC_V2)
+ assumeTrue(TestUtils.shouldTestTApis())
val si = NsdServiceInfo()
si.serviceType = SERVICE_TYPE
@@ -518,7 +520,7 @@
@Test
fun testNsdManager_ResolveOnNetwork() {
// This test requires shims supporting T+ APIs (NsdServiceInfo.network)
- assumeTrue(ConstantsShim.VERSION > SC_V2)
+ assumeTrue(TestUtils.shouldTestTApis())
val si = NsdServiceInfo()
si.serviceType = SERVICE_TYPE
@@ -562,7 +564,7 @@
@Test
fun testNsdManager_RegisterOnNetwork() {
// This test requires shims supporting T+ APIs (NsdServiceInfo.network)
- assumeTrue(ConstantsShim.VERSION > SC_V2)
+ assumeTrue(TestUtils.shouldTestTApis())
val si = NsdServiceInfo()
si.serviceType = SERVICE_TYPE
diff --git a/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java
index 45f3d3c..061dc87 100644
--- a/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -783,6 +783,23 @@
}
@Test
+ public void testSetNetworkForTunnelInterfaceFailsForNullLp() throws Exception {
+ final IpSecTunnelInterfaceResponse createTunnelResp =
+ createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
+ final Network newFakeNetwork = new Network(1000);
+ final int tunnelIfaceResourceId = createTunnelResp.resourceId;
+
+ try {
+ mIpSecService.setNetworkForTunnelInterface(
+ tunnelIfaceResourceId, newFakeNetwork, BLESSED_PACKAGE);
+ fail(
+ "Expected an IllegalArgumentException for underlying network with null"
+ + " LinkProperties");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
public void testSetNetworkForTunnelInterfaceFailsForInvalidResourceId() throws Exception {
final IpSecTunnelInterfaceResponse createTunnelResp =
createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetConfigStoreTest.java b/tests/unit/java/com/android/server/ethernet/EthernetConfigStoreTest.java
new file mode 100644
index 0000000..a9f80ea
--- /dev/null
+++ b/tests/unit/java/com/android/server/ethernet/EthernetConfigStoreTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2022 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.ethernet;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.net.InetAddresses;
+import android.net.IpConfiguration;
+import android.net.IpConfiguration.IpAssignment;
+import android.net.IpConfiguration.ProxySettings;
+import android.net.LinkAddress;
+import android.net.ProxyInfo;
+import android.net.StaticIpConfiguration;
+import android.util.ArrayMap;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class EthernetConfigStoreTest {
+ private static final LinkAddress LINKADDR = new LinkAddress("192.168.1.100/25");
+ private static final InetAddress GATEWAY = InetAddresses.parseNumericAddress("192.168.1.1");
+ private static final InetAddress DNS1 = InetAddresses.parseNumericAddress("8.8.8.8");
+ private static final InetAddress DNS2 = InetAddresses.parseNumericAddress("8.8.4.4");
+ private static final StaticIpConfiguration STATIC_IP_CONFIG =
+ new StaticIpConfiguration.Builder()
+ .setIpAddress(LINKADDR)
+ .setGateway(GATEWAY)
+ .setDnsServers(new ArrayList<InetAddress>(
+ List.of(DNS1, DNS2)))
+ .build();
+ private static final ProxyInfo PROXY_INFO = ProxyInfo.buildDirectProxy("test", 8888);
+ private static final IpConfiguration APEX_IP_CONFIG =
+ new IpConfiguration(IpAssignment.DHCP, ProxySettings.NONE, null, null);
+ private static final IpConfiguration LEGACY_IP_CONFIG =
+ new IpConfiguration(IpAssignment.STATIC, ProxySettings.STATIC, STATIC_IP_CONFIG,
+ PROXY_INFO);
+
+ private EthernetConfigStore mEthernetConfigStore;
+ private File mApexTestDir;
+ private File mLegacyTestDir;
+ private File mApexConfigFile;
+ private File mLegacyConfigFile;
+
+ private void createTestDir() {
+ final Context context = InstrumentationRegistry.getContext();
+ final File baseDir = context.getFilesDir();
+ mApexTestDir = new File(baseDir.getPath() + "/apex");
+ mApexTestDir.mkdirs();
+
+ mLegacyTestDir = new File(baseDir.getPath() + "/legacy");
+ mLegacyTestDir.mkdirs();
+ }
+
+ @Before
+ public void setUp() {
+ createTestDir();
+ mEthernetConfigStore = new EthernetConfigStore();
+ }
+
+ @After
+ public void tearDown() {
+ mApexTestDir.delete();
+ mLegacyTestDir.delete();
+ }
+
+ private void assertConfigFileExist(final String filepath) {
+ assertTrue(new File(filepath).exists());
+ }
+
+ /** Wait for the delayed write operation completes. */
+ private void waitForMs(long ms) {
+ try {
+ Thread.sleep(ms);
+ } catch (final InterruptedException e) {
+ fail("Thread was interrupted");
+ }
+ }
+
+ @Test
+ public void testWriteIpConfigToApexFilePathAndRead() throws Exception {
+ // Write the config file to the apex file path, pretend the config file exits and
+ // check if IP config should be read from apex file path.
+ mApexConfigFile = new File(mApexTestDir.getPath(), "test.txt");
+ mEthernetConfigStore.write("eth0", APEX_IP_CONFIG, mApexConfigFile.getPath());
+ waitForMs(50);
+
+ mEthernetConfigStore.read(mApexTestDir.getPath(), mLegacyTestDir.getPath(), "/test.txt");
+ final ArrayMap<String, IpConfiguration> ipConfigurations =
+ mEthernetConfigStore.getIpConfigurations();
+ assertEquals(APEX_IP_CONFIG, ipConfigurations.get("eth0"));
+
+ mApexConfigFile.delete();
+ }
+
+ @Test
+ public void testWriteIpConfigToLegacyFilePathAndRead() throws Exception {
+ // Write the config file to the legacy file path, pretend the config file exits and
+ // check if IP config should be read from legacy file path.
+ mLegacyConfigFile = new File(mLegacyTestDir, "test.txt");
+ mEthernetConfigStore.write("0", LEGACY_IP_CONFIG, mLegacyConfigFile.getPath());
+ waitForMs(50);
+
+ mEthernetConfigStore.read(mApexTestDir.getPath(), mLegacyTestDir.getPath(), "/test.txt");
+ final ArrayMap<String, IpConfiguration> ipConfigurations =
+ mEthernetConfigStore.getIpConfigurations();
+ assertEquals(LEGACY_IP_CONFIG, ipConfigurations.get("0"));
+
+ // Check the same config file in apex file path is created.
+ assertConfigFileExist(mApexTestDir.getPath() + "/test.txt");
+
+ final File apexConfigFile = new File(mApexTestDir.getPath() + "/test.txt");
+ apexConfigFile.delete();
+ mLegacyConfigFile.delete();
+ }
+}