Merge "Move static utils to the static library" am: 0fdd516a82 am: f13acd2190 am: e0788758af am: d94a280ac0
Original change: https://android-review.googlesource.com/c/platform/frameworks/libs/net/+/1849358
Change-Id: I6e7e1bff7f6ca70ac723e0d46db4debe444dfddc
diff --git a/staticlibs/client-libs/Android.bp b/staticlibs/client-libs/Android.bp
index 8c79d2a..5cc011e 100644
--- a/staticlibs/client-libs/Android.bp
+++ b/staticlibs/client-libs/Android.bp
@@ -16,6 +16,7 @@
"//frameworks/base/services:__subpackages__",
"//frameworks/base/packages:__subpackages__",
"//frameworks/libs/net/client-libs/tests:__subpackages__",
+ "//frameworks/libs/net/common:__subpackages__",
],
libs: ["androidx.annotation_annotation"],
static_libs: [
diff --git a/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java b/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java
index 1f31912..e78e336 100644
--- a/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java
+++ b/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java
@@ -18,21 +18,39 @@
import static android.net.INetd.IF_STATE_DOWN;
import static android.net.INetd.IF_STATE_UP;
+import static android.net.RouteInfo.RTN_THROW;
+import static android.net.RouteInfo.RTN_UNICAST;
+import static android.net.RouteInfo.RTN_UNREACHABLE;
+import static android.system.OsConstants.EBUSY;
import android.net.INetd;
import android.net.InterfaceConfigurationParcel;
+import android.net.IpPrefix;
+import android.net.RouteInfo;
+import android.net.TetherConfigParcel;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
+import android.os.SystemClock;
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import java.util.ArrayList;
+import java.util.List;
/**
* Collection of utilities for netd.
*/
public class NetdUtils {
+ private static final String TAG = NetdUtils.class.getSimpleName();
+
+ /** Used to modify the specified route. */
+ public enum ModifyOperation {
+ ADD,
+ REMOVE,
+ }
+
/**
* Get InterfaceConfigurationParcel from netd.
*/
@@ -102,4 +120,142 @@
IF_STATE_DOWN /* add */);
setInterfaceConfig(netd, configParcel);
}
+
+ /** Start tethering. */
+ public static void tetherStart(final INetd netd, final boolean usingLegacyDnsProxy,
+ final String[] dhcpRange) throws RemoteException, ServiceSpecificException {
+ final TetherConfigParcel config = new TetherConfigParcel();
+ config.usingLegacyDnsProxy = usingLegacyDnsProxy;
+ config.dhcpRanges = dhcpRange;
+ netd.tetherStartWithConfiguration(config);
+ }
+
+ /** Setup interface for tethering. */
+ public static void tetherInterface(final INetd netd, final String iface, final IpPrefix dest)
+ throws RemoteException, ServiceSpecificException {
+ tetherInterface(netd, iface, dest, 20 /* maxAttempts */, 50 /* pollingIntervalMs */);
+ }
+
+ /** Setup interface with configurable retries for tethering. */
+ public static void tetherInterface(final INetd netd, final String iface, final IpPrefix dest,
+ int maxAttempts, int pollingIntervalMs)
+ throws RemoteException, ServiceSpecificException {
+ netd.tetherInterfaceAdd(iface);
+ networkAddInterface(netd, iface, maxAttempts, pollingIntervalMs);
+ List<RouteInfo> routes = new ArrayList<>();
+ routes.add(new RouteInfo(dest, null, iface, RTN_UNICAST));
+ addRoutesToLocalNetwork(netd, iface, routes);
+ }
+
+ /**
+ * Retry Netd#networkAddInterface for EBUSY error code.
+ * If the same interface (e.g., wlan0) is in client mode and then switches to tethered mode.
+ * There can be a race where puts the interface into the local network but interface is still
+ * in use in netd because the ConnectivityService thread hasn't processed the disconnect yet.
+ * See b/158269544 for detail.
+ */
+ private static void networkAddInterface(final INetd netd, final String iface,
+ int maxAttempts, int pollingIntervalMs)
+ throws ServiceSpecificException, RemoteException {
+ for (int i = 1; i <= maxAttempts; i++) {
+ try {
+ netd.networkAddInterface(INetd.LOCAL_NET_ID, iface);
+ return;
+ } catch (ServiceSpecificException e) {
+ if (e.errorCode == EBUSY && i < maxAttempts) {
+ SystemClock.sleep(pollingIntervalMs);
+ continue;
+ }
+
+ Log.e(TAG, "Retry Netd#networkAddInterface failure: " + e);
+ throw e;
+ }
+ }
+ }
+
+ /** Reset interface for tethering. */
+ public static void untetherInterface(final INetd netd, String iface)
+ throws RemoteException, ServiceSpecificException {
+ try {
+ netd.tetherInterfaceRemove(iface);
+ } finally {
+ netd.networkRemoveInterface(INetd.LOCAL_NET_ID, iface);
+ }
+ }
+
+ /** Add |routes| to local network. */
+ public static void addRoutesToLocalNetwork(final INetd netd, final String iface,
+ final List<RouteInfo> routes) {
+
+ for (RouteInfo route : routes) {
+ if (!route.isDefaultRoute()) {
+ modifyRoute(netd, ModifyOperation.ADD, INetd.LOCAL_NET_ID, route);
+ }
+ }
+
+ // IPv6 link local should be activated always.
+ modifyRoute(netd, ModifyOperation.ADD, INetd.LOCAL_NET_ID,
+ new RouteInfo(new IpPrefix("fe80::/64"), null, iface, RTN_UNICAST));
+ }
+
+ /** Remove routes from local network. */
+ public static int removeRoutesFromLocalNetwork(final INetd netd, final List<RouteInfo> routes) {
+ int failures = 0;
+
+ for (RouteInfo route : routes) {
+ try {
+ modifyRoute(netd, ModifyOperation.REMOVE, INetd.LOCAL_NET_ID, route);
+ } catch (IllegalStateException e) {
+ failures++;
+ }
+ }
+
+ return failures;
+ }
+
+ private static String findNextHop(final RouteInfo route) {
+ final String nextHop;
+ switch (route.getType()) {
+ case RTN_UNICAST:
+ if (route.hasGateway()) {
+ nextHop = route.getGateway().getHostAddress();
+ } else {
+ nextHop = INetd.NEXTHOP_NONE;
+ }
+ break;
+ case RTN_UNREACHABLE:
+ nextHop = INetd.NEXTHOP_UNREACHABLE;
+ break;
+ case RTN_THROW:
+ nextHop = INetd.NEXTHOP_THROW;
+ break;
+ default:
+ nextHop = INetd.NEXTHOP_NONE;
+ break;
+ }
+ return nextHop;
+ }
+
+ /** Add or remove |route|. */
+ public static void modifyRoute(final INetd netd, final ModifyOperation op, final int netId,
+ final RouteInfo route) {
+ final String ifName = route.getInterface();
+ final String dst = route.getDestination().toString();
+ final String nextHop = findNextHop(route);
+
+ try {
+ switch(op) {
+ case ADD:
+ netd.networkAddRoute(netId, ifName, dst, nextHop);
+ break;
+ case REMOVE:
+ netd.networkRemoveRoute(netId, ifName, dst, nextHop);
+ break;
+ default:
+ throw new IllegalStateException("Unsupported modify operation:" + op);
+ }
+ } catch (RemoteException | ServiceSpecificException e) {
+ throw new IllegalStateException(e);
+ }
+ }
}
diff --git a/staticlibs/client-libs/tests/unit/src/com/android/net/module/util/NetdUtilsTest.java b/staticlibs/client-libs/tests/unit/src/com/android/net/module/util/NetdUtilsTest.java
index 306b1bb..dedda7e 100644
--- a/staticlibs/client-libs/tests/unit/src/com/android/net/module/util/NetdUtilsTest.java
+++ b/staticlibs/client-libs/tests/unit/src/com/android/net/module/util/NetdUtilsTest.java
@@ -16,15 +16,31 @@
package com.android.net.module.util;
+import static android.net.INetd.LOCAL_NET_ID;
+import static android.system.OsConstants.EBUSY;
+
import static com.android.testutils.MiscAsserts.assertThrows;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.net.INetd;
import android.net.InterfaceConfigurationParcel;
+import android.net.IpPrefix;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -43,6 +59,7 @@
@Mock private INetd mNetd;
private static final String IFACE = "TEST_IFACE";
+ private static final IpPrefix TEST_IPPREFIX = new IpPrefix("192.168.42.1/24");
@Before
public void setUp() throws Exception {
@@ -99,5 +116,106 @@
assertThrows(IllegalStateException.class,
() -> NetdUtils.removeAndAddFlags(flags, "down" /* remove */, "u p" /* add */));
}
-}
+ private void setNetworkAddInterfaceOutcome(final Exception cause, int numLoops)
+ throws Exception {
+ // This cannot be an int because local variables referenced from a lambda expression must
+ // be final or effectively final.
+ final Counter myCounter = new Counter();
+ doAnswer((invocation) -> {
+ myCounter.count();
+ if (myCounter.isCounterReached(numLoops)) {
+ if (cause == null) return null;
+
+ throw cause;
+ }
+
+ throw new ServiceSpecificException(EBUSY);
+ }).when(mNetd).networkAddInterface(LOCAL_NET_ID, IFACE);
+ }
+
+ class Counter {
+ private int mValue = 0;
+
+ private void count() {
+ mValue++;
+ }
+
+ private boolean isCounterReached(int target) {
+ return mValue >= target;
+ }
+ }
+
+ @Test
+ public void testTetherInterfaceSuccessful() throws Exception {
+ // Expect #networkAddInterface successful at first tries.
+ verifyTetherInterfaceSucceeds(1);
+
+ // Expect #networkAddInterface successful after 10 tries.
+ verifyTetherInterfaceSucceeds(10);
+ }
+
+ private void runTetherInterfaceWithServiceSpecificException(int expectedTries,
+ int expectedCode) throws Exception {
+ setNetworkAddInterfaceOutcome(new ServiceSpecificException(expectedCode), expectedTries);
+
+ try {
+ NetdUtils.tetherInterface(mNetd, IFACE, TEST_IPPREFIX, 20, 0);
+ fail("Expect throw ServiceSpecificException");
+ } catch (ServiceSpecificException e) {
+ assertEquals(e.errorCode, expectedCode);
+ }
+
+ verifyNetworkAddInterfaceFails(expectedTries);
+ reset(mNetd);
+ }
+
+ private void runTetherInterfaceWithRemoteException(int expectedTries) throws Exception {
+ setNetworkAddInterfaceOutcome(new RemoteException(), expectedTries);
+
+ try {
+ NetdUtils.tetherInterface(mNetd, IFACE, TEST_IPPREFIX, 20, 0);
+ fail("Expect throw RemoteException");
+ } catch (RemoteException e) { }
+
+ verifyNetworkAddInterfaceFails(expectedTries);
+ reset(mNetd);
+ }
+
+ private void verifyNetworkAddInterfaceFails(int expectedTries) throws Exception {
+ verify(mNetd).tetherInterfaceAdd(IFACE);
+ verify(mNetd, times(expectedTries)).networkAddInterface(LOCAL_NET_ID, IFACE);
+ verify(mNetd, never()).networkAddRoute(anyInt(), anyString(), any(), any());
+ verifyNoMoreInteractions(mNetd);
+ }
+
+ private void verifyTetherInterfaceSucceeds(int expectedTries) throws Exception {
+ setNetworkAddInterfaceOutcome(null, expectedTries);
+
+ NetdUtils.tetherInterface(mNetd, IFACE, TEST_IPPREFIX);
+ verify(mNetd).tetherInterfaceAdd(IFACE);
+ verify(mNetd, times(expectedTries)).networkAddInterface(LOCAL_NET_ID, IFACE);
+ verify(mNetd, times(2)).networkAddRoute(eq(LOCAL_NET_ID), eq(IFACE), any(), any());
+ verifyNoMoreInteractions(mNetd);
+ reset(mNetd);
+ }
+
+ @Test
+ public void testTetherInterfaceFailOnNetworkAddInterface() throws Exception {
+ // Test throwing ServiceSpecificException with EBUSY failure.
+ runTetherInterfaceWithServiceSpecificException(20, EBUSY);
+
+ // Test throwing ServiceSpecificException with unexpectedError.
+ final int unexpectedError = 999;
+ runTetherInterfaceWithServiceSpecificException(1, unexpectedError);
+
+ // Test throwing ServiceSpecificException with unexpectedError after 7 tries.
+ runTetherInterfaceWithServiceSpecificException(7, unexpectedError);
+
+ // Test throwing RemoteException.
+ runTetherInterfaceWithRemoteException(1);
+
+ // Test throwing RemoteException after 3 tries.
+ runTetherInterfaceWithRemoteException(3);
+ }
+}