Support LocalNetworkAgent for Tethering
The feature will be gradually enabled via flag rollout.
Sample ip rule output:
0: from all lookup local
10000: from all fwmark 0xc0000/0xd0000 lookup legacy_system
11000: from all iif lo oif dummy0 uidrange 0-0 lookup dummy0
11000: from all iif lo oif rmnet1 uidrange 0-0 lookup rmnet1
11000: from all iif lo oif wlan0 uidrange 0-0 lookup wlan0
16000: from all fwmark 0x10063/0x1ffff iif lo lookup local_network
16000: from all fwmark 0x10064/0x1ffff iif lo lookup rmnet1
16000: from all fwmark 0x10066/0x1ffff iif lo lookup wlan0
16000: from all fwmark 0x10063/0x1ffff iif lo lookup wlan0
17000: from all iif lo oif dummy0 lookup dummy0
17000: from all iif lo oif rmnet1 lookup rmnet1
17000: from all iif lo oif wlan0 lookup wlan0
18000: from all fwmark 0x0/0x10000 lookup legacy_system
19000: from all fwmark 0x0/0x10000 lookup legacy_network
20000: from all fwmark 0x0/0x10000 lookup local_network
20000: from all fwmark 0x0/0x10000 lookup wlan0
21000: from all iif wlan0 lookup rmnet1
23000: from all fwmark 0x64/0x1ffff iif lo lookup rmnet1
23000: from all fwmark 0x66/0x1ffff iif lo lookup wlan0
31000: from all fwmark 0x0/0xffff iif lo lookup rmnet1
32000: from all unreachable
Test: adb shell device_config put tethering tethering_local_network_agent 1
Test: atest TetheringTests:android.net.ip.IpServerTest TetheringManagerTest
Bug: 349487600
Change-Id: Iaa88dde645da917149fec0d44fff796c551bcc7e
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/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 */) {
+ };
+ }
}