diff options
author | TreeHugger Robot <treehugger-gerrit@google.com> | 2020-03-19 11:19:26 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2020-03-19 11:19:26 +0000 |
commit | 21b9e60863f8f0fe040210f966f0173c46130525 (patch) | |
tree | a58445e6ef8253bdd2ec70872220b53ea82c3a47 | |
parent | 7120763069c8f1257bc8e0f3367a5b4eebb7941a (diff) | |
parent | 21ea24de47194c277070fc2dc1d606536e5164d0 (diff) |
Merge "Detect upstream hotspot device type by checking the vendor specific IE." into rvc-dev
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); + } } |