From 98e5827cccbdd042251dceaccd5395416a42af99 Mon Sep 17 00:00:00 2001 From: Sarup Dalwani Date: Wed, 2 Jul 2025 17:25:59 +0000 Subject: [PATCH] Migrate NetdUtils from networkAddRoute to networkAddRouteParcel. This will allow us to deprecate networkAddRoute. Bug:412629841 Test: atest TetheringTests Flag: EXEMPT Using device config kill-switch Change-Id: I20e988f760b8ad22b51effdc1f0e204763c6e240 --- Tethering/src/android/net/ip/IpServer.java | 29 ++++-- .../unit/src/android/net/ip/IpServerTest.java | 97 ++++++++++++++----- .../networkstack/tethering/TetheringTest.java | 50 +++++++--- .../android/net/module/util/NetdUtils.java | 55 ++++------- .../module/util/ConnectivityCommonFlags.java | 30 ++++++ .../server/ConnectivityServiceTest.java | 2 +- 6 files changed, 184 insertions(+), 79 deletions(-) create mode 100644 staticlibs/framework/com/android/net/module/util/ConnectivityCommonFlags.java diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java index 500194b40c..783bff3622 100644 --- a/Tethering/src/android/net/ip/IpServer.java +++ b/Tethering/src/android/net/ip/IpServer.java @@ -39,6 +39,7 @@ import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; import static android.net.util.NetworkConstants.asByte; import static android.system.OsConstants.RT_SCOPE_UNIVERSE; +import static com.android.net.module.util.ConnectivityCommonFlags.USE_ROUTE_PARCEL_IPCS; 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.USE_SYNC_SM; @@ -232,6 +233,13 @@ public class IpServer extends StateMachineShim { return Flags.tetheringAndP2pGoLocalAgent(); } + /** + * @see DeviceConfigUtils#isTetheringFeatureNotChickenedOut + */ + public boolean isFeatureNotChickenedOut(Context context, String name) { + return DeviceConfigUtils.isTetheringFeatureNotChickenedOut(context, name); + } + /** Create a NetworkAgent instance to be used by IpServer. */ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) @SuppressLint("NewApi") @@ -296,6 +304,7 @@ public class IpServer extends StateMachineShim { private final boolean mUsingLegacyDhcp; private final int mP2pLeasesSubnetPrefixLength; private final boolean mIsWifiP2pDedicatedIpEnabled; + private final boolean mUseRouteParcel; private final Dependencies mDeps; @@ -400,6 +409,11 @@ public class IpServer extends StateMachineShim { TETHERING_AND_P2P_GO_LOCAL_AGENT) : mDeps.isTetheringAndP2pGoLocalAgentBetaFlagEnabled()); + // For post 25Q2 releases always use *RouteParcel methods. For 25Q2 or lower, decide based + // on kill-switch + mUseRouteParcel = SdkUtil.isAtLeast25Q4() + || mDeps.isFeatureNotChickenedOut(mContext, USE_ROUTE_PARCEL_IPCS); + mInitialState = new InitialState(); mLocalHotspotState = new LocalHotspotState(); mTetheredState = new TetheredState(); @@ -919,7 +933,8 @@ public class IpServer extends StateMachineShim { } private void removeRoutesFromNetwork(int netId, @NonNull final List toBeRemoved) { - final int removalFailures = NetdUtils.removeRoutesFromNetwork(mNetd, netId, toBeRemoved); + final int removalFailures = NetdUtils.removeRoutesFromNetwork(mNetd, netId, toBeRemoved, + mUseRouteParcel); if (removalFailures > 0) { mLog.e("Failed to remove " + removalFailures + " IPv6 routes from network " + netId + "."); @@ -950,15 +965,14 @@ public class IpServer extends StateMachineShim { } } - private void addRoutesToNetwork(int netId, - @NonNull final List toBeAdded) { + private void addRoutesToNetwork(int netId, @NonNull final List toBeAdded) { // It's safe to call addInterfaceToNetwork() even if // the interface is already in the network. addInterfaceToNetwork(netId, mIfaceName); try { // Add routes from local network. Note that adding routes that // already exist does not cause an error (EEXIST is silently ignored). - NetdUtils.addRoutesToNetwork(mNetd, netId, mIfaceName, toBeAdded); + NetdUtils.addRoutesToNetwork(mNetd, netId, mIfaceName, toBeAdded, mUseRouteParcel); } catch (IllegalStateException e) { mLog.e("Failed to add IPv4/v6 routes to local table: " + e); return; @@ -972,6 +986,7 @@ public class IpServer extends StateMachineShim { final List routesToBeRemoved = getLocalRoutesFor(mIfaceName, deprecatedPrefixes); if (mTetheringAgent == null) { + // Routes for local network are not considered local routes. removeRoutesFromNetwork(LOCAL_NET_ID, routesToBeRemoved); } for (RouteInfo route : routesToBeRemoved) mLinkProperties.removeRoute(route); @@ -1219,10 +1234,11 @@ public class IpServer extends StateMachineShim { 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)); + new RouteInfo(asIpPrefix(mIpv4Address), null, mIfaceName, + RTN_UNICAST), mUseRouteParcel); NetdUtils.modifyRoute(mNetd, NetdUtils.ModifyOperation.ADD, LOCAL_NET_ID, new RouteInfo(new IpPrefix("fe80::/64"), null, mIfaceName, - RTN_UNICAST)); + RTN_UNICAST), mUseRouteParcel); } } catch (RemoteException | ServiceSpecificException | IllegalStateException e) { mLog.e("Error Tethering", e); @@ -1352,6 +1368,7 @@ public class IpServer extends StateMachineShim { final List routesToBeRemoved = List.of(getDirectConnectedRoute(deprecatedLinkAddress)); if (mTetheringAgent == null) { + // Routes for local network are not considered local routes. removeRoutesFromNetwork(LOCAL_NET_ID, routesToBeRemoved); } for (RouteInfo route : routesToBeRemoved) mLinkProperties.removeRoute(route); diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java index 674b0ac398..8b01c47949 100644 --- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java +++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java @@ -39,6 +39,7 @@ 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.ConnectivityCommonFlags.USE_ROUTE_PARCEL_IPCS; import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH; import static com.android.networkstack.tethering.TetheringFeatureFlags.TETHERING_AND_P2P_GO_LOCAL_AGENT; @@ -234,6 +235,9 @@ public class IpServerTest { eq(TETHERING_AND_P2P_GO_LOCAL_AGENT)); doReturn(agentFeatureEnabled).when(mDependencies) .isTetheringAndP2pGoLocalAgentBetaFlagEnabled(); + doAnswer( + invocation -> mFeatureFlags.getOrDefault((String) invocation.getArgument(1), true) + ).when(mDependencies).isFeatureNotChickenedOut(any(), anyString()); if (isAtLeastV()) { when(mDependencies.makeNetworkAgent(any(), any(), anyString(), anyInt(), any())) .thenReturn(mNetworkAgent); @@ -385,6 +389,7 @@ public class IpServerTest { } @Test + @FeatureFlag(name = USE_ROUTE_PARCEL_IPCS) public void canBeTetheredAsBluetooth() throws Exception { initStateMachine(TETHERING_BLUETOOTH); @@ -404,13 +409,11 @@ public class IpServerTest { inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME); if (isTetheringNetworkAgentFeatureEnabled()) { inOrder.verify(mNetd, never()).networkAddInterface(anyInt(), anyString()); - inOrder.verify(mNetd, never()) - .networkAddRoute(anyInt(), anyString(), anyString(), anyString()); + verifyNeverNetworkAddRoute(inOrder); } 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()); + verifyNetworkAddRoute(inOrder, true, 2); } inOrder.verify(mCallback).updateInterfaceState( mIpServer, STATE_TETHERED, TETHER_ERROR_NO_ERROR); @@ -451,6 +454,7 @@ public class IpServerTest { } @Test + @FeatureFlag(name = USE_ROUTE_PARCEL_IPCS) public void canBeTetheredAsUsb() throws Exception { initStateMachine(TETHERING_USB); @@ -465,12 +469,10 @@ public class IpServerTest { inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME); if (isTetheringNetworkAgentFeatureEnabled()) { inOrder.verify(mNetd, never()).networkAddInterface(anyInt(), anyString()); - inOrder.verify(mNetd, never()) - .networkAddRoute(anyInt(), anyString(), anyString(), anyString()); + verifyNeverNetworkAddRoute(inOrder); } 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()); + verifyNetworkAddRoute(inOrder, true, 2); } inOrder.verify(mCallback).updateInterfaceState( mIpServer, STATE_TETHERED, TETHER_ERROR_NO_ERROR); @@ -481,6 +483,7 @@ public class IpServerTest { } @Test + @FeatureFlag(name = USE_ROUTE_PARCEL_IPCS) public void canBeTetheredAsWifiP2p_NotUsingDedicatedIp() throws Exception { initStateMachine(TETHERING_WIFI_P2P); @@ -494,8 +497,7 @@ public class IpServerTest { IFACE_NAME.equals(cfg.ifName) && assertNotContainsFlag(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()); + verifyNetworkAddRoute(inOrder, true, 2); inOrder.verify(mCallback).updateInterfaceState( mIpServer, STATE_LOCAL_ONLY, TETHER_ERROR_NO_ERROR); inOrder.verify(mCallback).updateLinkProperties( @@ -505,6 +507,7 @@ public class IpServerTest { } @Test + @FeatureFlag(name = USE_ROUTE_PARCEL_IPCS) public void canBeTetheredAsWifiP2p_UsingDedicatedIp() throws Exception { initStateMachine(TETHERING_WIFI_P2P, false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD, true /* shouldEnableWifiP2pDedicatedIp */); @@ -521,8 +524,7 @@ public class IpServerTest { IFACE_NAME.equals(cfg.ifName) && assertNotContainsFlag(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()); + verifyNetworkAddRoute(inOrder, true, 2); inOrder.verify(mCallback).updateInterfaceState( mIpServer, STATE_LOCAL_ONLY, TETHER_ERROR_NO_ERROR); inOrder.verify(mCallback).updateLinkProperties( @@ -789,6 +791,7 @@ public class IpServerTest { } @Test + @FeatureFlag(name = USE_ROUTE_PARCEL_IPCS) public void testOnNewPrefixRequest() throws Exception { initStateMachine(TETHERING_NCM); dispatchCommand(IpServer.CMD_TETHER_REQUESTED, 0, 0, @@ -810,8 +813,7 @@ public class IpServerTest { inOrder.verify(mRoutingCoordinatorManager, never()).requestDownstreamAddress(any()); 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()); + verifyNetworkAddRoute(inOrder, true, 2); inOrder.verify(mCallback).updateInterfaceState( mIpServer, STATE_LOCAL_ONLY, TETHER_ERROR_NO_ERROR); inOrder.verify(mCallback).updateLinkProperties(eq(mIpServer), lpCaptor.capture()); @@ -1120,24 +1122,67 @@ public class IpServerTest { return true; } - @FeatureFlag(name = Flags.FLAG_TETHERING_AND_P2P_GO_LOCAL_AGENT) + private void verifyNeverNetworkAddRoute(InOrder inOrder) throws Exception { + inOrder.verify(mNetd, never()).networkAddRoute(anyInt(), anyString(), anyString(), + anyString()); + } + + private void verifyNetworkAddRoute(InOrder inOrder, boolean useParcelMethods, int times) + throws Exception { + if (useParcelMethods) { + inOrder.verify(mNetd, times(times)).networkAddRouteParcel(eq(INetd.LOCAL_NET_ID), + argThat(routeInfo -> routeInfo != null && IFACE_NAME.equals(routeInfo.ifName))); + inOrder.verify(mNetd, never()).networkAddRoute(anyInt(), anyString(), anyString(), + anyString()); + } else { + inOrder.verify(mNetd, times(times)).networkAddRoute(eq(INetd.LOCAL_NET_ID), + eq(IFACE_NAME), any(), any()); + inOrder.verify(mNetd, never()).networkAddRouteParcel(anyInt(), any()); + } + } + @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @FeatureFlag(name = Flags.FLAG_TETHERING_AND_P2P_GO_LOCAL_AGENT) + @FeatureFlag(name = USE_ROUTE_PARCEL_IPCS) @Test public void testTetheringNetworkAgent_tetheringAgentEnabled() throws Exception { - doTestTetheringNetworkAgent(CONNECTIVITY_SCOPE_GLOBAL, true); - doTestTetheringNetworkAgent(CONNECTIVITY_SCOPE_LOCAL, true); + doTestTetheringNetworkAgent(CONNECTIVITY_SCOPE_GLOBAL, true, true); + doTestTetheringNetworkAgent(CONNECTIVITY_SCOPE_LOCAL, true, true); } - @FeatureFlag(name = Flags.FLAG_TETHERING_AND_P2P_GO_LOCAL_AGENT, enabled = false) - @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + + @FeatureFlag(name = USE_ROUTE_PARCEL_IPCS) @Test public void testTetheringNetworkAgent_tetheringAgentDisabled() throws Exception { - doTestTetheringNetworkAgent(CONNECTIVITY_SCOPE_GLOBAL, false); - doTestTetheringNetworkAgent(CONNECTIVITY_SCOPE_LOCAL, false); + doTestTetheringNetworkAgent(CONNECTIVITY_SCOPE_GLOBAL, false, true); + doTestTetheringNetworkAgent(CONNECTIVITY_SCOPE_LOCAL, false, true); } - private void doTestTetheringNetworkAgent(int scope, boolean expectAgentEnabled) + // todo(b/441359615) : remove the test while cleaning up flag USE_ROUTE_PARCEL_IPCS. + @IgnoreAfter(Build.VERSION_CODES.BAKLAVA) + @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @FeatureFlag(name = USE_ROUTE_PARCEL_IPCS, enabled = false) + @Test + public void testTetheringNetworkAgent_UseRouteParcelDisabled_RouteMethodsCalled() throws Exception { + doTestTetheringNetworkAgent(CONNECTIVITY_SCOPE_GLOBAL, false, false); + doTestTetheringNetworkAgent(CONNECTIVITY_SCOPE_LOCAL, false, false); + } + + @IgnoreUpTo(Build.VERSION_CODES.BAKLAVA) + @FeatureFlag(name = USE_ROUTE_PARCEL_IPCS, enabled = false) + @Test + public void testTetheringNetworkAgent_postBaklava_RouteParcelMethodsCalled() + throws Exception { + // On Android versions post-Baklava, the implementation exclusively uses route parcel + // IPCs. As a result, the USE_ROUTE_PARCEL_IPCS feature flag is no longer checked and + // has no effect. + doTestTetheringNetworkAgent(CONNECTIVITY_SCOPE_GLOBAL, false, true); + doTestTetheringNetworkAgent(CONNECTIVITY_SCOPE_LOCAL, false, true); + } + + private void doTestTetheringNetworkAgent(int scope, boolean expectAgentEnabled, + boolean useRouteParcelIpcs) throws Exception { initStateMachine(TETHERING_USB); final InOrder inOrder = inOrder(mNetworkAgent, mNetd); @@ -1148,12 +1193,12 @@ public class IpServerTest { 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()); + verifyNeverNetworkAddRoute(inOrder); 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()); + verifyNetworkAddRoute(inOrder, useRouteParcelIpcs, 2); inOrder.verify(mNetworkAgent, never()).sendLinkProperties(any()); inOrder.verify(mNetworkAgent, never()).markConnected(); } @@ -1226,7 +1271,7 @@ public class IpServerTest { inOrder.verify(mNetworkAgent).register(); inOrder.verify(mNetd, never()).networkCreate(any()); - inOrder.verify(mNetd, never()).networkAddRoute(anyInt(), anyString(), any(), any()); + verifyNeverNetworkAddRoute(inOrder); // Ipv6 link local route won't show up in the LinkProperties, so just // verify ipv4 route. @@ -1249,7 +1294,7 @@ public class IpServerTest { lp.setInterfaceName(UPSTREAM_IFACE); lp.setLinkAddresses(UPSTREAM_ADDRESSES); dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, -1); - inOrder.verify(mNetd, never()).networkAddRoute(anyInt(), anyString(), any(), any()); + verifyNeverNetworkAddRoute(inOrder); inOrder.verify(mNetworkAgent).sendLinkProperties(lpCaptor.capture()); // Expect one Ipv4 route, plus one Ipv6 route. 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 1421a7e149..ed823730a0 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java @@ -77,6 +77,7 @@ import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; 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.ConnectivityCommonFlags.USE_ROUTE_PARCEL_IPCS; 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; @@ -211,6 +212,7 @@ import com.android.net.module.util.InterfaceParams; import com.android.net.module.util.PrivateAddressCoordinator; import com.android.net.module.util.RoutingCoordinatorManager; import com.android.net.module.util.RoutingCoordinatorService; +import com.android.net.module.util.SdkUtil; import com.android.net.module.util.SharedLog; import com.android.net.module.util.ip.IpNeighborMonitor; import com.android.networkstack.apishim.common.BluetoothPanShim; @@ -494,6 +496,11 @@ public class TetheringTest { public boolean isTetheringAndP2pGoLocalAgentBetaFlagEnabled() { return mFeatureFlags.getOrDefault(Flags.FLAG_TETHERING_AND_P2P_GO_LOCAL_AGENT, false); } + + @Override + public boolean isFeatureNotChickenedOut(Context context, String name) { + return mFeatureFlags.getOrDefault(name, true); + } } public class MockTetheringDependencies extends TetheringDependencies { @@ -994,11 +1001,10 @@ public class TetheringTest { verify(mNetd).tetherInterfaceAdd(ifname); if (expectAgentEnabled) { verify(mNetd, never()).networkAddInterface(anyInt(), anyString()); - verify(mNetd, never()).networkAddRoute(anyInt(), anyString(), anyString(), anyString()); + verifyNeverNetworkAddRoute(); } else { verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, ifname); - verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(ifname), - anyString(), anyString()); + verifyNetworkAddRoute(ifname, 2); } } @@ -2250,11 +2256,10 @@ public class TetheringTest { verify(mNetd, times(1)).tetherInterfaceAdd(TEST_WLAN_IFNAME); if (isTetheringNetworkAgentFeatureEnabled()) { verify(mNetd, never()).networkAddInterface(anyInt(), anyString()); - verify(mNetd, never()).networkAddRoute(anyInt(), anyString(), anyString(), anyString()); + verifyNeverNetworkAddRoute(); } 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()); + verifyNetworkAddRoute(TEST_WLAN_IFNAME, 2); } verify(mWifiManager).updateInterfaceIpState( TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED); @@ -4183,19 +4188,17 @@ public class TetheringTest { verify(mNetd).tetherInterfaceAdd(TEST_BT_IFNAME); if (isTetheringNetworkAgentFeatureEnabled()) { verify(mNetd, never()).networkAddInterface(anyInt(), anyString()); - verify(mNetd, never()).networkAddRoute(anyInt(), anyString(), anyString(), anyString()); + verifyNeverNetworkAddRoute(); } 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()); + verifyNetworkAddRoute(TEST_BT_IFNAME, 2); } verify(mNetd).ipfwdEnableForwarding(TETHERING_NAME); verify(mNetd).tetherStartWithConfiguration(any()); if (isTetheringNetworkAgentFeatureEnabled()) { - verify(mNetd, never()).networkAddRoute(anyInt(), anyString(), anyString(), anyString()); + verifyNeverNetworkAddRoute(); } else { - verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(TEST_BT_IFNAME), - anyString(), anyString()); + verifyNetworkAddRoute(TEST_BT_IFNAME, 2); } verifyNoMoreInteractions(mNetd); reset(mNetd); @@ -4208,6 +4211,29 @@ public class TetheringTest { return false; } + private void verifyNeverNetworkAddRoute() throws Exception { + verify(mNetd, never()).networkAddRoute(anyInt(), anyString(), anyString(), anyString()); + verify(mNetd, never()).networkAddRouteParcel(anyInt(), any()); + } + + private boolean useRouteParcelIpcs() { + return SdkUtil.isAtLeast25Q4() + || mFeatureFlags.getOrDefault(USE_ROUTE_PARCEL_IPCS, true); + } + + private void verifyNetworkAddRoute(String ifName, int times) + throws Exception { + if (useRouteParcelIpcs()) { + verify(mNetd, times(times)) + .networkAddRouteParcel(eq(INetd.LOCAL_NET_ID), + argThat((parcel) -> ifName.equals(parcel.ifName))); + } else { + verify(mNetd, times(times)) + .networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(ifName), + anyString(), anyString()); + } + } + private void verifyNetdCommandForBtTearDown() throws Exception { verify(mNetd).tetherApplyDnsInterfaces(); verify(mNetd).tetherInterfaceRemove(TEST_BT_IFNAME); 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 4af516d0e9..e38b2c0eff 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 @@ -176,27 +176,27 @@ public class NetdUtils { /** Add |routes| to the given network. */ public static void addRoutesToNetwork(final INetd netd, int netId, final String iface, - final List routes) { + final List routes, final boolean useRouteParcel) { for (RouteInfo route : routes) { if (!route.isDefaultRoute()) { - modifyRoute(netd, ModifyOperation.ADD, netId, route); + modifyRoute(netd, ModifyOperation.ADD, netId, route, useRouteParcel); } } // IPv6 link local should be activated always. modifyRoute(netd, ModifyOperation.ADD, netId, - new RouteInfo(new IpPrefix("fe80::/64"), null, iface, RTN_UNICAST)); + new RouteInfo(new IpPrefix("fe80::/64"), null, iface, RTN_UNICAST), useRouteParcel); } /** Remove routes from the given network. */ public static int removeRoutesFromNetwork(final INetd netd, int netId, - final List routes) { + final List routes, final boolean useRouteParcel) { int failures = 0; for (RouteInfo route : routes) { try { - modifyRoute(netd, ModifyOperation.REMOVE, netId, route); + modifyRoute(netd, ModifyOperation.REMOVE, netId, route, useRouteParcel); } catch (IllegalStateException e) { failures++; } @@ -231,18 +231,26 @@ public class NetdUtils { /** Add or remove |route|. */ public static void modifyRoute(final INetd netd, final ModifyOperation op, final int netId, - final RouteInfo route) { - final String ifName = route.getInterface(); - final String dst = route.getDestination().toString(); - final String nextHop = findNextHop(route); - + final RouteInfo route, final boolean useRouteParcel) { try { switch(op) { case ADD: - netd.networkAddRoute(netId, ifName, dst, nextHop); + if (useRouteParcel) { + RouteInfoParcel routeParcel = NetdUtils.toRouteInfoParcel(route); + netd.networkAddRouteParcel(netId, routeParcel); + } else { + netd.networkAddRoute(netId, route.getInterface(), + route.getDestination().toString(), findNextHop(route)); + } break; case REMOVE: - netd.networkRemoveRoute(netId, ifName, dst, nextHop); + if (useRouteParcel) { + RouteInfoParcel routeParcel = NetdUtils.toRouteInfoParcel(route); + netd.networkRemoveRouteParcel(netId, routeParcel); + } else { + netd.networkRemoveRoute(netId, route.getInterface(), + route.getDestination().toString(), findNextHop(route)); + } break; default: throw new IllegalStateException("Unsupported modify operation:" + op); @@ -256,31 +264,10 @@ public class NetdUtils { * 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.nextHop = findNextHop(route); rip.mtu = route.getMtu(); return rip; diff --git a/staticlibs/framework/com/android/net/module/util/ConnectivityCommonFlags.java b/staticlibs/framework/com/android/net/module/util/ConnectivityCommonFlags.java new file mode 100644 index 0000000000..d48818d0b4 --- /dev/null +++ b/staticlibs/framework/com/android/net/module/util/ConnectivityCommonFlags.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2025 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; + +/** + * Collection of flag constants shared across modules. + * @hide + */ +public class ConnectivityCommonFlags { + + /** + * A feature flag to use the network{Add,Remove}RouteParcel netd IPCs instead of the legacy + * network{Add,Remote}Route IPCs. This flag is used by both the connectivity and wifi modules. + */ + public static final String USE_ROUTE_PARCEL_IPCS = "use_route_parcel_ipcs"; +} diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java index 7a65bd5a38..ed74a918c4 100644 --- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java @@ -189,6 +189,7 @@ import static com.android.testutils.MiscAsserts.assertLength; import static com.android.testutils.MiscAsserts.assertRunsInAtMost; import static com.android.testutils.MiscAsserts.assertSameElements; import static com.android.testutils.MiscAsserts.assertThrows; +import static com.android.testutils.TestPermissionUtil.runAsShell; import static com.android.testutils.TestableNetworkCallback.Event.AVAILABLE; import static com.android.testutils.TestableNetworkCallback.Event.BLOCKED_STATUS; import static com.android.testutils.TestableNetworkCallback.Event.BLOCKED_STATUS_INT; @@ -199,7 +200,6 @@ import static com.android.testutils.TestableNetworkCallback.Event.NETWORK_CAPS_U import static com.android.testutils.TestableNetworkCallback.Event.RESUMED; import static com.android.testutils.TestableNetworkCallback.Event.SUSPENDED; import static com.android.testutils.TestableNetworkCallback.Event.UNAVAILABLE; -import static com.android.testutils.TestPermissionUtil.runAsShell; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString;