Merge changes from topic "tetherlocalagent" into main am: 0b7d710ba0
Original change: https://android-review.googlesource.com/c/platform/packages/modules/Connectivity/+/3421099
Change-Id: I874999307ccf6eba6ad7af91db7f1137e159fac5
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index 70a3442..a651b1b 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -31,6 +31,7 @@
import static android.net.TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_INTERNAL_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL;
import static android.net.TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
import static android.net.TetheringManager.TetheringRequest.checkStaticAddressConfiguration;
@@ -40,19 +41,25 @@
import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH;
+import static com.android.networkstack.tethering.TetheringConfiguration.TETHERING_LOCAL_NETWORK_AGENT;
import static com.android.networkstack.tethering.TetheringConfiguration.USE_SYNC_SM;
import static com.android.networkstack.tethering.util.PrefixUtils.asIpPrefix;
import static com.android.networkstack.tethering.util.TetheringMessageBase.BASE_IPSERVER;
+import static com.android.networkstack.tethering.util.TetheringUtils.getTransportTypeForTetherableType;
+import android.annotation.SuppressLint;
+import android.content.Context;
import android.net.INetd;
import android.net.INetworkStackStatusCallback;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.MacAddress;
+import android.net.NetworkAgent;
import android.net.RouteInfo;
import android.net.TetheredClient;
import android.net.TetheringManager.TetheringRequest;
+import android.net.connectivity.ConnectivityInternalApiUtil;
import android.net.dhcp.DhcpLeaseParcelable;
import android.net.dhcp.DhcpServerCallbacks;
import android.net.dhcp.DhcpServingParamsParcel;
@@ -60,7 +67,9 @@
import android.net.dhcp.IDhcpEventCallbacks;
import android.net.dhcp.IDhcpServer;
import android.net.ip.RouterAdvertisementDaemon.RaParams;
+import android.os.Build;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
@@ -70,11 +79,13 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.MessageUtils;
import com.android.internal.util.State;
import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.DeviceConfigUtils;
import com.android.net.module.util.IIpv4PrefixRequest;
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.NetdUtils;
@@ -204,6 +215,23 @@
/** Create a DhcpServer instance to be used by IpServer. */
public abstract void makeDhcpServer(String ifName, DhcpServingParamsParcel params,
DhcpServerCallbacks cb);
+
+ /**
+ * @see DeviceConfigUtils#isTetheringFeatureEnabled
+ */
+ public boolean isFeatureEnabled(Context context, String name) {
+ return DeviceConfigUtils.isTetheringFeatureEnabled(context, name);
+ }
+
+ /** Create a NetworkAgent instance to be used by IpServer. */
+ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @SuppressLint("NewApi")
+ public NetworkAgent makeNetworkAgent(
+ @NonNull Context context, @NonNull Looper looper, @NonNull String logTag,
+ int interfaceType, @NonNull LinkProperties lp) {
+ return ConnectivityInternalApiUtil.buildTetheringNetworkAgent(
+ context, looper, logTag, getTransportTypeForTetherableType(interfaceType), lp);
+ }
}
// request from the user that it wants to tether
@@ -304,16 +332,28 @@
private final TetheringMetrics mTetheringMetrics;
private final Handler mHandler;
+ private final Context mContext;
+
+ private final boolean mSupportLocalAgent;
+
+ // This will be null if the TetheredState is not entered or feature not supported.
+ // This will be only accessed from the IpServer handler thread.
+ private NetworkAgent mTetheringAgent;
+
+ private static boolean everRegistered(@NonNull NetworkAgent agent) {
+ return agent.getNetwork() != null;
+ }
// TODO: Add a dependency object to pass the data members or variables from the tethering
// object. It helps to reduce the arguments of the constructor.
public IpServer(
- String ifaceName, Handler handler, int interfaceType, SharedLog log,
- INetd netd, @NonNull BpfCoordinator bpfCoordinator,
+ String ifaceName, @NonNull Context context, Handler handler, int interfaceType,
+ SharedLog log, INetd netd, @NonNull BpfCoordinator bpfCoordinator,
RoutingCoordinatorManager routingCoordinatorManager, Callback callback,
TetheringConfiguration config,
TetheringMetrics tetheringMetrics, Dependencies deps) {
super(ifaceName, USE_SYNC_SM ? null : handler.getLooper());
+ mContext = Objects.requireNonNull(context);
mHandler = handler;
mLog = log.forSubComponent(ifaceName);
mNetd = netd;
@@ -339,6 +379,10 @@
mLastError = TETHER_ERROR_NO_ERROR;
mServingMode = STATE_AVAILABLE;
+ // Tethering network agent is supported on V+, and will be rolled out gradually.
+ mSupportLocalAgent = SdkLevel.isAtLeastV()
+ && mDeps.isFeatureEnabled(mContext, TETHERING_LOCAL_NETWORK_AGENT);
+
mInitialState = new InitialState();
mLocalHotspotState = new LocalHotspotState();
mTetheredState = new TetheredState();
@@ -787,6 +831,7 @@
//
// TODO: Evaluate using a data structure than is more directly suited to
// communicating only the relevant information.
+ @SuppressLint("NewApi")
private void updateUpstreamIPv6LinkProperties(LinkProperties v6only, int ttlAdjustment) {
if (mRaDaemon == null) return;
@@ -847,8 +892,7 @@
}
private void removeRoutesFromNetwork(int netId, @NonNull final List<RouteInfo> toBeRemoved) {
- final int removalFailures = NetdUtils.removeRoutesFromNetwork(
- mNetd, netId, toBeRemoved);
+ final int removalFailures = NetdUtils.removeRoutesFromNetwork(mNetd, netId, toBeRemoved);
if (removalFailures > 0) {
mLog.e("Failed to remove " + removalFailures
+ " IPv6 routes from network " + netId + ".");
@@ -900,7 +944,9 @@
if (!deprecatedPrefixes.isEmpty()) {
final List<RouteInfo> routesToBeRemoved =
getLocalRoutesFor(mIfaceName, deprecatedPrefixes);
- removeRoutesFromNetwork(LOCAL_NET_ID, routesToBeRemoved);
+ if (mTetheringAgent == null) {
+ removeRoutesFromNetwork(LOCAL_NET_ID, routesToBeRemoved);
+ }
for (RouteInfo route : routesToBeRemoved) mLinkProperties.removeRoute(route);
}
@@ -914,7 +960,9 @@
if (!addedPrefixes.isEmpty()) {
final List<RouteInfo> routesToBeAdded =
getLocalRoutesFor(mIfaceName, addedPrefixes);
- addRoutesToNetwork(LOCAL_NET_ID, routesToBeAdded);
+ if (mTetheringAgent == null) {
+ addRoutesToNetwork(LOCAL_NET_ID, routesToBeAdded);
+ }
for (RouteInfo route : routesToBeAdded) mLinkProperties.addRoute(route);
}
}
@@ -1112,7 +1160,26 @@
return CONNECTIVITY_SCOPE_LOCAL;
}
+ @SuppressLint("NewApi")
private void startServingInterface() {
+ // TODO: Enable Network Agent for Wifi P2P Group Owner mode when Network Agent
+ // for Group Client mode is supported.
+ if (mSupportLocalAgent && getScope() == CONNECTIVITY_SCOPE_GLOBAL) {
+ try {
+ mTetheringAgent = mDeps.makeNetworkAgent(mContext, Looper.myLooper(), TAG,
+ mInterfaceType, mLinkProperties);
+ // Entering CONNECTING state, the ConnectivityService will create the
+ // native network.
+ mTetheringAgent.register();
+ } catch (RuntimeException e) {
+ mLog.e("Error Creating Local Network", e);
+ // If an exception occurs during the creation or registration of the
+ // NetworkAgent, it typically indicates a problem with the system services.
+ mLastError = TETHER_ERROR_SERVICE_UNAVAIL;
+ return;
+ }
+ }
+
if (!startIPv4(getScope())) {
mLastError = TETHER_ERROR_IFACE_CFG_ERROR;
return;
@@ -1122,14 +1189,16 @@
// Enable IPv6, disable accepting RA, etc. See TetherController::tetherInterface()
// for more detail.
mNetd.tetherInterfaceAdd(mIfaceName);
- NetdUtils.networkAddInterface(mNetd, LOCAL_NET_ID, mIfaceName,
- 20 /* maxAttempts */, 50 /* pollingIntervalMs */);
- // Activate a route to dest and IPv6 link local.
- NetdUtils.modifyRoute(mNetd, NetdUtils.ModifyOperation.ADD, LOCAL_NET_ID,
- new RouteInfo(asIpPrefix(mIpv4Address), null, mIfaceName, RTN_UNICAST));
- NetdUtils.modifyRoute(mNetd, NetdUtils.ModifyOperation.ADD, LOCAL_NET_ID,
- new RouteInfo(new IpPrefix("fe80::/64"), null, mIfaceName,
- RTN_UNICAST));
+ if (mTetheringAgent == null) {
+ NetdUtils.networkAddInterface(mNetd, LOCAL_NET_ID, mIfaceName,
+ 20 /* maxAttempts */, 50 /* pollingIntervalMs */);
+ // Activate a route to dest and IPv6 link local.
+ NetdUtils.modifyRoute(mNetd, NetdUtils.ModifyOperation.ADD, LOCAL_NET_ID,
+ new RouteInfo(asIpPrefix(mIpv4Address), null, mIfaceName, RTN_UNICAST));
+ NetdUtils.modifyRoute(mNetd, NetdUtils.ModifyOperation.ADD, LOCAL_NET_ID,
+ new RouteInfo(new IpPrefix("fe80::/64"), null, mIfaceName,
+ RTN_UNICAST));
+ }
} catch (RemoteException | ServiceSpecificException | IllegalStateException e) {
mLog.e("Error Tethering", e);
mLastError = TETHER_ERROR_TETHER_IFACE_ERROR;
@@ -1141,9 +1210,17 @@
// TODO: Make this a fatal error once Bluetooth IPv6 is sorted.
return;
}
+
+ if (mTetheringAgent != null && everRegistered(mTetheringAgent)) {
+ mTetheringAgent.sendLinkProperties(mLinkProperties);
+ // Mark it connected to notify the applications for
+ // the network availability.
+ mTetheringAgent.markConnected();
+ }
}
@Override
+ @SuppressLint("NewApi")
public void exit() {
// Note that at this point, we're leaving the tethered state. We can fail any
// of these operations, but it doesn't really change that we have to try them
@@ -1155,7 +1232,9 @@
try {
mNetd.tetherInterfaceRemove(mIfaceName);
} finally {
- mNetd.networkRemoveInterface(LOCAL_NET_ID, mIfaceName);
+ if (mTetheringAgent == null) {
+ mNetd.networkRemoveInterface(LOCAL_NET_ID, mIfaceName);
+ }
}
} catch (RemoteException | ServiceSpecificException e) {
mLastError = TETHER_ERROR_UNTETHER_IFACE_ERROR;
@@ -1165,6 +1244,11 @@
stopIPv4();
mBpfCoordinator.removeIpServer(IpServer.this);
+ if (mTetheringAgent != null && everRegistered(mTetheringAgent)) {
+ mTetheringAgent.unregister();
+ mTetheringAgent = null;
+ }
+
resetLinkProperties();
mTetheringMetrics.updateErrorCode(mInterfaceType, mLastError);
@@ -1184,6 +1268,12 @@
break;
case CMD_IPV6_TETHER_UPDATE:
updateUpstreamIPv6LinkProperties((LinkProperties) message.obj, message.arg1);
+ // Sends update to the NetworkAgent.
+ // TODO: Refactor the callers of sendLinkProperties()
+ // and move these code into sendLinkProperties().
+ if (mTetheringAgent != null && everRegistered(mTetheringAgent)) {
+ mTetheringAgent.sendLinkProperties(mLinkProperties);
+ }
sendLinkProperties();
break;
case CMD_IP_FORWARDING_ENABLE_ERROR:
@@ -1236,13 +1326,17 @@
// Remove deprecated routes from downstream network.
final List<RouteInfo> routesToBeRemoved =
List.of(getDirectConnectedRoute(deprecatedLinkAddress));
- removeRoutesFromNetwork(LOCAL_NET_ID, routesToBeRemoved);
+ if (mTetheringAgent == null) {
+ removeRoutesFromNetwork(LOCAL_NET_ID, routesToBeRemoved);
+ }
for (RouteInfo route : routesToBeRemoved) mLinkProperties.removeRoute(route);
mLinkProperties.removeLinkAddress(deprecatedLinkAddress);
// Add new routes to downstream network.
final List<RouteInfo> routesToBeAdded = List.of(getDirectConnectedRoute(mIpv4Address));
- addRoutesToNetwork(LOCAL_NET_ID, routesToBeAdded);
+ if (mTetheringAgent == null) {
+ addRoutesToNetwork(LOCAL_NET_ID, routesToBeAdded);
+ }
for (RouteInfo route : routesToBeAdded) mLinkProperties.addRoute(route);
mLinkProperties.addLinkAddress(mIpv4Address);
@@ -1255,6 +1349,10 @@
mLog.e("Failed to update local DNS caching server");
return;
}
+ // Sends update to the NetworkAgent.
+ if (mTetheringAgent != null && everRegistered(mTetheringAgent)) {
+ mTetheringAgent.sendLinkProperties(mLinkProperties);
+ }
sendLinkProperties();
// Notify DHCP server that new prefix/route has been applied on IpServer.
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 0cf008b..0730639 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -3105,7 +3105,7 @@
mLog.i("adding IpServer for: " + iface);
final TetherState tetherState = new TetherState(
- new IpServer(iface, mHandler, interfaceType, mLog, mNetd, mBpfCoordinator,
+ new IpServer(iface, mContext, mHandler, interfaceType, mLog, mNetd, mBpfCoordinator,
mRoutingCoordinator, new ControlCallback(), mConfig, mTetheringMetrics,
mDeps.makeIpServerDependencies()), isNcm);
mTetherStates.put(iface, tetherState);
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
index 5f0e5d0..e2609e7 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
@@ -148,6 +148,12 @@
*/
public static final String TETHER_ACTIVE_SESSIONS_METRICS = "tether_active_sessions_metrics";
+ /**
+ * A feature flag to control whether the tethering local network agent should be enabled.
+ * Disabled by default.
+ */
+ public static final String TETHERING_LOCAL_NETWORK_AGENT = "tethering_local_network_agent";
+
public final String[] tetherableUsbRegexs;
public final String[] tetherableWifiRegexs;
public final String[] tetherableWigigRegexs;
diff --git a/Tethering/src/com/android/networkstack/tethering/util/TetheringUtils.java b/Tethering/src/com/android/networkstack/tethering/util/TetheringUtils.java
index 79e6e16..9392ae8 100644
--- a/Tethering/src/com/android/networkstack/tethering/util/TetheringUtils.java
+++ b/Tethering/src/com/android/networkstack/tethering/util/TetheringUtils.java
@@ -15,8 +15,20 @@
*/
package com.android.networkstack.tethering.util;
+import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.NetworkCapabilities.TRANSPORT_USB;
import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL;
import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
+import static android.net.TetheringManager.TETHERING_BLUETOOTH;
+import static android.net.TetheringManager.TETHERING_ETHERNET;
+import static android.net.TetheringManager.TETHERING_NCM;
+import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_VIRTUAL;
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHERING_WIFI_P2P;
+import static android.net.TetheringManager.TETHERING_WIGIG;
import android.net.TetherStatsParcel;
import android.net.TetheringManager.TetheringRequest;
@@ -207,4 +219,30 @@
request.getParcel().connectivityScope = CONNECTIVITY_SCOPE_GLOBAL;
return request;
}
+
+ /**
+ * Returns the transport type for the given interface type.
+ *
+ * @param interfaceType The interface type.
+ * @return The transport type.
+ * @throws IllegalArgumentException if the interface type is invalid.
+ */
+ public static int getTransportTypeForTetherableType(int interfaceType) {
+ switch (interfaceType) {
+ case TETHERING_WIFI:
+ case TETHERING_WIGIG:
+ case TETHERING_WIFI_P2P:
+ return TRANSPORT_WIFI;
+ case TETHERING_USB:
+ case TETHERING_NCM:
+ return TRANSPORT_USB;
+ case TETHERING_BLUETOOTH:
+ return TRANSPORT_BLUETOOTH;
+ case TETHERING_ETHERNET:
+ case TETHERING_VIRTUAL: // For virtual machines.
+ return TRANSPORT_ETHERNET;
+ default:
+ throw new IllegalArgumentException("Invalid interface type: " + interfaceType);
+ }
+ }
}
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 3a5728e..11442ed 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -18,6 +18,8 @@
import static android.Manifest.permission.DUMP;
import static android.net.InetAddresses.parseNumericAddress;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL;
import static android.net.TetheringManager.TETHERING_ETHERNET;
@@ -27,6 +29,7 @@
import static android.net.TetheringTester.buildUdpPacket;
import static android.net.TetheringTester.isExpectedIcmpPacket;
import static android.net.TetheringTester.isExpectedUdpDnsPacket;
+import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
import static android.system.OsConstants.ICMP_ECHO;
import static android.system.OsConstants.ICMP_ECHOREPLY;
import static android.system.OsConstants.IPPROTO_UDP;
@@ -78,12 +81,16 @@
import com.android.net.module.util.bpf.TetherStatsValue;
import com.android.net.module.util.structs.Ipv4Header;
import com.android.net.module.util.structs.UdpHeader;
+import com.android.testutils.AutoReleaseNetworkCallbackRule;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DeviceConfigRule;
import com.android.testutils.DeviceInfoUtils;
import com.android.testutils.DumpTestUtils;
import com.android.testutils.NetworkStackModuleTest;
import com.android.testutils.PollPacketReader;
+import com.android.testutils.RecorderCallback.CallbackEntry;
+import com.android.testutils.TestableNetworkCallback;
import org.junit.After;
import org.junit.Rule;
@@ -111,6 +118,12 @@
public class EthernetTetheringTest extends EthernetTetheringTestBase {
@Rule
public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+ // For manipulating feature flag before and after testing.
+ @Rule
+ public final DeviceConfigRule mDeviceConfigRule = new DeviceConfigRule();
+ @Rule
+ public final AutoReleaseNetworkCallbackRule
+ mNetworkCallbackRule = new AutoReleaseNetworkCallbackRule();
private static final String TAG = EthernetTetheringTest.class.getSimpleName();
@@ -200,6 +213,9 @@
(byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04 /* Address: 1.2.3.4 */
};
+ // Shamelessly copied from TetheringConfiguration.
+ private static final String TETHERING_LOCAL_NETWORK_AGENT = "tethering_local_network_agent";
+
@After
public void tearDown() throws Exception {
super.tearDown();
@@ -1224,4 +1240,44 @@
maybeUnregisterTetheringEventCallback(tetheringEventCallback);
}
}
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ public void testLocalAgent_networkCallbacks() throws Exception {
+ mDeviceConfigRule.setConfig(NAMESPACE_TETHERING, TETHERING_LOCAL_NETWORK_AGENT, "1");
+ assumeFalse(isInterfaceForTetheringAvailable());
+ setIncludeTestInterfaces(true);
+
+ TestNetworkInterface downstreamIface = null;
+ MyTetheringEventCallback tetheringEventCallback = null;
+
+ final TestableNetworkCallback networkCallback = new TestableNetworkCallback();
+ final NetworkRequest networkRequest = new NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_LOCAL_NETWORK).build();
+ mNetworkCallbackRule.registerNetworkCallback(networkRequest, networkCallback);
+
+ try {
+ downstreamIface = createTestInterface();
+
+ final String iface = mTetheredInterfaceRequester.getInterface();
+ assertEquals("TetheredInterfaceCallback for unexpected interface",
+ downstreamIface.getInterfaceName(), iface);
+
+ final TetheringRequest request = new TetheringRequest.Builder(TETHERING_ETHERNET)
+ .setConnectivityScope(CONNECTIVITY_SCOPE_GLOBAL).build();
+ tetheringEventCallback = enableTethering(iface, request, null /* any upstream */);
+ tetheringEventCallback.awaitInterfaceTethered();
+
+ // Verify NetworkCallback works accordingly.
+ final Network network = networkCallback.expect(CallbackEntry.AVAILABLE).getNetwork();
+ final CallbackEntry.CapabilitiesChanged capEvent =
+ networkCallback.eventuallyExpect(CallbackEntry.NETWORK_CAPS_UPDATED);
+ assertEquals(network, capEvent.getNetwork());
+ assertTrue(capEvent.getCaps().hasTransport(TRANSPORT_ETHERNET));
+ assertTrue(capEvent.getCaps().hasCapability(NET_CAPABILITY_LOCAL_NETWORK));
+ } finally {
+ stopEthernetTethering(tetheringEventCallback);
+ maybeCloseTestInterface(downstreamIface);
+ }
+ }
}
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 280d2c5..dc90d68 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -28,6 +28,7 @@
import static android.net.TetheringManager.TETHERING_WIFI_P2P;
import static android.net.TetheringManager.TETHER_ERROR_ENABLE_FORWARDING_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL;
import static android.net.TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR;
import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
import static android.net.ip.IpServer.STATE_AVAILABLE;
@@ -37,7 +38,9 @@
import static android.net.ip.IpServer.getTetherableIpv6Prefixes;
import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
+import static com.android.modules.utils.build.SdkLevel.isAtLeastV;
import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
+import static com.android.networkstack.tethering.TetheringConfiguration.TETHERING_LOCAL_NETWORK_AGENT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -53,6 +56,7 @@
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
@@ -64,7 +68,7 @@
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
-import android.app.usage.NetworkStatsManager;
+import android.content.Context;
import android.net.INetd;
import android.net.InetAddresses;
import android.net.InterfaceConfigurationParcel;
@@ -72,6 +76,8 @@
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.MacAddress;
+import android.net.Network;
+import android.net.NetworkAgent;
import android.net.RouteInfo;
import android.net.TetheringManager.TetheringRequest;
import android.net.dhcp.DhcpServerCallbacks;
@@ -83,8 +89,10 @@
import android.os.Build;
import android.os.Handler;
import android.os.RemoteException;
+import android.os.ServiceSpecificException;
import android.os.test.TestLooper;
import android.text.TextUtils;
+import android.util.ArrayMap;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -100,6 +108,7 @@
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.com.android.testutils.SetFeatureFlagsRule;
import org.junit.Before;
import org.junit.Rule;
@@ -122,6 +131,16 @@
@Rule
public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+ final ArrayMap<String, Boolean> mFeatureFlags = new ArrayMap<>();
+ // This will set feature flags from @FeatureFlag annotations
+ // into the map before setUp() runs.
+ @Rule
+ public final SetFeatureFlagsRule mSetFeatureFlagsRule =
+ new SetFeatureFlagsRule((name, enabled) -> {
+ mFeatureFlags.put(name, enabled);
+ return null;
+ }, (name) -> mFeatureFlags.getOrDefault(name, false));
+
private static final String IFACE_NAME = "testnet1";
private static final String UPSTREAM_IFACE = "upstream0";
private static final String UPSTREAM_IFACE2 = "upstream1";
@@ -164,6 +183,7 @@
new LinkAddress("2001:db8:0:abcd::168/64"));
private static final Set<IpPrefix> UPSTREAM_PREFIXES2 = Set.of(
new IpPrefix("2001:db8:0:1234::/64"), new IpPrefix("2001:db8:0:abcd::/64"));
+ private static final int TEST_NET_ID = 123;
@Mock private INetd mNetd;
@Mock private IpServer.Callback mCallback;
@@ -173,10 +193,11 @@
@Mock private RouterAdvertisementDaemon mRaDaemon;
@Mock private IpServer.Dependencies mDependencies;
@Mock private RoutingCoordinatorManager mRoutingCoordinatorManager;
- @Mock private NetworkStatsManager mStatsManager;
@Mock private TetheringConfiguration mTetherConfig;
@Mock private TetheringMetrics mTetheringMetrics;
@Mock private BpfCoordinator mBpfCoordinator;
+ @Mock private Context mContext;
+ @Mock private NetworkAgent mNetworkAgent;
@Captor private ArgumentCaptor<DhcpServingParamsParcel> mDhcpParamsCaptor;
@@ -205,6 +226,18 @@
when(mDependencies.getInterfaceParams(UPSTREAM_IFACE)).thenReturn(UPSTREAM_IFACE_PARAMS);
when(mDependencies.getInterfaceParams(UPSTREAM_IFACE2)).thenReturn(UPSTREAM_IFACE_PARAMS2);
when(mDependencies.getInterfaceParams(IPSEC_IFACE)).thenReturn(IPSEC_IFACE_PARAMS);
+ doAnswer(
+ invocation -> mFeatureFlags.getOrDefault((String) invocation.getArgument(1), false)
+ ).when(mDependencies).isFeatureEnabled(any(), anyString());
+ if (isAtLeastV()) {
+ when(mDependencies.makeNetworkAgent(any(), any(), anyString(), anyInt(), any()))
+ .thenReturn(mNetworkAgent);
+ // Mock the returned network and modifying the status.
+ final Network network = mock(Network.class);
+ doReturn(TEST_NET_ID).when(network).getNetId();
+ doReturn(network).when(mNetworkAgent).register();
+ doReturn(network).when(mNetworkAgent).getNetwork();
+ }
mInterfaceConfiguration = new InterfaceConfigurationParcel();
mInterfaceConfiguration.flags = new String[0];
@@ -294,10 +327,9 @@
private IpServer createIpServer(final int interfaceType) {
mLooper = new TestLooper();
mHandler = new Handler(mLooper.getLooper());
- return new IpServer(IFACE_NAME, mHandler, interfaceType, mSharedLog, mNetd, mBpfCoordinator,
- mRoutingCoordinatorManager, mCallback, mTetherConfig,
+ return new IpServer(IFACE_NAME, mContext, mHandler, interfaceType, mSharedLog, mNetd,
+ mBpfCoordinator, mRoutingCoordinatorManager, mCallback, mTetherConfig,
mTetheringMetrics, mDependencies);
-
}
@Test
@@ -342,6 +374,10 @@
verifyNoMoreInteractions(mNetd, mCallback);
}
+ private boolean isTetheringNetworkAgentFeatureEnabled() {
+ return isAtLeastV() && mFeatureFlags.getOrDefault(TETHERING_LOCAL_NETWORK_AGENT, false);
+ }
+
@Test
public void canBeTetheredAsBluetooth() throws Exception {
initStateMachine(TETHERING_BLUETOOTH);
@@ -360,10 +396,16 @@
IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP)));
}
inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
- inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
- // One for ipv4 route, one for ipv6 link local route.
- inOrder.verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME),
- any(), any());
+ if (isTetheringNetworkAgentFeatureEnabled()) {
+ inOrder.verify(mNetd, never()).networkAddInterface(anyInt(), anyString());
+ inOrder.verify(mNetd, never())
+ .networkAddRoute(anyInt(), anyString(), anyString(), anyString());
+ } else {
+ inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
+ // One for ipv4 route, one for ipv6 link local route.
+ inOrder.verify(mNetd, times(2))
+ .networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME), any(), any());
+ }
inOrder.verify(mCallback).updateInterfaceState(
mIpServer, STATE_TETHERED, TETHER_ERROR_NO_ERROR);
inOrder.verify(mCallback).updateLinkProperties(
@@ -379,7 +421,11 @@
InOrder inOrder = inOrder(mCallback, mNetd, mRoutingCoordinatorManager);
inOrder.verify(mNetd).tetherApplyDnsInterfaces();
inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME);
- inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
+ if (isTetheringNetworkAgentFeatureEnabled()) {
+ inOrder.verify(mNetd, never()).networkRemoveInterface(anyInt(), anyString());
+ } else {
+ inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
+ }
// One is ipv4 address clear (set to 0.0.0.0), another is set interface down which only
// happen after T. Before T, the interface configuration control in bluetooth side.
if (isAtLeastT()) {
@@ -411,9 +457,15 @@
inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP)));
inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
- inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
- inOrder.verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME),
- any(), any());
+ if (isTetheringNetworkAgentFeatureEnabled()) {
+ inOrder.verify(mNetd, never()).networkAddInterface(anyInt(), anyString());
+ inOrder.verify(mNetd, never())
+ .networkAddRoute(anyInt(), anyString(), anyString(), anyString());
+ } else {
+ inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
+ inOrder.verify(mNetd, times(2))
+ .networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME), any(), any());
+ }
inOrder.verify(mCallback).updateInterfaceState(
mIpServer, STATE_TETHERED, TETHER_ERROR_NO_ERROR);
inOrder.verify(mCallback).updateLinkProperties(
@@ -592,7 +644,11 @@
inOrder.verify(mBpfCoordinator).clearAllIpv6Rules(mIpServer);
inOrder.verify(mNetd).tetherApplyDnsInterfaces();
inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME);
- inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
+ if (isTetheringNetworkAgentFeatureEnabled()) {
+ inOrder.verify(mNetd, never()).networkRemoveInterface(anyInt(), anyString());
+ } else {
+ inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
+ }
inOrder.verify(mNetd, times(isAtLeastT() ? 2 : 1)).interfaceSetCfg(
argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
inOrder.verify(mRoutingCoordinatorManager).releaseDownstream(any());
@@ -1058,6 +1114,162 @@
return true;
}
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @SetFeatureFlagsRule.FeatureFlag(name = TETHERING_LOCAL_NETWORK_AGENT)
+ @Test
+ public void testTetheringNetworkAgent_tetheringAgentEnabled() throws Exception {
+ doTestTetheringNetworkAgent(CONNECTIVITY_SCOPE_GLOBAL, true);
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @SetFeatureFlagsRule.FeatureFlag(name = TETHERING_LOCAL_NETWORK_AGENT, enabled = false)
+ @Test
+ public void testTetheringNetworkAgent_tetheringAgentDisabled() throws Exception {
+ doTestTetheringNetworkAgent(CONNECTIVITY_SCOPE_GLOBAL, false);
+ }
+
+ // Verify Tethering Network Agent feature doesn't affect Wi-fi P2P Group Owner although
+ // the code is mostly shared.
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @SetFeatureFlagsRule.FeatureFlag(name = TETHERING_LOCAL_NETWORK_AGENT)
+ @Test
+ public void testTetheringNetworkAgent_p2pGroupOwnerAgentDisabled() throws Exception {
+ doTestTetheringNetworkAgent(CONNECTIVITY_SCOPE_LOCAL, false);
+ }
+
+ private void doTestTetheringNetworkAgent(int scope, boolean expectAgentEnabled)
+ throws Exception {
+ initStateMachine(TETHERING_USB);
+
+ final InOrder inOrder = inOrder(mNetworkAgent, mNetd);
+ dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED,
+ 0, createMockTetheringRequest(scope));
+
+ inOrder.verify(mNetworkAgent, expectAgentEnabled ? times(1) : never()).register();
+ inOrder.verify(mNetd, times(1)).tetherInterfaceAdd(anyString());
+ if (expectAgentEnabled) {
+ inOrder.verify(mNetd, never()).networkAddInterface(anyInt(), anyString());
+ inOrder.verify(mNetd, never()).networkAddRoute(anyInt(), anyString(), any(), any());
+ inOrder.verify(mNetworkAgent, times(1)).sendLinkProperties(any());
+ inOrder.verify(mNetworkAgent, times(1)).markConnected();
+ } else {
+ inOrder.verify(mNetd, times(1)).networkAddInterface(anyInt(), anyString());
+ inOrder.verify(mNetd, times(2)).networkAddRoute(anyInt(), anyString(), any(), any());
+ inOrder.verify(mNetworkAgent, never()).sendLinkProperties(any());
+ inOrder.verify(mNetworkAgent, never()).markConnected();
+ }
+
+ dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
+ if (expectAgentEnabled) {
+ inOrder.verify(mNetworkAgent, times(1)).unregister();
+ inOrder.verify(mNetd, never()).networkRemoveInterface(anyInt(), anyString());
+ } else {
+ inOrder.verify(mNetworkAgent, never()).unregister();
+ inOrder.verify(mNetd, times(1)).networkRemoveInterface(anyInt(), anyString());
+ }
+ }
+
+ // Verify if the registration failed, tethering can be gracefully shutdown.
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @SetFeatureFlagsRule.FeatureFlag(name = TETHERING_LOCAL_NETWORK_AGENT)
+ @Test
+ public void testTetheringNetworkAgent_registerThrows() throws Exception {
+ initStateMachine(TETHERING_USB);
+
+ final InOrder inOrder = inOrder(mNetworkAgent, mNetd, mCallback);
+ doReturn(null).when(mNetworkAgent).getNetwork();
+ doThrow(IllegalStateException.class).when(mNetworkAgent).register();
+ dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED,
+ 0, createMockTetheringRequest(CONNECTIVITY_SCOPE_GLOBAL));
+
+ inOrder.verify(mNetworkAgent).register();
+ inOrder.verify(mNetd, never()).networkCreate(any());
+ inOrder.verify(mNetworkAgent, never()).sendLinkProperties(any());
+ inOrder.verify(mNetworkAgent, never()).markConnected();
+ inOrder.verify(mNetworkAgent, never()).unregister();
+ inOrder.verify(mNetd, never()).networkDestroy(anyInt());
+ inOrder.verify(mCallback).updateInterfaceState(
+ mIpServer, STATE_AVAILABLE, TETHER_ERROR_SERVICE_UNAVAIL);
+ }
+
+ // Verify if the network creation failed, tethering can be gracefully shutdown.
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @SetFeatureFlagsRule.FeatureFlag(name = TETHERING_LOCAL_NETWORK_AGENT)
+ @Test
+ public void testTetheringNetworkAgent_netdThrows() throws Exception {
+ initStateMachine(TETHERING_USB);
+
+ final InOrder inOrder = inOrder(mNetworkAgent, mNetd, mCallback);
+ doThrow(ServiceSpecificException.class).when(mNetd).tetherInterfaceAdd(any());
+ dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED,
+ 0, createMockTetheringRequest(CONNECTIVITY_SCOPE_GLOBAL));
+
+ inOrder.verify(mNetworkAgent).register();
+ inOrder.verify(mNetd, never()).networkCreate(any());
+ inOrder.verify(mNetworkAgent, never()).sendLinkProperties(any());
+ inOrder.verify(mNetworkAgent, never()).markConnected();
+ inOrder.verify(mNetworkAgent).unregister();
+ inOrder.verify(mNetd, never()).networkDestroy(anyInt());
+ inOrder.verify(mCallback).updateInterfaceState(
+ mIpServer, STATE_AVAILABLE, TETHER_ERROR_TETHER_IFACE_ERROR);
+ }
+
+ // Verify when IPv6 address update, set routes accordingly.
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @SetFeatureFlagsRule.FeatureFlag(name = TETHERING_LOCAL_NETWORK_AGENT)
+ @Test
+ public void testTetheringNetworkAgent_ipv6AddressUpdate() throws Exception {
+ initStateMachine(TETHERING_USB);
+
+ final InOrder inOrder = inOrder(mNetworkAgent, mNetd);
+ dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED,
+ 0, createMockTetheringRequest(CONNECTIVITY_SCOPE_GLOBAL));
+
+ inOrder.verify(mNetworkAgent).register();
+ inOrder.verify(mNetd, never()).networkCreate(any());
+ inOrder.verify(mNetd, never()).networkAddRoute(anyInt(), anyString(), any(), any());
+
+ // Ipv6 link local route won't show up in the LinkProperties, so just
+ // verify ipv4 route.
+ final ArgumentCaptor<LinkProperties> lpCaptor =
+ ArgumentCaptor.forClass(LinkProperties.class);
+ inOrder.verify(mNetworkAgent).sendLinkProperties(lpCaptor.capture());
+ final RouteInfo expectedIpv4Route = new RouteInfo(PrefixUtils.asIpPrefix(mTestAddress),
+ null, IFACE_NAME, RouteInfo.RTN_UNICAST);
+ assertRoutes(List.of(expectedIpv4Route), lpCaptor.getValue().getRoutes());
+ assertEquals(IFACE_NAME, lpCaptor.getValue().getInterfaceName());
+
+ inOrder.verify(mNetworkAgent).markConnected();
+
+ // Mock ipv4-only upstream show up.
+ dispatchTetherConnectionChanged(UPSTREAM_IFACE);
+ inOrder.verifyNoMoreInteractions();
+
+ // Verify LinkProperties is updated when IPv6 connectivity is available.
+ final LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName(UPSTREAM_IFACE);
+ lp.setLinkAddresses(UPSTREAM_ADDRESSES);
+ dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, -1);
+ inOrder.verify(mNetd, never()).networkAddRoute(anyInt(), anyString(), any(), any());
+ inOrder.verify(mNetworkAgent).sendLinkProperties(lpCaptor.capture());
+
+ // Expect one Ipv4 route, plus one Ipv6 route.
+ final RouteInfo expectedIpv6Route = new RouteInfo(UPSTREAM_PREFIXES.toArray(
+ new IpPrefix[0])[0], null, IFACE_NAME, RouteInfo.RTN_UNICAST);
+ assertRoutes(List.of(expectedIpv4Route, expectedIpv6Route),
+ lpCaptor.getValue().getRoutes());
+ assertEquals(IFACE_NAME, lpCaptor.getValue().getInterfaceName());
+
+ dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
+ inOrder.verify(mNetworkAgent).unregister();
+ inOrder.verify(mNetd, never()).networkDestroy(anyInt());
+ }
+
+ private void assertRoutes(List<RouteInfo> expectedRoutes, List<RouteInfo> actualRoutes) {
+ assertTrue("Expected Routes: " + expectedRoutes + ", but got: " + actualRoutes,
+ expectedRoutes.equals(actualRoutes));
+ }
+
@Test @IgnoreUpTo(Build.VERSION_CODES.R)
public void dadProxyUpdates() throws Exception {
InOrder inOrder = inOrder(mDadProxy);
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 0707e20..bbe15b4 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -75,6 +75,7 @@
import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
+import static com.android.modules.utils.build.SdkLevel.isAtLeastV;
import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH;
import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH;
@@ -83,6 +84,7 @@
import static com.android.networkstack.tethering.TestConnectivityManager.BROADCAST_FIRST;
import static com.android.networkstack.tethering.TestConnectivityManager.CALLBACKS_FIRST;
import static com.android.networkstack.tethering.Tethering.UserRestrictionActionListener;
+import static com.android.networkstack.tethering.TetheringConfiguration.TETHERING_LOCAL_NETWORK_AGENT;
import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_FORCE_USB_FUNCTIONS;
import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_USB_NCM_FUNCTION;
import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_USB_RNDIS_FUNCTION;
@@ -192,6 +194,7 @@
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.test.mock.MockContentResolver;
+import android.util.ArrayMap;
import android.util.ArraySet;
import androidx.annotation.NonNull;
@@ -219,6 +222,7 @@
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.MiscAsserts;
+import com.android.testutils.com.android.testutils.SetFeatureFlagsRule;
import org.junit.After;
import org.junit.Before;
@@ -250,6 +254,16 @@
public class TetheringTest {
@Rule public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+ final ArrayMap<String, Boolean> mFeatureFlags = new ArrayMap<>();
+ // This will set feature flags from @FeatureFlag annotations
+ // into the map before setUp() runs.
+ @Rule
+ public final SetFeatureFlagsRule mSetFeatureFlagsRule =
+ new SetFeatureFlagsRule((name, enabled) -> {
+ mFeatureFlags.put(name, enabled);
+ return null;
+ }, (name) -> mFeatureFlags.getOrDefault(name, false));
+
private static final int IFINDEX_OFFSET = 100;
private static final String TEST_MOBILE_IFNAME = "test_rmnet_data0";
@@ -461,6 +475,11 @@
public void setOnDhcpServerCreatedResult(final int result) {
mOnDhcpServerCreatedResult = result;
}
+
+ @Override
+ public boolean isFeatureEnabled(Context context, String name) {
+ return mFeatureFlags.getOrDefault(name, false);
+ }
}
public class MockTetheringDependencies extends TetheringDependencies {
@@ -954,12 +973,18 @@
verifyNoMoreInteractions(mCm);
}
- private void verifyInterfaceServingModeStarted(String ifname) throws Exception {
+ private void verifyInterfaceServingModeStarted(String ifname, boolean expectAgentEnabled)
+ throws Exception {
verify(mNetd).interfaceSetCfg(any(InterfaceConfigurationParcel.class));
verify(mNetd).tetherInterfaceAdd(ifname);
- verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, ifname);
- verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(ifname),
- anyString(), anyString());
+ if (expectAgentEnabled) {
+ verify(mNetd, never()).networkAddInterface(anyInt(), anyString());
+ verify(mNetd, never()).networkAddRoute(anyInt(), anyString(), anyString(), anyString());
+ } else {
+ verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, ifname);
+ verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(ifname),
+ anyString(), anyString());
+ }
}
private void verifyTetheringBroadcast(String ifname, String whichExtra) {
@@ -1061,10 +1086,18 @@
failingLocalOnlyHotspotLegacyApBroadcast(false);
}
- private void verifyStopHotpot() throws Exception {
+ private boolean isTetheringNetworkAgentFeatureEnabled() {
+ return isAtLeastV() && mFeatureFlags.getOrDefault(TETHERING_LOCAL_NETWORK_AGENT, false);
+ }
+
+ private void verifyStopHotpot(boolean isLocalOnly) throws Exception {
verify(mNetd).tetherApplyDnsInterfaces();
verify(mNetd).tetherInterfaceRemove(TEST_WLAN_IFNAME);
- verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, TEST_WLAN_IFNAME);
+ if (!isLocalOnly && isTetheringNetworkAgentFeatureEnabled()) {
+ verify(mNetd, never()).networkRemoveInterface(anyInt(), anyString());
+ } else {
+ verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, TEST_WLAN_IFNAME);
+ }
// interfaceSetCfg() called once for enabling and twice disabling IPv4.
verify(mNetd, times(3)).interfaceSetCfg(any(InterfaceConfigurationParcel.class));
verify(mNetd).tetherStop();
@@ -1083,7 +1116,8 @@
}
private void verifyStartHotspot(boolean isLocalOnly) throws Exception {
- verifyInterfaceServingModeStarted(TEST_WLAN_IFNAME);
+ final boolean expectAgentEnabled = !isLocalOnly && isTetheringNetworkAgentFeatureEnabled();
+ verifyInterfaceServingModeStarted(TEST_WLAN_IFNAME, expectAgentEnabled);
verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
verify(mWifiManager).updateInterfaceIpState(
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
@@ -1127,7 +1161,7 @@
mTethering.interfaceRemoved(TEST_WLAN_IFNAME);
mLooper.dispatchAll();
- verifyStopHotpot();
+ verifyStopHotpot(true /* isLocalOnly */);
}
/**
@@ -2073,7 +2107,7 @@
mTethering.interfaceRemoved(TEST_WLAN_IFNAME);
mLooper.dispatchAll();
- verifyStopHotpot();
+ verifyStopHotpot(false /* isLocalOnly */);
}
@Test
@@ -2218,9 +2252,14 @@
// code is refactored the two calls during shutdown will revert to one.
verify(mNetd, times(3)).interfaceSetCfg(argThat(p -> TEST_WLAN_IFNAME.equals(p.ifName)));
verify(mNetd, times(1)).tetherInterfaceAdd(TEST_WLAN_IFNAME);
- verify(mNetd, times(1)).networkAddInterface(INetd.LOCAL_NET_ID, TEST_WLAN_IFNAME);
- verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(TEST_WLAN_IFNAME),
- anyString(), anyString());
+ if (isTetheringNetworkAgentFeatureEnabled()) {
+ verify(mNetd, never()).networkAddInterface(anyInt(), anyString());
+ verify(mNetd, never()).networkAddRoute(anyInt(), anyString(), anyString(), anyString());
+ } else {
+ verify(mNetd, times(1)).networkAddInterface(INetd.LOCAL_NET_ID, TEST_WLAN_IFNAME);
+ verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(TEST_WLAN_IFNAME),
+ anyString(), anyString());
+ }
verify(mWifiManager).updateInterfaceIpState(
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
verify(mWifiManager).updateInterfaceIpState(
@@ -2239,7 +2278,11 @@
// so it can take down AP mode.
verify(mNetd, times(1)).tetherApplyDnsInterfaces();
verify(mNetd, times(1)).tetherInterfaceRemove(TEST_WLAN_IFNAME);
- verify(mNetd, times(1)).networkRemoveInterface(INetd.LOCAL_NET_ID, TEST_WLAN_IFNAME);
+ if (isTetheringNetworkAgentFeatureEnabled()) {
+ verify(mNetd, never()).networkRemoveInterface(anyInt(), anyString());
+ } else {
+ verify(mNetd, times(1)).networkRemoveInterface(INetd.LOCAL_NET_ID, TEST_WLAN_IFNAME);
+ }
verify(mWifiManager).updateInterfaceIpState(
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_CONFIGURATION_ERROR);
@@ -3100,7 +3143,7 @@
}
sendWifiP2pConnectionChanged(true, true, TEST_P2P_IFNAME);
- verifyInterfaceServingModeStarted(TEST_P2P_IFNAME);
+ verifyInterfaceServingModeStarted(TEST_P2P_IFNAME, false);
verifyTetheringBroadcast(TEST_P2P_IFNAME, EXTRA_AVAILABLE_TETHER);
verify(mNetd, times(1)).ipfwdEnableForwarding(TETHERING_NAME);
verify(mNetd, times(1)).tetherStartWithConfiguration(any());
@@ -4119,13 +4162,22 @@
&& assertContainsFlag(cfg.flags, INetd.IF_STATE_UP)));
}
verify(mNetd).tetherInterfaceAdd(TEST_BT_IFNAME);
- verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, TEST_BT_IFNAME);
- verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(TEST_BT_IFNAME),
- anyString(), anyString());
+ if (isTetheringNetworkAgentFeatureEnabled()) {
+ verify(mNetd, never()).networkAddInterface(anyInt(), anyString());
+ verify(mNetd, never()).networkAddRoute(anyInt(), anyString(), anyString(), anyString());
+ } else {
+ verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, TEST_BT_IFNAME);
+ verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(TEST_BT_IFNAME),
+ anyString(), anyString());
+ }
verify(mNetd).ipfwdEnableForwarding(TETHERING_NAME);
verify(mNetd).tetherStartWithConfiguration(any());
- verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(TEST_BT_IFNAME),
- anyString(), anyString());
+ if (isTetheringNetworkAgentFeatureEnabled()) {
+ verify(mNetd, never()).networkAddRoute(anyInt(), anyString(), anyString(), anyString());
+ } else {
+ verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(TEST_BT_IFNAME),
+ anyString(), anyString());
+ }
verifyNoMoreInteractions(mNetd);
reset(mNetd);
}
@@ -4140,7 +4192,11 @@
private void verifyNetdCommandForBtTearDown() throws Exception {
verify(mNetd).tetherApplyDnsInterfaces();
verify(mNetd).tetherInterfaceRemove(TEST_BT_IFNAME);
- verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, TEST_BT_IFNAME);
+ if (isTetheringNetworkAgentFeatureEnabled()) {
+ verify(mNetd, never()).networkRemoveInterface(anyInt(), anyString());
+ } else {
+ verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, TEST_BT_IFNAME);
+ }
// One is ipv4 address clear (set to 0.0.0.0), another is set interface down which only
// happen after T. Before T, the interface configuration control in bluetooth side.
verify(mNetd, times(isAtLeastT() ? 2 : 1)).interfaceSetCfg(
@@ -4405,7 +4461,7 @@
initTetheringOnTestThread();
// Enable wifi P2P.
sendWifiP2pConnectionChanged(true, true, TEST_P2P_IFNAME);
- verifyInterfaceServingModeStarted(TEST_P2P_IFNAME);
+ verifyInterfaceServingModeStarted(TEST_P2P_IFNAME, false);
verifyTetheringBroadcast(TEST_P2P_IFNAME, EXTRA_AVAILABLE_TETHER);
verifyTetheringBroadcast(TEST_P2P_IFNAME, EXTRA_ACTIVE_LOCAL_ONLY);
verify(mUpstreamNetworkMonitor).startObserveUpstreamNetworks();
diff --git a/framework/src/android/net/connectivity/ConnectivityInternalApiUtil.java b/framework/src/android/net/connectivity/ConnectivityInternalApiUtil.java
index ba39ca0..9478e91 100644
--- a/framework/src/android/net/connectivity/ConnectivityInternalApiUtil.java
+++ b/framework/src/android/net/connectivity/ConnectivityInternalApiUtil.java
@@ -16,11 +16,25 @@
package android.net.connectivity;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
+
+import android.annotation.SuppressLint;
import android.content.Context;
import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.LocalNetworkConfig;
+import android.net.NetworkAgent;
+import android.net.NetworkAgentConfig;
+import android.net.NetworkCapabilities;
+import android.net.NetworkScore;
import android.os.Build;
import android.os.IBinder;
+import android.os.Looper;
+import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
/**
@@ -57,4 +71,36 @@
final ConnectivityManager cm = ctx.getSystemService(ConnectivityManager.class);
return cm.getRoutingCoordinatorService();
}
+
+ /**
+ * Create a NetworkAgent instance to be used by Tethering.
+ * @param ctx the context
+ * @return an instance of the {@code NetworkAgent}
+ */
+ // TODO: Expose LocalNetworkConfig related APIs and delete this method. This method is
+ // only here because on R Tethering is installed and not Connectivity, requiring all
+ // shared classes to be public API. LocalNetworkConfig is not public yet, but it will
+ // only be used by Tethering on V+ so it's fine.
+ @SuppressLint("WrongConstant")
+ @NonNull
+ public static NetworkAgent buildTetheringNetworkAgent(@NonNull Context ctx,
+ @NonNull Looper looper, @NonNull String logTag, int transportType,
+ @NonNull LinkProperties lp) {
+ final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder()
+ .addCapability(NET_CAPABILITY_NOT_METERED)
+ .addCapability(NET_CAPABILITY_NOT_ROAMING)
+ .addCapability(NET_CAPABILITY_NOT_CONGESTED)
+ .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ .addTransportType(transportType);
+ // TODO: Change to use the constant definition. Flags.netCapabilityLocalNetwork() was not
+ // fully rolled out but the service will still process this capability, set it anyway.
+ builder.addCapability(36 /* NET_CAPABILITY_LOCAL_NETWORK */);
+ final NetworkCapabilities caps = builder.build();
+ final NetworkAgentConfig nac = new NetworkAgentConfig.Builder().build();
+ return new NetworkAgent(ctx, looper, logTag, caps, lp,
+ new LocalNetworkConfig.Builder().build(), new NetworkScore.Builder()
+ .setKeepConnectedReason(NetworkScore.KEEP_CONNECTED_LOCAL_NETWORK)
+ .build(), nac, null /* provider */) {
+ };
+ }
}