diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java index e030902637966630f9c4f08573153338abdfb053..246e5bcce2419b2a469f80176554ba264ab37dc0 100644 --- a/Tethering/src/android/net/ip/IpServer.java +++ b/Tethering/src/android/net/ip/IpServer.java @@ -44,6 +44,7 @@ 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.TetheringRequestParcel; @@ -71,6 +72,7 @@ import com.android.internal.util.StateMachine; 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.SdkUtil.LateSdk; import com.android.net.module.util.SharedLog; import com.android.net.module.util.ip.InterfaceController; import com.android.net.module.util.ip.IpNeighborMonitor; @@ -245,6 +247,11 @@ public class IpServer extends StateMachine { private final INetd mNetd; @NonNull private final BpfCoordinator mBpfCoordinator; + // Contains null if the connectivity module is unsupported, as the routing coordinator is not + // available. Must use LateSdk because MessageUtils enumerates fields in this class, so it + // must be able to find all classes at runtime. + @NonNull + private final LateSdk<RoutingCoordinatorManager> mRoutingCoordinator; private final Callback mCallback; private final InterfaceController mInterfaceCtrl; private final PrivateAddressCoordinator mPrivateAddressCoordinator; @@ -307,13 +314,15 @@ public class IpServer extends StateMachine { // object. It helps to reduce the arguments of the constructor. public IpServer( String ifaceName, Looper looper, int interfaceType, SharedLog log, - INetd netd, @NonNull BpfCoordinator coordinator, Callback callback, + INetd netd, @NonNull BpfCoordinator bpfCoordinator, + @Nullable LateSdk<RoutingCoordinatorManager> routingCoordinator, Callback callback, TetheringConfiguration config, PrivateAddressCoordinator addressCoordinator, TetheringMetrics tetheringMetrics, Dependencies deps) { super(ifaceName, looper); mLog = log.forSubComponent(ifaceName); mNetd = netd; - mBpfCoordinator = coordinator; + mBpfCoordinator = bpfCoordinator; + mRoutingCoordinator = routingCoordinator; mCallback = callback; mInterfaceCtrl = new InterfaceController(ifaceName, mNetd, mLog); mIfaceName = ifaceName; @@ -807,23 +816,33 @@ public class IpServer extends StateMachine { for (RouteInfo route : toBeRemoved) mLinkProperties.removeRoute(route); } - private void addRoutesToLocalNetwork(@NonNull final List<RouteInfo> toBeAdded) { + private void addInterfaceToNetwork(final int netId, @NonNull final String ifaceName) { try { - // It's safe to call networkAddInterface() even if - // the interface is already in the local_network. - mNetd.networkAddInterface(INetd.LOCAL_NET_ID, mIfaceName); - try { - // Add routes from local network. Note that adding routes that - // already exist does not cause an error (EEXIST is silently ignored). - NetdUtils.addRoutesToLocalNetwork(mNetd, mIfaceName, toBeAdded); - } catch (IllegalStateException e) { - mLog.e("Failed to add IPv4/v6 routes to local table: " + e); - return; + if (null != mRoutingCoordinator.value) { + // TODO : remove this call in favor of using the LocalNetworkConfiguration + // correctly, which will let ConnectivityService do it automatically. + mRoutingCoordinator.value.addInterfaceToNetwork(netId, ifaceName); + } else { + mNetd.networkAddInterface(netId, ifaceName); } } catch (ServiceSpecificException | RemoteException e) { mLog.e("Failed to add " + mIfaceName + " to local table: ", e); return; } + } + + private void addRoutesToLocalNetwork(@NonNull final List<RouteInfo> toBeAdded) { + // It's safe to call addInterfaceToNetwork() even if + // the interface is already in the local_network. + addInterfaceToNetwork(INetd.LOCAL_NET_ID, mIfaceName); + try { + // Add routes from local network. Note that adding routes that + // already exist does not cause an error (EEXIST is silently ignored). + NetdUtils.addRoutesToLocalNetwork(mNetd, mIfaceName, toBeAdded); + } catch (IllegalStateException e) { + mLog.e("Failed to add IPv4/v6 routes to local table: " + e); + return; + } for (RouteInfo route : toBeAdded) mLinkProperties.addRoute(route); } diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java index b371178244d67f02aaddfdcfd6244954a87a63b3..f52bed9ce67185cd6ec12d5bc2f08df3587ab40f 100644 --- a/Tethering/src/com/android/networkstack/tethering/Tethering.java +++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java @@ -90,6 +90,7 @@ 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; @@ -136,6 +137,7 @@ import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.BaseNetdUnsolicitedEventListener; import com.android.net.module.util.CollectionUtils; import com.android.net.module.util.NetdUtils; +import com.android.net.module.util.SdkUtil.LateSdk; import com.android.net.module.util.SharedLog; import com.android.networkstack.apishim.common.BluetoothPanShim; import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceCallbackShim; @@ -250,6 +252,10 @@ public class Tethering { private final Handler mHandler; private final INetd mNetd; private final NetdCallback mNetdCallback; + // Contains null if the connectivity module is unsupported, as the routing coordinator is not + // available. Must use LateSdk because MessageUtils enumerates fields in this class, so it + // must be able to find all classes at runtime. + @NonNull private final LateSdk<RoutingCoordinatorManager> mRoutingCoordinator; private final UserRestrictionActionListener mTetheringRestriction; private final ActiveDataSubIdListener mActiveDataSubIdListener; private final ConnectedClientsTracker mConnectedClientsTracker; @@ -296,6 +302,7 @@ public class Tethering { mDeps = deps; mContext = mDeps.getContext(); mNetd = mDeps.getINetd(mContext); + mRoutingCoordinator = mDeps.getRoutingCoordinator(mContext); mLooper = mDeps.getTetheringLooper(); mNotificationUpdater = mDeps.getNotificationUpdater(mContext, mLooper); mTetheringMetrics = mDeps.getTetheringMetrics(); @@ -2835,8 +2842,9 @@ public class Tethering { mLog.i("adding IpServer for: " + iface); final TetherState tetherState = new TetherState( new IpServer(iface, mLooper, interfaceType, mLog, mNetd, mBpfCoordinator, - makeControlCallback(), mConfig, mPrivateAddressCoordinator, - mTetheringMetrics, mDeps.getIpServerDependencies()), isNcm); + mRoutingCoordinator, makeControlCallback(), mConfig, + mPrivateAddressCoordinator, mTetheringMetrics, + mDeps.getIpServerDependencies()), isNcm); mTetherStates.put(iface, tetherState); tetherState.ipServer.start(); } diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java index 741a5c519488759454b5fad2cedf2a7d186864d8..c6468a085fa0f272967076fb32e5376ae04f671a 100644 --- a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java +++ b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java @@ -16,11 +16,14 @@ package com.android.networkstack.tethering; +import android.annotation.Nullable; import android.app.usage.NetworkStatsManager; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothPan; import android.content.Context; import android.net.INetd; +import android.net.RoutingCoordinatorManager; +import android.net.connectivity.TiramisuConnectivityInternalApiUtil; import android.net.ip.IpServer; import android.os.Build; import android.os.Handler; @@ -33,6 +36,8 @@ import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import com.android.internal.util.StateMachine; +import com.android.modules.utils.build.SdkLevel; +import com.android.net.module.util.SdkUtil.LateSdk; import com.android.net.module.util.SharedLog; import com.android.networkstack.apishim.BluetoothPanShimImpl; import com.android.networkstack.apishim.common.BluetoothPanShim; @@ -121,6 +126,16 @@ public abstract class TetheringDependencies { (IBinder) context.getSystemService(Context.NETD_SERVICE)); } + /** + * Get the routing coordinator, or null if below S. + */ + @Nullable + public LateSdk<RoutingCoordinatorManager> getRoutingCoordinator(Context context) { + if (!SdkLevel.isAtLeastS()) return new LateSdk<>(null); + return new LateSdk<>( + TiramisuConnectivityInternalApiUtil.getRoutingCoordinatorManager(context)); + } + /** * Get a reference to the TetheringNotificationUpdater to be used by tethering. */ @@ -135,7 +150,7 @@ public abstract class TetheringDependencies { public abstract Looper getTetheringLooper(); /** - * Get Context of TetheringSerice. + * Get Context of TetheringService. */ public abstract Context getContext(); diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java index d497a4dd6b85855b19f2ee8c06a7acb31c25763e..fc9928d6a7a29f610dcfc72fbd218ad412006069 100644 --- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java +++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java @@ -62,6 +62,7 @@ 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; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; @@ -80,6 +81,7 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.MacAddress; import android.net.RouteInfo; +import android.net.RoutingCoordinatorManager; import android.net.TetherOffloadRuleParcel; import android.net.TetherStatsParcel; import android.net.dhcp.DhcpServerCallbacks; @@ -99,9 +101,11 @@ import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.BpfMap; import com.android.net.module.util.InterfaceParams; import com.android.net.module.util.NetworkStackConstants; +import com.android.net.module.util.SdkUtil.LateSdk; import com.android.net.module.util.SharedLog; import com.android.net.module.util.Struct.S32; import com.android.net.module.util.bpf.Tether4Key; @@ -193,6 +197,8 @@ public class IpServerTest { @Mock private IpNeighborMonitor mIpNeighborMonitor; @Mock private IpServer.Dependencies mDependencies; @Mock private PrivateAddressCoordinator mAddressCoordinator; + private final LateSdk<RoutingCoordinatorManager> mRoutingCoordinatorManager = + new LateSdk<>(SdkLevel.isAtLeastS() ? mock(RoutingCoordinatorManager.class) : null); @Mock private NetworkStatsManager mStatsManager; @Mock private TetheringConfiguration mTetherConfig; @Mock private ConntrackMonitor mConntrackMonitor; @@ -249,7 +255,8 @@ public class IpServerTest { mBpfCoordinator = spy(new BpfCoordinator(mBpfDeps)); mIpServer = new IpServer( IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd, mBpfCoordinator, - mCallback, mTetherConfig, mAddressCoordinator, mTetheringMetrics, mDependencies); + mRoutingCoordinatorManager, mCallback, mTetherConfig, mAddressCoordinator, + mTetheringMetrics, mDependencies); mIpServer.start(); mNeighborEventConsumer = neighborCaptor.getValue(); @@ -396,8 +403,8 @@ public class IpServerTest { when(mDependencies.getIpNeighborMonitor(any(), any(), any())) .thenReturn(mIpNeighborMonitor); mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(), TETHERING_BLUETOOTH, mSharedLog, - mNetd, mBpfCoordinator, mCallback, mTetherConfig, mAddressCoordinator, - mTetheringMetrics, mDependencies); + mNetd, mBpfCoordinator, mRoutingCoordinatorManager, mCallback, mTetherConfig, + mAddressCoordinator, mTetheringMetrics, mDependencies); mIpServer.start(); mLooper.dispatchAll(); verify(mCallback).updateInterfaceState( 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 770507e186989ee47d1a5e28c27e552f541ecdcb..ba39f228c873cdce33cee141ad9b91cc8693a456 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java @@ -142,6 +142,7 @@ 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 +192,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.SdkUtil.LateSdk; import com.android.net.module.util.SharedLog; import com.android.net.module.util.ip.IpNeighborMonitor; import com.android.networkstack.apishim.common.BluetoothPanShim; @@ -482,6 +484,12 @@ public class TetheringTest { return mEntitleMgr; } + @Nullable + @Override + public LateSdk<RoutingCoordinatorManager> getRoutingCoordinator(final Context context) { + return new LateSdk<>(null); + } + @Override public TetheringConfiguration generateTetheringConfiguration(Context ctx, SharedLog log, int subId) { diff --git a/framework/Android.bp b/framework/Android.bp index 182c558b37efbb73a98b568c36a2801a96185802..02c678afe17802d275eb58730495741e9f79ee0b 100644 --- a/framework/Android.bp +++ b/framework/Android.bp @@ -292,17 +292,20 @@ java_genrule { // Library providing limited APIs within the connectivity module, so that R+ components like // Tethering have a controlled way to depend on newer components like framework-connectivity that // are not loaded on R. +// Note that this target needs to have access to hidden classes, and as such needs to list +// the full libraries instead of the .impl lib (which only expose API classes). java_library { name: "connectivity-internal-api-util", sdk_version: "module_current", libs: [ "androidx.annotation_annotation", - "framework-connectivity.impl", + "framework-connectivity-pre-jarjar", ], jarjar_rules: ":framework-connectivity-jarjar-rules", srcs: [ - // Files listed here MUST all be annotated with @RequiresApi(Build.VERSION_CODES.TIRAMISU), - // so that API checks are enforced for R+ users of this library + // 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/TiramisuConnectivityInternalApiUtil.java", ], visibility: [ diff --git a/framework/jarjar-excludes.txt b/framework/jarjar-excludes.txt index 1ac5e8ec4a71b124ee2bc3b300e4761249fec491..bc3c8d1ccb8e87cbda7de30598eb9f5741b3c464 100644 --- a/framework/jarjar-excludes.txt +++ b/framework/jarjar-excludes.txt @@ -14,6 +14,15 @@ android\.net\.INetworkAgentRegistry(\$.+)? # TODO: move files to android.net.connectivity.visiblefortesting android\.net\.IConnectivityDiagnosticsCallback(\$.+)? +# Classes used by tethering as a hidden API are compiled as a lib in target +# connectivity-internal-api-util. Because it's used by tethering, it can't +# be jarjared. Classes in android.net.connectivity are exempt from being +# listed here because they are already in the target package and as such +# are already not jarjared. +# Because Tethering can be installed on R without Connectivity, any use +# of these classes must be protected by a check for >= S SDK. +# It's unlikely anybody else declares a hidden class with this nameĆ ? +android\.net\.RoutingCoordinatorManager(\$.+)? # KeepaliveUtils is used by ConnectivityManager CTS # TODO: move into service-connectivity so framework-connectivity stops using diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 915c20da5fbbd20ad7dad96ee8884be6e7e1cc73..5cc196897c8f2ac6f60a5e1b86670a3a25e26125 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -29,6 +29,7 @@ import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresApi; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; @@ -6174,4 +6175,24 @@ public class ConnectivityManager { throw e.rethrowFromSystemServer(); } } + + private static final Object sRoutingCoordinatorManagerLock = new Object(); + @GuardedBy("sRoutingCoordinatorManagerLock") + private static RoutingCoordinatorManager sRoutingCoordinatorManager = null; + /** @hide */ + @RequiresApi(Build.VERSION_CODES.S) + public RoutingCoordinatorManager getRoutingCoordinatorManager() { + try { + synchronized (sRoutingCoordinatorManagerLock) { + if (null == sRoutingCoordinatorManager) { + sRoutingCoordinatorManager = new RoutingCoordinatorManager(mContext, + IRoutingCoordinator.Stub.asInterface( + mService.getRoutingCoordinatorService())); + } + return sRoutingCoordinatorManager; + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl index fe277732b0da32f1d7e0ace6ca0b81dc27348dbd..e495448c7c90c4be3ed64530e245a41e405d42ef 100644 --- a/framework/src/android/net/IConnectivityManager.aidl +++ b/framework/src/android/net/IConnectivityManager.aidl @@ -259,4 +259,6 @@ interface IConnectivityManager void setVpnNetworkPreference(String session, in UidRange[] ranges); void setTestLowTcpPollingTimerForKeepalive(long timeMs); + + IBinder getRoutingCoordinatorService(); } diff --git a/framework/src/android/net/IRoutingCoordinator.aidl b/framework/src/android/net/IRoutingCoordinator.aidl new file mode 100644 index 0000000000000000000000000000000000000000..a5cda9874e1606337e02e693bcd8c3efa5ca3540 --- /dev/null +++ b/framework/src/android/net/IRoutingCoordinator.aidl @@ -0,0 +1,75 @@ +/* + * 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 android.net; + +import android.net.RouteInfo; + +/** @hide */ +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); +} diff --git a/framework/src/android/net/RouteInfo.java b/framework/src/android/net/RouteInfo.java index df5f151a3f78a874fd39630bc92fd350fba1e587..e8ebf8137ace2502067198b033a615833136e628 100644 --- a/framework/src/android/net/RouteInfo.java +++ b/framework/src/android/net/RouteInfo.java @@ -584,7 +584,7 @@ public final class RouteInfo implements Parcelable { } RouteKey p = (RouteKey) o; // No need to do anything special for scoped addresses. Inet6Address#equals does not - // consider the scope ID, but the netd route IPCs (e.g., INetd#networkAddRouteParcel) + // consider the scope ID, but the route IPCs (e.g., RoutingCoordinatorManager#addRoute) // and the kernel ignore scoped addresses both in the prefix and in the nexthop and only // look at RTA_OIF. return Objects.equals(p.mDestination, mDestination) diff --git a/framework/src/android/net/RoutingCoordinatorManager.java b/framework/src/android/net/RoutingCoordinatorManager.java new file mode 100644 index 0000000000000000000000000000000000000000..5576cb0f23316b3bef880d338cf5a3111c122e55 --- /dev/null +++ b/framework/src/android/net/RoutingCoordinatorManager.java @@ -0,0 +1,126 @@ +/* + * 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 android.net; + +import android.content.Context; +import android.os.Build; +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 IRoutingCoordinator service) { + mContext = context; + mService = service; + } + + /** + * 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(); + } + } +} diff --git a/framework/src/android/net/connectivity/TiramisuConnectivityInternalApiUtil.java b/framework/src/android/net/connectivity/TiramisuConnectivityInternalApiUtil.java index d65858f9ab7730b40e3389165a367cc4dba60220..c2d75d23242b48f1e7666957eb620794f017adab 100644 --- a/framework/src/android/net/connectivity/TiramisuConnectivityInternalApiUtil.java +++ b/framework/src/android/net/connectivity/TiramisuConnectivityInternalApiUtil.java @@ -18,6 +18,7 @@ package android.net.connectivity; import android.content.Context; import android.net.ConnectivityManager; +import android.net.RoutingCoordinatorManager; import android.os.Build; import android.os.IBinder; @@ -34,15 +35,28 @@ import androidx.annotation.RequiresApi; * linter). * @hide */ -@RequiresApi(Build.VERSION_CODES.TIRAMISU) +// TODO : rename this so that it doesn't reference "Tiramisu" since it can be used in S. +@RequiresApi(Build.VERSION_CODES.S) public class TiramisuConnectivityInternalApiUtil { /** * Get a service binder token for * {@link com.android.server.connectivity.wear.CompanionDeviceManagerProxyService}. */ + @RequiresApi(Build.VERSION_CODES.TIRAMISU) public static IBinder getCompanionDeviceManagerProxyService(Context ctx) { final ConnectivityManager cm = ctx.getSystemService(ConnectivityManager.class); return cm.getCompanionDeviceManagerProxyService(); } + + /** + * Obtain a routing coordinator manager from a context, possibly cross-module. + * @param ctx the context + * @return an instance of the coordinator manager + */ + @RequiresApi(Build.VERSION_CODES.S) + public static RoutingCoordinatorManager getRoutingCoordinatorManager(Context ctx) { + final ConnectivityManager cm = ctx.getSystemService(ConnectivityManager.class); + return cm.getRoutingCoordinatorManager(); + } } diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index 2797b47e317bd2955584eb13a7f1b326f0df4f16..12f105d6bffc8f33cc02396e005b0935e3c8f57c 100755 --- a/service/src/com/android/server/ConnectivityService.java +++ b/service/src/com/android/server/ConnectivityService.java @@ -199,7 +199,6 @@ import android.net.QosFilter; import android.net.QosSocketFilter; import android.net.QosSocketInfo; import android.net.RouteInfo; -import android.net.RouteInfoParcel; import android.net.SocketKeepalive; import android.net.TetheringManager; import android.net.TransportInfo; @@ -330,6 +329,7 @@ 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.UidRangeUtils; import com.android.server.connectivity.VpnNetworkPreferenceInfo; import com.android.server.connectivity.wear.CompanionDeviceManagerProxyService; @@ -493,6 +493,7 @@ public class ConnectivityService extends IConnectivityManager.Stub @GuardedBy("mTNSLock") private TestNetworkService mTNS; private final CompanionDeviceManagerProxyService mCdmps; + private final RoutingCoordinatorService mRoutingCoordinatorService; private final Object mTNSLock = new Object(); @@ -1826,6 +1827,8 @@ public class ConnectivityService extends IConnectivityManager.Stub mCdmps = null; } + mRoutingCoordinatorService = new RoutingCoordinatorService(netd); + mDestroyFrozenSockets = mDeps.isAtLeastU() && mDeps.isFeatureEnabled(context, KEY_DESTROY_FROZEN_SOCKETS_VERSION); mDelayDestroyFrozenSockets = mDeps.isAtLeastU() @@ -8515,7 +8518,7 @@ public class ConnectivityService extends IConnectivityManager.Stub for (final String iface : interfaceDiff.added) { try { if (DBG) log("Adding iface " + iface + " to network " + netId); - mNetd.networkAddInterface(netId, iface); + mRoutingCoordinatorService.addInterfaceToNetwork(netId, iface); wakeupModifyInterface(iface, nai, true); mDeps.reportNetworkInterfaceForTransports(mContext, iface, nai.networkCapabilities.getTransportTypes()); @@ -8528,45 +8531,13 @@ public class ConnectivityService extends IConnectivityManager.Stub try { if (DBG) log("Removing iface " + iface + " from network " + netId); wakeupModifyInterface(iface, nai, false); - mNetd.networkRemoveInterface(netId, iface); + mRoutingCoordinatorService.removeInterfaceFromNetwork(netId, iface); } catch (Exception e) { loge("Exception removing interface: " + e); } } } - // TODO: move to frameworks/libs/net. - private RouteInfoParcel convertRouteInfo(RouteInfo route) { - final String nextHop; - - switch (route.getType()) { - case RouteInfo.RTN_UNICAST: - if (route.hasGateway()) { - nextHop = route.getGateway().getHostAddress(); - } else { - nextHop = INetd.NEXTHOP_NONE; - } - break; - case RouteInfo.RTN_UNREACHABLE: - nextHop = INetd.NEXTHOP_UNREACHABLE; - break; - case RouteInfo.RTN_THROW: - nextHop = INetd.NEXTHOP_THROW; - break; - default: - nextHop = INetd.NEXTHOP_NONE; - break; - } - - final RouteInfoParcel rip = new RouteInfoParcel(); - rip.ifName = route.getInterface(); - rip.destination = route.getDestination().toString(); - rip.nextHop = nextHop; - rip.mtu = route.getMtu(); - - return rip; - } - /** * Have netd update routes from oldLp to newLp. * @return true if routes changed between oldLp and newLp @@ -8587,10 +8558,10 @@ public class ConnectivityService extends IConnectivityManager.Stub if (route.hasGateway()) continue; if (VDBG || DDBG) log("Adding Route [" + route + "] to network " + netId); try { - mNetd.networkAddRouteParcel(netId, convertRouteInfo(route)); + mRoutingCoordinatorService.addRoute(netId, route); } catch (Exception e) { if ((route.getDestination().getAddress() instanceof Inet4Address) || VDBG) { - loge("Exception in networkAddRouteParcel for non-gateway: " + e); + loge("Exception in addRoute for non-gateway: " + e); } } } @@ -8598,10 +8569,10 @@ public class ConnectivityService extends IConnectivityManager.Stub if (!route.hasGateway()) continue; if (VDBG || DDBG) log("Adding Route [" + route + "] to network " + netId); try { - mNetd.networkAddRouteParcel(netId, convertRouteInfo(route)); + mRoutingCoordinatorService.addRoute(netId, route); } catch (Exception e) { if ((route.getGateway() instanceof Inet4Address) || VDBG) { - loge("Exception in networkAddRouteParcel for gateway: " + e); + loge("Exception in addRoute for gateway: " + e); } } } @@ -8609,18 +8580,18 @@ public class ConnectivityService extends IConnectivityManager.Stub for (RouteInfo route : routeDiff.removed) { if (VDBG || DDBG) log("Removing Route [" + route + "] from network " + netId); try { - mNetd.networkRemoveRouteParcel(netId, convertRouteInfo(route)); + mRoutingCoordinatorService.removeRoute(netId, route); } catch (Exception e) { - loge("Exception in networkRemoveRouteParcel: " + e); + loge("Exception in removeRoute: " + e); } } for (RouteInfo route : routeDiff.updated) { if (VDBG || DDBG) log("Updating Route [" + route + "] from network " + netId); try { - mNetd.networkUpdateRouteParcel(netId, convertRouteInfo(route)); + mRoutingCoordinatorService.updateRoute(netId, route); } catch (Exception e) { - loge("Exception in networkUpdateRouteParcel: " + e); + loge("Exception in updateRoute: " + e); } } return !routeDiff.added.isEmpty() || !routeDiff.removed.isEmpty() @@ -10261,7 +10232,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // If a rate limit has been configured and is applicable to this network (network // provides internet connectivity), apply it. The tc police filter cannot be attached // before the clsact qdisc is added which happens as part of updateLinkProperties -> - // updateInterfaces -> INetd#networkAddInterface. + // updateInterfaces -> RoutingCoordinatorManager#addInterfaceToNetwork // Note: in case of a system server crash, the NetworkController constructor in netd // (called when netd starts up) deletes the clsact qdisc of all interfaces. if (canNetworkBeRateLimited(networkAgent) && mIngressRateLimit >= 0) { @@ -12740,4 +12711,10 @@ public class ConnectivityService extends IConnectivityManager.Stub enforceNetworkStackPermission(mContext); return mCdmps; } + + @Override + public IBinder getRoutingCoordinatorService() { + enforceNetworkStackPermission(mContext); + return mRoutingCoordinatorService; + } } diff --git a/service/src/com/android/server/connectivity/RoutingCoordinatorService.java b/service/src/com/android/server/connectivity/RoutingCoordinatorService.java new file mode 100644 index 0000000000000000000000000000000000000000..50e84d4d220209efc19ba63066afe6c6f2bb3b4d --- /dev/null +++ b/service/src/com/android/server/connectivity/RoutingCoordinatorService.java @@ -0,0 +1,118 @@ +/* + * 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.server.connectivity; + +import static com.android.net.module.util.NetdUtils.toRouteInfoParcel; + +import android.annotation.NonNull; +import android.content.Context; +import android.net.INetd; +import android.net.IRoutingCoordinator; +import android.net.RouteInfo; +import android.os.RemoteException; +import android.os.ServiceSpecificException; + +/** + * 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 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); + } +} diff --git a/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java b/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java index ea18d3794e68def6426f325d42ca6732df4ffd6f..d99eedcfa0731a3722d474ed4fd90419ab5ce03e 100644 --- a/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java +++ b/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java @@ -28,6 +28,7 @@ import android.net.INetd; import android.net.InterfaceConfigurationParcel; import android.net.IpPrefix; import android.net.RouteInfo; +import android.net.RouteInfoParcel; import android.net.TetherConfigParcel; import android.os.RemoteException; import android.os.ServiceSpecificException; @@ -278,4 +279,38 @@ public class NetdUtils { throw new IllegalStateException(e); } } + + /** + * Convert a RouteInfo into a RouteInfoParcel. + */ + public static RouteInfoParcel toRouteInfoParcel(RouteInfo route) { + final String nextHop; + + switch (route.getType()) { + case RouteInfo.RTN_UNICAST: + if (route.hasGateway()) { + nextHop = route.getGateway().getHostAddress(); + } else { + nextHop = INetd.NEXTHOP_NONE; + } + break; + case RouteInfo.RTN_UNREACHABLE: + nextHop = INetd.NEXTHOP_UNREACHABLE; + break; + case RouteInfo.RTN_THROW: + nextHop = INetd.NEXTHOP_THROW; + break; + default: + nextHop = INetd.NEXTHOP_NONE; + break; + } + + final RouteInfoParcel rip = new RouteInfoParcel(); + rip.ifName = route.getInterface(); + rip.destination = route.getDestination().toString(); + rip.nextHop = nextHop; + rip.mtu = route.getMtu(); + + return rip; + } } diff --git a/staticlibs/framework/com/android/net/module/util/SdkUtil.java b/staticlibs/framework/com/android/net/module/util/SdkUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..5006ba989ad7c3398e4a028cf22fe1914a54ca0f --- /dev/null +++ b/staticlibs/framework/com/android/net/module/util/SdkUtil.java @@ -0,0 +1,49 @@ +/* + * 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.annotation.Nullable; + +/** + * Utilities to deal with multiple SDKs in a single mainline module. + * @hide + */ +public class SdkUtil { + /** + * Holder class taking advantage of erasure to avoid reflection running into class not found + * exceptions. + * + * This is useful to store a reference to a class that might not be present at runtime when + * fields are examined through reflection. An example is the MessageUtils class, which tries + * to get all fields in a class and therefore will try to load any class for which there + * is a member. Another example would be arguments or return values of methods in tests, + * when the testing framework uses reflection to list methods and their arguments. + * + * In these cases, LateSdk<T> can be used to hide type T from reflection, since it's erased + * and it becomes a vanilla LateSdk in Java bytecode. The T still can't be instantiated at + * runtime of course, but runtime tests will avoid that. + * + * @param <T> The held type + * @hide + */ + public static class LateSdk<T> { + @Nullable public final T value; + public LateSdk(@Nullable final T value) { + this.value = value; + } + } +}