Merge "Also update connected clients for local only tethering"
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 2c91d10..62ae88c 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -140,8 +140,8 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashSet;
import java.util.Iterator;
-import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
@@ -216,13 +216,12 @@
private final ArrayMap<String, TetherState> mTetherStates;
private final BroadcastReceiver mStateReceiver;
private final Looper mLooper;
- private final StateMachine mTetherMainSM;
+ private final TetherMainSM mTetherMainSM;
private final OffloadController mOffloadController;
private final UpstreamNetworkMonitor mUpstreamNetworkMonitor;
// TODO: Figure out how to merge this and other downstream-tracking objects
// into a single coherent structure.
- // Use LinkedHashSet for predictable ordering order for ConnectedClientsTracker.
- private final LinkedHashSet<IpServer> mForwardedDownstreams;
+ private final HashSet<IpServer> mForwardedDownstreams;
private final VersionedBroadcastListener mCarrierConfigChange;
private final TetheringDependencies mDeps;
private final EntitlementManager mEntitlementMgr;
@@ -287,7 +286,7 @@
});
mUpstreamNetworkMonitor = mDeps.getUpstreamNetworkMonitor(mContext, mTetherMainSM, mLog,
TetherMainSM.EVENT_UPSTREAM_CALLBACK);
- mForwardedDownstreams = new LinkedHashSet<>();
+ mForwardedDownstreams = new HashSet<>();
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_CARRIER_CONFIG_CHANGED);
@@ -1423,6 +1422,7 @@
// interfaces.
// 2) mNotifyList contains all state machines that may have outstanding tethering state
// that needs to be torn down.
+ // 3) Use mNotifyList for predictable ordering order for ConnectedClientsTracker.
//
// Because we excise interfaces immediately from mTetherStates, we must maintain mNotifyList
// so that the garbage collector does not clean up the state machine before it has a chance
@@ -1459,6 +1459,15 @@
setInitialState(mInitialState);
}
+ /**
+ * Returns all downstreams that are serving clients, regardless of they are actually
+ * tethered or localOnly. This must be called on the tethering thread (not thread-safe).
+ */
+ @NonNull
+ public List<IpServer> getAllDownstreams() {
+ return mNotifyList;
+ }
+
class InitialState extends State {
@Override
public boolean processMessage(Message message) {
@@ -2300,7 +2309,8 @@
}
private void updateConnectedClients(final List<WifiClient> wifiClients) {
- if (mConnectedClientsTracker.updateConnectedClients(mForwardedDownstreams, wifiClients)) {
+ if (mConnectedClientsTracker.updateConnectedClients(mTetherMainSM.getAllDownstreams(),
+ wifiClients)) {
reportTetherClientsChanged(mConnectedClientsTracker.getLastTetheredClients());
}
}
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 0a37f54..f4b3749 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -16,6 +16,7 @@
package com.android.networkstack.tethering;
+import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.content.pm.PackageManager.GET_ACTIVITIES;
import static android.hardware.usb.UsbManager.USB_CONFIGURED;
import static android.hardware.usb.UsbManager.USB_CONNECTED;
@@ -36,6 +37,7 @@
import static android.net.TetheringManager.TETHERING_NCM;
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.TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE;
@@ -49,12 +51,15 @@
import static android.net.wifi.WifiManager.IFACE_IP_MODE_LOCAL_ONLY;
import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED;
import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
+import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH;
import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
import static com.android.networkstack.tethering.Tethering.UserRestrictionActionListener;
import static com.android.networkstack.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE;
import static com.android.networkstack.tethering.UpstreamNetworkMonitor.EVENT_ON_CAPABILITIES;
+import static com.android.testutils.TestPermissionUtil.runAsShell;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@@ -108,11 +113,14 @@
import android.net.RouteInfo;
import android.net.TetherStatesParcel;
import android.net.TetheredClient;
+import android.net.TetheredClient.AddressInfo;
import android.net.TetheringCallbackStartedParcel;
import android.net.TetheringConfigurationParcel;
import android.net.TetheringRequestParcel;
+import android.net.dhcp.DhcpLeaseParcelable;
import android.net.dhcp.DhcpServerCallbacks;
import android.net.dhcp.DhcpServingParamsParcel;
+import android.net.dhcp.IDhcpEventCallbacks;
import android.net.dhcp.IDhcpServer;
import android.net.ip.DadProxy;
import android.net.ip.IpNeighborMonitor;
@@ -122,7 +130,9 @@
import android.net.util.NetworkConstants;
import android.net.util.SharedLog;
import android.net.wifi.SoftApConfiguration;
+import android.net.wifi.WifiClient;
import android.net.wifi.WifiManager;
+import android.net.wifi.WifiManager.SoftApCallback;
import android.net.wifi.p2p.WifiP2pGroup;
import android.net.wifi.p2p.WifiP2pInfo;
import android.net.wifi.p2p.WifiP2pManager;
@@ -168,6 +178,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.Vector;
@@ -237,6 +248,7 @@
private EntitlementManager mEntitleMgr;
private OffloadController mOffloadCtrl;
private PrivateAddressCoordinator mPrivateAddressCoordinator;
+ private SoftApCallback mSoftApCallback;
private class TestContext extends BroadcastInterceptingContext {
TestContext(Context base) {
@@ -568,8 +580,12 @@
ArgumentCaptor.forClass(PhoneStateListener.class);
verify(mTelephonyManager).listen(phoneListenerCaptor.capture(),
eq(PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE));
- verify(mWifiManager).registerSoftApCallback(any(), any());
mPhoneStateListener = phoneListenerCaptor.getValue();
+
+ final ArgumentCaptor<SoftApCallback> softApCallbackCaptor =
+ ArgumentCaptor.forClass(SoftApCallback.class);
+ verify(mWifiManager).registerSoftApCallback(any(), softApCallbackCaptor.capture());
+ mSoftApCallback = softApCallbackCaptor.getValue();
}
private void setTetheringSupported(final boolean supported) {
@@ -1293,6 +1309,7 @@
new ArrayList<>();
private final ArrayList<TetherStatesParcel> mTetherStates = new ArrayList<>();
private final ArrayList<Integer> mOffloadStatus = new ArrayList<>();
+ private final ArrayList<List<TetheredClient>> mTetheredClients = new ArrayList<>();
// This function will remove the recorded callbacks, so it must be called once for
// each callback. If this is called after multiple callback, the order matters.
@@ -1338,6 +1355,13 @@
return mTetherStates.remove(0);
}
+ public void expectTetheredClientChanged(List<TetheredClient> leases) {
+ assertFalse(mTetheredClients.isEmpty());
+ final List<TetheredClient> result = mTetheredClients.remove(0);
+ assertEquals(leases.size(), result.size());
+ assertTrue(leases.containsAll(result));
+ }
+
@Override
public void onUpstreamChanged(Network network) {
mActualUpstreams.add(network);
@@ -1355,7 +1379,7 @@
@Override
public void onTetherClientsChanged(List<TetheredClient> clients) {
- // TODO: check this
+ mTetheredClients.add(clients);
}
@Override
@@ -1369,6 +1393,7 @@
mTetheringConfigs.add(parcel.config);
mTetherStates.add(parcel.states);
mOffloadStatus.add(parcel.offloadStatus);
+ mTetheredClients.add(parcel.tetheredClients);
}
@Override
@@ -1398,6 +1423,7 @@
assertNoUpstreamChangeCallback();
assertNoConfigChangeCallback();
assertNoStateChangeCallback();
+ assertTrue(mTetheredClients.isEmpty());
}
private void assertTetherConfigParcelEqual(@NonNull TetheringConfigurationParcel actual,
@@ -1437,6 +1463,7 @@
// 1. Register one callback before running any tethering.
mTethering.registerTetheringEventCallback(callback);
mLooper.dispatchAll();
+ callback.expectTetheredClientChanged(Collections.emptyList());
callback.expectUpstreamChanged(new Network[] {null});
callback.expectConfigurationChanged(
mTethering.getTetheringConfiguration().toStableParcelable());
@@ -1463,6 +1490,7 @@
// 3. Register second callback.
mTethering.registerTetheringEventCallback(callback2);
mLooper.dispatchAll();
+ callback2.expectTetheredClientChanged(Collections.emptyList());
callback2.expectUpstreamChanged(upstreamState.network);
callback2.expectConfigurationChanged(
mTethering.getTetheringConfiguration().toStableParcelable());
@@ -2054,6 +2082,114 @@
verify(mPackageManager).getPackageInfo(PROVISIONING_APP_NAME[0], GET_ACTIVITIES);
}
+ @Test
+ public void testUpdateConnectedClients() throws Exception {
+ TestTetheringEventCallback callback = new TestTetheringEventCallback();
+ runAsShell(NETWORK_SETTINGS, () -> {
+ mTethering.registerTetheringEventCallback(callback);
+ mLooper.dispatchAll();
+ });
+ callback.expectTetheredClientChanged(Collections.emptyList());
+
+ IDhcpEventCallbacks eventCallbacks;
+ final ArgumentCaptor<IDhcpEventCallbacks> dhcpEventCbsCaptor =
+ ArgumentCaptor.forClass(IDhcpEventCallbacks.class);
+ // Run local only tethering.
+ mTethering.interfaceStatusChanged(TEST_P2P_IFNAME, true);
+ sendWifiP2pConnectionChanged(true, true, TEST_P2P_IFNAME);
+ mLooper.dispatchAll();
+ verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS)).startWithCallbacks(
+ any(), dhcpEventCbsCaptor.capture());
+ eventCallbacks = dhcpEventCbsCaptor.getValue();
+ // Update lease for local only tethering.
+ final MacAddress testMac1 = MacAddress.fromString("11:11:11:11:11:11");
+ final ArrayList<DhcpLeaseParcelable> p2pLeases = new ArrayList<>();
+ p2pLeases.add(createDhcpLeaseParcelable("clientId1", testMac1, "192.168.50.24", 24,
+ Long.MAX_VALUE, "test1"));
+ notifyDhcpLeasesChanged(p2pLeases, eventCallbacks);
+ final List<TetheredClient> clients = toTetheredClients(p2pLeases, TETHERING_WIFI_P2P);
+ callback.expectTetheredClientChanged(clients);
+ reset(mDhcpServer);
+
+ // Run wifi tethering.
+ mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
+ sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+ mLooper.dispatchAll();
+ verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS)).startWithCallbacks(
+ any(), dhcpEventCbsCaptor.capture());
+ eventCallbacks = dhcpEventCbsCaptor.getValue();
+ // Update mac address from softAp callback before getting dhcp lease.
+ final ArrayList<WifiClient> wifiClients = new ArrayList<>();
+ final MacAddress testMac2 = MacAddress.fromString("22:22:22:22:22:22");
+ final WifiClient testClient = mock(WifiClient.class);
+ when(testClient.getMacAddress()).thenReturn(testMac2);
+ wifiClients.add(testClient);
+ mSoftApCallback.onConnectedClientsChanged(wifiClients);
+ final TetheredClient noAddrClient = new TetheredClient(testMac2,
+ Collections.emptyList() /* addresses */, TETHERING_WIFI);
+ clients.add(noAddrClient);
+ callback.expectTetheredClientChanged(clients);
+
+ // Update dhcp lease for wifi tethering.
+ clients.remove(noAddrClient);
+ final ArrayList<DhcpLeaseParcelable> wifiLeases = new ArrayList<>();
+ wifiLeases.add(createDhcpLeaseParcelable("clientId2", testMac2, "192.168.43.24", 24,
+ Long.MAX_VALUE, "test2"));
+ notifyDhcpLeasesChanged(wifiLeases, eventCallbacks);
+ clients.addAll(toTetheredClients(wifiLeases, TETHERING_WIFI));
+ callback.expectTetheredClientChanged(clients);
+
+ // Test onStarted callback that register second callback when tethering is running.
+ TestTetheringEventCallback callback2 = new TestTetheringEventCallback();
+ runAsShell(NETWORK_SETTINGS, () -> {
+ mTethering.registerTetheringEventCallback(callback2);
+ mLooper.dispatchAll();
+ });
+ callback2.expectTetheredClientChanged(clients);
+ }
+
+ private void notifyDhcpLeasesChanged(List<DhcpLeaseParcelable> leaseParcelables,
+ IDhcpEventCallbacks callback) throws Exception {
+ callback.onLeasesChanged(leaseParcelables);
+ mLooper.dispatchAll();
+ }
+
+ private List<TetheredClient> toTetheredClients(List<DhcpLeaseParcelable> leaseParcelables,
+ int type) throws Exception {
+ final ArrayList<TetheredClient> leases = new ArrayList<>();
+ for (DhcpLeaseParcelable lease : leaseParcelables) {
+ final LinkAddress address = new LinkAddress(
+ intToInet4AddressHTH(lease.netAddr), lease.prefixLength,
+ 0 /* flags */, RT_SCOPE_UNIVERSE /* as per RFC6724#3.2 */,
+ lease.expTime /* deprecationTime */, lease.expTime /* expirationTime */);
+
+ final MacAddress macAddress = MacAddress.fromBytes(lease.hwAddr);
+
+ final AddressInfo addressInfo = new TetheredClient.AddressInfo(address, lease.hostname);
+ leases.add(new TetheredClient(
+ macAddress,
+ Collections.singletonList(addressInfo),
+ type));
+ }
+
+ return leases;
+ }
+
+ private DhcpLeaseParcelable createDhcpLeaseParcelable(final String clientId,
+ final MacAddress hwAddr, final String netAddr, final int prefixLength,
+ final long expTime, final String hostname) throws Exception {
+ final DhcpLeaseParcelable lease = new DhcpLeaseParcelable();
+ lease.clientId = clientId.getBytes();
+ lease.hwAddr = hwAddr.toByteArray();
+ lease.netAddr = inet4AddressToIntHTH(
+ (Inet4Address) InetAddresses.parseNumericAddress(netAddr));
+ lease.prefixLength = prefixLength;
+ lease.expTime = expTime;
+ lease.hostname = hostname;
+
+ return lease;
+ }
+
// TODO: Test that a request for hotspot mode doesn't interfere with an
// already operating tethering mode interface.
}