diff options
Diffstat (limited to 'tests')
13 files changed, 2085 insertions, 50 deletions
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 a8402421ee..f4601808b0 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 @@ -149,7 +149,7 @@ public abstract class AbstractRestrictBackgroundNetworkTestCase { private static final String APP_NOT_FOREGROUND_ERROR = "app_not_fg"; - protected static final long TEMP_POWERSAVE_WHITELIST_DURATION_MS = 5_000; // 5 sec + protected static final long TEMP_POWERSAVE_WHITELIST_DURATION_MS = 20_000; // 20 sec private static final long BROADCAST_TIMEOUT_MS = 15_000; diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java index 89a9bd6664..b6218d2acc 100644 --- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java +++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java @@ -38,6 +38,7 @@ import android.app.ActivityManager; import android.app.Instrumentation; import android.app.UiAutomation; import android.content.Context; +import android.content.pm.PackageManager; import android.location.LocationManager; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; @@ -99,6 +100,10 @@ public class NetworkPolicyTestUtils { return mBatterySaverSupported; } + private static boolean isWear() { + return getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH); + } + /** * As per CDD requirements, if the device doesn't support data saver mode then * ConnectivityManager.getRestrictBackgroundStatus() will always return @@ -107,6 +112,9 @@ public class NetworkPolicyTestUtils { * RESTRICT_BACKGROUND_STATUS_DISABLED or not. */ public static boolean isDataSaverSupported() { + if (isWear()) { + return false; + } if (mDataSaverSupported == null) { assertMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED); try { diff --git a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java index 9fa146ffca..0c4c370bbb 100644 --- a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java +++ b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java @@ -20,14 +20,18 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.cts.util.CtsNetUtils.TestNetworkCallback; +import static android.net.cts.util.IkeSessionTestUtils.CHILD_PARAMS; +import static android.net.cts.util.IkeSessionTestUtils.IKE_PARAMS; import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity; +import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2; import static com.android.testutils.TestableNetworkCallbackKt.anyNetwork; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; @@ -46,6 +50,7 @@ import android.net.ProxyInfo; import android.net.TestNetworkInterface; import android.net.VpnManager; import android.net.cts.util.CtsNetUtils; +import android.net.ipsec.ike.IkeTunnelConnectionParams; import android.os.Build; import android.os.Process; import android.platform.test.annotations.AppModeFull; @@ -55,8 +60,10 @@ import androidx.test.InstrumentationRegistry; import com.android.internal.util.HexDump; import com.android.networkstack.apishim.Ikev2VpnProfileBuilderShimImpl; import com.android.networkstack.apishim.Ikev2VpnProfileShimImpl; +import com.android.networkstack.apishim.common.Ikev2VpnProfileBuilderShim; import com.android.networkstack.apishim.common.Ikev2VpnProfileShim; import com.android.networkstack.apishim.common.UnsupportedApiLevelException; +import com.android.testutils.DevSdkIgnoreRule; import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; import com.android.testutils.DevSdkIgnoreRunner; import com.android.testutils.RecorderCallback.CallbackEntry; @@ -64,6 +71,7 @@ import com.android.testutils.TestableNetworkCallback; import org.bouncycastle.x509.X509V1CertificateGenerator; import org.junit.After; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -85,7 +93,8 @@ import javax.security.auth.x500.X500Principal; @AppModeFull(reason = "Appops state changes disallowed for instant apps (OP_ACTIVATE_PLATFORM_VPN)") public class Ikev2VpnTest { private static final String TAG = Ikev2VpnTest.class.getSimpleName(); - + @Rule + public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule(); // Test vectors for IKE negotiation in test mode. private static final String SUCCESSFUL_IKE_INIT_RESP_V4 = "46b8eca1e0d72a18b2b5d9006d47a0022120222000000000000002d0220000300000002c01010004030000" @@ -204,51 +213,55 @@ public class Ikev2VpnTest { }, Manifest.permission.MANAGE_TEST_NETWORKS); } - private Ikev2VpnProfile buildIkev2VpnProfileCommon(@NonNull Ikev2VpnProfile.Builder builder, - boolean isRestrictedToTestNetworks, + private Ikev2VpnProfile buildIkev2VpnProfileCommon( + @NonNull Ikev2VpnProfileBuilderShim builderShim, boolean isRestrictedToTestNetworks, boolean requiresValidation) throws Exception { - if (isRestrictedToTestNetworks) { - builder.restrictToTestNetworks(); - } - builder.setBypassable(true) + builderShim.setBypassable(true) .setAllowedAlgorithms(TEST_ALLOWED_ALGORITHMS) .setProxy(TEST_PROXY_INFO) .setMaxMtu(TEST_MTU) .setMetered(false); if (TestUtils.shouldTestTApis()) { - Ikev2VpnProfileBuilderShimImpl.newInstance().setRequiresInternetValidation( - builder, requiresValidation); + builderShim.setRequiresInternetValidation(requiresValidation); } + + // Convert shim back to Ikev2VpnProfile.Builder since restrictToTestNetworks is a hidden + // method and does not defined in shims. + // TODO: replace it in alternative way to remove the hidden method usage + final Ikev2VpnProfile.Builder builder = (Ikev2VpnProfile.Builder) builderShim.getBuilder(); + if (isRestrictedToTestNetworks) { + builder.restrictToTestNetworks(); + } + return builder.build(); } private Ikev2VpnProfile buildIkev2VpnProfilePsk(@NonNull String remote, boolean isRestrictedToTestNetworks, boolean requiresValidation) throws Exception { - final Ikev2VpnProfile.Builder builder = - new Ikev2VpnProfile.Builder(remote, TEST_IDENTITY).setAuthPsk(TEST_PSK); - + final Ikev2VpnProfileBuilderShim builder = + Ikev2VpnProfileBuilderShimImpl.newInstance(remote, TEST_IDENTITY, null) + .setAuthPsk(TEST_PSK); return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks, requiresValidation); } private Ikev2VpnProfile buildIkev2VpnProfileUsernamePassword(boolean isRestrictedToTestNetworks) throws Exception { - final Ikev2VpnProfile.Builder builder = - new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR_V6, TEST_IDENTITY) - .setAuthUsernamePassword(TEST_USER, TEST_PASSWORD, mServerRootCa); + final Ikev2VpnProfileBuilderShim builder = + Ikev2VpnProfileBuilderShimImpl.newInstance(TEST_SERVER_ADDR_V6, TEST_IDENTITY, null) + .setAuthUsernamePassword(TEST_USER, TEST_PASSWORD, mServerRootCa); return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks, false /* requiresValidation */); } private Ikev2VpnProfile buildIkev2VpnProfileDigitalSignature(boolean isRestrictedToTestNetworks) throws Exception { - final Ikev2VpnProfile.Builder builder = - new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR_V6, TEST_IDENTITY) + final Ikev2VpnProfileBuilderShim builder = + Ikev2VpnProfileBuilderShimImpl.newInstance(TEST_SERVER_ADDR_V6, TEST_IDENTITY, null) .setAuthDigitalSignature( mUserCertKey.cert, mUserCertKey.key, mServerRootCa); - return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks, false /* requiresValidation */); } @@ -279,18 +292,42 @@ public class Ikev2VpnTest { assertNull(profile.getServerRootCaCert()); assertNull(profile.getRsaPrivateKey()); assertNull(profile.getUserCert()); - final Ikev2VpnProfileShim<Ikev2VpnProfile> shim = Ikev2VpnProfileShimImpl.newInstance(); + final Ikev2VpnProfileShim<Ikev2VpnProfile> shim = new Ikev2VpnProfileShimImpl(profile); if (TestUtils.shouldTestTApis()) { - assertEquals(requiresValidation, shim.isInternetValidationRequired(profile)); + assertEquals(requiresValidation, shim.isInternetValidationRequired()); } else { try { - shim.isInternetValidationRequired(profile); + shim.isInternetValidationRequired(); fail("Only supported from API level 33"); } catch (UnsupportedApiLevelException expected) { } } } + @IgnoreUpTo(SC_V2) + @Test + public void testBuildIkev2VpnProfileWithIkeTunnelConnectionParams() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); + assumeTrue(TestUtils.shouldTestTApis()); + + final IkeTunnelConnectionParams expectedParams = + new IkeTunnelConnectionParams(IKE_PARAMS, CHILD_PARAMS); + final Ikev2VpnProfileBuilderShim ikeProfileBuilder = + Ikev2VpnProfileBuilderShimImpl.newInstance(null, null, expectedParams); + // Verify the other Ike options could not be set with IkeTunnelConnectionParams. + final Class<IllegalArgumentException> expected = IllegalArgumentException.class; + assertThrows(expected, () -> ikeProfileBuilder.setAuthPsk(TEST_PSK)); + assertThrows(expected, () -> + ikeProfileBuilder.setAuthUsernamePassword(TEST_USER, TEST_PASSWORD, mServerRootCa)); + assertThrows(expected, () -> ikeProfileBuilder.setAuthDigitalSignature( + mUserCertKey.cert, mUserCertKey.key, mServerRootCa)); + + final Ikev2VpnProfile profile = (Ikev2VpnProfile) ikeProfileBuilder.build().getProfile(); + + assertEquals(expectedParams, + new Ikev2VpnProfileShimImpl(profile).getIkeTunnelConnectionParams()); + } + @Test public void testBuildIkev2VpnProfilePsk() throws Exception { doTestBuildIkev2VpnProfilePsk(true /* requiresValidation */); diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt index f007b83b9e..050497392e 100644 --- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt +++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt @@ -72,6 +72,7 @@ import android.os.Handler import android.os.HandlerThread import android.os.Message import android.os.SystemClock +import android.platform.test.annotations.AppModeFull import android.telephony.TelephonyManager import android.telephony.data.EpsBearerQosSessionAttributes import android.util.DebugUtils.valueToString @@ -106,7 +107,6 @@ import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnValidationStat import com.android.testutils.TestableNetworkCallback import com.android.testutils.assertThrows import org.junit.After -import org.junit.Assume.assumeFalse import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -946,11 +946,9 @@ class NetworkAgentTest { return Pair(agent, qosTestSocket!!) } + @AppModeFull(reason = "Instant apps don't have permission to bind sockets.") @Test fun testQosCallbackRegisterWithUnregister() { - // Instant apps can't bind sockets to localhost - // TODO: use @AppModeFull when supported by DevSdkIgnoreRunner - assumeFalse(realContext.packageManager.isInstantApp()) val (agent, socket) = setupForQosCallbackTesting() val qosCallback = TestableQosCallback() @@ -975,11 +973,9 @@ class NetworkAgentTest { } } + @AppModeFull(reason = "Instant apps don't have permission to bind sockets.") @Test fun testQosCallbackOnQosSession() { - // Instant apps can't bind sockets to localhost - // TODO: use @AppModeFull when supported by DevSdkIgnoreRunner - assumeFalse(realContext.packageManager.isInstantApp()) val (agent, socket) = setupForQosCallbackTesting() val qosCallback = TestableQosCallback() Executors.newSingleThreadExecutor().let { executor -> @@ -1023,11 +1019,9 @@ class NetworkAgentTest { } } + @AppModeFull(reason = "Instant apps don't have permission to bind sockets.") @Test fun testQosCallbackOnError() { - // Instant apps can't bind sockets to localhost - // TODO: use @AppModeFull when supported by DevSdkIgnoreRunner - assumeFalse(realContext.packageManager.isInstantApp()) val (agent, socket) = setupForQosCallbackTesting() val qosCallback = TestableQosCallback() Executors.newSingleThreadExecutor().let { executor -> @@ -1064,11 +1058,9 @@ class NetworkAgentTest { } } + @AppModeFull(reason = "Instant apps don't have permission to bind sockets.") @Test fun testQosCallbackIdsAreMappedCorrectly() { - // Instant apps can't bind sockets to localhost - // TODO: use @AppModeFull when supported by DevSdkIgnoreRunner - assumeFalse(realContext.packageManager.isInstantApp()) val (agent, socket) = setupForQosCallbackTesting() val qosCallback1 = TestableQosCallback() val qosCallback2 = TestableQosCallback() @@ -1107,11 +1099,9 @@ class NetworkAgentTest { } } + @AppModeFull(reason = "Instant apps don't have permission to bind sockets.") @Test fun testQosCallbackWhenNetworkReleased() { - // Instant apps can't bind sockets to localhost - // TODO: use @AppModeFull when supported by DevSdkIgnoreRunner - assumeFalse(realContext.packageManager.isInstantApp()) val (agent, socket) = setupForQosCallbackTesting() Executors.newSingleThreadExecutor().let { executor -> try { @@ -1151,6 +1141,7 @@ class NetworkAgentTest { ) } + @AppModeFull(reason = "Instant apps don't have permission to bind sockets.") @Test fun testUnregisterAfterReplacement() { // Keeps an eye on all test networks. diff --git a/tests/cts/net/util/java/android/net/cts/util/IkeSessionTestUtils.java b/tests/cts/net/util/java/android/net/cts/util/IkeSessionTestUtils.java new file mode 100644 index 0000000000..b4ebcdb611 --- /dev/null +++ b/tests/cts/net/util/java/android/net/cts/util/IkeSessionTestUtils.java @@ -0,0 +1,59 @@ +/* + * 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 android.net.cts.util; + +import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_128; +import static android.net.ipsec.ike.SaProposal.KEY_LEN_UNUSED; + +import android.net.ipsec.ike.ChildSaProposal; +import android.net.ipsec.ike.IkeFqdnIdentification; +import android.net.ipsec.ike.IkeSaProposal; +import android.net.ipsec.ike.IkeSessionParams; +import android.net.ipsec.ike.SaProposal; +import android.net.ipsec.ike.TunnelModeChildSessionParams; + +/** Shared testing parameters and util methods for testing IKE */ +public class IkeSessionTestUtils { + private static final String TEST_CLIENT_ADDR = "test.client.com"; + private static final String TEST_SERVER_ADDR = "test.server.com"; + private static final String TEST_SERVER = "2001:0db8:85a3:0000:0000:8a2e:0370:7334"; + + public static final IkeSaProposal SA_PROPOSAL = new IkeSaProposal.Builder() + .addEncryptionAlgorithm(SaProposal.ENCRYPTION_ALGORITHM_3DES, KEY_LEN_UNUSED) + .addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96) + .addPseudorandomFunction(SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC) + .addDhGroup(SaProposal.DH_GROUP_1024_BIT_MODP) + .build(); + public static final ChildSaProposal CHILD_PROPOSAL = new ChildSaProposal.Builder() + .addEncryptionAlgorithm(SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_128) + .addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_NONE) + .addDhGroup(SaProposal.DH_GROUP_1024_BIT_MODP) + .build(); + + public static final IkeSessionParams IKE_PARAMS = + new IkeSessionParams.Builder() + .setServerHostname(TEST_SERVER) + .addSaProposal(SA_PROPOSAL) + .setLocalIdentification(new IkeFqdnIdentification(TEST_CLIENT_ADDR)) + .setRemoteIdentification(new IkeFqdnIdentification(TEST_SERVER_ADDR)) + .setAuthPsk("psk".getBytes()) + .build(); + public static final TunnelModeChildSessionParams CHILD_PARAMS = + new TunnelModeChildSessionParams.Builder() + .addSaProposal(CHILD_PROPOSAL) + .build(); +} diff --git a/tests/native/Android.bp b/tests/native/Android.bp new file mode 100644 index 0000000000..cd438f66c2 --- /dev/null +++ b/tests/native/Android.bp @@ -0,0 +1,30 @@ +cc_test { + name: "connectivity_native_test", + test_suites: [ + "general-tests", + "mts-tethering", + "vts", + ], + min_sdk_version: "31", + require_root: true, + tidy: false, + srcs: [ + "connectivity_native_test.cpp", + ], + header_libs: ["bpf_connectivity_headers"], + shared_libs: [ + "libbase", + "libbinder_ndk", + "liblog", + "libnetutils", + "libprocessgroup", + ], + static_libs: [ + "connectivity_native_aidl_interface-lateststable-ndk", + "libcutils", + "libmodules-utils-build", + "libutils", + ], + compile_multilib: "first", + defaults: ["connectivity-mainline-presubmit-cc-defaults"], +} diff --git a/tests/native/connectivity_native_test.cpp b/tests/native/connectivity_native_test.cpp new file mode 100644 index 0000000000..8b089ab581 --- /dev/null +++ b/tests/native/connectivity_native_test.cpp @@ -0,0 +1,281 @@ +/* + * 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. + */ + +#include <android-modules-utils/sdk_level.h> +#include <cutils/misc.h> // FIRST_APPLICATION_UID +#include <gtest/gtest.h> +#include <netinet/in.h> +#include <android/binder_manager.h> +#include <android/binder_process.h> + +#include <aidl/android/net/connectivity/aidl/ConnectivityNative.h> + +using aidl::android::net::connectivity::aidl::IConnectivityNative; + +class ConnectivityNativeBinderTest : public ::testing::Test { + public: + std::vector<int32_t> mActualBlockedPorts; + + ConnectivityNativeBinderTest() { + AIBinder* binder = AServiceManager_getService("connectivity_native"); + ndk::SpAIBinder sBinder = ndk::SpAIBinder(binder); + mService = aidl::android::net::connectivity::aidl::IConnectivityNative::fromBinder(sBinder); + } + + void SetUp() override { + // Skip test case if not on T. + if (!android::modules::sdklevel::IsAtLeastT()) GTEST_SKIP() << + "Should be at least T device."; + + ASSERT_NE(nullptr, mService.get()); + + // If there are already ports being blocked on device unblockAllPortsForBind() store + // the currently blocked ports and add them back at the end of the test. Do this for + // every test case so additional test cases do not forget to add ports back. + ndk::ScopedAStatus status = mService->getPortsBlockedForBind(&mActualBlockedPorts); + EXPECT_TRUE(status.isOk()) << status.getDescription (); + + } + + void TearDown() override { + ndk::ScopedAStatus status; + if (mActualBlockedPorts.size() > 0) { + for (int i : mActualBlockedPorts) { + mService->blockPortForBind(i); + EXPECT_TRUE(status.isOk()) << status.getDescription (); + } + } + } + + protected: + std::shared_ptr<IConnectivityNative> mService; + + void runSocketTest (sa_family_t family, const int type, bool blockPort) { + ndk::ScopedAStatus status; + in_port_t port = 0; + int sock, sock2; + // Open two sockets with SO_REUSEADDR and expect they can both bind to port. + sock = openSocket(&port, family, type, false /* expectBindFail */); + sock2 = openSocket(&port, family, type, false /* expectBindFail */); + + int blockedPort = 0; + if (blockPort) { + blockedPort = ntohs(port); + status = mService->blockPortForBind(blockedPort); + EXPECT_TRUE(status.isOk()) << status.getDescription (); + } + + int sock3 = openSocket(&port, family, type, blockPort /* expectBindFail */); + + if (blockPort) { + EXPECT_EQ(-1, sock3); + status = mService->unblockPortForBind(blockedPort); + EXPECT_TRUE(status.isOk()) << status.getDescription (); + } else { + EXPECT_NE(-1, sock3); + } + + close(sock); + close(sock2); + close(sock3); + } + + /* + * Open the socket and update the port. + */ + int openSocket(in_port_t* port, sa_family_t family, const int type, bool expectBindFail) { + int ret = 0; + int enable = 1; + const int sock = socket(family, type, 0); + ret = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); + EXPECT_EQ(0, ret); + + if (family == AF_INET) { + struct sockaddr_in addr4 = { .sin_family = family, .sin_port = htons(*port) }; + ret = bind(sock, (struct sockaddr*) &addr4, sizeof(addr4)); + } else { + struct sockaddr_in6 addr6 = { .sin6_family = family, .sin6_port = htons(*port) }; + ret = bind(sock, (struct sockaddr*) &addr6, sizeof(addr6)); + } + + if (expectBindFail) { + EXPECT_NE(0, ret); + // If port is blocked, return here since the port is not needed + // for subsequent sockets. + close(sock); + return -1; + } + EXPECT_EQ(0, ret) << "bind unexpectedly failed, errno: " << errno; + + if (family == AF_INET) { + struct sockaddr_in sin; + socklen_t len = sizeof(sin); + EXPECT_NE(-1, getsockname(sock, (struct sockaddr *)&sin, &len)); + EXPECT_NE(0, ntohs(sin.sin_port)); + if (*port != 0) EXPECT_EQ(*port, ntohs(sin.sin_port)); + *port = ntohs(sin.sin_port); + } else { + struct sockaddr_in6 sin; + socklen_t len = sizeof(sin); + EXPECT_NE(-1, getsockname(sock, (struct sockaddr *)&sin, &len)); + EXPECT_NE(0, ntohs(sin.sin6_port)); + if (*port != 0) EXPECT_EQ(*port, ntohs(sin.sin6_port)); + *port = ntohs(sin.sin6_port); + } + return sock; + } +}; + +TEST_F(ConnectivityNativeBinderTest, PortUnblockedV4Udp) { + runSocketTest(AF_INET, SOCK_DGRAM, false); +} + +TEST_F(ConnectivityNativeBinderTest, PortUnblockedV4Tcp) { + runSocketTest(AF_INET, SOCK_STREAM, false); +} + +TEST_F(ConnectivityNativeBinderTest, PortUnblockedV6Udp) { + runSocketTest(AF_INET6, SOCK_DGRAM, false); +} + +TEST_F(ConnectivityNativeBinderTest, PortUnblockedV6Tcp) { + runSocketTest(AF_INET6, SOCK_STREAM, false); +} + +TEST_F(ConnectivityNativeBinderTest, BlockPort4Udp) { + runSocketTest(AF_INET, SOCK_DGRAM, true); +} + +TEST_F(ConnectivityNativeBinderTest, BlockPort4Tcp) { + runSocketTest(AF_INET, SOCK_STREAM, true); +} + +TEST_F(ConnectivityNativeBinderTest, BlockPort6Udp) { + runSocketTest(AF_INET6, SOCK_DGRAM, true); +} + +TEST_F(ConnectivityNativeBinderTest, BlockPort6Tcp) { + runSocketTest(AF_INET6, SOCK_STREAM, true); +} + +TEST_F(ConnectivityNativeBinderTest, BlockPortTwice) { + ndk::ScopedAStatus status = mService->blockPortForBind(5555); + EXPECT_TRUE(status.isOk()) << status.getDescription (); + status = mService->blockPortForBind(5555); + EXPECT_TRUE(status.isOk()) << status.getDescription (); + status = mService->unblockPortForBind(5555); + EXPECT_TRUE(status.isOk()) << status.getDescription (); +} + +TEST_F(ConnectivityNativeBinderTest, GetBlockedPorts) { + ndk::ScopedAStatus status; + std::vector<int> blockedPorts{1, 100, 1220, 1333, 2700, 5555, 5600, 65000}; + for (int i : blockedPorts) { + status = mService->blockPortForBind(i); + EXPECT_TRUE(status.isOk()) << status.getDescription (); + } + std::vector<int32_t> actualBlockedPorts; + status = mService->getPortsBlockedForBind(&actualBlockedPorts); + EXPECT_TRUE(status.isOk()) << status.getDescription (); + EXPECT_FALSE(actualBlockedPorts.empty()); + EXPECT_EQ(blockedPorts, actualBlockedPorts); + + // Remove the ports we added. + status = mService->unblockAllPortsForBind(); + EXPECT_TRUE(status.isOk()) << status.getDescription (); + status = mService->getPortsBlockedForBind(&actualBlockedPorts); + EXPECT_TRUE(status.isOk()) << status.getDescription (); + EXPECT_TRUE(actualBlockedPorts.empty()); +} + +TEST_F(ConnectivityNativeBinderTest, UnblockAllPorts) { + ndk::ScopedAStatus status; + std::vector<int> blockedPorts{1, 100, 1220, 1333, 2700, 5555, 5600, 65000}; + + if (mActualBlockedPorts.size() > 0) { + status = mService->unblockAllPortsForBind(); + } + + for (int i : blockedPorts) { + status = mService->blockPortForBind(i); + EXPECT_TRUE(status.isOk()) << status.getDescription (); + } + + std::vector<int32_t> actualBlockedPorts; + status = mService->getPortsBlockedForBind(&actualBlockedPorts); + EXPECT_TRUE(status.isOk()) << status.getDescription (); + EXPECT_FALSE(actualBlockedPorts.empty()); + + status = mService->unblockAllPortsForBind(); + EXPECT_TRUE(status.isOk()) << status.getDescription (); + status = mService->getPortsBlockedForBind(&actualBlockedPorts); + EXPECT_TRUE(status.isOk()) << status.getDescription (); + EXPECT_TRUE(actualBlockedPorts.empty()); + // If mActualBlockedPorts is not empty, ports will be added back in teardown. +} + +TEST_F(ConnectivityNativeBinderTest, BlockNegativePort) { + int retry = 0; + ndk::ScopedAStatus status; + do { + status = mService->blockPortForBind(-1); + // TODO: find out why transaction failed is being thrown on the first attempt. + } while (status.getExceptionCode() == EX_TRANSACTION_FAILED && retry++ < 5); + EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode()); +} + +TEST_F(ConnectivityNativeBinderTest, UnblockNegativePort) { + int retry = 0; + ndk::ScopedAStatus status; + do { + status = mService->unblockPortForBind(-1); + // TODO: find out why transaction failed is being thrown on the first attempt. + } while (status.getExceptionCode() == EX_TRANSACTION_FAILED && retry++ < 5); + EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode()); +} + +TEST_F(ConnectivityNativeBinderTest, BlockMaxPort) { + int retry = 0; + ndk::ScopedAStatus status; + do { + status = mService->blockPortForBind(65536); + // TODO: find out why transaction failed is being thrown on the first attempt. + } while (status.getExceptionCode() == EX_TRANSACTION_FAILED && retry++ < 5); + EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode()); +} + +TEST_F(ConnectivityNativeBinderTest, UnblockMaxPort) { + int retry = 0; + ndk::ScopedAStatus status; + do { + status = mService->unblockPortForBind(65536); + // TODO: find out why transaction failed is being thrown on the first attempt. + } while (status.getExceptionCode() == EX_TRANSACTION_FAILED && retry++ < 5); + EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode()); +} + +TEST_F(ConnectivityNativeBinderTest, CheckPermission) { + int retry = 0; + int curUid = getuid(); + EXPECT_EQ(0, seteuid(FIRST_APPLICATION_UID + 2000)) << "seteuid failed: " << strerror(errno); + ndk::ScopedAStatus status; + do { + status = mService->blockPortForBind(5555); + // TODO: find out why transaction failed is being thrown on the first attempt. + } while (status.getExceptionCode() == EX_TRANSACTION_FAILED && retry++ < 5); + EXPECT_EQ(EX_SECURITY, status.getExceptionCode()); + EXPECT_EQ(0, seteuid(curUid)) << "seteuid failed: " << strerror(errno); +} diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp index 07dcae3ae7..c27c973009 100644 --- a/tests/unit/Android.bp +++ b/tests/unit/Android.bp @@ -76,6 +76,7 @@ filegroup { "java/com/android/server/IpSecServiceParameterizedTest.java", "java/com/android/server/IpSecServiceRefcountedResourceTest.java", "java/com/android/server/IpSecServiceTest.java", + "java/com/android/server/NativeDaemonConnectorTest.java", "java/com/android/server/NetworkManagementServiceTest.java", "java/com/android/server/NsdServiceTest.java", "java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java", @@ -83,8 +84,10 @@ filegroup { "java/com/android/server/connectivity/MultipathPolicyTrackerTest.java", "java/com/android/server/connectivity/NetdEventListenerServiceTest.java", "java/com/android/server/connectivity/VpnTest.java", + "java/com/android/server/ethernet/*.java", "java/com/android/server/net/ipmemorystore/*.java", "java/com/android/server/net/BpfInterfaceMapUpdaterTest.java", + "java/com/android/server/net/IpConfigStoreTest.java", "java/com/android/server/net/NetworkStats*.java", "java/com/android/server/net/TestableUsageCallback.kt", ] diff --git a/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java b/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java index 553cb83548..157507bf28 100644 --- a/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java +++ b/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java @@ -69,8 +69,6 @@ import java.util.List; @RunWith(DevSdkIgnoreRunner.class) @IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available public class CarrierPrivilegeAuthenticatorTest { - // TODO : use ConstantsShim.RECEIVER_NOT_EXPORTED when it's available in tests. - private static final int RECEIVER_NOT_EXPORTED = 4; private static final int SUBSCRIPTION_COUNT = 2; private static final int TEST_SUBSCRIPTION_ID = 1; @@ -117,7 +115,7 @@ public class CarrierPrivilegeAuthenticatorTest { private IntentFilter getIntentFilter() { final ArgumentCaptor<IntentFilter> captor = ArgumentCaptor.forClass(IntentFilter.class); - verify(mContext).registerReceiver(any(), captor.capture(), any(), any(), anyInt()); + verify(mContext).registerReceiver(any(), captor.capture(), any(), any()); return captor.getValue(); } @@ -140,11 +138,10 @@ public class CarrierPrivilegeAuthenticatorTest { @Test public void testConstructor() throws Exception { verify(mContext).registerReceiver( - eq(mCarrierPrivilegeAuthenticator), - any(IntentFilter.class), - any(), - any(), - eq(RECEIVER_NOT_EXPORTED)); + eq(mCarrierPrivilegeAuthenticator), + any(IntentFilter.class), + any(), + any()); final IntentFilter filter = getIntentFilter(); assertEquals(1, filter.countActions()); assertTrue(filter.hasAction(ACTION_MULTI_SIM_CONFIG_CHANGED)); diff --git a/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt b/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt index e7f6245547..c03a9cde70 100644 --- a/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt +++ b/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt @@ -22,6 +22,7 @@ import android.net.NetworkScore.KEEP_CONNECTED_NONE import android.os.Build import android.text.TextUtils import android.util.ArraySet +import android.util.Log import androidx.test.filters.SmallTest import com.android.server.connectivity.FullScore.MAX_CS_MANAGED_POLICY import com.android.server.connectivity.FullScore.POLICY_ACCEPT_UNVALIDATED @@ -32,11 +33,12 @@ import com.android.server.connectivity.FullScore.POLICY_IS_VALIDATED import com.android.server.connectivity.FullScore.POLICY_IS_VPN import com.android.testutils.DevSdkIgnoreRule import com.android.testutils.DevSdkIgnoreRunner +import org.junit.After +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import kotlin.reflect.full.staticProperties import kotlin.test.assertEquals -import kotlin.test.assertFailsWith import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -63,6 +65,23 @@ class FullScoreTest { return mixInScore(nc, nac, validated, false /* yieldToBadWifi */, destroyed) } + private val TAG = this::class.simpleName + + private var wtfHandler: Log.TerribleFailureHandler? = null + + @Before + fun setUp() { + // policyNameOf will call Log.wtf if passed an invalid policy. + wtfHandler = Log.setWtfHandler() { tagString, what, system -> + Log.d(TAG, "WTF captured, ignoring: $tagString $what") + } + } + + @After + fun tearDown() { + Log.setWtfHandler(wtfHandler) + } + @Test fun testGetLegacyInt() { val ns = FullScore(50, 0L /* policy */, KEEP_CONNECTED_NONE) @@ -101,10 +120,9 @@ class FullScoreTest { assertFalse(foundNames.contains(name)) foundNames.add(name) } - assertFailsWith<IllegalArgumentException> { - FullScore.policyNameOf(MAX_CS_MANAGED_POLICY + 1) - } assertEquals("IS_UNMETERED", FullScore.policyNameOf(POLICY_IS_UNMETERED)) + val invalidPolicy = MAX_CS_MANAGED_POLICY + 1 + assertEquals(Integer.toString(invalidPolicy), FullScore.policyNameOf(invalidPolicy)) } fun getAllPolicies() = Regex("POLICY_.*").let { nameRegex -> diff --git a/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java b/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java new file mode 100644 index 0000000000..4d3e4d36d2 --- /dev/null +++ b/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java @@ -0,0 +1,783 @@ +/* + * Copyright (C) 2020 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 com.android.testutils.DevSdkIgnoreRuleKt.SC_V2; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.annotation.NonNull; +import android.app.test.MockAnswerUtil.AnswerWithArguments; +import android.content.Context; +import android.content.res.Resources; +import android.net.ConnectivityManager; +import android.net.EthernetNetworkSpecifier; +import android.net.EthernetNetworkManagementException; +import android.net.INetworkInterfaceOutcomeReceiver; +import android.net.IpConfiguration; +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkAgentConfig; +import android.net.NetworkCapabilities; +import android.net.NetworkProvider; +import android.net.NetworkRequest; +import android.net.StaticIpConfiguration; +import android.net.ip.IpClientCallbacks; +import android.net.ip.IpClientManager; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.test.TestLooper; +import android.util.Pair; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.connectivity.resources.R; +import com.android.net.module.util.InterfaceParams; + +import com.android.testutils.DevSdkIgnoreRule; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class EthernetNetworkFactoryTest { + private static final int TIMEOUT_MS = 2_000; + private static final String TEST_IFACE = "test123"; + private static final INetworkInterfaceOutcomeReceiver NULL_LISTENER = null; + private static final String IP_ADDR = "192.0.2.2/25"; + private static final LinkAddress LINK_ADDR = new LinkAddress(IP_ADDR); + private static final String HW_ADDR = "01:02:03:04:05:06"; + private TestLooper mLooper; + private Handler mHandler; + private EthernetNetworkFactory mNetFactory = null; + private IpClientCallbacks mIpClientCallbacks; + @Mock private Context mContext; + @Mock private Resources mResources; + @Mock private EthernetNetworkFactory.Dependencies mDeps; + @Mock private IpClientManager mIpClient; + @Mock private EthernetNetworkAgent mNetworkAgent; + @Mock private InterfaceParams mInterfaceParams; + @Mock private Network mMockNetwork; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + setupNetworkAgentMock(); + setupIpClientMock(); + setupContext(); + } + + //TODO: Move away from usage of TestLooper in order to move this logic back into @Before. + private void initEthernetNetworkFactory() { + mLooper = new TestLooper(); + mHandler = new Handler(mLooper.getLooper()); + mNetFactory = new EthernetNetworkFactory(mHandler, mContext, mDeps); + } + + private void setupNetworkAgentMock() { + when(mDeps.makeEthernetNetworkAgent(any(), any(), any(), any(), any(), any(), any())) + .thenAnswer(new AnswerWithArguments() { + public EthernetNetworkAgent answer( + Context context, + Looper looper, + NetworkCapabilities nc, + LinkProperties lp, + NetworkAgentConfig config, + NetworkProvider provider, + EthernetNetworkAgent.Callbacks cb) { + when(mNetworkAgent.getCallbacks()).thenReturn(cb); + when(mNetworkAgent.getNetwork()) + .thenReturn(mMockNetwork); + return mNetworkAgent; + } + } + ); + } + + private void setupIpClientMock() throws Exception { + doAnswer(inv -> { + // these tests only support one concurrent IpClient, so make sure we do not accidentally + // create a mess. + assertNull("An IpClient has already been created.", mIpClientCallbacks); + + mIpClientCallbacks = inv.getArgument(2); + mIpClientCallbacks.onIpClientCreated(null); + mLooper.dispatchAll(); + return null; + }).when(mDeps).makeIpClient(any(Context.class), anyString(), any()); + + doAnswer(inv -> { + mIpClientCallbacks.onQuit(); + mLooper.dispatchAll(); + mIpClientCallbacks = null; + return null; + }).when(mIpClient).shutdown(); + + when(mDeps.makeIpClientManager(any())).thenReturn(mIpClient); + } + + private void triggerOnProvisioningSuccess() { + mIpClientCallbacks.onProvisioningSuccess(new LinkProperties()); + mLooper.dispatchAll(); + } + + private void triggerOnProvisioningFailure() { + mIpClientCallbacks.onProvisioningFailure(new LinkProperties()); + mLooper.dispatchAll(); + } + + private void triggerOnReachabilityLost() { + mIpClientCallbacks.onReachabilityLost("ReachabilityLost"); + mLooper.dispatchAll(); + } + + private void setupContext() { + when(mDeps.getTcpBufferSizesFromResource(eq(mContext))).thenReturn(""); + } + + @After + public void tearDown() { + // looper is shared with the network agents, so there may still be messages to dispatch on + // tear down. + mLooper.dispatchAll(); + } + + private NetworkCapabilities createDefaultFilterCaps() { + return NetworkCapabilities.Builder.withoutDefaultCapabilities() + .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET) + .build(); + } + + private NetworkCapabilities.Builder createInterfaceCapsBuilder(final int transportType) { + return new NetworkCapabilities.Builder() + .addTransportType(transportType) + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED); + } + + private NetworkRequest.Builder createDefaultRequestBuilder() { + return new NetworkRequest.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET) + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + } + + private NetworkRequest createDefaultRequest() { + return createDefaultRequestBuilder().build(); + } + + private IpConfiguration createDefaultIpConfig() { + IpConfiguration ipConfig = new IpConfiguration(); + ipConfig.setIpAssignment(IpConfiguration.IpAssignment.DHCP); + ipConfig.setProxySettings(IpConfiguration.ProxySettings.NONE); + return ipConfig; + } + + /** + * Create an {@link IpConfiguration} with an associated {@link StaticIpConfiguration}. + * + * @return {@link IpConfiguration} with its {@link StaticIpConfiguration} set. + */ + private IpConfiguration createStaticIpConfig() { + final IpConfiguration ipConfig = new IpConfiguration(); + ipConfig.setIpAssignment(IpConfiguration.IpAssignment.STATIC); + ipConfig.setStaticIpConfiguration( + new StaticIpConfiguration.Builder().setIpAddress(LINK_ADDR).build()); + return ipConfig; + } + + // creates an interface with provisioning in progress (since updating the interface link state + // automatically starts the provisioning process) + private void createInterfaceUndergoingProvisioning(String iface) { + // Default to the ethernet transport type. + createInterfaceUndergoingProvisioning(iface, NetworkCapabilities.TRANSPORT_ETHERNET); + } + + private void createInterfaceUndergoingProvisioning( + @NonNull final String iface, final int transportType) { + final IpConfiguration ipConfig = createDefaultIpConfig(); + mNetFactory.addInterface(iface, HW_ADDR, ipConfig, + createInterfaceCapsBuilder(transportType).build()); + assertTrue(mNetFactory.updateInterfaceLinkState(iface, true, NULL_LISTENER)); + verifyStart(ipConfig); + clearInvocations(mDeps); + clearInvocations(mIpClient); + } + + // creates a provisioned interface + private void createAndVerifyProvisionedInterface(String iface) throws Exception { + // Default to the ethernet transport type. + createAndVerifyProvisionedInterface(iface, NetworkCapabilities.TRANSPORT_ETHERNET, + ConnectivityManager.TYPE_ETHERNET); + } + + private void createVerifyAndRemoveProvisionedInterface(final int transportType, + final int expectedLegacyType) throws Exception { + createAndVerifyProvisionedInterface(TEST_IFACE, transportType, + expectedLegacyType); + mNetFactory.removeInterface(TEST_IFACE); + } + + private void createAndVerifyProvisionedInterface( + @NonNull final String iface, final int transportType, final int expectedLegacyType) + throws Exception { + createInterfaceUndergoingProvisioning(iface, transportType); + triggerOnProvisioningSuccess(); + // provisioning succeeded, verify that the network agent is created, registered, marked + // as connected and legacy type are correctly set. + final ArgumentCaptor<NetworkCapabilities> ncCaptor = ArgumentCaptor.forClass( + NetworkCapabilities.class); + verify(mDeps).makeEthernetNetworkAgent(any(), any(), ncCaptor.capture(), any(), + argThat(x -> x.getLegacyType() == expectedLegacyType), any(), any()); + assertEquals( + new EthernetNetworkSpecifier(iface), ncCaptor.getValue().getNetworkSpecifier()); + verifyNetworkAgentRegistersAndConnects(); + clearInvocations(mDeps); + clearInvocations(mNetworkAgent); + } + + // creates an unprovisioned interface + private void createUnprovisionedInterface(String iface) throws Exception { + // To create an unprovisioned interface, provision and then "stop" it, i.e. stop its + // NetworkAgent and IpClient. One way this can be done is by provisioning an interface and + // then calling onNetworkUnwanted. + createAndVerifyProvisionedInterface(iface); + + mNetworkAgent.getCallbacks().onNetworkUnwanted(); + mLooper.dispatchAll(); + verifyStop(); + + clearInvocations(mIpClient); + clearInvocations(mNetworkAgent); + } + + @Test + public void testAcceptRequest() throws Exception { + initEthernetNetworkFactory(); + createInterfaceUndergoingProvisioning(TEST_IFACE); + assertTrue(mNetFactory.acceptRequest(createDefaultRequest())); + + NetworkRequest wifiRequest = createDefaultRequestBuilder() + .removeTransportType(NetworkCapabilities.TRANSPORT_ETHERNET) + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build(); + assertFalse(mNetFactory.acceptRequest(wifiRequest)); + } + + @Test + public void testUpdateInterfaceLinkStateForActiveProvisioningInterface() throws Exception { + initEthernetNetworkFactory(); + createInterfaceUndergoingProvisioning(TEST_IFACE); + final TestNetworkManagementListener listener = new TestNetworkManagementListener(); + + // verify that the IpClient gets shut down when interface state changes to down. + final boolean ret = + mNetFactory.updateInterfaceLinkState(TEST_IFACE, false /* up */, listener); + + assertTrue(ret); + verify(mIpClient).shutdown(); + assertEquals(listener.expectOnResult(), TEST_IFACE); + } + + @Test + public void testUpdateInterfaceLinkStateForProvisionedInterface() throws Exception { + initEthernetNetworkFactory(); + createAndVerifyProvisionedInterface(TEST_IFACE); + final TestNetworkManagementListener listener = new TestNetworkManagementListener(); + + final boolean ret = + mNetFactory.updateInterfaceLinkState(TEST_IFACE, false /* up */, listener); + + assertTrue(ret); + verifyStop(); + assertEquals(listener.expectOnResult(), TEST_IFACE); + } + + @Test + public void testUpdateInterfaceLinkStateForUnprovisionedInterface() throws Exception { + initEthernetNetworkFactory(); + createUnprovisionedInterface(TEST_IFACE); + final TestNetworkManagementListener listener = new TestNetworkManagementListener(); + + final boolean ret = + mNetFactory.updateInterfaceLinkState(TEST_IFACE, false /* up */, listener); + + assertTrue(ret); + // There should not be an active IPClient or NetworkAgent. + verify(mDeps, never()).makeIpClient(any(), any(), any()); + verify(mDeps, never()) + .makeEthernetNetworkAgent(any(), any(), any(), any(), any(), any(), any()); + assertEquals(listener.expectOnResult(), TEST_IFACE); + } + + @Test + public void testUpdateInterfaceLinkStateForNonExistingInterface() throws Exception { + initEthernetNetworkFactory(); + final TestNetworkManagementListener listener = new TestNetworkManagementListener(); + + // if interface was never added, link state cannot be updated. + final boolean ret = + mNetFactory.updateInterfaceLinkState(TEST_IFACE, true /* up */, listener); + + assertFalse(ret); + verifyNoStopOrStart(); + listener.expectOnErrorWithMessage("can't be updated as it is not available"); + } + + @Test + public void testUpdateInterfaceLinkStateWithNoChanges() throws Exception { + initEthernetNetworkFactory(); + createAndVerifyProvisionedInterface(TEST_IFACE); + final TestNetworkManagementListener listener = new TestNetworkManagementListener(); + + final boolean ret = + mNetFactory.updateInterfaceLinkState(TEST_IFACE, true /* up */, listener); + + assertFalse(ret); + verifyNoStopOrStart(); + listener.expectOnErrorWithMessage("No changes"); + } + + @Test + public void testNeedNetworkForOnProvisionedInterface() throws Exception { + initEthernetNetworkFactory(); + createAndVerifyProvisionedInterface(TEST_IFACE); + mNetFactory.needNetworkFor(createDefaultRequest()); + verify(mIpClient, never()).startProvisioning(any()); + } + + @Test + public void testNeedNetworkForOnUnprovisionedInterface() throws Exception { + initEthernetNetworkFactory(); + createUnprovisionedInterface(TEST_IFACE); + mNetFactory.needNetworkFor(createDefaultRequest()); + verify(mIpClient).startProvisioning(any()); + + triggerOnProvisioningSuccess(); + verifyNetworkAgentRegistersAndConnects(); + } + + @Test + public void testNeedNetworkForOnInterfaceUndergoingProvisioning() throws Exception { + initEthernetNetworkFactory(); + createInterfaceUndergoingProvisioning(TEST_IFACE); + mNetFactory.needNetworkFor(createDefaultRequest()); + verify(mIpClient, never()).startProvisioning(any()); + + triggerOnProvisioningSuccess(); + verifyNetworkAgentRegistersAndConnects(); + } + + @Test + public void testProvisioningLoss() throws Exception { + initEthernetNetworkFactory(); + when(mDeps.getNetworkInterfaceByName(TEST_IFACE)).thenReturn(mInterfaceParams); + createAndVerifyProvisionedInterface(TEST_IFACE); + + triggerOnProvisioningFailure(); + verifyStop(); + // provisioning loss should trigger a retry, since the interface is still there + verify(mIpClient).startProvisioning(any()); + } + + @Test + public void testProvisioningLossForDisappearedInterface() throws Exception { + initEthernetNetworkFactory(); + // mocked method returns null by default, but just to be explicit in the test: + when(mDeps.getNetworkInterfaceByName(eq(TEST_IFACE))).thenReturn(null); + + createAndVerifyProvisionedInterface(TEST_IFACE); + triggerOnProvisioningFailure(); + + // the interface disappeared and getNetworkInterfaceByName returns null, we should not retry + verify(mIpClient, never()).startProvisioning(any()); + verifyNoStopOrStart(); + } + + private void verifyNoStopOrStart() { + verify(mNetworkAgent, never()).register(); + verify(mIpClient, never()).shutdown(); + verify(mNetworkAgent, never()).unregister(); + verify(mIpClient, never()).startProvisioning(any()); + } + + @Test + public void testIpClientIsNotStartedWhenLinkIsDown() throws Exception { + initEthernetNetworkFactory(); + createUnprovisionedInterface(TEST_IFACE); + mNetFactory.updateInterfaceLinkState(TEST_IFACE, false, NULL_LISTENER); + + mNetFactory.needNetworkFor(createDefaultRequest()); + + verify(mDeps, never()).makeIpClient(any(), any(), any()); + + // BUG(b/191854824): requesting a network with a specifier (Android Auto use case) should + // not start an IpClient when the link is down, but fixing this may make matters worse by + // tiggering b/197548738. + NetworkRequest specificNetRequest = new NetworkRequest.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET) + .setNetworkSpecifier(new EthernetNetworkSpecifier(TEST_IFACE)) + .build(); + mNetFactory.needNetworkFor(specificNetRequest); + mNetFactory.releaseNetworkFor(specificNetRequest); + + mNetFactory.updateInterfaceLinkState(TEST_IFACE, true, NULL_LISTENER); + // TODO: change to once when b/191854824 is fixed. + verify(mDeps, times(2)).makeIpClient(any(), eq(TEST_IFACE), any()); + } + + @Test + public void testLinkPropertiesChanged() throws Exception { + initEthernetNetworkFactory(); + createAndVerifyProvisionedInterface(TEST_IFACE); + + LinkProperties lp = new LinkProperties(); + mIpClientCallbacks.onLinkPropertiesChange(lp); + mLooper.dispatchAll(); + verify(mNetworkAgent).sendLinkPropertiesImpl(same(lp)); + } + + @Test + public void testNetworkUnwanted() throws Exception { + initEthernetNetworkFactory(); + createAndVerifyProvisionedInterface(TEST_IFACE); + + mNetworkAgent.getCallbacks().onNetworkUnwanted(); + mLooper.dispatchAll(); + verifyStop(); + } + + @Test + public void testNetworkUnwantedWithStaleNetworkAgent() throws Exception { + initEthernetNetworkFactory(); + // ensures provisioning is restarted after provisioning loss + when(mDeps.getNetworkInterfaceByName(TEST_IFACE)).thenReturn(mInterfaceParams); + createAndVerifyProvisionedInterface(TEST_IFACE); + + EthernetNetworkAgent.Callbacks oldCbs = mNetworkAgent.getCallbacks(); + // replace network agent in EthernetNetworkFactory + // Loss of provisioning will restart the ip client and network agent. + triggerOnProvisioningFailure(); + verify(mDeps).makeIpClient(any(), any(), any()); + + triggerOnProvisioningSuccess(); + verify(mDeps).makeEthernetNetworkAgent(any(), any(), any(), any(), any(), any(), any()); + + // verify that unwanted is ignored + clearInvocations(mIpClient); + clearInvocations(mNetworkAgent); + oldCbs.onNetworkUnwanted(); + verify(mIpClient, never()).shutdown(); + verify(mNetworkAgent, never()).unregister(); + } + + @Test + public void testTransportOverrideIsCorrectlySet() throws Exception { + initEthernetNetworkFactory(); + // createProvisionedInterface() has verifications in place for transport override + // functionality which for EthernetNetworkFactory is network score and legacy type mappings. + createVerifyAndRemoveProvisionedInterface(NetworkCapabilities.TRANSPORT_ETHERNET, + ConnectivityManager.TYPE_ETHERNET); + createVerifyAndRemoveProvisionedInterface(NetworkCapabilities.TRANSPORT_BLUETOOTH, + ConnectivityManager.TYPE_BLUETOOTH); + createVerifyAndRemoveProvisionedInterface(NetworkCapabilities.TRANSPORT_WIFI, + ConnectivityManager.TYPE_WIFI); + createVerifyAndRemoveProvisionedInterface(NetworkCapabilities.TRANSPORT_CELLULAR, + ConnectivityManager.TYPE_MOBILE); + createVerifyAndRemoveProvisionedInterface(NetworkCapabilities.TRANSPORT_LOWPAN, + ConnectivityManager.TYPE_NONE); + createVerifyAndRemoveProvisionedInterface(NetworkCapabilities.TRANSPORT_WIFI_AWARE, + ConnectivityManager.TYPE_NONE); + createVerifyAndRemoveProvisionedInterface(NetworkCapabilities.TRANSPORT_TEST, + ConnectivityManager.TYPE_NONE); + } + + @Test + public void testReachabilityLoss() throws Exception { + initEthernetNetworkFactory(); + createAndVerifyProvisionedInterface(TEST_IFACE); + + triggerOnReachabilityLost(); + + // Reachability loss should trigger a stop and start, since the interface is still there + verifyRestart(createDefaultIpConfig()); + } + + private IpClientCallbacks getStaleIpClientCallbacks() throws Exception { + createAndVerifyProvisionedInterface(TEST_IFACE); + final IpClientCallbacks staleIpClientCallbacks = mIpClientCallbacks; + mNetFactory.removeInterface(TEST_IFACE); + verifyStop(); + assertNotSame(mIpClientCallbacks, staleIpClientCallbacks); + return staleIpClientCallbacks; + } + + @Test + public void testIgnoreOnIpLayerStartedCallbackForStaleCallback() throws Exception { + initEthernetNetworkFactory(); + final IpClientCallbacks staleIpClientCallbacks = getStaleIpClientCallbacks(); + + staleIpClientCallbacks.onProvisioningSuccess(new LinkProperties()); + mLooper.dispatchAll(); + + verify(mIpClient, never()).startProvisioning(any()); + verify(mNetworkAgent, never()).register(); + } + + @Test + public void testIgnoreOnIpLayerStoppedCallbackForStaleCallback() throws Exception { + initEthernetNetworkFactory(); + when(mDeps.getNetworkInterfaceByName(TEST_IFACE)).thenReturn(mInterfaceParams); + final IpClientCallbacks staleIpClientCallbacks = getStaleIpClientCallbacks(); + + staleIpClientCallbacks.onProvisioningFailure(new LinkProperties()); + mLooper.dispatchAll(); + + verify(mIpClient, never()).startProvisioning(any()); + } + + @Test + public void testIgnoreLinkPropertiesCallbackForStaleCallback() throws Exception { + initEthernetNetworkFactory(); + final IpClientCallbacks staleIpClientCallbacks = getStaleIpClientCallbacks(); + final LinkProperties lp = new LinkProperties(); + + staleIpClientCallbacks.onLinkPropertiesChange(lp); + mLooper.dispatchAll(); + + verify(mNetworkAgent, never()).sendLinkPropertiesImpl(eq(lp)); + } + + @Test + public void testIgnoreNeighborLossCallbackForStaleCallback() throws Exception { + initEthernetNetworkFactory(); + final IpClientCallbacks staleIpClientCallbacks = getStaleIpClientCallbacks(); + + staleIpClientCallbacks.onReachabilityLost("Neighbor Lost"); + mLooper.dispatchAll(); + + verify(mIpClient, never()).startProvisioning(any()); + verify(mNetworkAgent, never()).register(); + } + + private void verifyRestart(@NonNull final IpConfiguration ipConfig) { + verifyStop(); + verifyStart(ipConfig); + } + + private void verifyStart(@NonNull final IpConfiguration ipConfig) { + verify(mDeps).makeIpClient(any(Context.class), anyString(), any()); + verify(mIpClient).startProvisioning( + argThat(x -> Objects.equals(x.mStaticIpConfig, ipConfig.getStaticIpConfiguration())) + ); + } + + private void verifyStop() { + verify(mIpClient).shutdown(); + verify(mNetworkAgent).unregister(); + } + + private void verifyNetworkAgentRegistersAndConnects() { + verify(mNetworkAgent).register(); + verify(mNetworkAgent).markConnected(); + } + + private static final class TestNetworkManagementListener + implements INetworkInterfaceOutcomeReceiver { + private final CompletableFuture<String> mResult = new CompletableFuture<>(); + private final CompletableFuture<EthernetNetworkManagementException> mError = + new CompletableFuture<>(); + + @Override + public void onResult(@NonNull String iface) { + mResult.complete(iface); + } + + @Override + public void onError(@NonNull EthernetNetworkManagementException exception) { + mError.complete(exception); + } + + String expectOnResult() throws Exception { + return mResult.get(TIMEOUT_MS, TimeUnit.MILLISECONDS); + } + + EthernetNetworkManagementException expectOnError() throws Exception { + return mError.get(TIMEOUT_MS, TimeUnit.MILLISECONDS); + } + + void expectOnErrorWithMessage(String msg) throws Exception { + assertTrue(expectOnError().getMessage().contains(msg)); + } + + @Override + public IBinder asBinder() { + return null; + } + } + + @Test + public void testUpdateInterfaceCallsListenerCorrectlyOnSuccess() throws Exception { + initEthernetNetworkFactory(); + createAndVerifyProvisionedInterface(TEST_IFACE); + final NetworkCapabilities capabilities = createDefaultFilterCaps(); + final IpConfiguration ipConfiguration = createStaticIpConfig(); + final TestNetworkManagementListener listener = new TestNetworkManagementListener(); + + mNetFactory.updateInterface(TEST_IFACE, ipConfiguration, capabilities, listener); + triggerOnProvisioningSuccess(); + + assertEquals(listener.expectOnResult(), TEST_IFACE); + } + + @DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available + @Test + public void testUpdateInterfaceAbortsOnConcurrentRemoveInterface() throws Exception { + initEthernetNetworkFactory(); + verifyNetworkManagementCallIsAbortedWhenInterrupted( + TEST_IFACE, + () -> mNetFactory.removeInterface(TEST_IFACE)); + } + + @DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available + @Test + public void testUpdateInterfaceAbortsOnConcurrentUpdateInterfaceLinkState() throws Exception { + initEthernetNetworkFactory(); + verifyNetworkManagementCallIsAbortedWhenInterrupted( + TEST_IFACE, + () -> mNetFactory.updateInterfaceLinkState(TEST_IFACE, false, NULL_LISTENER)); + } + + @DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available + @Test + public void testUpdateInterfaceCallsListenerCorrectlyOnConcurrentRequests() throws Exception { + initEthernetNetworkFactory(); + final NetworkCapabilities capabilities = createDefaultFilterCaps(); + final IpConfiguration ipConfiguration = createStaticIpConfig(); + final TestNetworkManagementListener successfulListener = + new TestNetworkManagementListener(); + + // If two calls come in before the first one completes, the first listener will be aborted + // and the second one will be successful. + verifyNetworkManagementCallIsAbortedWhenInterrupted( + TEST_IFACE, + () -> { + mNetFactory.updateInterface( + TEST_IFACE, ipConfiguration, capabilities, successfulListener); + triggerOnProvisioningSuccess(); + }); + + assertEquals(successfulListener.expectOnResult(), TEST_IFACE); + } + + private void verifyNetworkManagementCallIsAbortedWhenInterrupted( + @NonNull final String iface, + @NonNull final Runnable interruptingRunnable) throws Exception { + createAndVerifyProvisionedInterface(iface); + final NetworkCapabilities capabilities = createDefaultFilterCaps(); + final IpConfiguration ipConfiguration = createStaticIpConfig(); + final TestNetworkManagementListener failedListener = new TestNetworkManagementListener(); + + // An active update request will be aborted on interrupt prior to provisioning completion. + mNetFactory.updateInterface(iface, ipConfiguration, capabilities, failedListener); + interruptingRunnable.run(); + + failedListener.expectOnErrorWithMessage("aborted"); + } + + @Test + public void testUpdateInterfaceRestartsAgentCorrectly() throws Exception { + initEthernetNetworkFactory(); + createAndVerifyProvisionedInterface(TEST_IFACE); + final NetworkCapabilities capabilities = createDefaultFilterCaps(); + final IpConfiguration ipConfiguration = createStaticIpConfig(); + final TestNetworkManagementListener listener = new TestNetworkManagementListener(); + + mNetFactory.updateInterface(TEST_IFACE, ipConfiguration, capabilities, listener); + triggerOnProvisioningSuccess(); + + assertEquals(listener.expectOnResult(), TEST_IFACE); + verify(mDeps).makeEthernetNetworkAgent(any(), any(), + eq(capabilities), any(), any(), any(), any()); + verifyRestart(ipConfiguration); + } + + @Test + public void testUpdateInterfaceForNonExistingInterface() throws Exception { + initEthernetNetworkFactory(); + // No interface exists due to not calling createAndVerifyProvisionedInterface(...). + final NetworkCapabilities capabilities = createDefaultFilterCaps(); + final IpConfiguration ipConfiguration = createStaticIpConfig(); + final TestNetworkManagementListener listener = new TestNetworkManagementListener(); + + mNetFactory.updateInterface(TEST_IFACE, ipConfiguration, capabilities, listener); + + verifyNoStopOrStart(); + listener.expectOnErrorWithMessage("can't be updated as it is not available"); + } + + @Test + public void testUpdateInterfaceWithNullIpConfiguration() throws Exception { + initEthernetNetworkFactory(); + createAndVerifyProvisionedInterface(TEST_IFACE); + + final IpConfiguration initialIpConfig = createStaticIpConfig(); + mNetFactory.updateInterface(TEST_IFACE, initialIpConfig, null /*capabilities*/, + null /*listener*/); + triggerOnProvisioningSuccess(); + verifyRestart(initialIpConfig); + + // TODO: have verifyXyz functions clear invocations. + clearInvocations(mDeps); + clearInvocations(mIpClient); + clearInvocations(mNetworkAgent); + + + // verify that sending a null ipConfig does not update the current ipConfig. + mNetFactory.updateInterface(TEST_IFACE, null /*ipConfig*/, null /*capabilities*/, + null /*listener*/); + triggerOnProvisioningSuccess(); + verifyRestart(initialIpConfig); + } +} diff --git a/tests/unit/java/com/android/server/ethernet/EthernetServiceImplTest.java b/tests/unit/java/com/android/server/ethernet/EthernetServiceImplTest.java new file mode 100644 index 0000000000..dd1f1edba7 --- /dev/null +++ b/tests/unit/java/com/android/server/ethernet/EthernetServiceImplTest.java @@ -0,0 +1,372 @@ +/* + * Copyright (C) 2021 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 android.net.NetworkCapabilities.TRANSPORT_TEST; + +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.fail; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.Manifest; +import android.annotation.NonNull; +import android.content.Context; +import android.content.pm.PackageManager; +import android.net.INetworkInterfaceOutcomeReceiver; +import android.net.EthernetNetworkUpdateRequest; +import android.net.IpConfiguration; +import android.net.NetworkCapabilities; +import android.os.Handler; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class EthernetServiceImplTest { + private static final String TEST_IFACE = "test123"; + private static final EthernetNetworkUpdateRequest UPDATE_REQUEST = + new EthernetNetworkUpdateRequest.Builder() + .setIpConfiguration(new IpConfiguration()) + .setNetworkCapabilities(new NetworkCapabilities.Builder().build()) + .build(); + private static final EthernetNetworkUpdateRequest UPDATE_REQUEST_WITHOUT_CAPABILITIES = + new EthernetNetworkUpdateRequest.Builder() + .setIpConfiguration(new IpConfiguration()) + .build(); + private static final EthernetNetworkUpdateRequest UPDATE_REQUEST_WITHOUT_IP_CONFIG = + new EthernetNetworkUpdateRequest.Builder() + .setNetworkCapabilities(new NetworkCapabilities.Builder().build()) + .build(); + private static final INetworkInterfaceOutcomeReceiver NULL_LISTENER = null; + private EthernetServiceImpl mEthernetServiceImpl; + @Mock private Context mContext; + @Mock private Handler mHandler; + @Mock private EthernetTracker mEthernetTracker; + @Mock private PackageManager mPackageManager; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + doReturn(mPackageManager).when(mContext).getPackageManager(); + mEthernetServiceImpl = new EthernetServiceImpl(mContext, mHandler, mEthernetTracker); + mEthernetServiceImpl.mStarted.set(true); + toggleAutomotiveFeature(true); + shouldTrackIface(TEST_IFACE, true); + } + + private void toggleAutomotiveFeature(final boolean isEnabled) { + doReturn(isEnabled) + .when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); + } + + private void shouldTrackIface(@NonNull final String iface, final boolean shouldTrack) { + doReturn(shouldTrack).when(mEthernetTracker).isTrackingInterface(iface); + } + + @Test + public void testSetConfigurationRejectsWhenEthNotStarted() { + mEthernetServiceImpl.mStarted.set(false); + assertThrows(IllegalStateException.class, () -> { + mEthernetServiceImpl.setConfiguration("" /* iface */, new IpConfiguration()); + }); + } + + @Test + public void testUpdateConfigurationRejectsWhenEthNotStarted() { + mEthernetServiceImpl.mStarted.set(false); + assertThrows(IllegalStateException.class, () -> { + mEthernetServiceImpl.updateConfiguration( + "" /* iface */, UPDATE_REQUEST, null /* listener */); + }); + } + + @Test + public void testConnectNetworkRejectsWhenEthNotStarted() { + mEthernetServiceImpl.mStarted.set(false); + assertThrows(IllegalStateException.class, () -> { + mEthernetServiceImpl.connectNetwork("" /* iface */, null /* listener */); + }); + } + + @Test + public void testDisconnectNetworkRejectsWhenEthNotStarted() { + mEthernetServiceImpl.mStarted.set(false); + assertThrows(IllegalStateException.class, () -> { + mEthernetServiceImpl.disconnectNetwork("" /* iface */, null /* listener */); + }); + } + + @Test + public void testUpdateConfigurationRejectsNullIface() { + assertThrows(NullPointerException.class, () -> { + mEthernetServiceImpl.updateConfiguration(null, UPDATE_REQUEST, NULL_LISTENER); + }); + } + + @Test + public void testConnectNetworkRejectsNullIface() { + assertThrows(NullPointerException.class, () -> { + mEthernetServiceImpl.connectNetwork(null /* iface */, NULL_LISTENER); + }); + } + + @Test + public void testDisconnectNetworkRejectsNullIface() { + assertThrows(NullPointerException.class, () -> { + mEthernetServiceImpl.disconnectNetwork(null /* iface */, NULL_LISTENER); + }); + } + + @Test + public void testUpdateConfigurationWithCapabilitiesRejectsWithoutAutomotiveFeature() { + toggleAutomotiveFeature(false); + assertThrows(UnsupportedOperationException.class, () -> { + mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST, NULL_LISTENER); + }); + } + + @Test + public void testUpdateConfigurationWithCapabilitiesWithAutomotiveFeature() { + toggleAutomotiveFeature(false); + mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST_WITHOUT_CAPABILITIES, + NULL_LISTENER); + verify(mEthernetTracker).updateConfiguration(eq(TEST_IFACE), + eq(UPDATE_REQUEST_WITHOUT_CAPABILITIES.getIpConfiguration()), + eq(UPDATE_REQUEST_WITHOUT_CAPABILITIES.getNetworkCapabilities()), isNull()); + } + + @Test + public void testConnectNetworkRejectsWithoutAutomotiveFeature() { + toggleAutomotiveFeature(false); + assertThrows(UnsupportedOperationException.class, () -> { + mEthernetServiceImpl.connectNetwork("" /* iface */, NULL_LISTENER); + }); + } + + @Test + public void testDisconnectNetworkRejectsWithoutAutomotiveFeature() { + toggleAutomotiveFeature(false); + assertThrows(UnsupportedOperationException.class, () -> { + mEthernetServiceImpl.disconnectNetwork("" /* iface */, NULL_LISTENER); + }); + } + + private void denyManageEthPermission() { + doThrow(new SecurityException("")).when(mContext) + .enforceCallingOrSelfPermission( + eq(Manifest.permission.MANAGE_ETHERNET_NETWORKS), anyString()); + } + + private void denyManageTestNetworksPermission() { + doThrow(new SecurityException("")).when(mContext) + .enforceCallingOrSelfPermission( + eq(Manifest.permission.MANAGE_TEST_NETWORKS), anyString()); + } + + @Test + public void testUpdateConfigurationRejectsWithoutManageEthPermission() { + denyManageEthPermission(); + assertThrows(SecurityException.class, () -> { + mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST, NULL_LISTENER); + }); + } + + @Test + public void testConnectNetworkRejectsWithoutManageEthPermission() { + denyManageEthPermission(); + assertThrows(SecurityException.class, () -> { + mEthernetServiceImpl.connectNetwork(TEST_IFACE, NULL_LISTENER); + }); + } + + @Test + public void testDisconnectNetworkRejectsWithoutManageEthPermission() { + denyManageEthPermission(); + assertThrows(SecurityException.class, () -> { + mEthernetServiceImpl.disconnectNetwork(TEST_IFACE, NULL_LISTENER); + }); + } + + private void enableTestInterface() { + when(mEthernetTracker.isValidTestInterface(eq(TEST_IFACE))).thenReturn(true); + } + + @Test + public void testUpdateConfigurationRejectsTestRequestWithoutTestPermission() { + enableTestInterface(); + denyManageTestNetworksPermission(); + assertThrows(SecurityException.class, () -> { + mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST, NULL_LISTENER); + }); + } + + @Test + public void testConnectNetworkRejectsTestRequestWithoutTestPermission() { + enableTestInterface(); + denyManageTestNetworksPermission(); + assertThrows(SecurityException.class, () -> { + mEthernetServiceImpl.connectNetwork(TEST_IFACE, NULL_LISTENER); + }); + } + + @Test + public void testDisconnectNetworkRejectsTestRequestWithoutTestPermission() { + enableTestInterface(); + denyManageTestNetworksPermission(); + assertThrows(SecurityException.class, () -> { + mEthernetServiceImpl.disconnectNetwork(TEST_IFACE, NULL_LISTENER); + }); + } + + @Test + public void testUpdateConfiguration() { + mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST, NULL_LISTENER); + verify(mEthernetTracker).updateConfiguration( + eq(TEST_IFACE), + eq(UPDATE_REQUEST.getIpConfiguration()), + eq(UPDATE_REQUEST.getNetworkCapabilities()), eq(NULL_LISTENER)); + } + + @Test + public void testConnectNetwork() { + mEthernetServiceImpl.connectNetwork(TEST_IFACE, NULL_LISTENER); + verify(mEthernetTracker).connectNetwork(eq(TEST_IFACE), eq(NULL_LISTENER)); + } + + @Test + public void testDisconnectNetwork() { + mEthernetServiceImpl.disconnectNetwork(TEST_IFACE, NULL_LISTENER); + verify(mEthernetTracker).disconnectNetwork(eq(TEST_IFACE), eq(NULL_LISTENER)); + } + + @Test + public void testUpdateConfigurationAcceptsTestRequestWithNullCapabilities() { + enableTestInterface(); + final EthernetNetworkUpdateRequest request = + new EthernetNetworkUpdateRequest + .Builder() + .setIpConfiguration(new IpConfiguration()).build(); + mEthernetServiceImpl.updateConfiguration(TEST_IFACE, request, NULL_LISTENER); + verify(mEthernetTracker).updateConfiguration(eq(TEST_IFACE), + eq(request.getIpConfiguration()), + eq(request.getNetworkCapabilities()), isNull()); + } + + @Test + public void testUpdateConfigurationAcceptsRequestWithNullIpConfiguration() { + mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST_WITHOUT_IP_CONFIG, + NULL_LISTENER); + verify(mEthernetTracker).updateConfiguration(eq(TEST_IFACE), + eq(UPDATE_REQUEST_WITHOUT_IP_CONFIG.getIpConfiguration()), + eq(UPDATE_REQUEST_WITHOUT_IP_CONFIG.getNetworkCapabilities()), isNull()); + } + + @Test + public void testUpdateConfigurationRejectsInvalidTestRequest() { + enableTestInterface(); + assertThrows(IllegalArgumentException.class, () -> { + mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST, NULL_LISTENER); + }); + } + + private EthernetNetworkUpdateRequest createTestNetworkUpdateRequest() { + final NetworkCapabilities nc = new NetworkCapabilities + .Builder(UPDATE_REQUEST.getNetworkCapabilities()) + .addTransportType(TRANSPORT_TEST).build(); + + return new EthernetNetworkUpdateRequest + .Builder(UPDATE_REQUEST) + .setNetworkCapabilities(nc).build(); + } + + @Test + public void testUpdateConfigurationForTestRequestDoesNotRequireAutoOrEthernetPermission() { + enableTestInterface(); + toggleAutomotiveFeature(false); + denyManageEthPermission(); + final EthernetNetworkUpdateRequest request = createTestNetworkUpdateRequest(); + + mEthernetServiceImpl.updateConfiguration(TEST_IFACE, request, NULL_LISTENER); + verify(mEthernetTracker).updateConfiguration( + eq(TEST_IFACE), + eq(request.getIpConfiguration()), + eq(request.getNetworkCapabilities()), eq(NULL_LISTENER)); + } + + @Test + public void testConnectNetworkForTestRequestDoesNotRequireAutoOrNetPermission() { + enableTestInterface(); + toggleAutomotiveFeature(false); + denyManageEthPermission(); + + mEthernetServiceImpl.connectNetwork(TEST_IFACE, NULL_LISTENER); + verify(mEthernetTracker).connectNetwork(eq(TEST_IFACE), eq(NULL_LISTENER)); + } + + @Test + public void testDisconnectNetworkForTestRequestDoesNotRequireAutoOrNetPermission() { + enableTestInterface(); + toggleAutomotiveFeature(false); + denyManageEthPermission(); + + mEthernetServiceImpl.disconnectNetwork(TEST_IFACE, NULL_LISTENER); + verify(mEthernetTracker).disconnectNetwork(eq(TEST_IFACE), eq(NULL_LISTENER)); + } + + private void denyPermissions(String... permissions) { + for (String permission: permissions) { + doReturn(PackageManager.PERMISSION_DENIED).when(mContext) + .checkCallingOrSelfPermission(eq(permission)); + } + } + + @Test + public void testSetEthernetEnabled() { + denyPermissions(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); + mEthernetServiceImpl.setEthernetEnabled(true); + verify(mEthernetTracker).setEthernetEnabled(true); + reset(mEthernetTracker); + + denyPermissions(Manifest.permission.NETWORK_STACK); + mEthernetServiceImpl.setEthernetEnabled(false); + verify(mEthernetTracker).setEthernetEnabled(false); + reset(mEthernetTracker); + + denyPermissions(Manifest.permission.NETWORK_SETTINGS); + try { + mEthernetServiceImpl.setEthernetEnabled(true); + fail("Should get SecurityException"); + } catch (SecurityException e) { } + verify(mEthernetTracker, never()).setEthernetEnabled(false); + } +} diff --git a/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java b/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java new file mode 100644 index 0000000000..b1831c411b --- /dev/null +++ b/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java @@ -0,0 +1,456 @@ +/* + * Copyright (C) 2018 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 android.net.TestNetworkManager.TEST_TAP_PREFIX; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.res.Resources; +import android.net.EthernetManager; +import android.net.InetAddresses; +import android.net.INetworkInterfaceOutcomeReceiver; +import android.net.IEthernetServiceListener; +import android.net.INetd; +import android.net.IpConfiguration; +import android.net.IpConfiguration.IpAssignment; +import android.net.IpConfiguration.ProxySettings; +import android.net.InterfaceConfigurationParcel; +import android.net.LinkAddress; +import android.net.NetworkCapabilities; +import android.net.StaticIpConfiguration; +import android.os.HandlerThread; +import android.os.RemoteException; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.connectivity.resources.R; +import com.android.testutils.HandlerUtils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.net.InetAddress; +import java.util.ArrayList; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class EthernetTrackerTest { + private static final String TEST_IFACE = "test123"; + private static final int TIMEOUT_MS = 1_000; + private static final String THREAD_NAME = "EthernetServiceThread"; + private static final INetworkInterfaceOutcomeReceiver NULL_LISTENER = null; + private EthernetTracker tracker; + private HandlerThread mHandlerThread; + @Mock private Context mContext; + @Mock private EthernetNetworkFactory mFactory; + @Mock private INetd mNetd; + @Mock private EthernetTracker.Dependencies mDeps; + + @Before + public void setUp() throws RemoteException { + MockitoAnnotations.initMocks(this); + initMockResources(); + when(mFactory.updateInterfaceLinkState(anyString(), anyBoolean(), any())).thenReturn(false); + when(mNetd.interfaceGetList()).thenReturn(new String[0]); + mHandlerThread = new HandlerThread(THREAD_NAME); + mHandlerThread.start(); + tracker = new EthernetTracker(mContext, mHandlerThread.getThreadHandler(), mFactory, mNetd, + mDeps); + } + + @After + public void cleanUp() { + mHandlerThread.quitSafely(); + } + + private void initMockResources() { + when(mDeps.getInterfaceRegexFromResource(eq(mContext))).thenReturn(""); + when(mDeps.getInterfaceConfigFromResource(eq(mContext))).thenReturn(new String[0]); + } + + private void waitForIdle() { + HandlerUtils.waitForIdle(mHandlerThread, TIMEOUT_MS); + } + + /** + * Test: Creation of various valid static IP configurations + */ + @Test + public void createStaticIpConfiguration() { + // Empty gives default StaticIPConfiguration object + assertStaticConfiguration(new StaticIpConfiguration(), ""); + + // Setting only the IP address properly cascades and assumes defaults + assertStaticConfiguration(new StaticIpConfiguration.Builder() + .setIpAddress(new LinkAddress("192.0.2.10/24")).build(), "ip=192.0.2.10/24"); + + final ArrayList<InetAddress> dnsAddresses = new ArrayList<>(); + dnsAddresses.add(InetAddresses.parseNumericAddress("4.4.4.4")); + dnsAddresses.add(InetAddresses.parseNumericAddress("8.8.8.8")); + // Setting other fields properly cascades them + assertStaticConfiguration(new StaticIpConfiguration.Builder() + .setIpAddress(new LinkAddress("192.0.2.10/24")) + .setDnsServers(dnsAddresses) + .setGateway(InetAddresses.parseNumericAddress("192.0.2.1")) + .setDomains("android").build(), + "ip=192.0.2.10/24 dns=4.4.4.4,8.8.8.8 gateway=192.0.2.1 domains=android"); + + // Verify order doesn't matter + assertStaticConfiguration(new StaticIpConfiguration.Builder() + .setIpAddress(new LinkAddress("192.0.2.10/24")) + .setDnsServers(dnsAddresses) + .setGateway(InetAddresses.parseNumericAddress("192.0.2.1")) + .setDomains("android").build(), + "domains=android ip=192.0.2.10/24 gateway=192.0.2.1 dns=4.4.4.4,8.8.8.8 "); + } + + /** + * Test: Attempt creation of various bad static IP configurations + */ + @Test + public void createStaticIpConfiguration_Bad() { + assertStaticConfigurationFails("ip=192.0.2.1/24 gateway= blah=20.20.20.20"); // Unknown key + assertStaticConfigurationFails("ip=192.0.2.1"); // mask is missing + assertStaticConfigurationFails("ip=a.b.c"); // not a valid ip address + assertStaticConfigurationFails("dns=4.4.4.4,1.2.3.A"); // not valid ip address in dns + assertStaticConfigurationFails("="); // Key and value is empty + assertStaticConfigurationFails("ip="); // Value is empty + assertStaticConfigurationFails("ip=192.0.2.1/24 gateway="); // Gateway is empty + } + + private void assertStaticConfigurationFails(String config) { + try { + EthernetTracker.parseStaticIpConfiguration(config); + fail("Expected to fail: " + config); + } catch (IllegalArgumentException e) { + // expected + } + } + + private void assertStaticConfiguration(StaticIpConfiguration expectedStaticIpConfig, + String configAsString) { + final IpConfiguration expectedIpConfiguration = new IpConfiguration(); + expectedIpConfiguration.setIpAssignment(IpAssignment.STATIC); + expectedIpConfiguration.setProxySettings(ProxySettings.NONE); + expectedIpConfiguration.setStaticIpConfiguration(expectedStaticIpConfig); + + assertEquals(expectedIpConfiguration, + EthernetTracker.parseStaticIpConfiguration(configAsString)); + } + + private NetworkCapabilities.Builder makeEthernetCapabilitiesBuilder(boolean clearAll) { + final NetworkCapabilities.Builder builder = + clearAll ? NetworkCapabilities.Builder.withoutDefaultCapabilities() + : new NetworkCapabilities.Builder(); + return builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING) + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED) + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED); + } + + /** + * Test: Attempt to create a capabilties with various valid sets of capabilities/transports + */ + @Test + public void createNetworkCapabilities() { + + // Particularly common expected results + NetworkCapabilities defaultEthernetCleared = + makeEthernetCapabilitiesBuilder(true /* clearAll */) + .setLinkUpstreamBandwidthKbps(100000) + .setLinkDownstreamBandwidthKbps(100000) + .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET) + .build(); + + NetworkCapabilities ethernetClearedWithCommonCaps = + makeEthernetCapabilitiesBuilder(true /* clearAll */) + .setLinkUpstreamBandwidthKbps(100000) + .setLinkDownstreamBandwidthKbps(100000) + .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET) + .addCapability(12) + .addCapability(13) + .addCapability(14) + .addCapability(15) + .build(); + + // Empty capabilities and transports lists with a "please clear defaults" should + // yield an empty capabilities set with TRANPORT_ETHERNET + assertParsedNetworkCapabilities(defaultEthernetCleared, true, "", ""); + + // Empty capabilities and transports without the clear defaults flag should return the + // default capabilities set with TRANSPORT_ETHERNET + assertParsedNetworkCapabilities( + makeEthernetCapabilitiesBuilder(false /* clearAll */) + .setLinkUpstreamBandwidthKbps(100000) + .setLinkDownstreamBandwidthKbps(100000) + .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET) + .build(), + false, "", ""); + + // A list of capabilities without the clear defaults flag should return the default + // capabilities, mixed with the desired capabilities, and TRANSPORT_ETHERNET + assertParsedNetworkCapabilities( + makeEthernetCapabilitiesBuilder(false /* clearAll */) + .setLinkUpstreamBandwidthKbps(100000) + .setLinkDownstreamBandwidthKbps(100000) + .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET) + .addCapability(11) + .addCapability(12) + .build(), + false, "11,12", ""); + + // Adding a list of capabilities with a clear defaults will leave exactly those capabilities + // with a default TRANSPORT_ETHERNET since no overrides are specified + assertParsedNetworkCapabilities(ethernetClearedWithCommonCaps, true, "12,13,14,15", ""); + + // Adding any invalid capabilities to the list will cause them to be ignored + assertParsedNetworkCapabilities(ethernetClearedWithCommonCaps, true, "12,13,14,15,65,73", ""); + assertParsedNetworkCapabilities(ethernetClearedWithCommonCaps, true, "12,13,14,15,abcdefg", ""); + + // Adding a valid override transport will remove the default TRANSPORT_ETHERNET transport + // and apply only the override to the capabiltities object + assertParsedNetworkCapabilities( + makeEthernetCapabilitiesBuilder(true /* clearAll */) + .setLinkUpstreamBandwidthKbps(100000) + .setLinkDownstreamBandwidthKbps(100000) + .addTransportType(0) + .build(), + true, "", "0"); + assertParsedNetworkCapabilities( + makeEthernetCapabilitiesBuilder(true /* clearAll */) + .setLinkUpstreamBandwidthKbps(100000) + .setLinkDownstreamBandwidthKbps(100000) + .addTransportType(1) + .build(), + true, "", "1"); + assertParsedNetworkCapabilities( + makeEthernetCapabilitiesBuilder(true /* clearAll */) + .setLinkUpstreamBandwidthKbps(100000) + .setLinkDownstreamBandwidthKbps(100000) + .addTransportType(2) + .build(), + true, "", "2"); + assertParsedNetworkCapabilities( + makeEthernetCapabilitiesBuilder(true /* clearAll */) + .setLinkUpstreamBandwidthKbps(100000) + .setLinkDownstreamBandwidthKbps(100000) + .addTransportType(3) + .build(), + true, "", "3"); + + // "4" is TRANSPORT_VPN, which is unsupported. Should default back to TRANPORT_ETHERNET + assertParsedNetworkCapabilities(defaultEthernetCleared, true, "", "4"); + + // "5" is TRANSPORT_WIFI_AWARE, which is currently supported due to no legacy TYPE_NONE + // conversion. When that becomes available, this test must be updated + assertParsedNetworkCapabilities(defaultEthernetCleared, true, "", "5"); + + // "6" is TRANSPORT_LOWPAN, which is currently supported due to no legacy TYPE_NONE + // conversion. When that becomes available, this test must be updated + assertParsedNetworkCapabilities(defaultEthernetCleared, true, "", "6"); + + // Adding an invalid override transport will leave the transport as TRANSPORT_ETHERNET + assertParsedNetworkCapabilities(defaultEthernetCleared,true, "", "100"); + assertParsedNetworkCapabilities(defaultEthernetCleared, true, "", "abcdefg"); + + // Ensure the adding of both capabilities and transports work + assertParsedNetworkCapabilities( + makeEthernetCapabilitiesBuilder(true /* clearAll */) + .setLinkUpstreamBandwidthKbps(100000) + .setLinkDownstreamBandwidthKbps(100000) + .addCapability(12) + .addCapability(13) + .addCapability(14) + .addCapability(15) + .addTransportType(3) + .build(), + true, "12,13,14,15", "3"); + + // Ensure order does not matter for capability list + assertParsedNetworkCapabilities(ethernetClearedWithCommonCaps, true, "13,12,15,14", ""); + } + + private void assertParsedNetworkCapabilities(NetworkCapabilities expectedNetworkCapabilities, + boolean clearCapabilties, String configCapabiltiies,String configTransports) { + assertEquals(expectedNetworkCapabilities, + EthernetTracker.createNetworkCapabilities(clearCapabilties, configCapabiltiies, + configTransports).build()); + } + + @Test + public void testCreateEthernetTrackerConfigReturnsCorrectValue() { + final String capabilities = "2"; + final String ipConfig = "3"; + final String transport = "4"; + final String configString = String.join(";", TEST_IFACE, capabilities, ipConfig, transport); + + final EthernetTracker.EthernetTrackerConfig config = + EthernetTracker.createEthernetTrackerConfig(configString); + + assertEquals(TEST_IFACE, config.mIface); + assertEquals(capabilities, config.mCapabilities); + assertEquals(ipConfig, config.mIpConfig); + assertEquals(transport, config.mTransport); + } + + @Test + public void testCreateEthernetTrackerConfigThrowsNpeWithNullInput() { + assertThrows(NullPointerException.class, + () -> EthernetTracker.createEthernetTrackerConfig(null)); + } + + @Test + public void testUpdateConfiguration() { + final NetworkCapabilities capabilities = new NetworkCapabilities.Builder().build(); + final LinkAddress linkAddr = new LinkAddress("192.0.2.2/25"); + final StaticIpConfiguration staticIpConfig = + new StaticIpConfiguration.Builder().setIpAddress(linkAddr).build(); + final IpConfiguration ipConfig = + new IpConfiguration.Builder().setStaticIpConfiguration(staticIpConfig).build(); + final INetworkInterfaceOutcomeReceiver listener = null; + + tracker.updateConfiguration(TEST_IFACE, ipConfig, capabilities, listener); + waitForIdle(); + + verify(mFactory).updateInterface( + eq(TEST_IFACE), eq(ipConfig), eq(capabilities), eq(listener)); + } + + @Test + public void testConnectNetworkCorrectlyCallsFactory() { + tracker.connectNetwork(TEST_IFACE, NULL_LISTENER); + waitForIdle(); + + verify(mFactory).updateInterfaceLinkState(eq(TEST_IFACE), eq(true /* up */), + eq(NULL_LISTENER)); + } + + @Test + public void testDisconnectNetworkCorrectlyCallsFactory() { + tracker.disconnectNetwork(TEST_IFACE, NULL_LISTENER); + waitForIdle(); + + verify(mFactory).updateInterfaceLinkState(eq(TEST_IFACE), eq(false /* up */), + eq(NULL_LISTENER)); + } + + @Test + public void testIsValidTestInterfaceIsFalseWhenTestInterfacesAreNotIncluded() { + final String validIfaceName = TEST_TAP_PREFIX + "123"; + tracker.setIncludeTestInterfaces(false); + waitForIdle(); + + final boolean isValidTestInterface = tracker.isValidTestInterface(validIfaceName); + + assertFalse(isValidTestInterface); + } + + @Test + public void testIsValidTestInterfaceIsFalseWhenTestInterfaceNameIsInvalid() { + final String invalidIfaceName = "123" + TEST_TAP_PREFIX; + tracker.setIncludeTestInterfaces(true); + waitForIdle(); + + final boolean isValidTestInterface = tracker.isValidTestInterface(invalidIfaceName); + + assertFalse(isValidTestInterface); + } + + @Test + public void testIsValidTestInterfaceIsTrueWhenTestInterfacesIncludedAndValidName() { + final String validIfaceName = TEST_TAP_PREFIX + "123"; + tracker.setIncludeTestInterfaces(true); + waitForIdle(); + + final boolean isValidTestInterface = tracker.isValidTestInterface(validIfaceName); + + assertTrue(isValidTestInterface); + } + + public static class EthernetStateListener extends IEthernetServiceListener.Stub { + @Override + public void onEthernetStateChanged(int state) { } + + @Override + public void onInterfaceStateChanged(String iface, int state, int role, + IpConfiguration configuration) { } + } + + @Test + public void testListenEthernetStateChange() throws Exception { + final String testIface = "testtap123"; + final String testHwAddr = "11:22:33:44:55:66"; + final InterfaceConfigurationParcel ifaceParcel = new InterfaceConfigurationParcel(); + ifaceParcel.ifName = testIface; + ifaceParcel.hwAddr = testHwAddr; + ifaceParcel.flags = new String[] {INetd.IF_STATE_UP}; + + tracker.setIncludeTestInterfaces(true); + waitForIdle(); + + when(mNetd.interfaceGetList()).thenReturn(new String[] {testIface}); + when(mNetd.interfaceGetCfg(eq(testIface))).thenReturn(ifaceParcel); + doReturn(new String[] {testIface}).when(mFactory).getAvailableInterfaces(anyBoolean()); + doReturn(EthernetManager.STATE_LINK_UP).when(mFactory).getInterfaceState(eq(testIface)); + + final EthernetStateListener listener = spy(new EthernetStateListener()); + tracker.addListener(listener, true /* canUseRestrictedNetworks */); + // Check default state. + waitForIdle(); + verify(listener).onInterfaceStateChanged(eq(testIface), eq(EthernetManager.STATE_LINK_UP), + anyInt(), any()); + verify(listener).onEthernetStateChanged(eq(EthernetManager.ETHERNET_STATE_ENABLED)); + reset(listener); + + doReturn(EthernetManager.STATE_ABSENT).when(mFactory).getInterfaceState(eq(testIface)); + tracker.setEthernetEnabled(false); + waitForIdle(); + verify(mFactory).removeInterface(eq(testIface)); + verify(listener).onEthernetStateChanged(eq(EthernetManager.ETHERNET_STATE_DISABLED)); + verify(listener).onInterfaceStateChanged(eq(testIface), eq(EthernetManager.STATE_ABSENT), + anyInt(), any()); + reset(listener); + + doReturn(EthernetManager.STATE_LINK_UP).when(mFactory).getInterfaceState(eq(testIface)); + tracker.setEthernetEnabled(true); + waitForIdle(); + verify(mFactory).addInterface(eq(testIface), eq(testHwAddr), any(), any()); + verify(listener).onEthernetStateChanged(eq(EthernetManager.ETHERNET_STATE_ENABLED)); + verify(listener).onInterfaceStateChanged(eq(testIface), eq(EthernetManager.STATE_LINK_UP), + anyInt(), any()); + } +} |
