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
This commit is contained in:
Junyu Lai 2024-12-13 15:47:44 +08:00
commit d55bd89bd6
5 changed files with 206 additions and 18 deletions

View file

@ -31,6 +31,7 @@ import static android.net.TetheringManager.TETHER_ERROR_ENABLE_FORWARDING_ERROR;
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 android.system.OsConstants.RT_SCOPE_UNIVERSE;
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.DhcpServingParamsParcelExt;
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 android.util.SparseArray;
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 @@ public class IpServer extends StateMachineShim {
/** 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 @@ public class IpServer extends StateMachineShim {
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 @@ public class IpServer extends StateMachineShim {
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 @@ public class IpServer extends StateMachineShim {
//
// 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 @@ public class IpServer extends StateMachineShim {
}
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 @@ public class IpServer extends StateMachineShim {
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 @@ public class IpServer extends StateMachineShim {
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 @@ public class IpServer extends StateMachineShim {
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 @@ public class IpServer extends StateMachineShim {
// 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 @@ public class IpServer extends StateMachineShim {
// 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 @@ public class IpServer extends StateMachineShim {
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 @@ public class IpServer extends StateMachineShim {
stopIPv4();
mBpfCoordinator.removeIpServer(IpServer.this);
if (mTetheringAgent != null && everRegistered(mTetheringAgent)) {
mTetheringAgent.unregister();
mTetheringAgent = null;
}
resetLinkProperties();
mTetheringMetrics.updateErrorCode(mInterfaceType, mLastError);
@ -1184,6 +1268,12 @@ public class IpServer extends StateMachineShim {
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 @@ public class IpServer extends StateMachineShim {
// 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 @@ public class IpServer extends StateMachineShim {
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.

View file

@ -3105,7 +3105,7 @@ public class Tethering {
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);

View file

@ -148,6 +148,12 @@ public class TetheringConfiguration {
*/
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;

View file

@ -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 @@ public class TetheringUtils {
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);
}
}
}

View file

@ -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 @@ public class ConnectivityInternalApiUtil {
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 */) {
};
}
}