Refactor RoutingCoordinator

- This CL puts RoutingCoordinator into staticlibs to make it usable by
both Tethering and ConnectivityService on R.
- This CL also changes the return type of
  ConnectivityInternalApi#getRoutingCoordinatorManager() to IBinder so
  it has fewer dependencies.

Bug: 350699020
Test: CI

Change-Id: Iba096e940795cd33e61c5141721394ad39e5ce6d
This commit is contained in:
Handa Wang 2024-07-02 13:18:04 +00:00
commit 8f2c30664d
14 changed files with 26 additions and 30 deletions

View file

@ -44,7 +44,6 @@ import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.MacAddress;
import android.net.RouteInfo;
import android.net.RoutingCoordinatorManager;
import android.net.TetheredClient;
import android.net.TetheringManager;
import android.net.TetheringManager.TetheringRequest;
@ -72,6 +71,7 @@ import com.android.internal.util.State;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.NetdUtils;
import com.android.net.module.util.RoutingCoordinatorManager;
import com.android.net.module.util.SdkUtil.LateSdk;
import com.android.net.module.util.SharedLog;
import com.android.net.module.util.SyncStateMachine.StateInfo;

View file

@ -91,7 +91,6 @@ import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkInfo;
import android.net.RoutingCoordinatorManager;
import android.net.TetherStatesParcel;
import android.net.TetheredClient;
import android.net.TetheringCallbackStartedParcel;
@ -138,6 +137,7 @@ import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.HandlerUtils;
import com.android.net.module.util.NetdUtils;
import com.android.net.module.util.RoutingCoordinatorManager;
import com.android.net.module.util.SdkUtil.LateSdk;
import com.android.net.module.util.SharedLog;
import com.android.networkstack.apishim.common.BluetoothPanShim;

View file

