Merge changes from topic "refactor-pac" into main

* changes:
  Update unit tests to reflect the changes to RoutingCoordinator and PrivateAddressCoordinator
  Move PrivateAddressCoordinator from Tethering to staticlibs
  Refator IpServer to allocate IPv4 address via RoutingCoordinatorManager API
  Introduce PrivateAddressCoordinator methods at RoutingCoordinatorManager
This commit is contained in:
Treehugger Robot 2024-10-17 01:28:02 +00:00 • committed by Gerrit Code Review
commit e2a031b159
14 changed files with 454 additions and 143 deletions

View file

@ -70,13 +70,13 @@ 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.InterfaceParams;
import com.android.net.module.util.IIpv4PrefixRequest;
import com.android.net.module.util.NetdUtils;
import com.android.net.module.util.RoutingCoordinatorManager;
import com.android.net.module.util.SharedLog;
import com.android.net.module.util.SyncStateMachine.StateInfo;
import com.android.net.module.util.ip.InterfaceController;
import com.android.networkstack.tethering.BpfCoordinator;
import com.android.networkstack.tethering.PrivateAddressCoordinator;
import com.android.networkstack.tethering.TetheringConfiguration;
import com.android.networkstack.tethering.metrics.TetheringMetrics;
import com.android.networkstack.tethering.util.InterfaceSet;
@ -242,9 +242,10 @@ public class IpServer extends StateMachineShim {
private final BpfCoordinator mBpfCoordinator;
@NonNull
private final RoutingCoordinatorManager mRoutingCoordinator;
@NonNull
private final IIpv4PrefixRequest mIpv4PrefixRequest;
private final Callback mCallback;
private final InterfaceController mInterfaceCtrl;
private final PrivateAddressCoordinator mPrivateAddressCoordinator;
private final String mIfaceName;
private final int mInterfaceType;
@ -301,7 +302,7 @@ public class IpServer extends StateMachineShim {
String ifaceName, Handler handler, int interfaceType, SharedLog log,
INetd netd, @NonNull BpfCoordinator bpfCoordinator,
RoutingCoordinatorManager routingCoordinatorManager, Callback callback,
TetheringConfiguration config, PrivateAddressCoordinator addressCoordinator,
TetheringConfiguration config,
TetheringMetrics tetheringMetrics, Dependencies deps) {
super(ifaceName, USE_SYNC_SM ? null : handler.getLooper());
mHandler = handler;
@ -309,6 +310,12 @@ public class IpServer extends StateMachineShim {
mNetd = netd;
mBpfCoordinator = bpfCoordinator;
mRoutingCoordinator = routingCoordinatorManager;
mIpv4PrefixRequest = new IIpv4PrefixRequest.Stub() {
@Override
public void onIpv4PrefixConflict(IpPrefix ipPrefix) throws RemoteException {
sendMessage(CMD_NOTIFY_PREFIX_CONFLICT);
}
};
mCallback = callback;
mInterfaceCtrl = new InterfaceController(ifaceName, mNetd, mLog);
mIfaceName = ifaceName;
@ -317,7 +324,6 @@ public class IpServer extends StateMachineShim {
mUsingLegacyDhcp = config.useLegacyDhcpServer();
mP2pLeasesSubnetPrefixLength = config.getP2pLeasesSubnetPrefixLength();
mIsWifiP2pDedicatedIpEnabled = config.shouldEnableWifiP2pDedicatedIp();
mPrivateAddressCoordinator = addressCoordinator;
mDeps = deps;
mTetheringMetrics = tetheringMetrics;
resetLinkProperties();
@ -395,6 +401,11 @@ public class IpServer extends StateMachineShim {
return mInterfaceParams;
}
@VisibleForTesting
public IIpv4PrefixRequest getIpv4PrefixRequest() {
return mIpv4PrefixRequest;
}
/**
* Get the latest list of DHCP leases that was reported. Must be called on the IpServer looper
* thread.
@ -643,7 +654,7 @@ public class IpServer extends StateMachineShim {
// NOTE: All of configureIPv4() will be refactored out of existence
// into calls to InterfaceController, shared with startIPv4().
mInterfaceCtrl.clearIPv4Address();
mPrivateAddressCoordinator.releaseDownstream(this);
mRoutingCoordinator.releaseDownstream(mIpv4PrefixRequest);
mBpfCoordinator.tetherOffloadClientClear(this);
mIpv4Address = null;
mStaticIpv4ServerAddr = null;
@ -714,7 +725,8 @@ public class IpServer extends StateMachineShim {
if (shouldUseWifiP2pDedicatedIp()) return new LinkAddress(LEGACY_WIFI_P2P_IFACE_ADDRESS);
return mPrivateAddressCoordinator.requestDownstreamAddress(this, scope, useLastAddress);
return mRoutingCoordinator.requestDownstreamAddress(mInterfaceType, scope, useLastAddress,
mIpv4PrefixRequest);
}
private boolean startIPv6() {

View file

@ -253,7 +253,6 @@ public class Tethering {
private final TetheringNotificationUpdater mNotificationUpdater;
private final UserManager mUserManager;
private final BpfCoordinator mBpfCoordinator;
private final PrivateAddressCoordinator mPrivateAddressCoordinator;
private final TetheringMetrics mTetheringMetrics;
private final WearableConnectionManager mWearableConnectionManager;
private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID;
@ -359,7 +358,6 @@ public class Tethering {
// Load tethering configuration.
updateConfiguration();
mConfig.readEnableSyncSM(mContext);
mPrivateAddressCoordinator = mDeps.makePrivateAddressCoordinator(mContext);
// Must be initialized after tethering configuration is loaded because BpfCoordinator
// constructor needs to use the configuration.
@ -2001,11 +1999,11 @@ public class Tethering {
final UpstreamNetworkState ns = (UpstreamNetworkState) o;
switch (arg1) {
case UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES:
mPrivateAddressCoordinator.updateUpstreamPrefix(
mRoutingCoordinator.updateUpstreamPrefix(
ns.linkProperties, ns.networkCapabilities, ns.network);
break;
case UpstreamNetworkMonitor.EVENT_ON_LOST:
mPrivateAddressCoordinator.removeUpstreamPrefix(ns.network);
mRoutingCoordinator.removeUpstreamPrefix(ns.network);
break;
}
@ -2075,7 +2073,7 @@ public class Tethering {
return;
}
mPrivateAddressCoordinator.maybeRemoveDeprecatedUpstreams();
mRoutingCoordinator.maybeRemoveDeprecatedUpstreams();
mUpstreamNetworkMonitor.startObserveAllNetworks();
// TODO: De-duplicate with updateUpstreamWanted() below.
@ -2663,11 +2661,6 @@ public class Tethering {
dumpBpf(pw);
pw.println("Private address coordinator:");
pw.increaseIndent();
mPrivateAddressCoordinator.dump(pw);
pw.decreaseIndent();
if (mWearableConnectionManager != null) {
pw.println("WearableConnectionManager:");
pw.increaseIndent();
@ -2821,8 +2814,7 @@ public class Tethering {
mLog.i("adding IpServer for: " + iface);
final TetherState tetherState = new TetherState(
new IpServer(iface, mHandler, interfaceType, mLog, mNetd, mBpfCoordinator,
mRoutingCoordinator, new ControlCallback(), mConfig,
mPrivateAddressCoordinator, mTetheringMetrics,
mRoutingCoordinator, new ControlCallback(), mConfig, mTetheringMetrics,
mDeps.makeIpServerDependencies()), isNcm);
mTetherStates.put(iface, tetherState);
tetherState.ipServer.start();

View file

@ -36,6 +36,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import com.android.modules.utils.build.SdkLevel;
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.SharedLog;
@ -136,7 +137,10 @@ public abstract class TetheringDependencies {
public RoutingCoordinatorManager getRoutingCoordinator(Context context, SharedLog log) {
IBinder binder;
if (!SdkLevel.isAtLeastS()) {
binder = new RoutingCoordinatorService(getINetd(context, log));
final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
binder =
new RoutingCoordinatorService(
getINetd(context, log), cm::getAllNetworks, context);
} else {
binder = ConnectivityInternalApiUtil.getRoutingCoordinator(context);
}
@ -174,14 +178,6 @@ public abstract class TetheringDependencies {
return TextUtils.equals(SystemProperties.get("ro.tether.denied"), "true");
}
/**
* Make PrivateAddressCoordinator to be used by Tethering.
*/
public PrivateAddressCoordinator makePrivateAddressCoordinator(Context ctx) {
final ConnectivityManager cm = ctx.getSystemService(ConnectivityManager.class);
return new PrivateAddressCoordinator(cm::getAllNetworks, ctx);
}
/**
* Make BluetoothPanShim object to enable/disable bluetooth tethering.
*

View file

@ -89,13 +89,10 @@ import android.text.TextUtils;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.RoutingCoordinatorManager;
import com.android.net.module.util.SdkUtil.LateSdk;
import com.android.net.module.util.SharedLog;
import com.android.networkstack.tethering.BpfCoordinator;
import com.android.networkstack.tethering.PrivateAddressCoordinator;
import com.android.networkstack.tethering.TetheringConfiguration;
import com.android.networkstack.tethering.metrics.TetheringMetrics;
import com.android.networkstack.tethering.util.InterfaceSet;
@ -175,7 +172,6 @@ public class IpServerTest {
@Mock private DadProxy mDadProxy;
@Mock private RouterAdvertisementDaemon mRaDaemon;
@Mock private IpServer.Dependencies mDependencies;
@Mock private PrivateAddressCoordinator mAddressCoordinator;
@Mock private RoutingCoordinatorManager mRoutingCoordinatorManager;
@Mock private NetworkStatsManager mStatsManager;
@Mock private TetheringConfiguration mTetherConfig;
@ -261,9 +257,9 @@ public class IpServerTest {
verify(mBpfCoordinator).updateIpv6UpstreamInterface(
mIpServer, interfaceParams.index, upstreamPrefixes);
}
reset(mNetd, mBpfCoordinator, mCallback, mAddressCoordinator);
when(mAddressCoordinator.requestDownstreamAddress(any(), anyInt(),
anyBoolean())).thenReturn(mTestAddress);
reset(mNetd, mBpfCoordinator, mCallback, mRoutingCoordinatorManager);
when(mRoutingCoordinatorManager.requestDownstreamAddress(anyInt(), anyInt(),
anyBoolean(), any())).thenReturn(mTestAddress);
}
@SuppressWarnings("DoNotCall") // Ignore warning for synchronous to call to Thread.run()
@ -284,8 +280,8 @@ public class IpServerTest {
@Before public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
when(mSharedLog.forSubComponent(anyString())).thenReturn(mSharedLog);
when(mAddressCoordinator.requestDownstreamAddress(any(), anyInt(),
anyBoolean())).thenReturn(mTestAddress);
when(mRoutingCoordinatorManager.requestDownstreamAddress(anyInt(), anyInt(),
anyBoolean(), any())).thenReturn(mTestAddress);
when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(DEFAULT_USING_BPF_OFFLOAD);
when(mTetherConfig.useLegacyDhcpServer()).thenReturn(false /* default value */);
@ -297,7 +293,7 @@ public class IpServerTest {
mLooper = new TestLooper();
mHandler = new Handler(mLooper.getLooper());
return new IpServer(IFACE_NAME, mHandler, interfaceType, mSharedLog, mNetd, mBpfCoordinator,
mRoutingCoordinatorManager, mCallback, mTetherConfig, mAddressCoordinator,
mRoutingCoordinatorManager, mCallback, mTetherConfig,
mTetheringMetrics, mDependencies);
}
@ -349,10 +345,14 @@ public class IpServerTest {
initStateMachine(TETHERING_BLUETOOTH);
dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator);
InOrder inOrder = inOrder(mCallback, mNetd, mRoutingCoordinatorManager);
if (isAtLeastT()) {
inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(),
eq(CONNECTIVITY_SCOPE_GLOBAL), eq(true));
inOrder.verify(mRoutingCoordinatorManager)
.requestDownstreamAddress(
eq(TETHERING_BLUETOOTH),
eq(CONNECTIVITY_SCOPE_GLOBAL),
eq(true),
any());
inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP)));
}
@ -373,7 +373,7 @@ public class IpServerTest {
initTetheredStateMachine(TETHERING_BLUETOOTH, null);
dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator);
InOrder inOrder = inOrder(mCallback, mNetd, mRoutingCoordinatorManager);
inOrder.verify(mNetd).tetherApplyDnsInterfaces();
inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME);
inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
@ -384,7 +384,7 @@ public class IpServerTest {
argThat(cfg -> assertContainsFlag(cfg.flags, IF_STATE_DOWN)));
}
inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> cfg.flags.length == 0));
inOrder.verify(mAddressCoordinator).releaseDownstream(any());
inOrder.verify(mRoutingCoordinatorManager).releaseDownstream(any());
inOrder.verify(mCallback).updateInterfaceState(
mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
inOrder.verify(mCallback).updateLinkProperties(
@ -392,7 +392,7 @@ public class IpServerTest {
verify(mTetheringMetrics).updateErrorCode(eq(TETHERING_BLUETOOTH),
eq(TETHER_ERROR_NO_ERROR));
verify(mTetheringMetrics).sendReport(eq(TETHERING_BLUETOOTH));
verifyNoMoreInteractions(mNetd, mCallback, mAddressCoordinator);
verifyNoMoreInteractions(mNetd, mCallback, mRoutingCoordinatorManager);
}
@Test
@ -400,9 +400,9 @@ public class IpServerTest {
initStateMachine(TETHERING_USB);
dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator);
inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(),
eq(CONNECTIVITY_SCOPE_GLOBAL), eq(true));
InOrder inOrder = inOrder(mCallback, mNetd, mRoutingCoordinatorManager);
inOrder.verify(mRoutingCoordinatorManager).requestDownstreamAddress(anyInt(),
eq(CONNECTIVITY_SCOPE_GLOBAL), eq(true), any());
inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP)));
inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
@ -414,7 +414,7 @@ public class IpServerTest {
inOrder.verify(mCallback).updateLinkProperties(
eq(mIpServer), mLinkPropertiesCaptor.capture());
assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue());
verifyNoMoreInteractions(mNetd, mCallback, mAddressCoordinator);
verifyNoMoreInteractions(mNetd, mCallback, mRoutingCoordinatorManager);
}
@Test
@ -422,9 +422,9 @@ public class IpServerTest {
initStateMachine(TETHERING_WIFI_P2P);
dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_LOCAL_ONLY);
InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator);
inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(),
eq(CONNECTIVITY_SCOPE_LOCAL), eq(true));
InOrder inOrder = inOrder(mCallback, mNetd, mRoutingCoordinatorManager);
inOrder.verify(mRoutingCoordinatorManager).requestDownstreamAddress(anyInt(),
eq(CONNECTIVITY_SCOPE_LOCAL), eq(true), any());
inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
IFACE_NAME.equals(cfg.ifName) && assertNotContainsFlag(cfg.flags, IF_STATE_UP)));
inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
@ -436,7 +436,7 @@ public class IpServerTest {
inOrder.verify(mCallback).updateLinkProperties(
eq(mIpServer), mLinkPropertiesCaptor.capture());
assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue());
verifyNoMoreInteractions(mNetd, mCallback, mAddressCoordinator);
verifyNoMoreInteractions(mNetd, mCallback, mRoutingCoordinatorManager);
}
@Test
@ -445,11 +445,11 @@ public class IpServerTest {
true /* shouldEnableWifiP2pDedicatedIp */);
dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_LOCAL_ONLY);
InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator);
InOrder inOrder = inOrder(mCallback, mNetd, mRoutingCoordinatorManager);
// When using WiFi P2p dedicated IP, the IpServer just picks the IP address without
// requesting for it at PrivateAddressCoordinator.
inOrder.verify(mAddressCoordinator, never()).requestDownstreamAddress(any(), anyInt(),
anyBoolean());
// requesting for it at RoutingCoordinatorManager.
inOrder.verify(mRoutingCoordinatorManager, never())
.requestDownstreamAddress(anyInt(), anyInt(), anyBoolean(), any());
inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
IFACE_NAME.equals(cfg.ifName) && assertNotContainsFlag(cfg.flags, IF_STATE_UP)));
inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
@ -463,7 +463,7 @@ public class IpServerTest {
assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue());
assertEquals(List.of(new LinkAddress(LEGACY_WIFI_P2P_IFACE_ADDRESS)),
mLinkPropertiesCaptor.getValue().getLinkAddresses());
verifyNoMoreInteractions(mNetd, mCallback, mAddressCoordinator);
verifyNoMoreInteractions(mNetd, mCallback, mRoutingCoordinatorManager);
}
@Test
@ -569,15 +569,9 @@ public class IpServerTest {
initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
clearInvocations(
mNetd, mCallback, mAddressCoordinator, mBpfCoordinator, mRoutingCoordinatorManager);
mNetd, mCallback, mBpfCoordinator, mRoutingCoordinatorManager);
dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
InOrder inOrder =
inOrder(
mNetd,
mCallback,
mAddressCoordinator,
mBpfCoordinator,
mRoutingCoordinatorManager);
InOrder inOrder = inOrder(mNetd, mCallback, mBpfCoordinator, mRoutingCoordinatorManager);
inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mRoutingCoordinatorManager)
.removeInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
@ -592,15 +586,14 @@ public class IpServerTest {
inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
inOrder.verify(mNetd, times(isAtLeastT() ? 2 : 1)).interfaceSetCfg(
argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
inOrder.verify(mAddressCoordinator).releaseDownstream(any());
inOrder.verify(mRoutingCoordinatorManager).releaseDownstream(any());
inOrder.verify(mBpfCoordinator).tetherOffloadClientClear(mIpServer);
inOrder.verify(mBpfCoordinator).removeIpServer(mIpServer);
inOrder.verify(mCallback).updateInterfaceState(
mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
inOrder.verify(mCallback).updateLinkProperties(
eq(mIpServer), any(LinkProperties.class));
verifyNoMoreInteractions(
mNetd, mCallback, mAddressCoordinator, mBpfCoordinator, mRoutingCoordinatorManager);
verifyNoMoreInteractions(mNetd, mCallback, mRoutingCoordinatorManager, mBpfCoordinator);
}
@Test
@ -737,9 +730,9 @@ public class IpServerTest {
final ArgumentCaptor<LinkProperties> lpCaptor =
ArgumentCaptor.forClass(LinkProperties.class);
InOrder inOrder = inOrder(mNetd, mCallback, mAddressCoordinator);
inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(),
eq(CONNECTIVITY_SCOPE_LOCAL), eq(true));
InOrder inOrder = inOrder(mNetd, mCallback, mRoutingCoordinatorManager);
inOrder.verify(mRoutingCoordinatorManager).requestDownstreamAddress(anyInt(),
eq(CONNECTIVITY_SCOPE_LOCAL), eq(true), 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),
@ -747,18 +740,18 @@ public class IpServerTest {
inOrder.verify(mCallback).updateInterfaceState(
mIpServer, STATE_LOCAL_ONLY, TETHER_ERROR_NO_ERROR);
inOrder.verify(mCallback).updateLinkProperties(eq(mIpServer), lpCaptor.capture());
verifyNoMoreInteractions(mCallback, mAddressCoordinator);
verifyNoMoreInteractions(mCallback, mRoutingCoordinatorManager);
// Simulate the DHCP server receives DHCPDECLINE on MirrorLink and then signals
// onNewPrefixRequest callback.
final LinkAddress newAddress = new LinkAddress("192.168.100.125/24");
when(mAddressCoordinator.requestDownstreamAddress(any(), anyInt(),
anyBoolean())).thenReturn(newAddress);
when(mRoutingCoordinatorManager.requestDownstreamAddress(anyInt(), anyInt(),
anyBoolean(), any())).thenReturn(newAddress);
eventCallbacks.onNewPrefixRequest(new IpPrefix("192.168.42.0/24"));
mLooper.dispatchAll();
inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(),
eq(CONNECTIVITY_SCOPE_LOCAL), eq(false));
inOrder.verify(mRoutingCoordinatorManager).requestDownstreamAddress(anyInt(),
eq(CONNECTIVITY_SCOPE_LOCAL), eq(false), any());
inOrder.verify(mNetd).tetherApplyDnsInterfaces();
inOrder.verify(mCallback).updateLinkProperties(eq(mIpServer), lpCaptor.capture());
verifyNoMoreInteractions(mCallback);

View file

@ -24,8 +24,9 @@ import static android.net.TetheringManager.TETHERING_ETHERNET;
import static android.net.TetheringManager.TETHERING_USB;
import static android.net.TetheringManager.TETHERING_WIFI;
import static android.net.TetheringManager.TETHERING_WIFI_P2P;
import static android.net.ip.IpServer.CMD_NOTIFY_PREFIX_CONFLICT;
import static com.android.networkstack.tethering.PrivateAddressCoordinator.TETHER_FORCE_RANDOM_PREFIX_BASE_SELECTION;
import static com.android.net.module.util.PrivateAddressCoordinator.TETHER_FORCE_RANDOM_PREFIX_BASE_SELECTION;
import static com.android.networkstack.tethering.util.PrefixUtils.asIpPrefix;
import static org.junit.Assert.assertEquals;
@ -34,6 +35,9 @@ import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@ -47,10 +51,14 @@ import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.ip.IpServer;
import android.os.IBinder;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.net.module.util.IIpv4PrefixRequest;
import com.android.net.module.util.PrivateAddressCoordinator;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -92,12 +100,26 @@ public final class PrivateAddressCoordinatorTest {
new IpPrefix("172.16.0.0/12"),
new IpPrefix("10.0.0.0/8")));
private void setUpIpServer(IpServer ipServer, int interfaceType) throws Exception {
when(ipServer.interfaceType()).thenReturn(interfaceType);
final IIpv4PrefixRequest request = mock(IIpv4PrefixRequest.class);
when(ipServer.getIpv4PrefixRequest()).thenReturn(request);
when(request.asBinder()).thenReturn(mock(IBinder.class));
doAnswer(
invocation -> {
ipServer.sendMessage(CMD_NOTIFY_PREFIX_CONFLICT);
return null;
})
.when(request)
.onIpv4PrefixConflict(any());
}
private void setUpIpServers() throws Exception {
when(mUsbIpServer.interfaceType()).thenReturn(TETHERING_USB);
when(mEthernetIpServer.interfaceType()).thenReturn(TETHERING_ETHERNET);
when(mHotspotIpServer.interfaceType()).thenReturn(TETHERING_WIFI);
when(mLocalHotspotIpServer.interfaceType()).thenReturn(TETHERING_WIFI);
when(mWifiP2pIpServer.interfaceType()).thenReturn(TETHERING_WIFI_P2P);
setUpIpServer(mUsbIpServer, TETHERING_USB);
setUpIpServer(mEthernetIpServer, TETHERING_ETHERNET);
setUpIpServer(mHotspotIpServer, TETHERING_WIFI);
setUpIpServer(mLocalHotspotIpServer, TETHERING_WIFI);
setUpIpServer(mWifiP2pIpServer, TETHERING_WIFI_P2P);
}
@Before
@ -112,14 +134,22 @@ public final class PrivateAddressCoordinatorTest {
spy(new PrivateAddressCoordinator(mConnectivityMgr::getAllNetworks, mDeps));
}
private LinkAddress requestDownstreamAddress(final IpServer ipServer, int scope,
boolean useLastAddress) {
final LinkAddress address = mPrivateAddressCoordinator.requestDownstreamAddress(
ipServer, scope, useLastAddress);
private LinkAddress requestDownstreamAddress(
final IpServer ipServer, int scope, boolean useLastAddress) throws Exception {
final LinkAddress address =
mPrivateAddressCoordinator.requestDownstreamAddress(
ipServer.interfaceType(),
scope,
useLastAddress,
ipServer.getIpv4PrefixRequest());
when(ipServer.getAddress()).thenReturn(address);
return address;
}
private void releaseDownstream(final IpServer ipServer) {
mPrivateAddressCoordinator.releaseDownstream(ipServer.getIpv4PrefixRequest());
}
private void updateUpstreamPrefix(UpstreamNetworkState ns) {
mPrivateAddressCoordinator.updateUpstreamPrefix(
ns.linkProperties, ns.networkCapabilities, ns.network);
@ -145,8 +175,8 @@ public final class PrivateAddressCoordinatorTest {
assertNotEquals(usbPrefix, bluetoothPrefix);
assertNotEquals(usbPrefix, newHotspotPrefix);
mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
mPrivateAddressCoordinator.releaseDownstream(mUsbIpServer);
releaseDownstream(mHotspotIpServer);
releaseDownstream(mUsbIpServer);
}
@Test
@ -158,7 +188,7 @@ public final class PrivateAddressCoordinatorTest {
CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */);
final IpPrefix hotspotPrefix = asIpPrefix(hotspotAddress);
assertNotEquals(asIpPrefix(mBluetoothAddress), hotspotPrefix);
mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
releaseDownstream(mHotspotIpServer);
// - Test previous enabled hotspot prefix(cached prefix) is reserved.
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(
@ -168,7 +198,7 @@ public final class PrivateAddressCoordinatorTest {
final IpPrefix usbPrefix = asIpPrefix(usbAddress);
assertNotEquals(asIpPrefix(mBluetoothAddress), usbPrefix);
assertNotEquals(hotspotPrefix, usbPrefix);
mPrivateAddressCoordinator.releaseDownstream(mUsbIpServer);
releaseDownstream(mUsbIpServer);
// - Test wifi p2p prefix is reserved.
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(
@ -179,7 +209,7 @@ public final class PrivateAddressCoordinatorTest {
assertNotEquals(asIpPrefix(mLegacyWifiP2pAddress), etherPrefix);
assertNotEquals(asIpPrefix(mBluetoothAddress), etherPrefix);
assertNotEquals(hotspotPrefix, etherPrefix);
mPrivateAddressCoordinator.releaseDownstream(mEthernetIpServer);
releaseDownstream(mEthernetIpServer);
}
@Test
@ -190,8 +220,8 @@ public final class PrivateAddressCoordinatorTest {
final LinkAddress usbAddress = requestDownstreamAddress(mUsbIpServer,
CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
mPrivateAddressCoordinator.releaseDownstream(mUsbIpServer);
releaseDownstream(mHotspotIpServer);
releaseDownstream(mUsbIpServer);
final LinkAddress newHotspotAddress = requestDownstreamAddress(mHotspotIpServer,
CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
@ -254,10 +284,11 @@ public final class PrivateAddressCoordinatorTest {
}
private void verifyNotifyConflictAndRelease(final IpServer ipServer) throws Exception {
verify(ipServer).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
mPrivateAddressCoordinator.releaseDownstream(ipServer);
verify(ipServer).sendMessage(CMD_NOTIFY_PREFIX_CONFLICT);
releaseDownstream(ipServer);
final int interfaceType = ipServer.interfaceType();
reset(ipServer);
setUpIpServers();
setUpIpServer(ipServer, interfaceType);
}
private int getSubAddress(final byte... ipv4Address) {
@ -273,7 +304,7 @@ public final class PrivateAddressCoordinatorTest {
final IpPrefix hotspotPrefix = asIpPrefix(address);
final IpPrefix legacyWifiP2pPrefix = asIpPrefix(mLegacyWifiP2pAddress);
assertNotEquals(legacyWifiP2pPrefix, hotspotPrefix);
mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
releaseDownstream(mHotspotIpServer);
}
@Test

View file

@ -71,7 +71,6 @@ 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.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_HIDL_1_0;
import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_NONE;
import static com.android.networkstack.tethering.PrivateAddressCoordinator.TETHER_FORCE_RANDOM_PREFIX_BASE_SELECTION;
import static com.android.networkstack.tethering.TestConnectivityManager.BROADCAST_FIRST;
import static com.android.networkstack.tethering.TestConnectivityManager.CALLBACKS_FIRST;
import static com.android.networkstack.tethering.Tethering.UserRestrictionActionListener;
@ -97,6 +96,7 @@ import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
@ -192,7 +192,9 @@ 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.PrivateAddressCoordinator;
import com.android.net.module.util.RoutingCoordinatorManager;
import com.android.net.module.util.RoutingCoordinatorService;
import com.android.net.module.util.SharedLog;
import com.android.net.module.util.ip.IpNeighborMonitor;
import com.android.networkstack.apishim.common.BluetoothPanShim;
@ -293,7 +295,6 @@ public class TetheringTest {
@Mock private BluetoothPanShim mBluetoothPanShim;
@Mock private TetheredInterfaceRequestShim mTetheredInterfaceRequestShim;
@Mock private TetheringMetrics mTetheringMetrics;
@Mock private RoutingCoordinatorManager mRoutingCoordinatorManager;
@Mock private PrivateAddressCoordinator.Dependencies mPrivateAddressCoordinatorDependencies;
private final MockIpServerDependencies mIpServerDependencies =
@ -318,12 +319,12 @@ public class TetheringTest {
private TetheringConfiguration mConfig;
private EntitlementManager mEntitleMgr;
private OffloadController mOffloadCtrl;
private PrivateAddressCoordinator mPrivateAddressCoordinator;
private SoftApCallback mSoftApCallback;
private SoftApCallback mLocalOnlyHotspotCallback;
private UpstreamNetworkMonitor mUpstreamNetworkMonitor;
private UpstreamNetworkMonitor.EventListener mEventListener;
private TetheredInterfaceCallbackShim mTetheredInterfaceCallbackShim;
private RoutingCoordinatorManager mRoutingCoordinatorManager;
private TestConnectivityManager mCm;
private boolean mForceEthernetServiceUnavailable = false;
@ -489,8 +490,16 @@ public class TetheringTest {
}
@Override
public RoutingCoordinatorManager getRoutingCoordinator(final Context context,
SharedLog log) {
public RoutingCoordinatorManager getRoutingCoordinator(
final Context context, SharedLog log) {
ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
when(mPrivateAddressCoordinatorDependencies.isFeatureEnabled(anyString()))
.thenReturn(false);
RoutingCoordinatorService service = new RoutingCoordinatorService(
getINetd(context, log),
cm::getAllNetworks,
mPrivateAddressCoordinatorDependencies);
mRoutingCoordinatorManager = spy(new RoutingCoordinatorManager(context, service));
return mRoutingCoordinatorManager;
}
@ -536,15 +545,6 @@ public class TetheringTest {
return mTetheringMetrics;
}
@Override
public PrivateAddressCoordinator makePrivateAddressCoordinator(Context ctx) {
ConnectivityManager cm = ctx.getSystemService(ConnectivityManager.class);
mPrivateAddressCoordinator =
new PrivateAddressCoordinator(
cm::getAllNetworks, mPrivateAddressCoordinatorDependencies);
return mPrivateAddressCoordinator;
}
@Override
public BluetoothPanShim makeBluetoothPanShim(BluetoothPan pan) {
try {
@ -668,8 +668,6 @@ public class TetheringTest {
.thenReturn(true);
initOffloadConfiguration(OFFLOAD_HAL_VERSION_HIDL_1_0, 0 /* defaultDisabled */);
when(mOffloadHardwareInterface.getForwardedStats(any())).thenReturn(mForwardedStats);
when(mPrivateAddressCoordinatorDependencies.isFeatureEnabled(anyString()))
.thenReturn(false);
mServiceContext = new TestContext(mContext);
mServiceContext.setUseRegisteredHandlers(true);
@ -687,6 +685,7 @@ public class TetheringTest {
new IntentFilter(ACTION_TETHER_STATE_CHANGED));
mCm = spy(new TestConnectivityManager(mServiceContext, mock(IConnectivityManager.class)));
when(mCm.getAllNetworks()).thenReturn(new Network[] {});
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)).thenReturn(true);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)).thenReturn(true);
@ -870,6 +869,9 @@ public class TetheringTest {
assertTrue(TestConnectivityManager.looksLikeDefaultRequest(reqCaptor.getValue()));
}
// Ignore calls to {@link ConnectivityManager#getallNetworks}.
verify(mCm, atLeast(0)).getAllNetworks();
// The default network request is only ever filed once.
verifyNoMoreInteractions(mCm);
}

View file

@ -2028,7 +2028,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
mCdmps = null;
}
mRoutingCoordinatorService = new RoutingCoordinatorService(netd);
mRoutingCoordinatorService =
new RoutingCoordinatorService(netd, this::getAllNetworks, mContext);
mMulticastRoutingCoordinatorService =
mDeps.makeMulticastRoutingCoordinatorService(mHandler);

View file

@ -650,6 +650,7 @@ filegroup {
name: "net-utils-all-srcs",
srcs: [
"device/**/*.java",
":framework-connectivity-shared-srcs",
":net-utils-framework-common-srcs",
],
visibility: ["//visibility:private"],

View file

@ -0,0 +1,29 @@
/*
* Copyright (C) 2024 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.net.IpPrefix;
import android.net.LinkAddress;
/** @hide */
// TODO: b/350630377 - This @Descriptor annotation workaround is to prevent the class from being
// jarjared which changes the DESCRIPTOR and casues "java.lang.SecurityException: Binder invocation
// to an incorrect interface" when calling the IPC.
@Descriptor("value=no.jarjar.com.android.net.module.util.IIpv4PrefixRequest")
interface IIpv4PrefixRequest {
void onIpv4PrefixConflict(in IpPrefix ipPrefix);
}

View file

@ -16,8 +16,14 @@
package com.android.net.module.util;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.RouteInfo;
import com.android.net.module.util.IIpv4PrefixRequest;
/** @hide */
// TODO: b/350630377 - This @Descriptor annotation workaround is to prevent the DESCRIPTOR from
// being jarjared which changes the DESCRIPTOR and casues "java.lang.SecurityException: Binder
@ -96,4 +102,34 @@ interface IRoutingCoordinator {
* cause of the failure.
*/
void removeInterfaceForward(in String fromIface, in String toIface);
/** Update the prefix of an upstream. */
void updateUpstreamPrefix(in @nullable LinkProperties lp,
in @nullable NetworkCapabilities nc,
in Network network);
/** Remove the upstream prefix of the given {@link Network}. */
void removeUpstreamPrefix(in Network network);
/** Remove the deprecated upstream networks if any. */
void maybeRemoveDeprecatedUpstreams();
/**
* Request an IPv4 address for the downstream.
*
* @param interfaceType the Tethering type (see TetheringManager#TETHERING_*).
* @param scope CONNECTIVITY_SCOPE_GLOBAL or CONNECTIVITY_SCOPE_LOCAL
* @param useLastAddress whether to use the last address
* @param request a {@link IIpv4PrefixRequest} to report conflicts
* @return an IPv4 address allocated for the downstream, could be null
*/
@nullable
LinkAddress requestDownstreamAddress(
in int interfaceType,
in int scope,
in boolean useLastAddress,
in IIpv4PrefixRequest request);
/** Release the IPv4 address allocated for the downstream. */
void releaseDownstream(in IIpv4PrefixRequest request);
}

View file

@ -0,0 +1,496 @@
/*
* Copyright (C) 2020 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 static android.net.NetworkCapabilities.TRANSPORT_VPN;
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_WIFI_P2P;
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.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTH;
import static java.util.Arrays.asList;
import android.content.Context;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
import java.util.Set;
import java.util.function.Supplier;
/**
* This class coordinate IP addresses conflict problem.
*
* Tethering downstream IP addresses may conflict with network assigned addresses. This
* coordinator is responsible for recording all of network assigned addresses and dispatched
* free address to downstream interfaces.
*
* This class is not thread-safe.
* @hide
*/
public class PrivateAddressCoordinator {
// WARNING: Keep in sync with chooseDownstreamAddress
public static final int PREFIX_LENGTH = 24;
public static final String TETHER_FORCE_RANDOM_PREFIX_BASE_SELECTION =
"tether_force_random_prefix_base_selection";
// Upstream monitor would be stopped when tethering is down. When tethering restart, downstream
// address may be requested before coordinator get current upstream notification. To ensure
// coordinator do not select conflict downstream prefix, mUpstreamPrefixMap would not be cleared
// when tethering is down. Instead tethering would remove all deprecated upstreams from
// mUpstreamPrefixMap when tethering is starting. See #maybeRemoveDeprecatedUpstreams().
private final ArrayMap<Network, List<IpPrefix>> mUpstreamPrefixMap;
// The downstreams are indexed by Ipv4PrefixRequest, which is a wrapper of the Binder object of
// IIpv4PrefixRequest.
private final ArrayMap<Ipv4PrefixRequest, Downstream> mDownstreams;
private static final String LEGACY_WIFI_P2P_IFACE_ADDRESS = "192.168.49.1/24";
private static final String LEGACY_BLUETOOTH_IFACE_ADDRESS = "192.168.44.1/24";
private final List<IpPrefix> mTetheringPrefixes;
// A supplier that returns ConnectivityManager#getAllNetworks.
private final Supplier<Network[]> mGetAllNetworksSupplier;
private final Dependencies mDeps;
// keyed by downstream type(TetheringManager.TETHERING_*).
private final ArrayMap<AddressKey, LinkAddress> mCachedAddresses;
private final Random mRandom;
/** Capture PrivateAddressCoordinator dependencies for injection. */
public static class Dependencies {
private final Context mContext;
Dependencies(Context context) {
mContext = context;
}
/**
* Check whether or not one specific experimental feature is enabled according to {@link
* DeviceConfigUtils}.
*
* @param featureName The feature's name to look up.
* @return true if this feature is enabled, or false if disabled.
*/
public boolean isFeatureEnabled(String featureName) {
return DeviceConfigUtils.isTetheringFeatureEnabled(mContext, featureName);
}
}
public PrivateAddressCoordinator(Supplier<Network[]> getAllNetworksSupplier, Context context) {
this(getAllNetworksSupplier, new Dependencies(context));
}
@VisibleForTesting
public PrivateAddressCoordinator(Supplier<Network[]> getAllNetworksSupplier,
Dependencies deps) {
mDownstreams = new ArrayMap<>();
mUpstreamPrefixMap = new ArrayMap<>();
mGetAllNetworksSupplier = getAllNetworksSupplier;
mDeps = deps;
mCachedAddresses = new ArrayMap<AddressKey, LinkAddress>();
// Reserved static addresses for bluetooth and wifi p2p.
mCachedAddresses.put(new AddressKey(TETHERING_BLUETOOTH, CONNECTIVITY_SCOPE_GLOBAL),
new LinkAddress(LEGACY_BLUETOOTH_IFACE_ADDRESS));
mCachedAddresses.put(new AddressKey(TETHERING_WIFI_P2P, CONNECTIVITY_SCOPE_LOCAL),
new LinkAddress(LEGACY_WIFI_P2P_IFACE_ADDRESS));
mTetheringPrefixes = new ArrayList<>(Arrays.asList(new IpPrefix("192.168.0.0/16"),
new IpPrefix("172.16.0.0/12"), new IpPrefix("10.0.0.0/8")));
mRandom = new Random();
}
/**
* Record a new upstream IpPrefix which may conflict with tethering downstreams. The downstreams
* will be notified if a conflict is found. When updateUpstreamPrefix is called,
* UpstreamNetworkState must have an already populated LinkProperties.
*/
public void updateUpstreamPrefix(
final LinkProperties lp, final NetworkCapabilities nc, final Network network) {
// Do not support VPN as upstream. Normally, networkCapabilities is not expected to be null,
// but just checking to be sure.
if (nc != null && nc.hasTransport(TRANSPORT_VPN)) {
removeUpstreamPrefix(network);
return;
}
final ArrayList<IpPrefix> ipv4Prefixes = getIpv4Prefixes(lp.getAllLinkAddresses());
if (ipv4Prefixes.isEmpty()) {
removeUpstreamPrefix(network);
return;
}
mUpstreamPrefixMap.put(network, ipv4Prefixes);
handleMaybePrefixConflict(ipv4Prefixes);
}
private ArrayList<IpPrefix> getIpv4Prefixes(final List<LinkAddress> linkAddresses) {
final ArrayList<IpPrefix> list = new ArrayList<>();
for (LinkAddress address : linkAddresses) {
if (!address.isIpv4()) continue;
list.add(asIpPrefix(address));
}
return list;
}
private void handleMaybePrefixConflict(final List<IpPrefix> prefixes) {
for (Map.Entry<Ipv4PrefixRequest, Downstream> entry : mDownstreams.entrySet()) {
final Ipv4PrefixRequest request = entry.getKey();
final Downstream downstream = entry.getValue();
final IpPrefix target = asIpPrefix(downstream.getAddress());
for (IpPrefix source : prefixes) {
if (isConflictPrefix(source, target)) {
try {
request.getRequest().onIpv4PrefixConflict(target);
} catch (RemoteException ignored) {
// ignore
}
break;
}
}
}
}
/** Remove IpPrefix records corresponding to input network. */
public void removeUpstreamPrefix(final Network network) {
mUpstreamPrefixMap.remove(network);
}
/**
* Maybe remove deprecated upstream records, this would be called once tethering started without
* any exiting tethered downstream.
*/
public void maybeRemoveDeprecatedUpstreams() {
if (mUpstreamPrefixMap.isEmpty()) return;
// Remove all upstreams that are no longer valid networks
final Set<Network> toBeRemoved = new HashSet<>(mUpstreamPrefixMap.keySet());
toBeRemoved.removeAll(asList(mGetAllNetworksSupplier.get()));
mUpstreamPrefixMap.removeAll(toBeRemoved);
}
// TODO: There needs to be a reserveDownstreamAddress() method for the cases where
// TetheringRequest has been set a static IPv4 address.
/**
* Pick a random available address and mark its prefix as in use for the provided IpServer,
* returns null if there is no available address.
*/
@Nullable
public LinkAddress requestDownstreamAddress(int interfaceType, final int scope,
boolean useLastAddress,
IIpv4PrefixRequest request) {
final Ipv4PrefixRequest wrappedRequest = new Ipv4PrefixRequest(request);
final AddressKey addrKey = new AddressKey(interfaceType, scope);
// This ensures that tethering isn't started on 2 different interfaces with the same type.
// Once tethering could support multiple interface with the same type,
// TetheringSoftApCallback would need to handle it among others.
final LinkAddress cachedAddress = mCachedAddresses.get(addrKey);
if (useLastAddress && cachedAddress != null
&& !isConflictWithUpstream(asIpPrefix(cachedAddress))) {
mDownstreams.put(wrappedRequest, new Downstream(interfaceType, cachedAddress));
return cachedAddress;
}
final int prefixIndex = getRandomPrefixIndex();
for (int i = 0; i < mTetheringPrefixes.size(); i++) {
final IpPrefix prefixRange = mTetheringPrefixes.get(
(prefixIndex + i) % mTetheringPrefixes.size());
final LinkAddress newAddress = chooseDownstreamAddress(prefixRange);
if (newAddress != null) {
mDownstreams.put(wrappedRequest, new Downstream(interfaceType, newAddress));
mCachedAddresses.put(addrKey, newAddress);
return newAddress;
}
}
// No available address.
return null;
}
private int getRandomPrefixIndex() {
if (!mDeps.isFeatureEnabled(TETHER_FORCE_RANDOM_PREFIX_BASE_SELECTION)) return 0;
final int random = getRandomInt() & 0xffffff;
// This is to select the starting prefix range (/8, /12, or /16) instead of the actual
// LinkAddress. To avoid complex operations in the selection logic and make the selected
// rate approximate consistency with that /8 is around 2^4 times of /12 and /12 is around
// 2^4 times of /16, we simply define a map between the value and the prefix value like
// this:
//
// Value 0 ~ 0xffff (65536/16777216 = 0.39%) -> 192.168.0.0/16
// Value 0x10000 ~ 0xfffff (983040/16777216 = 5.86%) -> 172.16.0.0/12
// Value 0x100000 ~ 0xffffff (15728640/16777216 = 93.7%) -> 10.0.0.0/8
if (random > 0xfffff) {
return 2;
} else if (random > 0xffff) {
return 1;
} else {
return 0;
}
}
private int getPrefixBaseAddress(final IpPrefix prefix) {
return inet4AddressToIntHTH((Inet4Address) prefix.getAddress());
}
/**
* Check whether input prefix conflict with upstream prefixes or in-use downstream prefixes.
* If yes, return one of them.
*/
private IpPrefix getConflictPrefix(final IpPrefix prefix) {
final IpPrefix upstream = getConflictWithUpstream(prefix);
if (upstream != null) return upstream;
return getInUseDownstreamPrefix(prefix);
}
@VisibleForTesting
public LinkAddress chooseDownstreamAddress(final IpPrefix prefixRange) {
// The netmask of the prefix assignment block (e.g., 0xfff00000 for 172.16.0.0/12).
final int prefixRangeMask = prefixLengthToV4NetmaskIntHTH(prefixRange.getPrefixLength());
// The zero address in the block (e.g., 0xac100000 for 172.16.0.0/12).
final int baseAddress = getPrefixBaseAddress(prefixRange);
// Try to get an address within the given prefix that does not conflict with any other
// prefix in the system.
for (int i = 0; i < 20; ++i) {
final int randomSuffix = mRandom.nextInt() & ~prefixRangeMask;
final int randomAddress = baseAddress | randomSuffix;
// Avoid selecting x.x.x.[0, 1, 255] addresses.
switch (randomAddress & 0xFF) {
case 0:
case 1:
case 255:
// Try selecting a different address
continue;
}
// Avoid selecting commonly used subnets.
switch (randomAddress & 0xFFFFFF00) {
case 0xC0A80000: // 192.168.0.0/24
case 0xC0A80100: // 192.168.1.0/24
case 0xC0A85800: // 192.168.88.0/24
case 0xC0A86400: // 192.168.100.0/24
continue;
}
// Avoid 10.0.0.0 - 10.10.255.255
if (randomAddress >= 0x0A000000 && randomAddress <= 0x0A0AFFFF) {
continue;
}
final InetAddress address = intToInet4AddressHTH(randomAddress);
final IpPrefix prefix = new IpPrefix(address, PREFIX_LENGTH);
if (getConflictPrefix(prefix) != null) {
// Prefix is conflicting with another prefix used in the system, find another one.
continue;
}
return new LinkAddress(address, PREFIX_LENGTH);
}
// Could not find a prefix, return null and let caller try another range.
return null;
}
/** Get random int which could be used to generate random address. */
// TODO: get rid of this function and mock getRandomPrefixIndex in tests.
@VisibleForTesting
public int getRandomInt() {
return mRandom.nextInt();
}
/** Release downstream record for IpServer. */
public void releaseDownstream(IIpv4PrefixRequest request) {
mDownstreams.remove(new Ipv4PrefixRequest(request));
}
/** Clear current upstream prefixes records. */
public void clearUpstreamPrefixes() {
mUpstreamPrefixMap.clear();
}
private IpPrefix getConflictWithUpstream(final IpPrefix prefix) {
for (int i = 0; i < mUpstreamPrefixMap.size(); i++) {
final List<IpPrefix> list = mUpstreamPrefixMap.valueAt(i);
for (IpPrefix upstream : list) {
if (isConflictPrefix(prefix, upstream)) return upstream;
}
}
return null;
}
private boolean isConflictWithUpstream(final IpPrefix prefix) {
return getConflictWithUpstream(prefix) != null;
}
private boolean isConflictPrefix(final IpPrefix prefix1, final IpPrefix prefix2) {
if (prefix2.getPrefixLength() < prefix1.getPrefixLength()) {
return prefix2.contains(prefix1.getAddress());
}
return prefix1.contains(prefix2.getAddress());
}
// InUse Prefixes are prefixes of mCachedAddresses which are active downstream addresses, last
// downstream addresses(reserved for next time) and static addresses(e.g. bluetooth, wifi p2p).
private IpPrefix getInUseDownstreamPrefix(final IpPrefix prefix) {
for (int i = 0; i < mCachedAddresses.size(); i++) {
final IpPrefix downstream = asIpPrefix(mCachedAddresses.valueAt(i));
if (isConflictPrefix(prefix, downstream)) return downstream;
}
// IpServer may use manually-defined address (mStaticIpv4ServerAddr) which does not include
// in mCachedAddresses.
for (Downstream downstream : mDownstreams.values()) {
final IpPrefix target = asIpPrefix(downstream.getAddress());
if (isConflictPrefix(prefix, target)) return target;
}
return null;
}
private static IpPrefix asIpPrefix(LinkAddress addr) {
return new IpPrefix(addr.getAddress(), addr.getPrefixLength());
}
private static final class Ipv4PrefixRequest {
private final IIpv4PrefixRequest mRequest;
Ipv4PrefixRequest(IIpv4PrefixRequest request) {
mRequest = request;
}
public IIpv4PrefixRequest getRequest() {
return mRequest;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Ipv4PrefixRequest)) return false;
return Objects.equals(
mRequest.asBinder(), ((Ipv4PrefixRequest) obj).mRequest.asBinder());
}
@Override
public int hashCode() {
return Objects.hashCode(mRequest.asBinder());
}
}
private static final class Downstream {
private final int mInterfaceType;
private final LinkAddress mAddress;
private Downstream(int interfaceType, LinkAddress address) {
mInterfaceType = interfaceType;
mAddress = address;
}
public int getInterfaceType() {
return mInterfaceType;
}
public LinkAddress getAddress() {
return mAddress;
}
}
private static class AddressKey {
private final int mTetheringType;
private final int mScope;
private AddressKey(int type, int scope) {
mTetheringType = type;
mScope = scope;
}
@Override
public int hashCode() {
return (mTetheringType << 16) + mScope;
}
@Override
public boolean equals(@Nullable Object obj) {
if (!(obj instanceof AddressKey)) return false;
final AddressKey other = (AddressKey) obj;
return mTetheringType == other.mTetheringType && mScope == other.mScope;
}
@Override
public String toString() {
return "AddressKey(" + mTetheringType + ", " + mScope + ")";
}
}
// TODO: dump PrivateAddressCoordinator when dumping RoutingCoordinatorService.
void dump(final IndentingPrintWriter pw) {
pw.println("mTetheringPrefixes:");
pw.increaseIndent();
for (IpPrefix prefix : mTetheringPrefixes) {
pw.println(prefix);
}
pw.decreaseIndent();
pw.println("mUpstreamPrefixMap:");
pw.increaseIndent();
for (int i = 0; i < mUpstreamPrefixMap.size(); i++) {
pw.println(mUpstreamPrefixMap.keyAt(i) + " - " + mUpstreamPrefixMap.valueAt(i));
}
pw.decreaseIndent();
pw.println("mDownstreams:");
pw.increaseIndent();
for (Downstream downstream : mDownstreams.values()) {
pw.println(downstream.getInterfaceType() + " - " + downstream.getAddress());
}
pw.decreaseIndent();
pw.println("mCachedAddresses:");
pw.increaseIndent();
for (int i = 0; i < mCachedAddresses.size(); i++) {
pw.println(mCachedAddresses.keyAt(i) + " - " + mCachedAddresses.valueAt(i));
}
pw.decreaseIndent();
}
}

View file

@ -17,17 +17,27 @@
package com.android.net.module.util;
import android.content.Context;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.RouteInfo;
import android.os.IBinder;
import android.os.RemoteException;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* 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.
*
* This class has following functionalities:
* - Manage routes and forwarding for networks.
* - Manage IPv4 prefix allocation for network interfaces.
*
* @hide
*/
public class RoutingCoordinatorManager {
@ -154,4 +164,65 @@ public class RoutingCoordinatorManager {
throw e.rethrowFromSystemServer();
}
}
// PrivateAddressCoordinator methods:
/** Update the prefix of an upstream. */
public void updateUpstreamPrefix(LinkProperties lp, NetworkCapabilities nc, Network network) {
try {
mService.updateUpstreamPrefix(lp, nc, network);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/** Remove the upstream prefix of the given {@link Network}. */
public void removeUpstreamPrefix(Network network) {
try {
mService.removeUpstreamPrefix(network);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/** Remove the deprecated upstream networks if any. */
public void maybeRemoveDeprecatedUpstreams() {
try {
mService.maybeRemoveDeprecatedUpstreams();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Request an IPv4 address for the downstream.
*
* @param interfaceType the Tethering type (see TetheringManager#TETHERING_*).
* @param scope CONNECTIVITY_SCOPE_GLOBAL or CONNECTIVITY_SCOPE_LOCAL
* @param useLastAddress whether to use the last address
* @param request a {@link IIpv4PrefixRequest} to report conflicts
* @return an IPv4 address allocated for the downstream, could be null
*/
@Nullable
public LinkAddress requestDownstreamAddress(
int interfaceType,
int scope,
boolean useLastAddress,
IIpv4PrefixRequest request) {
try {
return mService.requestDownstreamAddress(
interfaceType, scope, useLastAddress, request);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/** Release the IPv4 address allocated for the downstream. */
public void releaseDownstream(IIpv4PrefixRequest request) {
try {
mService.releaseDownstream(request);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}

View file

@ -19,8 +19,13 @@ package com.android.net.module.util;
import static com.android.net.module.util.NetdUtils.toRouteInfoParcel;
import android.annotation.NonNull;
import android.content.Context;
import android.net.INetd;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.RouteInfo;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
@ -28,8 +33,10 @@ import android.util.ArraySet;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.util.Objects;
import java.util.function.Supplier;
/**
* Class to coordinate routing across multiple clients.
@ -45,8 +52,22 @@ public class RoutingCoordinatorService extends IRoutingCoordinator.Stub {
private static final String TAG = RoutingCoordinatorService.class.getSimpleName();
private final INetd mNetd;
public RoutingCoordinatorService(@NonNull INetd netd) {
private final Object mPrivateAddressCoordinatorLock = new Object();
@GuardedBy("mPrivateAddressCoordinatorLock")
private final PrivateAddressCoordinator mPrivateAddressCoordinator;
public RoutingCoordinatorService(@NonNull INetd netd,
@NonNull Supplier<Network[]> getAllNetworksSupplier,
@NonNull Context context) {
this(netd, getAllNetworksSupplier, new PrivateAddressCoordinator.Dependencies(context));
}
@VisibleForTesting
public RoutingCoordinatorService(@NonNull INetd netd,
@NonNull Supplier<Network[]> getAllNetworksSupplier,
@NonNull PrivateAddressCoordinator.Dependencies pacDeps) {
mNetd = netd;
mPrivateAddressCoordinator = new PrivateAddressCoordinator(getAllNetworksSupplier, pacDeps);
}
/**
@ -225,4 +246,74 @@ public class RoutingCoordinatorService extends IRoutingCoordinator.Stub {
}
}
}
// PrivateAddressCoordinator methods:
/** Update the prefix of an upstream. */
@Override
public void updateUpstreamPrefix(LinkProperties lp, NetworkCapabilities nc, Network network) {
BinderUtils.withCleanCallingIdentity(
() -> {
synchronized (mPrivateAddressCoordinatorLock) {
mPrivateAddressCoordinator.updateUpstreamPrefix(lp, nc, network);
}
});
}
/** Remove the upstream prefix of the given {@link Network}. */
@Override
public void removeUpstreamPrefix(Network network) {
Objects.requireNonNull(network);
BinderUtils.withCleanCallingIdentity(
() -> {
synchronized (mPrivateAddressCoordinatorLock) {
mPrivateAddressCoordinator.removeUpstreamPrefix(network);
}
});
}
/** Remove the deprecated upstream networks if any. */
@Override
public void maybeRemoveDeprecatedUpstreams() {
BinderUtils.withCleanCallingIdentity(
() -> {
synchronized (mPrivateAddressCoordinatorLock) {
mPrivateAddressCoordinator.maybeRemoveDeprecatedUpstreams();
}
});
}
/**
* Request an IPv4 address for the downstream.
*
* @param interfaceType the Tethering type (see TetheringManager#TETHERING_*).
* @param scope CONNECTIVITY_SCOPE_GLOBAL or CONNECTIVITY_SCOPE_LOCAL
* @param useLastAddress whether to use the last address
* @param request a {@link IIpv4PrefixRequest} to report conflicts
* @return an IPv4 address allocated for the downstream, could be null
*/
@Override
public LinkAddress requestDownstreamAddress(int interfaceType, int scope,
boolean useLastAddress, IIpv4PrefixRequest request) {
Objects.requireNonNull(request);
return BinderUtils.withCleanCallingIdentity(
() -> {
synchronized (mPrivateAddressCoordinatorLock) {
return mPrivateAddressCoordinator.requestDownstreamAddress(
interfaceType, scope, useLastAddress, request);
}
});
}
/** Release the IPv4 address allocated for the downstream. */
@Override
public void releaseDownstream(IIpv4PrefixRequest request) {
Objects.requireNonNull(request);
BinderUtils.withCleanCallingIdentity(
() -> {
synchronized (mPrivateAddressCoordinatorLock) {
mPrivateAddressCoordinator.releaseDownstream(request);
}
});
}
}

View file

@ -16,7 +16,9 @@
package com.android.net.module.util
import android.content.Context
import android.net.INetd
import android.net.Network
import android.os.Build
import android.util.Log
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
@ -34,7 +36,9 @@ import org.mockito.Mockito.mock
@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
class RoutingCoordinatorServiceTest {
val mNetd = mock(INetd::class.java)
val mService = RoutingCoordinatorService(mNetd)
val mGetAllNetworksSupplier = { emptyArray<Network>() }
val mContext = mock(Context::class.java)
val mService = RoutingCoordinatorService(mNetd, mGetAllNetworksSupplier, mContext)
@Test
fun testInterfaceForward() {