From 8a4c9c356459f55a854c55fc09ac9e0ec9c0bb58 Mon Sep 17 00:00:00 2001 From: markchien Date: Wed, 16 Mar 2022 15:59:33 +0800 Subject: Add global change ethernet state API Provide a new API to enable or disable ethernet. Also have a listener for call to check whether enable state is sucessful. Bug: 171872016 Test: atest EthernetServiceTests Change-Id: Iee4b48511ff668a2a7df90fd9bfe563d7ff23940 --- .../server/ethernet/EthernetNetworkFactory.java | 9 ++-- .../server/ethernet/EthernetServiceImpl.java | 8 +++ .../android/server/ethernet/EthernetTracker.java | 53 +++++++++++++++++++ .../server/ethernet/EthernetServiceImplTest.java | 30 +++++++++++ .../server/ethernet/EthernetTrackerTest.java | 60 ++++++++++++++++++++++ 5 files changed, 157 insertions(+), 3 deletions(-) diff --git a/java/com/android/server/ethernet/EthernetNetworkFactory.java b/java/com/android/server/ethernet/EthernetNetworkFactory.java index 4f15355..342d507 100644 --- a/java/com/android/server/ethernet/EthernetNetworkFactory.java +++ b/java/com/android/server/ethernet/EthernetNetworkFactory.java @@ -183,7 +183,8 @@ public class EthernetNetworkFactory extends NetworkFactory { * Returns an array of available interface names. The array is sorted: unrestricted interfaces * goes first, then sorted by name. */ - String[] getAvailableInterfaces(boolean includeRestricted) { + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + protected String[] getAvailableInterfaces(boolean includeRestricted) { return mTrackingInterfaces.values() .stream() .filter(iface -> !iface.isRestricted() || includeRestricted) @@ -195,7 +196,8 @@ public class EthernetNetworkFactory extends NetworkFactory { .toArray(String[]::new); } - void addInterface(@NonNull final String ifaceName, @NonNull final String hwAddress, + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + protected void addInterface(@NonNull final String ifaceName, @NonNull final String hwAddress, @NonNull final IpConfiguration ipConfig, @NonNull final NetworkCapabilities capabilities) { if (mTrackingInterfaces.containsKey(ifaceName)) { @@ -282,7 +284,8 @@ public class EthernetNetworkFactory extends NetworkFactory { .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET).build(); } - void removeInterface(String interfaceName) { + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + protected void removeInterface(String interfaceName) { NetworkInterfaceState iface = mTrackingInterfaces.remove(interfaceName); if (iface != null) { iface.maybeSendNetworkManagementCallbackForAbort(); diff --git a/java/com/android/server/ethernet/EthernetServiceImpl.java b/java/com/android/server/ethernet/EthernetServiceImpl.java index edda321..afed01a 100644 --- a/java/com/android/server/ethernet/EthernetServiceImpl.java +++ b/java/com/android/server/ethernet/EthernetServiceImpl.java @@ -281,4 +281,12 @@ public class EthernetServiceImpl extends IEthernetManager.Stub { mTracker.disconnectNetwork(iface, listener); } + + @Override + public void setEthernetEnabled(boolean enabled) { + PermissionUtils.enforceNetworkStackPermissionOr(mContext, + android.Manifest.permission.NETWORK_SETTINGS); + + mTracker.setEthernetEnabled(enabled); + } } diff --git a/java/com/android/server/ethernet/EthernetTracker.java b/java/com/android/server/ethernet/EthernetTracker.java index 0a0e327..c38c900 100644 --- a/java/com/android/server/ethernet/EthernetTracker.java +++ b/java/com/android/server/ethernet/EthernetTracker.java @@ -16,6 +16,8 @@ package com.android.server.ethernet; +import static android.net.EthernetManager.ETHERNET_STATE_DISABLED; +import static android.net.EthernetManager.ETHERNET_STATE_ENABLED; import static android.net.TestNetworkManager.TEST_TAP_PREFIX; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; @@ -119,6 +121,8 @@ public class EthernetTracker { private boolean mTetheredInterfaceWasAvailable = false; private volatile IpConfiguration mIpConfigForDefaultInterface; + private int mEthernetState = ETHERNET_STATE_ENABLED; + private class TetheredInterfaceRequestList extends RemoteCallbackList { @Override @@ -355,6 +359,8 @@ public class EthernetTracker { for (String iface : getInterfaces(canUseRestrictedNetworks)) { unicastInterfaceStateChange(listener, iface); } + + unicastEthernetStateChange(listener, mEthernetState); }); } @@ -825,6 +831,53 @@ public class EthernetTracker { } } + @VisibleForTesting(visibility = PACKAGE) + protected void setEthernetEnabled(boolean enabled) { + mHandler.post(() -> { + int newState = enabled ? ETHERNET_STATE_ENABLED : ETHERNET_STATE_DISABLED; + if (mEthernetState == newState) return; + + mEthernetState = newState; + + if (enabled) { + trackAvailableInterfaces(); + } else { + // TODO: maybe also disable server mode interface as well. + untrackFactoryInterfaces(); + } + broadcastEthernetStateChange(mEthernetState); + }); + } + + private void untrackFactoryInterfaces() { + for (String iface : mFactory.getAvailableInterfaces(true /* includeRestricted */)) { + stopTrackingInterface(iface); + } + } + + private void unicastEthernetStateChange(@NonNull IEthernetServiceListener listener, + int state) { + ensureRunningOnEthernetServiceThread(); + try { + listener.onEthernetStateChanged(state); + } catch (RemoteException e) { + // Do nothing here. + } + } + + private void broadcastEthernetStateChange(int state) { + ensureRunningOnEthernetServiceThread(); + final int n = mListeners.beginBroadcast(); + for (int i = 0; i < n; i++) { + try { + mListeners.getBroadcastItem(i).onEthernetStateChanged(state); + } catch (RemoteException e) { + // Do nothing here. + } + } + mListeners.finishBroadcast(); + } + void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) { postAndWaitForRunnable(() -> { pw.println(getClass().getSimpleName()); diff --git a/tests/java/com/android/server/ethernet/EthernetServiceImplTest.java b/tests/java/com/android/server/ethernet/EthernetServiceImplTest.java index e67c4c8..dd1f1ed 100644 --- a/tests/java/com/android/server/ethernet/EthernetServiceImplTest.java +++ b/tests/java/com/android/server/ethernet/EthernetServiceImplTest.java @@ -19,12 +19,15 @@ 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; @@ -339,4 +342,31 @@ public class EthernetServiceImplTest { 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/java/com/android/server/ethernet/EthernetTrackerTest.java b/tests/java/com/android/server/ethernet/EthernetTrackerTest.java index bab9643..b1831c4 100644 --- a/tests/java/com/android/server/ethernet/EthernetTrackerTest.java +++ b/tests/java/com/android/server/ethernet/EthernetTrackerTest.java @@ -25,19 +25,26 @@ 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; @@ -393,4 +400,57 @@ public class EthernetTrackerTest { 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()); + } } -- cgit v1.2.3