summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTreeHugger Robot <treehugger-gerrit@google.com>2020-03-19 11:19:26 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2020-03-19 11:19:26 +0000
commit21b9e60863f8f0fe040210f966f0173c46130525 (patch)
treea58445e6ef8253bdd2ec70872220b53ea82c3a47
parent7120763069c8f1257bc8e0f3367a5b4eebb7941a (diff)
parent21ea24de47194c277070fc2dc1d606536e5164d0 (diff)
Merge "Detect upstream hotspot device type by checking the vendor specific IE." into rvc-dev
-rw-r--r--src/android/net/ip/IpClient.java75
-rw-r--r--src/android/net/util/ConnectivityPacketSummary.java8
-rw-r--r--src/com/android/server/util/NetworkStackConstants.java8
-rw-r--r--tests/integration/src/android/net/ip/IpClientIntegrationTest.java127
4 files changed, 204 insertions, 14 deletions
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java
index bfeac8b..ebb31c6 100644
--- a/src/android/net/ip/IpClient.java
+++ b/src/android/net/ip/IpClient.java
@@ -20,6 +20,7 @@ import static android.net.RouteInfo.RTN_UNICAST;
import static android.net.shared.IpConfigurationParcelableUtil.toStableParcelable;
import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
+import static com.android.server.util.NetworkStackConstants.VENDOR_SPECIFIC_IE_ID;
import static com.android.server.util.PermissionUtil.enforceNetworkStackCallingPermission;
import android.content.Context;
@@ -40,10 +41,13 @@ import android.net.Uri;
import android.net.apf.ApfCapabilities;
import android.net.apf.ApfFilter;
import android.net.dhcp.DhcpClient;
+import android.net.dhcp.DhcpPacket;
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.IpManagerEvent;
import android.net.shared.InitialConfiguration;
import android.net.shared.ProvisioningConfiguration;
+import android.net.shared.ProvisioningConfiguration.ScanResultInfo;
+import android.net.shared.ProvisioningConfiguration.ScanResultInfo.InformationElement;
import android.net.util.InterfaceParams;
import android.net.util.NetworkStackUtils;
import android.net.util.SharedLog;
@@ -64,6 +68,7 @@ import android.util.SparseArray;
import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.HexDump;
import com.android.internal.util.IState;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.MessageUtils;
@@ -80,6 +85,10 @@ import com.android.server.NetworkStackService.NetworkStackServiceManager;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.net.InetAddress;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -406,6 +415,13 @@ public class IpClient extends StateMachine {
private static final int PROV_CHANGE_GAINED_PROVISIONING = 3;
private static final int PROV_CHANGE_STILL_PROVISIONED = 4;
+ // Specific vendor OUI(3 bytes)/vendor specific type(1 byte) pattern for upstream hotspot
+ // device detection. Add new byte array pattern below in turn.
+ private static final List<byte[]> METERED_IE_PATTERN_LIST = Collections.unmodifiableList(
+ Arrays.asList(
+ new byte[] { (byte) 0x00, (byte) 0x17, (byte) 0xf2, (byte) 0x06 }
+ ));
+
private final State mStoppedState = new StoppedState();
private final State mStoppingState = new StoppingState();
private final State mClearingIpAddressesState = new ClearingIpAddressesState();
@@ -1269,16 +1285,71 @@ public class IpClient extends StateMachine {
return (delta != PROV_CHANGE_LOST_PROVISIONING);
}
+ @VisibleForTesting
+ static String removeDoubleQuotes(@NonNull String ssid) {
+ final int length = ssid.length();
+ if ((length > 1) && (ssid.charAt(0) == '"') && (ssid.charAt(length - 1) == '"')) {
+ return ssid.substring(1, length - 1);
+ }
+ return ssid;
+ }
+
+ private List<ByteBuffer> getVendorSpecificIEs(@NonNull ScanResultInfo scanResultInfo) {
+ ArrayList<ByteBuffer> vendorSpecificPayloadList = new ArrayList<>();
+ for (InformationElement ie : scanResultInfo.getInformationElements()) {
+ if (ie.getId() == VENDOR_SPECIFIC_IE_ID) {
+ vendorSpecificPayloadList.add(ie.getPayload());
+ }
+ }
+ return vendorSpecificPayloadList;
+ }
+
+ private boolean detectUpstreamHotspotFromVendorIe() {
+ if (mConfiguration.mScanResultInfo == null) return false;
+ final ScanResultInfo scanResultInfo = mConfiguration.mScanResultInfo;
+ final String ssid = scanResultInfo.getSsid();
+ final List<ByteBuffer> vendorSpecificPayloadList = getVendorSpecificIEs(scanResultInfo);
+
+ if (mConfiguration.mDisplayName == null
+ || !removeDoubleQuotes(mConfiguration.mDisplayName).equals(ssid)) {
+ return false;
+ }
+
+ for (ByteBuffer payload : vendorSpecificPayloadList) {
+ byte[] ouiAndType = new byte[4];
+ try {
+ payload.get(ouiAndType);
+ } catch (BufferUnderflowException e) {
+ Log.e(mTag, "Couldn't parse vendor specific IE, buffer underflow");
+ return false;
+ }
+ for (byte[] pattern : METERED_IE_PATTERN_LIST) {
+ if (Arrays.equals(pattern, ouiAndType)) {
+ if (DBG) {
+ Log.d(mTag, "detected upstream hotspot that matches OUI:"
+ + HexDump.toHexString(ouiAndType));
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
private void handleIPv4Success(DhcpResults dhcpResults) {
mDhcpResults = new DhcpResults(dhcpResults);
final LinkProperties newLp = assembleLinkProperties();
final int delta = setLinkProperties(newLp);
+ if (mDhcpResults.vendorInfo == null && detectUpstreamHotspotFromVendorIe()) {
+ mDhcpResults.vendorInfo = DhcpPacket.VENDOR_INFO_ANDROID_METERED;
+ }
+
if (DBG) {
- Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")");
+ Log.d(mTag, "onNewDhcpResults(" + Objects.toString(mDhcpResults) + ")");
Log.d(mTag, "handleIPv4Success newLp{" + newLp + "}");
}
- mCallback.onNewDhcpResults(dhcpResults);
+ mCallback.onNewDhcpResults(mDhcpResults);
maybeSaveNetworkToIpMemoryStore();
dispatchCallback(delta, newLp);
diff --git a/src/android/net/util/ConnectivityPacketSummary.java b/src/android/net/util/ConnectivityPacketSummary.java
index 4d04911..8164c00 100644
--- a/src/android/net/util/ConnectivityPacketSummary.java
+++ b/src/android/net/util/ConnectivityPacketSummary.java
@@ -84,8 +84,8 @@ public class ConnectivityPacketSummary {
/**
* Create a string summary of a received packet.
- * @param hwaddr MacAddress of the receiving device.
- * @param buffer Buffer of the packet. Length is assumed to be the buffer length.
+ * @param hwaddr MacAddress of the interface sending/receiving the packet.
+ * @param buffer The packet bytes. Length is assumed to be the buffer length.
* @return A summary of the packet.
*/
public static String summarize(MacAddress hwaddr, byte[] buffer) {
@@ -97,8 +97,8 @@ public class ConnectivityPacketSummary {
/**
* Create a string summary of a received packet.
- * @param macAddr MacAddress of the receiving device.
- * @param buffer Buffer of the packet.
+ * @param macAddr MacAddress of the interface sending/receiving the packet.
+ * @param buffer The packet bytes.
* @param length Length of the packet.
* @return A summary of the packet.
*/
diff --git a/src/com/android/server/util/NetworkStackConstants.java b/src/com/android/server/util/NetworkStackConstants.java
index 36bac27..660f0a6 100644
--- a/src/com/android/server/util/NetworkStackConstants.java
+++ b/src/com/android/server/util/NetworkStackConstants.java
@@ -148,6 +148,14 @@ public final class NetworkStackConstants {
public static final int INFINITE_LEASE = 0xffffffff;
public static final int DHCP4_CLIENT_PORT = 68;
+ /**
+ * IEEE802.11 standard constants.
+ *
+ * See also:
+ * - https://ieeexplore.ieee.org/document/7786995
+ */
+ public static final int VENDOR_SPECIFIC_IE_ID = 0xdd;
+
private NetworkStackConstants() {
throw new UnsupportedOperationException("This class is not to be instantiated");
}
diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
index a60e32c..97da9fb 100644
--- a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
+++ b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
@@ -24,9 +24,11 @@ import static android.net.dhcp.DhcpPacket.DHCP_SERVER;
import static android.net.dhcp.DhcpPacket.ENCAP_L2;
import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST;
import static android.net.dhcp.DhcpPacket.INFINITE_LEASE;
+import static android.net.ip.IpClient.removeDoubleQuotes;
import static android.net.ipmemorystore.Status.SUCCESS;
import static android.net.shared.Inet4AddressUtils.getBroadcastAddress;
import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address;
+import static android.net.shared.IpConfigurationParcelableUtil.fromStableParcelable;
import static android.system.OsConstants.ETH_P_IPV6;
import static android.system.OsConstants.IPPROTO_ICMPV6;
import static android.system.OsConstants.IPPROTO_TCP;
@@ -47,6 +49,7 @@ import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICI
import static com.android.server.util.NetworkStackConstants.IPV6_HEADER_LEN;
import static com.android.server.util.NetworkStackConstants.IPV6_LEN_OFFSET;
import static com.android.server.util.NetworkStackConstants.IPV6_PROTOCOL_OFFSET;
+import static com.android.server.util.NetworkStackConstants.VENDOR_SPECIFIC_IE_ID;
import static junit.framework.Assert.fail;
@@ -78,6 +81,8 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.net.ConnectivityManager;
+import android.net.DhcpResults;
+import android.net.DhcpResultsParcelable;
import android.net.INetd;
import android.net.InetAddresses;
import android.net.InterfaceConfigurationParcel;
@@ -100,6 +105,7 @@ import android.net.ipmemorystore.NetworkAttributes;
import android.net.ipmemorystore.OnNetworkAttributesRetrievedListener;
import android.net.ipmemorystore.Status;
import android.net.shared.ProvisioningConfiguration;
+import android.net.shared.ProvisioningConfiguration.ScanResultInfo;
import android.net.util.InterfaceParams;
import android.net.util.IpUtils;
import android.net.util.NetworkStackUtils;
@@ -152,6 +158,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
+import java.util.Random;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -226,6 +233,10 @@ public class IpClientIntegrationTest {
private static final String TEST_HOST_NAME = "AOSP on Crosshatch";
private static final String TEST_HOST_NAME_TRANSLITERATION = "AOSP-on-Crosshatch";
private static final String TEST_CAPTIVE_PORTAL_URL = "https://example.com/capportapi";
+ private static final byte[] TEST_HOTSPOT_OUI = new byte[] {
+ (byte) 0x00, (byte) 0x17, (byte) 0xF2
+ };
+ private static final byte TEST_VENDOR_SPECIFIC_TYPE = 0x06;
private static class TapPacketReader extends PacketReader {
private final ParcelFileDescriptor mTapFd;
@@ -549,12 +560,15 @@ public class IpClientIntegrationTest {
private void startIpClientProvisioning(final boolean isDhcpLeaseCacheEnabled,
final boolean shouldReplyRapidCommitAck, final boolean isPreconnectionEnabled,
final boolean isDhcpIpConflictDetectEnabled,
- final boolean isHostnameConfigurationEnabled, final String hostname)
+ final boolean isHostnameConfigurationEnabled, final String hostname,
+ final String displayName, final ScanResultInfo scanResultInfo)
throws RemoteException {
ProvisioningConfiguration.Builder builder = new ProvisioningConfiguration.Builder()
.withoutIpReachabilityMonitor()
.withoutIPv6();
if (isPreconnectionEnabled) builder.withPreconnection();
+ if (displayName != null) builder.withDisplayName(displayName);
+ if (scanResultInfo != null) builder.withScanResultInfo(scanResultInfo);
mDependencies.setDhcpLeaseCacheEnabled(isDhcpLeaseCacheEnabled);
mDependencies.setDhcpRapidCommitEnabled(shouldReplyRapidCommitAck);
@@ -575,7 +589,8 @@ public class IpClientIntegrationTest {
throws RemoteException {
startIpClientProvisioning(isDhcpLeaseCacheEnabled, isDhcpRapidCommitEnabled,
isPreconnectionEnabled, isDhcpIpConflictDetectEnabled,
- false /* isHostnameConfigurationEnabled */, null /* hostname */);
+ false /* isHostnameConfigurationEnabled */, null /* hostname */,
+ null /* displayName */, null /* ScanResultInfo */);
}
private void assertIpMemoryStoreNetworkAttributes(final Integer leaseTimeSec,
@@ -633,10 +648,11 @@ public class IpClientIntegrationTest {
final boolean shouldReplyRapidCommitAck, final int mtu,
final boolean isDhcpIpConflictDetectEnabled,
final boolean isHostnameConfigurationEnabled, final String hostname,
- final String captivePortalApiUrl) throws Exception {
+ final String captivePortalApiUrl, final String displayName,
+ final ScanResultInfo scanResultInfo) throws Exception {
startIpClientProvisioning(isDhcpLeaseCacheEnabled, shouldReplyRapidCommitAck,
false /* isPreconnectionEnabled */, isDhcpIpConflictDetectEnabled,
- isHostnameConfigurationEnabled, hostname);
+ isHostnameConfigurationEnabled, hostname, displayName, scanResultInfo);
return handleDhcpPackets(isSuccessLease, leaseTimeSec, shouldReplyRapidCommitAck, mtu,
isDhcpIpConflictDetectEnabled, captivePortalApiUrl);
}
@@ -693,7 +709,7 @@ public class IpClientIntegrationTest {
return performDhcpHandshake(isSuccessLease, leaseTimeSec, isDhcpLeaseCacheEnabled,
isDhcpRapidCommitEnabled, mtu, isDhcpIpConflictDetectEnabled,
false /* isHostnameConfigurationEnabled */, null /* hostname */,
- null /* captivePortalApiUrl */);
+ null /* captivePortalApiUrl */, null /* displayName */, null /* scanResultInfo */);
}
private DhcpPacket getNextDhcpPacket() throws ParseException {
@@ -1484,7 +1500,7 @@ public class IpClientIntegrationTest {
false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU,
false /* isDhcpIpConflictDetectEnabled */,
true /* isHostnameConfigurationEnabled */, TEST_HOST_NAME /* hostname */,
- null /* captivePortalApiUrl */);
+ null /* captivePortalApiUrl */, null /* displayName */, null /* scanResultInfo */);
assertEquals(2, sentPackets.size());
assertHostname(true, TEST_HOST_NAME, TEST_HOST_NAME_TRANSLITERATION, sentPackets);
assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
@@ -1498,7 +1514,7 @@ public class IpClientIntegrationTest {
false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU,
false /* isDhcpIpConflictDetectEnabled */,
false /* isHostnameConfigurationEnabled */, TEST_HOST_NAME,
- null /* captivePortalApiUrl */);
+ null /* captivePortalApiUrl */, null /* displayName */, null /* scanResultInfo */);
assertEquals(2, sentPackets.size());
assertHostname(false, TEST_HOST_NAME, TEST_HOST_NAME_TRANSLITERATION, sentPackets);
assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
@@ -1512,7 +1528,7 @@ public class IpClientIntegrationTest {
false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU,
false /* isDhcpIpConflictDetectEnabled */,
true /* isHostnameConfigurationEnabled */, null /* hostname */,
- null /* captivePortalApiUrl */);
+ null /* captivePortalApiUrl */, null /* displayName */, null /* scanResultInfo */);
assertEquals(2, sentPackets.size());
assertHostname(true, null /* hostname */, null /* hostnameAfterTransliteration */,
sentPackets);
@@ -1566,4 +1582,99 @@ public class IpClientIntegrationTest {
assumeFalse(CaptivePortalDataShimImpl.isSupported());
runDhcpClientCaptivePortalApiTest(false /* featureEnabled */, true /* serverSendsOption */);
}
+
+ private ScanResultInfo makeScanResultInfo(final int id, final String ssid,
+ final byte[] oui, final byte type, final byte[] data) {
+ final ByteBuffer payload = ByteBuffer.allocate(4 + data.length);
+ payload.put(oui);
+ payload.put(type);
+ payload.put(data);
+ payload.flip();
+ final ScanResultInfo.InformationElement ie =
+ new ScanResultInfo.InformationElement(id /* IE id */, payload);
+ return new ScanResultInfo(ssid, Collections.singletonList(ie));
+ }
+
+ private void doUpstreamHotspotDetectionTest(final int id, final String displayName,
+ final String ssid, final byte[] oui, final byte type, final byte[] data)
+ throws Exception {
+ final ScanResultInfo info = makeScanResultInfo(id, ssid, oui, type, data);
+ final long currentTime = System.currentTimeMillis();
+ final List<DhcpPacket> sentPackets = performDhcpHandshake(true /* isSuccessLease */,
+ TEST_LEASE_DURATION_S, true /* isDhcpLeaseCacheEnabled */,
+ false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU,
+ false /* isDhcpIpConflictDetectEnabled */,
+ false /* isHostnameConfigurationEnabled */, null /* hostname */,
+ null /* captivePortalApiUrl */, displayName, info /* scanResultInfo */);
+ assertEquals(2, sentPackets.size());
+
+ ArgumentCaptor<DhcpResultsParcelable> captor =
+ ArgumentCaptor.forClass(DhcpResultsParcelable.class);
+ verify(mCb, timeout(TEST_TIMEOUT_MS)).onNewDhcpResults(captor.capture());
+ DhcpResults lease = fromStableParcelable(captor.getValue());
+ assertNotNull(lease);
+ assertEquals(lease.getIpAddress().getAddress(), CLIENT_ADDR);
+ assertEquals(lease.getGateway(), SERVER_ADDR);
+ assertEquals(1, lease.getDnsServers().size());
+ assertTrue(lease.getDnsServers().contains(SERVER_ADDR));
+ assertEquals(lease.getServerAddress(), SERVER_ADDR);
+ assertEquals(lease.getMtu(), TEST_DEFAULT_MTU);
+ if (id == VENDOR_SPECIFIC_IE_ID
+ && ssid.equals(removeDoubleQuotes(displayName))
+ && Arrays.equals(oui, TEST_HOTSPOT_OUI)
+ && type == TEST_VENDOR_SPECIFIC_TYPE) {
+ assertEquals(lease.vendorInfo, DhcpPacket.VENDOR_INFO_ANDROID_METERED);
+ } else {
+ assertNull(lease.vendorInfo);
+ }
+
+ assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
+ }
+
+ @Test
+ public void testUpstreamHotspotDetection() throws Exception {
+ byte[] data = new byte[10];
+ new Random().nextBytes(data);
+ doUpstreamHotspotDetectionTest(0xdd, "\"ssid\"", "ssid",
+ new byte[] { (byte) 0x00, (byte) 0x17, (byte) 0xF2 }, (byte) 0x06, data);
+ }
+
+ @Test
+ public void testUpstreamHotspotDetection_incorrectIeId() throws Exception {
+ byte[] data = new byte[10];
+ new Random().nextBytes(data);
+ doUpstreamHotspotDetectionTest(0xdc, "\"ssid\"", "ssid",
+ new byte[] { (byte) 0x00, (byte) 0x17, (byte) 0xF2 }, (byte) 0x06, data);
+ }
+
+ @Test
+ public void testUpstreamHotspotDetection_incorrectOUI() throws Exception {
+ byte[] data = new byte[10];
+ new Random().nextBytes(data);
+ doUpstreamHotspotDetectionTest(0xdd, "\"ssid\"", "ssid",
+ new byte[] { (byte) 0x00, (byte) 0x1A, (byte) 0x11 }, (byte) 0x06, data);
+ }
+
+ @Test
+ public void testUpstreamHotspotDetection_incorrectSsid() throws Exception {
+ byte[] data = new byte[10];
+ new Random().nextBytes(data);
+ doUpstreamHotspotDetectionTest(0xdd, "\"another ssid\"", "ssid",
+ new byte[] { (byte) 0x00, (byte) 0x17, (byte) 0xF2 }, (byte) 0x06, data);
+ }
+
+ @Test
+ public void testUpstreamHotspotDetection_incorrectType() throws Exception {
+ byte[] data = new byte[10];
+ new Random().nextBytes(data);
+ doUpstreamHotspotDetectionTest(0xdd, "\"ssid\"", "ssid",
+ new byte[] { (byte) 0x00, (byte) 0x17, (byte) 0xF2 }, (byte) 0x0a, data);
+ }
+
+ @Test
+ public void testUpstreamHotspotDetection_zeroLengthData() throws Exception {
+ byte[] data = new byte[0];
+ doUpstreamHotspotDetectionTest(0xdd, "\"ssid\"", "ssid",
+ new byte[] { (byte) 0x00, (byte) 0x17, (byte) 0xF2 }, (byte) 0x06, data);
+ }
}