@ -22,7 +22,6 @@ import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothPan;
import android.content.Context;
import android.net.INetd;
import android.net.RoutingCoordinatorManager;
import android.net.connectivity.ConnectivityInternalApiUtil;
import android.net.ip.IpServer;
import android.os.Build;
@ -36,6 +35,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.RoutingCoordinatorManager;
import com.android.net.module.util.SdkUtil.LateSdk;
import com.android.net.module.util.SharedLog;
import com.android.networkstack.apishim.BluetoothPanShimImpl;
@ -132,7 +132,8 @@ public abstract class TetheringDependencies {
public LateSdk<RoutingCoordinatorManager> getRoutingCoordinator(Context context) {
if (!SdkLevel.isAtLeastS()) return new LateSdk<>(null);
return new LateSdk<>(
ConnectivityInternalApiUtil.getRoutingCoordinatorManager(context));
new RoutingCoordinatorManager(
context, ConnectivityInternalApiUtil.getRoutingCoordinator(context)));
}
/**

View file

@ -73,7 +73,6 @@ import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.MacAddress;
import android.net.RouteInfo;
import android.net.RoutingCoordinatorManager;
import android.net.dhcp.DhcpServerCallbacks;
import android.net.dhcp.DhcpServingParamsParcel;
import android.net.dhcp.IDhcpEventCallbacks;
@ -91,6 +90,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.RoutingCoordinatorManager;
import com.android.net.module.util.SdkUtil.LateSdk;
import com.android.net.module.util.SharedLog;
import com.android.networkstack.tethering.BpfCoordinator;

View file

@ -142,7 +142,6 @@ import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.RouteInfo;
import android.net.RoutingCoordinatorManager;
import android.net.TetherStatesParcel;
import android.net.TetheredClient;
import android.net.TetheredClient.AddressInfo;
@ -191,6 +190,7 @@ import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.RoutingCoordinatorManager;
import com.android.net.module.util.SdkUtil.LateSdk;
import com.android.net.module.util.SharedLog;
import com.android.net.module.util.ip.IpNeighborMonitor;

View file

@ -330,7 +330,6 @@ java_library {
srcs: [
// Files listed here MUST all be annotated with @RequiresApi(Build.VERSION_CODES.S)
// or above as appropriate so that API checks are enforced for R+ users of this library
"src/android/net/RoutingCoordinatorManager.java",
"src/android/net/connectivity/ConnectivityInternalApiUtil.java",
],
visibility: [

View file

@ -6737,21 +6737,11 @@ public class ConnectivityManager {
}
}
private static final Object sRoutingCoordinatorManagerLock = new Object();
@GuardedBy("sRoutingCoordinatorManagerLock")
private static RoutingCoordinatorManager sRoutingCoordinatorManager = null;
/** @hide */
@RequiresApi(Build.VERSION_CODES.S)
public RoutingCoordinatorManager getRoutingCoordinatorManager() {
public IBinder getRoutingCoordinatorService() {
try {
synchronized (sRoutingCoordinatorManagerLock) {
if (null == sRoutingCoordinatorManager) {
sRoutingCoordinatorManager = new RoutingCoordinatorManager(mContext,
IRoutingCoordinator.Stub.asInterface(
mService.getRoutingCoordinatorService()));
}
return sRoutingCoordinatorManager;
}
return mService.getRoutingCoordinatorService();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}

View file

@ -18,7 +18,6 @@ package android.net.connectivity;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.RoutingCoordinatorManager;
import android.os.Build;
import android.os.IBinder;
@ -54,8 +53,8 @@ public class ConnectivityInternalApiUtil {
* @return an instance of the coordinator manager
*/
@RequiresApi(Build.VERSION_CODES.S)
public static RoutingCoordinatorManager getRoutingCoordinatorManager(Context ctx) {
public static IBinder getRoutingCoordinator(Context ctx) {
final ConnectivityManager cm = ctx.getSystemService(ConnectivityManager.class);
return cm.getRoutingCoordinatorManager();
return cm.getRoutingCoordinatorService();
}
}

View file

@ -329,6 +329,7 @@ import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.LinkPropertiesUtils.CompareOrUpdateResult;
import com.android.net.module.util.LinkPropertiesUtils.CompareResult;
import com.android.net.module.util.LocationPermissionChecker;
import com.android.net.module.util.RoutingCoordinatorService;
import com.android.net.module.util.PerUidCounter;
import com.android.net.module.util.PermissionUtils;
import com.android.net.module.util.TcUtils;
@ -368,7 +369,6 @@ import com.android.server.connectivity.PermissionMonitor;
import com.android.server.connectivity.ProfileNetworkPreferenceInfo;
import com.android.server.connectivity.ProxyTracker;
import com.android.server.connectivity.QosCallbackTracker;
import com.android.server.connectivity.RoutingCoordinatorService;
import com.android.server.connectivity.SatelliteAccessController;
import com.android.server.connectivity.UidRangeUtils;
import com.android.server.connectivity.VpnNetworkPreferenceInfo;

View file

@ -0,0 +1,99 @@
/*
* Copyright (C) 2023 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.net.module.util;
import android.net.RouteInfo;
/** @hide */
// TODO: b/350630377 - This @Descriptor annotation workaround is to prevent the DESCRIPTOR from
// being jarjared which changes the DESCRIPTOR and casues "java.lang.SecurityException: Binder
// invocation to an incorrect interface" when calling the IPC.
@Descriptor("value=no.jarjar.com.android.net.module.util.IRoutingCoordinator")
interface IRoutingCoordinator {
/**
* Add a route for specific network
*
* @param netId the network to add the route to
* @param route the route to add
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
void addRoute(int netId, in RouteInfo route);
/**
* Remove a route for specific network
*
* @param netId the network to remove the route from
* @param route the route to remove
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
void removeRoute(int netId, in RouteInfo route);
/**
* Update a route for specific network
*
* @param netId the network to update the route for
* @param route parcelable with route information
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
void updateRoute(int netId, in RouteInfo route);
/**
* Adds an interface to a network. The interface must not be assigned to any network, including
* the specified network.
*
* @param netId the network to add the interface to.
* @param iface the name of the interface to add.
*
* @throws ServiceSpecificException in case of failure, with an error code corresponding to the
* unix errno.
*/
void addInterfaceToNetwork(int netId, in String iface);
/**
* Removes an interface from a network. The interface must be assigned to the specified network.
*
* @param netId the network to remove the interface from.
* @param iface the name of the interface to remove.
*
* @throws ServiceSpecificException in case of failure, with an error code corresponding to the
* unix errno.
*/
void removeInterfaceFromNetwork(int netId, in String iface);
/**
* Add forwarding ip rule
*
* @param fromIface interface name to add forwarding ip rule
* @param toIface interface name to add forwarding ip rule
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
void addInterfaceForward(in String fromIface, in String toIface);
/**
* Remove forwarding ip rule
*
* @param fromIface interface name to remove forwarding ip rule
* @param toIface interface name to remove forwarding ip rule
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
void removeInterfaceForward(in String fromIface, in String toIface);
}

View file

@ -0,0 +1,160 @@
/*
* Copyright (C) 2023 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.net.module.util;
import android.content.Context;
import android.net.RouteInfo;
import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
/**
* A manager class for talking to the routing coordinator service.
*
* This class should only be used by the connectivity and tethering module. This is enforced
* by the build rules. Do not change build rules to gain access to this class from elsewhere.
* @hide
*/
@RequiresApi(Build.VERSION_CODES.S)
public class RoutingCoordinatorManager {
@NonNull final Context mContext;
@NonNull final IRoutingCoordinator mService;
public RoutingCoordinatorManager(@NonNull final Context context,
@NonNull final IBinder binder) {
mContext = context;
mService = IRoutingCoordinator.Stub.asInterface(binder);
}
/**
* Add a route for specific network
*
* @param netId the network to add the route to
* @param route the route to add
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
public void addRoute(final int netId, final RouteInfo route) {
try {
mService.addRoute(netId, route);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Remove a route for specific network
*
* @param netId the network to remove the route from
* @param route the route to remove
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
public void removeRoute(final int netId, final RouteInfo route) {
try {
mService.removeRoute(netId, route);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Update a route for specific network
*
* @param netId the network to update the route for
* @param route parcelable with route information
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
public void updateRoute(final int netId, final RouteInfo route) {
try {
mService.updateRoute(netId, route);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Adds an interface to a network. The interface must not be assigned to any network, including
* the specified network.
*
* @param netId the network to add the interface to.
* @param iface the name of the interface to add.
*
* @throws ServiceSpecificException in case of failure, with an error code corresponding to the
* unix errno.
*/
public void addInterfaceToNetwork(final int netId, final String iface) {
try {
mService.addInterfaceToNetwork(netId, iface);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Removes an interface from a network. The interface must be assigned to the specified network.
*
* @param netId the network to remove the interface from.
* @param iface the name of the interface to remove.
*
* @throws ServiceSpecificException in case of failure, with an error code corresponding to the
* unix errno.
*/
public void removeInterfaceFromNetwork(final int netId, final String iface) {
try {
mService.removeInterfaceFromNetwork(netId, iface);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Add forwarding ip rule
*
* @param fromIface interface name to add forwarding ip rule
* @param toIface interface name to add forwarding ip rule
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
public void addInterfaceForward(final String fromIface, final String toIface) {
try {
mService.addInterfaceForward(fromIface, toIface);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Remove forwarding ip rule
*
* @param fromIface interface name to remove forwarding ip rule
* @param toIface interface name to remove forwarding ip rule
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
public void removeInterfaceForward(final String fromIface, final String toIface) {
try {
mService.removeInterfaceForward(fromIface, toIface);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}

View file

@ -0,0 +1,228 @@
/*
* Copyright (C) 2023 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.net.module.util;
import static com.android.net.module.util.NetdUtils.toRouteInfoParcel;
import android.annotation.NonNull;
import android.net.INetd;
import android.net.RouteInfo;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.util.ArraySet;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import java.util.Objects;
/**
* Class to coordinate routing across multiple clients.
*
* At present this is just a wrapper for netd methods, but it will be used to host some more
* coordination logic in the near future. It can be used to pull up some of the routing logic
* from netd into Java land.
*
* Note that usage of this class is not thread-safe. Clients are responsible for their own
* synchronization.
*/
public class RoutingCoordinatorService extends IRoutingCoordinator.Stub {
private static final String TAG = RoutingCoordinatorService.class.getSimpleName();
private final INetd mNetd;
public RoutingCoordinatorService(@NonNull INetd netd) {
mNetd = netd;
}
/**
* Add a route for specific network
*
* @param netId the network to add the route to
* @param route the route to add
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
@Override
public void addRoute(final int netId, final RouteInfo route)
throws ServiceSpecificException, RemoteException {
mNetd.networkAddRouteParcel(netId, toRouteInfoParcel(route));
}
/**
* Remove a route for specific network
*
* @param netId the network to remove the route from
* @param route the route to remove
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
@Override
public void removeRoute(final int netId, final RouteInfo route)
throws ServiceSpecificException, RemoteException {
mNetd.networkRemoveRouteParcel(netId, toRouteInfoParcel(route));
}
/**
* Update a route for specific network
*
* @param netId the network to update the route for
* @param route parcelable with route information
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
@Override
public void updateRoute(final int netId, final RouteInfo route)
throws ServiceSpecificException, RemoteException {
mNetd.networkUpdateRouteParcel(netId, toRouteInfoParcel(route));
}
/**
* Adds an interface to a network. The interface must not be assigned to any network, including
* the specified network.
*
* @param netId the network to add the interface to.
* @param iface the name of the interface to add.
*
* @throws ServiceSpecificException in case of failure, with an error code corresponding to the
* unix errno.
*/
@Override
public void addInterfaceToNetwork(final int netId, final String iface)
throws ServiceSpecificException, RemoteException {
mNetd.networkAddInterface(netId, iface);
}
/**
* Removes an interface from a network. The interface must be assigned to the specified network.
*
* @param netId the network to remove the interface from.
* @param iface the name of the interface to remove.
*
* @throws ServiceSpecificException in case of failure, with an error code corresponding to the
* unix errno.
*/
@Override
public void removeInterfaceFromNetwork(final int netId, final String iface)
throws ServiceSpecificException, RemoteException {
mNetd.networkRemoveInterface(netId, iface);
}
private final Object mIfacesLock = new Object();
private static final class ForwardingPair {
@NonNull public final String fromIface;
@NonNull public final String toIface;
ForwardingPair(@NonNull final String fromIface, @NonNull final String toIface) {
this.fromIface = fromIface;
this.toIface = toIface;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (!(o instanceof ForwardingPair)) return false;
final ForwardingPair that = (ForwardingPair) o;
return fromIface.equals(that.fromIface) && toIface.equals(that.toIface);
}
@Override
public int hashCode() {
int result = fromIface.hashCode();
result = 2 * result + toIface.hashCode();
return result;
}
}
@GuardedBy("mIfacesLock")
private final ArraySet<ForwardingPair> mForwardedInterfaces = new ArraySet<>();
/**
* Add forwarding ip rule
*
* @param fromIface interface name to add forwarding ip rule
* @param toIface interface name to add forwarding ip rule
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
public void addInterfaceForward(final String fromIface, final String toIface)
throws ServiceSpecificException, RemoteException {
Objects.requireNonNull(fromIface);
Objects.requireNonNull(toIface);
Log.i(TAG, "Adding interface forward " + fromIface + " → " + toIface);
synchronized (mIfacesLock) {
if (mForwardedInterfaces.size() == 0) {
mNetd.ipfwdEnableForwarding("RoutingCoordinator");
}
final ForwardingPair fwp = new ForwardingPair(fromIface, toIface);
if (mForwardedInterfaces.contains(fwp)) {
// TODO: remove if no reports are observed from the below log
Log.wtf(TAG, "Forward already exists between ifaces "
+ fromIface + " → " + toIface);
}
mForwardedInterfaces.add(fwp);
// Enables NAT for v4 and filters packets from unknown interfaces
mNetd.tetherAddForward(fromIface, toIface);
mNetd.ipfwdAddInterfaceForward(fromIface, toIface);
}
}
/**
* Remove forwarding ip rule
*
* @param fromIface interface name to remove forwarding ip rule
* @param toIface interface name to remove forwarding ip rule
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
public void removeInterfaceForward(final String fromIface, final String toIface)
throws ServiceSpecificException, RemoteException {
Objects.requireNonNull(fromIface);
Objects.requireNonNull(toIface);
Log.i(TAG, "Removing interface forward " + fromIface + " → " + toIface);
synchronized (mIfacesLock) {
final ForwardingPair fwp = new ForwardingPair(fromIface, toIface);
if (!mForwardedInterfaces.contains(fwp)) {
// This can happen when an upstream was unregisteredAfterReplacement. The forward
// is removed immediately when the upstream is destroyed, but later when the
// network actually disconnects CS does not know that and it asks for removal
// again.
// This can also happen if the network was destroyed before being set as an
// upstream, because then CS does not set up the forward rules seeing how the
// interface was removed anyway.
// Either way, this is benign.
Log.i(TAG, "No forward set up between interfaces " + fromIface + " → " + toIface);
return;
}
mForwardedInterfaces.remove(fwp);
try {
mNetd.ipfwdRemoveInterfaceForward(fromIface, toIface);
} catch (RemoteException | ServiceSpecificException e) {
Log.e(TAG, "Exception in ipfwdRemoveInterfaceForward", e);
}
try {
mNetd.tetherRemoveForward(fromIface, toIface);
} catch (RemoteException | ServiceSpecificException e) {
Log.e(TAG, "Exception in tetherRemoveForward", e);
}
if (mForwardedInterfaces.size() == 0) {
mNetd.ipfwdDisableForwarding("RoutingCoordinator");
}
}
}
}

View file

@ -27,6 +27,7 @@ android_library {
"net-utils-device-common-ip",
"net-utils-device-common-struct-base",
"net-utils-device-common-wear",
"net-utils-service-connectivity",
],
libs: [
"android.test.runner",

View file

@ -0,0 +1,73 @@
/*
* Copyright (C) 2023 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.net.module.util
import android.net.INetd
import android.os.Build
import android.util.Log
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.tryTest
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.test.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.mock
@RunWith(DevSdkIgnoreRunner::class)
@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
class RoutingCoordinatorServiceTest {
val mNetd = mock(INetd::class.java)
val mService = RoutingCoordinatorService(mNetd)
@Test
fun testInterfaceForward() {
val inOrder = inOrder(mNetd)
mService.addInterfaceForward("from1", "to1")
inOrder.verify(mNetd).ipfwdEnableForwarding(any())
inOrder.verify(mNetd).tetherAddForward("from1", "to1")
inOrder.verify(mNetd).ipfwdAddInterfaceForward("from1", "to1")
mService.addInterfaceForward("from2", "to1")
inOrder.verify(mNetd).tetherAddForward("from2", "to1")
inOrder.verify(mNetd).ipfwdAddInterfaceForward("from2", "to1")
val hasFailed = AtomicBoolean(false)
val prevHandler = Log.setWtfHandler { tag, what, system ->
hasFailed.set(true)
}
tryTest {
mService.addInterfaceForward("from2", "to1")
assertTrue(hasFailed.get())
} cleanup {
Log.setWtfHandler(prevHandler)
}
mService.removeInterfaceForward("from1", "to1")
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward("from1", "to1")
inOrder.verify(mNetd).tetherRemoveForward("from1", "to1")
mService.removeInterfaceForward("from2", "to1")
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward("from2", "to1")
inOrder.verify(mNetd).tetherRemoveForward("from2", "to1")
inOrder.verify(mNetd).ipfwdDisableForwarding(any())
}
}