diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-09-05 04:43:42 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-09-05 04:43:42 +0000 |
commit | 63a232365575574479c35efd59ef954eb9e343ea (patch) | |
tree | efaf9e838b13caea604ccea565056c8a4f7f3630 | |
parent | 22971b2332e35ac9ba78dd3a6b33c71a83e454f1 (diff) | |
parent | c28fc7974331b64aa2d4b96ce9871a7bb6b24f7f (diff) | |
download | net-63a232365575574479c35efd59ef954eb9e343ea.tar.gz |
Snap for 10760240 from c28fc7974331b64aa2d4b96ce9871a7bb6b24f7f to mainline-appsearch-release
Change-Id: I52d3ac9f4ca5dbfe70d63c98b7969417c002e4a4
35 files changed, 1530 insertions, 210 deletions
@@ -1,4 +1,4 @@ # Bug template url: http://b/new?component=31808 # TODO: move bug template config to common owners file once b/226427845 is resolved set noparent -file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking +file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking diff --git a/common/Android.bp b/common/Android.bp index ff65228a..d8d0accb 100644 --- a/common/Android.bp +++ b/common/Android.bp @@ -34,12 +34,15 @@ package { java_library { name: "net-utils-device-common", srcs: [ + "device/com/android/net/module/util/arp/ArpPacket.java", "device/com/android/net/module/util/DeviceConfigUtils.java", + "device/com/android/net/module/util/DomainUtils.java", "device/com/android/net/module/util/FdEventsReader.java", "device/com/android/net/module/util/NetworkMonitorUtils.java", "device/com/android/net/module/util/PacketReader.java", "device/com/android/net/module/util/SharedLog.java", "device/com/android/net/module/util/SocketUtils.java", + "device/com/android/net/module/util/FeatureVersions.java", // This library is used by system modules, for which the system health impact of Kotlin // has not yet been evaluated. Annotations may need jarjar'ing. // "src_devicecommon/**/*.kt", @@ -243,6 +246,7 @@ java_library { "framework-annotations-lib", "framework-connectivity.stubs.module_lib", "framework-connectivity-t.stubs.module_lib", + "framework-location.stubs.module_lib", ], jarjar_rules: "jarjar-rules-shared.txt", visibility: [ diff --git a/common/device/com/android/net/module/util/BpfMap.java b/common/device/com/android/net/module/util/BpfMap.java index 9df2b035..d45caceb 100644 --- a/common/device/com/android/net/module/util/BpfMap.java +++ b/common/device/com/android/net/module/util/BpfMap.java @@ -194,9 +194,11 @@ public class BpfMap<K extends Struct, V extends Struct> implements IBpfMap<K, V> } private K getNextKeyInternal(@Nullable K key) throws ErrnoException { - final byte[] rawKey = getNextRawKey( - key == null ? null : key.writeToBytes()); - if (rawKey == null) return null; + byte[] rawKey = new byte[mKeySize]; + + if (!nativeGetNextMapKey(mMapFd.getFd(), + key == null ? null : key.writeToBytes(), + rawKey)) return null; final ByteBuffer buffer = ByteBuffer.wrap(rawKey); buffer.order(ByteOrder.nativeOrder()); @@ -215,13 +217,6 @@ public class BpfMap<K extends Struct, V extends Struct> implements IBpfMap<K, V> return getNextKeyInternal(key); } - private byte[] getNextRawKey(@Nullable final byte[] key) throws ErrnoException { - byte[] nextKey = new byte[mKeySize]; - if (nativeGetNextMapKey(mMapFd.getFd(), key, nextKey)) return nextKey; - - return null; - } - /** Get the first key of eBpf map. */ @Override public K getFirstKey() throws ErrnoException { @@ -233,30 +228,23 @@ public class BpfMap<K extends Struct, V extends Struct> implements IBpfMap<K, V> public boolean containsKey(@NonNull K key) throws ErrnoException { Objects.requireNonNull(key); - final byte[] rawValue = getRawValue(key.writeToBytes()); - return rawValue != null; + byte[] rawValue = new byte[mValueSize]; + return nativeFindMapEntry(mMapFd.getFd(), key.writeToBytes(), rawValue); } /** Retrieve a value from the map. Return null if there is no such key. */ @Override public V getValue(@NonNull K key) throws ErrnoException { Objects.requireNonNull(key); - final byte[] rawValue = getRawValue(key.writeToBytes()); - if (rawValue == null) return null; + byte[] rawValue = new byte[mValueSize]; + if (!nativeFindMapEntry(mMapFd.getFd(), key.writeToBytes(), rawValue)) return null; final ByteBuffer buffer = ByteBuffer.wrap(rawValue); buffer.order(ByteOrder.nativeOrder()); return Struct.parse(mValueClass, buffer); } - private byte[] getRawValue(final byte[] key) throws ErrnoException { - byte[] value = new byte[mValueSize]; - if (nativeFindMapEntry(mMapFd.getFd(), key, value)) return value; - - return null; - } - /** * Iterate through the map and handle each key -> value retrieved base on the given BiConsumer. * The given BiConsumer may to delete the passed-in entry, but is not allowed to perform any diff --git a/common/device/com/android/net/module/util/DeviceConfigUtils.java b/common/device/com/android/net/module/util/DeviceConfigUtils.java index 138c1e54..fb130f67 100644 --- a/common/device/com/android/net/module/util/DeviceConfigUtils.java +++ b/common/device/com/android/net/module/util/DeviceConfigUtils.java @@ -17,6 +17,13 @@ package com.android.net.module.util; import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; +import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; +import static android.provider.DeviceConfig.NAMESPACE_TETHERING; + +import static com.android.net.module.util.FeatureVersions.CONNECTIVITY_MODULE_ID; +import static com.android.net.module.util.FeatureVersions.MODULE_MASK; +import static com.android.net.module.util.FeatureVersions.NETWORK_STACK_MODULE_ID; +import static com.android.net.module.util.FeatureVersions.VERSION_MASK; import android.content.Context; import android.content.Intent; @@ -53,12 +60,14 @@ public final class DeviceConfigUtils { "com.android.server.connectivity.intent.action.SERVICE_CONNECTIVITY_RESOURCES_APK"; private static final String CONNECTIVITY_RES_PKG_DIR = "/apex/" + TETHERING_MODULE_NAME + "/"; - private static final long DEFAULT_PACKAGE_VERSION = 1000; + @VisibleForTesting + public static final long DEFAULT_PACKAGE_VERSION = 1000; @VisibleForTesting public static void resetPackageVersionCacheForTest() { sPackageVersion = -1; sModuleVersion = -1; + sNetworkStackModuleVersion = -1; } private static volatile long sPackageVersion = -1; @@ -199,8 +208,9 @@ public final class DeviceConfigUtils { * null. * @return true if this feature is enabled, or false if disabled. */ - public static boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace, - @NonNull String name, @NonNull String moduleName, boolean defaultEnabled) { + public static boolean isTetheringFeatureEnabled(@NonNull Context context, + @NonNull String namespace, @NonNull String name, @NonNull String moduleName, + boolean defaultEnabled) { // TODO: migrate callers to a non-generic isTetheringFeatureEnabled method. if (!TETHERING_MODULE_NAME.equals(moduleName)) { throw new IllegalArgumentException( @@ -224,14 +234,7 @@ public final class DeviceConfigUtils { // If that fails retry by appending "go.tethering" instead private static long resolveTetheringModuleVersion(@NonNull Context context) throws PackageManager.NameNotFoundException { - final String connResourcesPackage = getConnectivityResourcesPackageName(context); - final int pkgPrefixLen = connResourcesPackage.indexOf("connectivity"); - if (pkgPrefixLen < 0) { - throw new IllegalStateException( - "Invalid connectivity resources package: " + connResourcesPackage); - } - - final String pkgPrefix = connResourcesPackage.substring(0, pkgPrefixLen); + final String pkgPrefix = resolvePkgPrefix(context); final PackageManager packageManager = context.getPackageManager(); try { return packageManager.getPackageInfo(pkgPrefix + "tethering", @@ -245,6 +248,17 @@ public final class DeviceConfigUtils { PackageManager.MATCH_APEX).getLongVersionCode(); } + private static String resolvePkgPrefix(Context context) { + final String connResourcesPackage = getConnectivityResourcesPackageName(context); + final int pkgPrefixLen = connResourcesPackage.indexOf("connectivity"); + if (pkgPrefixLen < 0) { + throw new IllegalStateException( + "Invalid connectivity resources package: " + connResourcesPackage); + } + + return connResourcesPackage.substring(0, pkgPrefixLen); + } + private static volatile long sModuleVersion = -1; private static long getTetheringModuleVersion(@NonNull Context context) { if (sModuleVersion >= 0) return sModuleVersion; @@ -260,6 +274,106 @@ public final class DeviceConfigUtils { return sModuleVersion; } + private static volatile long sNetworkStackModuleVersion = -1; + + /** + * Get networkstack module version. + */ + @VisibleForTesting + static long getNetworkStackModuleVersion(@NonNull Context context) { + if (sNetworkStackModuleVersion >= 0) return sNetworkStackModuleVersion; + + try { + sNetworkStackModuleVersion = resolveNetworkStackModuleVersion(context); + } catch (PackageManager.NameNotFoundException e) { + Log.wtf(TAG, "Failed to resolve networkstack module version: " + e); + return DEFAULT_PACKAGE_VERSION; + } + return sNetworkStackModuleVersion; + } + + private static long resolveNetworkStackModuleVersion(@NonNull Context context) + throws PackageManager.NameNotFoundException { + // TODO(b/293975546): Strictly speaking this is the prefix for connectivity and not + // network stack. In practice, it's the same. Read the prefix from network stack instead. + final String pkgPrefix = resolvePkgPrefix(context); + final PackageManager packageManager = context.getPackageManager(); + try { + return packageManager.getPackageInfo(pkgPrefix + "networkstack", + PackageManager.MATCH_SYSTEM_ONLY).getLongVersionCode(); + } catch (PackageManager.NameNotFoundException e) { + Log.d(TAG, "Device is using go or non-mainline modules"); + // fall through + } + + return packageManager.getPackageInfo(pkgPrefix + "go.networkstack", + PackageManager.MATCH_ALL).getLongVersionCode(); + } + + /** + * Check whether one specific feature is supported from the feature Id. The feature Id is + * composed by a module package Id and version Id from {@link FeatureVersions}. + * + * This is useful when a feature required minimal module version supported and cannot function + * well with a standalone newer module. + * @param context The global context information about an app environment. + * @param featureId The feature id that contains required module id and minimal module version + * @return true if this feature is supported, or false if not supported. + **/ + public static boolean isFeatureSupported(@NonNull Context context, long featureId) { + final long moduleVersion; + final long moduleId = featureId & MODULE_MASK; + if (moduleId == CONNECTIVITY_MODULE_ID) { + moduleVersion = getTetheringModuleVersion(context); + } else if (moduleId == NETWORK_STACK_MODULE_ID) { + moduleVersion = getNetworkStackModuleVersion(context); + } else { + throw new IllegalArgumentException("Unknown module " + moduleId); + } + // Support by default if no module version is available. + return moduleVersion == DEFAULT_PACKAGE_VERSION + || moduleVersion >= (featureId & VERSION_MASK); + } + + /** + * Check whether one specific experimental feature in specific namespace from + * {@link DeviceConfig} is not disabled. Feature can be disabled by setting a non-zero + * value in the property. If the feature is enabled by default and disabled by flag push + * (kill switch), this method should be used. If the feature is disabled by default and + * enabled by flag push, {@link #isFeatureEnabled} should be used. + * + * @param namespace The namespace containing the property to look up. + * @param name The name of the property to look up. + * @return true if this feature is enabled, or false if disabled. + */ + private static boolean isFeatureNotChickenedOut(String namespace, String name) { + final int propertyVersion = getDeviceConfigPropertyInt(namespace, name, + 0 /* default value */); + return propertyVersion == 0; + } + + /** + * Check whether one specific experimental feature in Tethering module from {@link DeviceConfig} + * is not disabled. + * + * @param name The name of the property in tethering module to look up. + * @return true if this feature is enabled, or false if disabled. + */ + public static boolean isTetheringFeatureNotChickenedOut(String name) { + return isFeatureNotChickenedOut(NAMESPACE_TETHERING, name); + } + + /** + * Check whether one specific experimental feature in NetworkStack module from + * {@link DeviceConfig} is not disabled. + * + * @param name The name of the property in NetworkStack module to look up. + * @return true if this feature is enabled, or false if disabled. + */ + public static boolean isNetworkStackFeatureNotChickenedOut(String name) { + return isFeatureNotChickenedOut(NAMESPACE_CONNECTIVITY, name); + } + /** * Gets boolean config from resources. */ diff --git a/common/device/com/android/net/module/util/DomainUtils.java b/common/device/com/android/net/module/util/DomainUtils.java new file mode 100644 index 00000000..b327fd08 --- /dev/null +++ b/common/device/com/android/net/module/util/DomainUtils.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2023 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.util.ArrayMap; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.net.module.util.DnsPacketUtils.DnsRecordParser; + +import java.nio.BufferOverflowException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; + +/** + * Utilities for encoding/decoding the domain name or domain search list. + * + * @hide + */ +public final class DomainUtils { + private static final String TAG = "DomainUtils"; + private static final int MAX_OPTION_LEN = 255; + + @NonNull + private static String getSubstring(@NonNull final String string, @NonNull final String[] labels, + int index) { + int beginIndex = 0; + for (int i = 0; i < index; i++) { + beginIndex += labels[i].length() + 1; // include the dot + } + return string.substring(beginIndex); + } + + /** + * Encode the given single domain name to byte array, comply with RFC1035 section-3.1. + * + * @return null if the given domain string is invalid, otherwise, return a byte array + * wrapping the encoded domain, not including any padded octets, caller should + * pad zero octets at the end if needed. + */ + @Nullable + public static byte[] encode(@NonNull final String domain) { + if (!DnsRecordParser.isHostName(domain)) return null; + return encode(new String[]{ domain }, false /* compression */); + } + + /** + * Encode the given multiple domain names to byte array, comply with RFC1035 section-3.1 + * and section 4.1.4 (message compression) if enabled. + * + * @return Null if encode fails due to BufferOverflowException, otherwise, return a byte + * array wrapping the encoded domains, not including any padded octets, caller + * should pad zero octets at the end if needed. The byte array may be empty if + * the given domain strings are invalid. + */ + @Nullable + public static byte[] encode(@NonNull final String[] domains, boolean compression) { + try { + final ByteBuffer buffer = ByteBuffer.allocate(MAX_OPTION_LEN); + final ArrayMap<String, Integer> offsetMap = new ArrayMap<>(); + for (int i = 0; i < domains.length; i++) { + if (!DnsRecordParser.isHostName(domains[i])) { + Log.e(TAG, "Skip invalid domain name " + domains[i]); + continue; + } + final String[] labels = domains[i].split("\\."); + for (int j = 0; j < labels.length; j++) { + if (compression) { + final String suffix = getSubstring(domains[i], labels, j); + if (offsetMap.containsKey(suffix)) { + int offsetOfSuffix = offsetMap.get(suffix); + offsetOfSuffix |= 0xC000; + buffer.putShort((short) offsetOfSuffix); + break; // unnecessary to put the compressed string into map + } else { + offsetMap.put(suffix, buffer.position()); + } + } + // encode the domain name string without compression when: + // - compression feature isn't enabled, + // - suffix does not match any string in the map. + final byte[] labelBytes = labels[j].getBytes(StandardCharsets.UTF_8); + buffer.put((byte) labelBytes.length); + buffer.put(labelBytes); + if (j == labels.length - 1) { + // Pad terminate label at the end of last label. + buffer.put((byte) 0); + } + } + } + buffer.flip(); + final byte[] out = new byte[buffer.limit()]; + buffer.get(out); + return out; + } catch (BufferOverflowException e) { + Log.e(TAG, "Fail to encode domain name and stop encoding", e); + return null; + } + } + + /** + * Decode domain name(s) from the given byteBuffer. Decode follows RFC1035 section 3.1 and + * section 4.1.4(message compression). + * + * @return domain name(s) string array with space separated, or empty string if decode fails. + */ + @NonNull + public static ArrayList<String> decode(@NonNull final ByteBuffer buffer, boolean compression) { + final ArrayList<String> domainList = new ArrayList<>(); + while (buffer.remaining() > 0) { + try { + // TODO: replace the recursion with loop in parseName and don't need to pass in the + // maxLabelCount parameter to prevent recursion from overflowing stack. + final String domain = DnsRecordParser.parseName(buffer, 0 /* depth */, + 15 /* maxLabelCount */, compression); + if (!DnsRecordParser.isHostName(domain)) continue; + domainList.add(domain); + } catch (BufferUnderflowException | DnsPacket.ParseException e) { + Log.e(TAG, "Fail to parse domain name and stop parsing", e); + break; + } + } + return domainList; + } +} diff --git a/common/device/com/android/net/module/util/FeatureVersions.java b/common/device/com/android/net/module/util/FeatureVersions.java new file mode 100644 index 00000000..149756c3 --- /dev/null +++ b/common/device/com/android/net/module/util/FeatureVersions.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 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; + +/** + * Class to centralize feature version control that requires a specific module or a specific + * module version. + * @hide + */ +public class FeatureVersions { + /** + * This constant is used to do bitwise shift operation to create module ids. + * The module version is composed with 9 digits which is placed in the lower 36 bits. + */ + private static final int MODULE_SHIFT = 36; + /** + * The bitmask to do bitwise-and(i.e. {@code &}) operation to get the module id. + */ + public static final long MODULE_MASK = 0xFF0_0000_0000L; + /** + * The bitmask to do bitwise-and(i.e. {@code &}) operation to get the module version. + */ + public static final long VERSION_MASK = 0x00F_FFFF_FFFFL; + public static final long CONNECTIVITY_MODULE_ID = 0x01L << MODULE_SHIFT; + public static final long NETWORK_STACK_MODULE_ID = 0x02L << MODULE_SHIFT; + // CLAT_ADDRESS_TRANSLATE is a feature of the network stack, which doesn't throw when system + // try to add a NAT-T keepalive packet filter with v6 address, introduced in version + // M-2023-Sept on July 3rd, 2023. + public static final long FEATURE_CLAT_ADDRESS_TRANSLATE = + NETWORK_STACK_MODULE_ID + 34_09_00_000L; +} diff --git a/common/device/com/android/net/module/util/SharedLog.java b/common/device/com/android/net/module/util/SharedLog.java index 17b061e6..6b12c80d 100644 --- a/common/device/com/android/net/module/util/SharedLog.java +++ b/common/device/com/android/net/module/util/SharedLog.java @@ -46,6 +46,8 @@ public class SharedLog { ERROR, MARK, WARN, + VERBOSE, + TERRIBLE, } private final LocalLog mLocalLog; @@ -159,6 +161,41 @@ public class SharedLog { Log.w(mTag, record(Category.WARN, msg)); } + /** + * Log a verbose message. + * + * <p>The log entry will be also added to the system log. + */ + public void v(String msg) { + Log.v(mTag, record(Category.VERBOSE, msg)); + } + + /** + * Log a terrible failure message. + * + * <p>The log entry will be also added to the system log and will trigger system reporting + * for terrible failures. + */ + public void wtf(String msg) { + Log.wtf(mTag, record(Category.TERRIBLE, msg)); + } + + /** + * Log a terrible failure due to an exception, with the exception stacktrace if provided. + * + * <p>The error and exception message appear in the shared log, but the stacktrace is only + * logged in general log output (logcat). The log entry will be also added to the system log + * and will trigger system reporting for terrible failures. + */ + public void wtf(@NonNull String msg, @Nullable Throwable exception) { + if (exception == null) { + e(msg); + return; + } + Log.wtf(mTag, record(Category.TERRIBLE, msg + ": " + exception.getMessage()), exception); + } + + ////// // Methods that only log an entry (and do NOT emit to the system log). ////// diff --git a/common/device/com/android/net/module/util/arp/ArpPacket.java b/common/device/com/android/net/module/util/arp/ArpPacket.java new file mode 100644 index 00000000..dab9694b --- /dev/null +++ b/common/device/com/android/net/module/util/arp/ArpPacket.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2019 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.arp; + +import static android.system.OsConstants.ETH_P_ARP; +import static android.system.OsConstants.ETH_P_IP; + +import static com.android.net.module.util.NetworkStackConstants.ARP_ETHER_IPV4_LEN; +import static com.android.net.module.util.NetworkStackConstants.ARP_HWTYPE_ETHER; +import static com.android.net.module.util.NetworkStackConstants.ARP_REPLY; +import static com.android.net.module.util.NetworkStackConstants.ARP_REQUEST; +import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN; +import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_LEN; + +import android.net.MacAddress; + +import com.android.internal.annotations.VisibleForTesting; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; + +/** + * Defines basic data and operations needed to build and parse packets for the + * ARP protocol. + * + * @hide + */ +public class ArpPacket { + private static final String TAG = "ArpPacket"; + + public final short opCode; + public final Inet4Address senderIp; + public final Inet4Address targetIp; + public final MacAddress senderHwAddress; + public final MacAddress targetHwAddress; + + ArpPacket(short opCode, MacAddress senderHwAddress, Inet4Address senderIp, + MacAddress targetHwAddress, Inet4Address targetIp) { + this.opCode = opCode; + this.senderHwAddress = senderHwAddress; + this.senderIp = senderIp; + this.targetHwAddress = targetHwAddress; + this.targetIp = targetIp; + } + + /** + * Build an ARP packet from the required specified parameters. + */ + @VisibleForTesting + public static ByteBuffer buildArpPacket(final byte[] dstMac, final byte[] srcMac, + final byte[] targetIp, final byte[] targetHwAddress, byte[] senderIp, + final short opCode) { + final ByteBuffer buf = ByteBuffer.allocate(ARP_ETHER_IPV4_LEN); + + // Ether header + buf.put(dstMac); + buf.put(srcMac); + buf.putShort((short) ETH_P_ARP); + + // ARP header + buf.putShort((short) ARP_HWTYPE_ETHER); // hrd + buf.putShort((short) ETH_P_IP); // pro + buf.put((byte) ETHER_ADDR_LEN); // hln + buf.put((byte) IPV4_ADDR_LEN); // pln + buf.putShort(opCode); // op + buf.put(srcMac); // sha + buf.put(senderIp); // spa + buf.put(targetHwAddress); // tha + buf.put(targetIp); // tpa + buf.flip(); + return buf; + } + + /** + * Parse an ARP packet from a ByteBuffer object. + */ + @VisibleForTesting + public static ArpPacket parseArpPacket(final byte[] recvbuf, final int length) + throws ParseException { + try { + if (length < ARP_ETHER_IPV4_LEN || recvbuf.length < length) { + throw new ParseException("Invalid packet length: " + length); + } + + final ByteBuffer buffer = ByteBuffer.wrap(recvbuf, 0, length); + byte[] l2dst = new byte[ETHER_ADDR_LEN]; + byte[] l2src = new byte[ETHER_ADDR_LEN]; + buffer.get(l2dst); + buffer.get(l2src); + + final short etherType = buffer.getShort(); + if (etherType != ETH_P_ARP) { + throw new ParseException("Incorrect Ether Type: " + etherType); + } + + final short hwType = buffer.getShort(); + if (hwType != ARP_HWTYPE_ETHER) { + throw new ParseException("Incorrect HW Type: " + hwType); + } + + final short protoType = buffer.getShort(); + if (protoType != ETH_P_IP) { + throw new ParseException("Incorrect Protocol Type: " + protoType); + } + + final byte hwAddrLength = buffer.get(); + if (hwAddrLength != ETHER_ADDR_LEN) { + throw new ParseException("Incorrect HW address length: " + hwAddrLength); + } + + final byte ipAddrLength = buffer.get(); + if (ipAddrLength != IPV4_ADDR_LEN) { + throw new ParseException("Incorrect Protocol address length: " + ipAddrLength); + } + + final short opCode = buffer.getShort(); + if (opCode != ARP_REQUEST && opCode != ARP_REPLY) { + throw new ParseException("Incorrect opCode: " + opCode); + } + + byte[] senderHwAddress = new byte[ETHER_ADDR_LEN]; + byte[] senderIp = new byte[IPV4_ADDR_LEN]; + buffer.get(senderHwAddress); + buffer.get(senderIp); + + byte[] targetHwAddress = new byte[ETHER_ADDR_LEN]; + byte[] targetIp = new byte[IPV4_ADDR_LEN]; + buffer.get(targetHwAddress); + buffer.get(targetIp); + + return new ArpPacket(opCode, MacAddress.fromBytes(senderHwAddress), + (Inet4Address) InetAddress.getByAddress(senderIp), + MacAddress.fromBytes(targetHwAddress), + (Inet4Address) InetAddress.getByAddress(targetIp)); + } catch (IndexOutOfBoundsException e) { + throw new ParseException("Invalid index when wrapping a byte array into a buffer"); + } catch (BufferUnderflowException e) { + throw new ParseException("Invalid buffer position"); + } catch (IllegalArgumentException e) { + throw new ParseException("Invalid MAC address representation"); + } catch (UnknownHostException e) { + throw new ParseException("Invalid IP address of Host"); + } + } + + /** + * Thrown when parsing ARP packet failed. + */ + public static class ParseException extends Exception { + ParseException(String message) { + super(message); + } + } +} diff --git a/common/device/com/android/net/module/util/netlink/NetlinkUtils.java b/common/device/com/android/net/module/util/netlink/NetlinkUtils.java index 73c88c77..33bd36d8 100644 --- a/common/device/com/android/net/module/util/netlink/NetlinkUtils.java +++ b/common/device/com/android/net/module/util/netlink/NetlinkUtils.java @@ -33,6 +33,7 @@ import static android.system.OsConstants.SO_SNDTIMEO; import android.net.util.SocketUtils; import android.system.ErrnoException; import android.system.Os; +import android.system.OsConstants; import android.system.StructTimeval; import android.util.Log; @@ -291,5 +292,20 @@ public class NetlinkUtils { return Os.write(fd, bytes, offset, count); } + private static final long CLOCK_TICKS_PER_SECOND = Os.sysconf(OsConstants._SC_CLK_TCK); + + /** + * Convert the system time in clock ticks(clock_t type in times(), not in clock()) to + * milliseconds. times() clock_t ticks at the kernel's USER_HZ (100) while clock() clock_t + * ticks at CLOCKS_PER_SEC (1000000). + * + * See the NOTES on https://man7.org/linux/man-pages/man2/times.2.html for the difference + * of clock_t used in clock() and times(). + */ + public static long ticksToMilliSeconds(int intClockTicks) { + final long longClockTicks = intClockTicks & 0xffffffffL; + return (longClockTicks * 1000) / CLOCK_TICKS_PER_SECOND; + } + private NetlinkUtils() {} } diff --git a/common/device/com/android/net/module/util/netlink/StructNdaCacheInfo.java b/common/device/com/android/net/module/util/netlink/StructNdaCacheInfo.java index 79d5ff4a..1f9bb7ec 100644 --- a/common/device/com/android/net/module/util/netlink/StructNdaCacheInfo.java +++ b/common/device/com/android/net/module/util/netlink/StructNdaCacheInfo.java @@ -16,8 +16,7 @@ package com.android.net.module.util.netlink; -import android.system.Os; -import android.system.OsConstants; +import static com.android.net.module.util.netlink.NetlinkUtils.ticksToMilliSeconds; import java.nio.ByteBuffer; @@ -57,15 +56,6 @@ public class StructNdaCacheInfo { return struct; } - // TODO: investigate whether this can change during device runtime and - // decide what (if anything) should be done about that. - private static final long CLOCK_TICKS_PER_SECOND = Os.sysconf(OsConstants._SC_CLK_TCK); - - private static long ticksToMilliSeconds(int intClockTicks) { - final long longClockTicks = (long) intClockTicks & 0xffffffff; - return (longClockTicks * 1000) / CLOCK_TICKS_PER_SECOND; - } - /** * Explanatory notes, for reference. * diff --git a/common/device/com/android/net/module/util/structs/Ipv6PktInfo.java b/common/device/com/android/net/module/util/structs/Ipv6PktInfo.java new file mode 100644 index 00000000..0dccb727 --- /dev/null +++ b/common/device/com/android/net/module/util/structs/Ipv6PktInfo.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 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.structs; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +import java.net.Inet6Address; + +/** + * structure in6_pktinfo + * + * see also: + * + * include/uapi/linux/ipv6.h + */ +public class Ipv6PktInfo extends Struct { + @Field(order = 0, type = Type.Ipv6Address) + public final Inet6Address addr; // IPv6 source or destination address + @Field(order = 1, type = Type.S32) + public final int ifindex; // interface index + + public Ipv6PktInfo(final Inet6Address addr, final int ifindex) { + this.addr = addr; + this.ifindex = ifindex; + } +} diff --git a/common/device/com/android/net/module/util/structs/RouteInformationOption.java b/common/device/com/android/net/module/util/structs/RouteInformationOption.java new file mode 100644 index 00000000..49bafedb --- /dev/null +++ b/common/device/com/android/net/module/util/structs/RouteInformationOption.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2023 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.structs; + +import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_RIO; + +import android.net.IpPrefix; + +import androidx.annotation.NonNull; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * ICMPv6 route information option, as per https://tools.ietf.org/html/rfc4191. + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Type | Length | Prefix Length |Resvd|Prf|Resvd| + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Route Lifetime | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Prefix (Variable Length) | + * . . + * . . + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +public class RouteInformationOption extends Struct { + public enum Preference { + HIGH((byte) 0x1), + MEDIUM((byte) 0x0), + LOW((byte) 0x3), + RESERVED((byte) 0x2); + + final byte mValue; + Preference(byte value) { + this.mValue = value; + } + } + + @Field(order = 0, type = Type.S8) + public final byte type; + @Field(order = 1, type = Type.S8) + public final byte length; // Length in 8-byte octets + @Field(order = 2, type = Type.U8) + public final short prefixLen; + @Field(order = 3, type = Type.S8) + public final byte prf; + @Field(order = 4, type = Type.U32) + public final long routeLifetime; + @Field(order = 5, type = Type.ByteArray, arraysize = 16) + public final byte[] prefix; + + RouteInformationOption(final byte type, final byte length, final short prefixLen, + final byte prf, final long routeLifetime, @NonNull final byte[] prefix) { + this.type = type; + this.length = length; + this.prefixLen = prefixLen; + this.prf = prf; + this.routeLifetime = routeLifetime; + this.prefix = prefix; + } + + /** + * Build a Route Information option from the required specified parameters. + */ + public static ByteBuffer build(final IpPrefix prefix, final Preference prf, + final long routeLifetime) { + // The prefix field is always assumed to have 16 bytes, but the number of leading + // bits in this prefix depends on IpPrefix#prefixLength, then we can simply set the + // option length to 3. + final RouteInformationOption option = new RouteInformationOption( + (byte) ICMPV6_ND_OPTION_RIO, (byte) 3 /* option length */, + (short) prefix.getPrefixLength(), (byte) (prf.mValue << 3), routeLifetime, + prefix.getRawAddress()); + return ByteBuffer.wrap(option.writeToBytes(ByteOrder.BIG_ENDIAN)); + } +} diff --git a/common/framework/com/android/net/module/util/DnsPacketUtils.java b/common/framework/com/android/net/module/util/DnsPacketUtils.java index c47bfa07..105d7837 100644 --- a/common/framework/com/android/net/module/util/DnsPacketUtils.java +++ b/common/framework/com/android/net/module/util/DnsPacketUtils.java @@ -130,16 +130,25 @@ public final class DnsPacketUtils { /** * Parses the domain / target name of a DNS record. + */ + public static String parseName(final ByteBuffer buf, int depth, + boolean isNameCompressionSupported) throws + BufferUnderflowException, DnsPacket.ParseException { + return parseName(buf, depth, MAXLABELCOUNT, isNameCompressionSupported); + } + + /** + * Parses the domain / target name of a DNS record. * * As described in RFC 1035 Section 4.1.3, the NAME field of a DNS Resource Record always * supports Name Compression, whereas domain names contained in the RDATA payload of a DNS * record may or may not support Name Compression, depending on the record TYPE. Moreover, * even if Name Compression is supported, its usage is left to the implementation. */ - public static String parseName(ByteBuffer buf, int depth, + public static String parseName(final ByteBuffer buf, int depth, int maxLabelCount, boolean isNameCompressionSupported) throws BufferUnderflowException, DnsPacket.ParseException { - if (depth > MAXLABELCOUNT) { + if (depth > maxLabelCount) { throw new DnsPacket.ParseException("Failed to parse name, too many labels"); } final int len = Byte.toUnsignedInt(buf.get()); @@ -158,7 +167,8 @@ public final class DnsPacketUtils { "Parse compression name fail, invalid compression"); } buf.position(offset); - final String pointed = parseName(buf, depth + 1, isNameCompressionSupported); + final String pointed = parseName(buf, depth + 1, maxLabelCount, + isNameCompressionSupported); buf.position(oldPos); return pointed; } else { @@ -168,7 +178,8 @@ public final class DnsPacketUtils { if (head.length() > MAXLABELSIZE) { throw new DnsPacket.ParseException("Parse name fail, invalid label length"); } - final String tail = parseName(buf, depth + 1, isNameCompressionSupported); + final String tail = parseName(buf, depth + 1, maxLabelCount, + isNameCompressionSupported); return TextUtils.isEmpty(tail) ? head : head + "." + tail; } } diff --git a/common/framework/com/android/net/module/util/NetworkStackConstants.java b/common/framework/com/android/net/module/util/NetworkStackConstants.java index 6a6f5e10..91491605 100644 --- a/common/framework/com/android/net/module/util/NetworkStackConstants.java +++ b/common/framework/com/android/net/module/util/NetworkStackConstants.java @@ -17,6 +17,7 @@ package com.android.net.module.util; import android.net.InetAddresses; +import android.net.IpPrefix; import java.net.Inet4Address; import java.net.Inet6Address; @@ -99,6 +100,8 @@ public final class NetworkStackConstants { public static final int IPV4_ADDR_LEN = 4; public static final int IPV4_FLAG_MF = 0x2000; public static final int IPV4_FLAG_DF = 0x4000; + // getSockOpt() for v4 MTU + public static final int IP_MTU = 14; public static final Inet4Address IPV4_ADDR_ALL = makeInet4Address( (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff); public static final Inet4Address IPV4_ADDR_ANY = makeInet4Address( @@ -108,6 +111,12 @@ public final class NetworkStackConstants { (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0 }); + + /** + * CLAT constants + */ + public static final IpPrefix CLAT_PREFIX = new IpPrefix("192.0.0.0/29"); + /** * IPv6 constants. * @@ -121,6 +130,10 @@ public final class NetworkStackConstants { public static final int IPV6_SRC_ADDR_OFFSET = 8; public static final int IPV6_DST_ADDR_OFFSET = 24; public static final int IPV6_MIN_MTU = 1280; + public static final int IPV6_FRAGMENT_HEADER_LEN = 8; + public static final int RFC7421_PREFIX_LENGTH = 64; + // getSockOpt() for v6 MTU + public static final int IPV6_MTU = 24; public static final Inet6Address IPV6_ADDR_ALL_NODES_MULTICAST = (Inet6Address) InetAddresses.parseNumericAddress("ff02::1"); public static final Inet6Address IPV6_ADDR_ALL_ROUTERS_MULTICAST = @@ -140,6 +153,7 @@ public final class NetworkStackConstants { * ICMPv6 constants. * * See also: + * - https://tools.ietf.org/html/rfc4191 * - https://tools.ietf.org/html/rfc4443 * - https://tools.ietf.org/html/rfc4861 */ @@ -157,6 +171,7 @@ public final class NetworkStackConstants { public static final int ICMPV6_ND_OPTION_TLLA = 2; public static final int ICMPV6_ND_OPTION_PIO = 3; public static final int ICMPV6_ND_OPTION_MTU = 5; + public static final int ICMPV6_ND_OPTION_RIO = 24; public static final int ICMPV6_ND_OPTION_RDNSS = 25; public static final int ICMPV6_ND_OPTION_PREF64 = 38; diff --git a/common/native/bpf_headers/include/bpf/BpfClassic.h b/common/native/bpf_headers/include/bpf/BpfClassic.h index 9b38deec..dd0804c3 100644 --- a/common/native/bpf_headers/include/bpf/BpfClassic.h +++ b/common/native/bpf_headers/include/bpf/BpfClassic.h @@ -22,11 +22,25 @@ // Reject the packet #define BPF_REJECT BPF_STMT(BPF_RET | BPF_K, 0) +// *TWO* instructions: compare and if not equal jump over the accept statement +#define BPF2_ACCEPT_IF_EQUAL(v) \ + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, (v), 0, 1), \ + BPF_ACCEPT + // *TWO* instructions: compare and if equal jump over the reject statement #define BPF2_REJECT_IF_NOT_EQUAL(v) \ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, (v), 1, 0), \ BPF_REJECT +// *TWO* instructions: compare and if none of the bits are set jump over the reject statement +#define BPF2_REJECT_IF_ANY_BITS_SET(v) \ + BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, (v), 0, 1), \ + BPF_REJECT + +// loads skb->protocol +#define BPF_LOAD_SKB_PROTOCOL \ + BPF_STMT(BPF_LD | BPF_H | BPF_ABS, (__u32)SKF_AD_OFF + SKF_AD_PROTOCOL) + // 8-bit load relative to start of link layer (mac/ethernet) header. #define BPF_LOAD_MAC_RELATIVE_U8(ofs) \ BPF_STMT(BPF_LD | BPF_B | BPF_ABS, (__u32)SKF_LL_OFF + (ofs)) diff --git a/common/native/bpf_headers/include/bpf/BpfMap.h b/common/native/bpf_headers/include/bpf/BpfMap.h index 51e6d162..847083e5 100644 --- a/common/native/bpf_headers/include/bpf/BpfMap.h +++ b/common/native/bpf_headers/include/bpf/BpfMap.h @@ -29,6 +29,10 @@ namespace android { namespace bpf { +using base::Result; +using base::unique_fd; +using std::function; + // This is a class wrapper for eBPF maps. The eBPF map is a special in-kernel // data structure that stores data in <Key, Value> pairs. It can be read/write // from userspace by passing syscalls with the map file descriptor. This class @@ -80,7 +84,7 @@ class BpfMap { } #endif - base::Result<Key> getFirstKey() const { + Result<Key> getFirstKey() const { Key firstKey; if (getFirstMapKey(mMapFd, &firstKey)) { return ErrnoErrorf("Get firstKey map {} failed", mMapFd.get()); @@ -88,7 +92,7 @@ class BpfMap { return firstKey; } - base::Result<Key> getNextKey(const Key& key) const { + Result<Key> getNextKey(const Key& key) const { Key nextKey; if (getNextMapKey(mMapFd, &key, &nextKey)) { return ErrnoErrorf("Get next key of map {} failed", mMapFd.get()); @@ -96,14 +100,14 @@ class BpfMap { return nextKey; } - base::Result<void> writeValue(const Key& key, const Value& value, uint64_t flags) { + Result<void> writeValue(const Key& key, const Value& value, uint64_t flags) { if (writeToMapEntry(mMapFd, &key, &value, flags)) { return ErrnoErrorf("Write to map {} failed", mMapFd.get()); } return {}; } - base::Result<Value> readValue(const Key key) const { + Result<Value> readValue(const Key key) const { Value value; if (findMapEntry(mMapFd, &key, &value)) { return ErrnoErrorf("Read value of map {} failed", mMapFd.get()); @@ -111,7 +115,7 @@ class BpfMap { return value; } - base::Result<void> deleteValue(const Key& key) { + Result<void> deleteValue(const Key& key) { if (deleteMapEntry(mMapFd, &key)) { return ErrnoErrorf("Delete entry from map {} failed", mMapFd.get()); } @@ -119,7 +123,7 @@ class BpfMap { } protected: - [[clang::reinitializes]] base::Result<void> init(const char* path, int fd) { + [[clang::reinitializes]] Result<void> init(const char* path, int fd) { mMapFd.reset(fd); if (!mMapFd.ok()) { return ErrnoErrorf("Pinned map not accessible or does not exist: ({})", path); @@ -134,7 +138,7 @@ class BpfMap { public: // Function that tries to get map from a pinned path. - [[clang::reinitializes]] base::Result<void> init(const char* path) { + [[clang::reinitializes]] Result<void> init(const char* path) { return init(path, mapRetrieveRW(path)); } @@ -144,7 +148,7 @@ class BpfMap { // this should only ever be used by test code, it is equivalent to: // .reset(createMap(type, keysize, valuesize, max_entries, map_flags) // TODO: derive map_flags from BpfMap vs BpfMapRO - [[clang::reinitializes]] base::Result<void> resetMap(bpf_map_type map_type, + [[clang::reinitializes]] Result<void> resetMap(bpf_map_type map_type, uint32_t max_entries, uint32_t map_flags = 0) { mMapFd.reset(createMap(map_type, sizeof(Key), sizeof(Value), max_entries, map_flags)); @@ -155,30 +159,30 @@ class BpfMap { // Iterate through the map and handle each key retrieved based on the filter // without modification of map content. - base::Result<void> iterate( - const std::function<base::Result<void>(const Key& key, const BpfMap<Key, Value>& map)>& - filter) const; + Result<void> iterate( + const function<Result<void>(const Key& key, + const BpfMap<Key, Value>& map)>& filter) const; // Iterate through the map and get each <key, value> pair, handle each <key, // value> pair based on the filter without modification of map content. - base::Result<void> iterateWithValue( - const std::function<base::Result<void>(const Key& key, const Value& value, - const BpfMap<Key, Value>& map)>& filter) const; + Result<void> iterateWithValue( + const function<Result<void>(const Key& key, const Value& value, + const BpfMap<Key, Value>& map)>& filter) const; // Iterate through the map and handle each key retrieved based on the filter - base::Result<void> iterate( - const std::function<base::Result<void>(const Key& key, BpfMap<Key, Value>& map)>& - filter); + Result<void> iterate( + const function<Result<void>(const Key& key, + BpfMap<Key, Value>& map)>& filter); // Iterate through the map and get each <key, value> pair, handle each <key, // value> pair based on the filter. - base::Result<void> iterateWithValue( - const std::function<base::Result<void>(const Key& key, const Value& value, - BpfMap<Key, Value>& map)>& filter); - - const base::unique_fd& getMap() const { return mMapFd; }; + Result<void> iterateWithValue( + const function<Result<void>(const Key& key, const Value& value, + BpfMap<Key, Value>& map)>& filter); #ifdef BPF_MAP_MAKE_VISIBLE_FOR_TESTING + const unique_fd& getMap() const { return mMapFd; }; + // Copy assignment operator - due to need for fd duping, should not be used in non-test code. BpfMap<Key, Value>& operator=(const BpfMap<Key, Value>& other) { if (this != &other) mMapFd.reset(fcntl(other.mMapFd.get(), F_DUPFD_CLOEXEC, 0)); @@ -197,7 +201,7 @@ class BpfMap { return *this; } - void reset(base::unique_fd fd) = delete; + void reset(unique_fd fd) = delete; #ifdef BPF_MAP_MAKE_VISIBLE_FOR_TESTING // Note that unique_fd.reset() carefully saves and restores the errno, @@ -216,7 +220,7 @@ class BpfMap { bool isValid() const { return mMapFd.ok(); } - base::Result<void> clear() { + Result<void> clear() { while (true) { auto key = getFirstKey(); if (!key.ok()) { @@ -233,28 +237,25 @@ class BpfMap { } } - base::Result<bool> isEmpty() const { + Result<bool> isEmpty() const { auto key = getFirstKey(); - if (!key.ok()) { - // Return error code ENOENT means the map is empty - if (key.error().code() == ENOENT) return true; - return key.error(); - } - return false; + if (key.ok()) return false; + if (key.error().code() == ENOENT) return true; + return key.error(); } private: - base::unique_fd mMapFd; + unique_fd mMapFd; }; template <class Key, class Value> -base::Result<void> BpfMap<Key, Value>::iterate( - const std::function<base::Result<void>(const Key& key, const BpfMap<Key, Value>& map)>& - filter) const { - base::Result<Key> curKey = getFirstKey(); +Result<void> BpfMap<Key, Value>::iterate( + const function<Result<void>(const Key& key, + const BpfMap<Key, Value>& map)>& filter) const { + Result<Key> curKey = getFirstKey(); while (curKey.ok()) { - const base::Result<Key>& nextKey = getNextKey(curKey.value()); - base::Result<void> status = filter(curKey.value(), *this); + const Result<Key>& nextKey = getNextKey(curKey.value()); + Result<void> status = filter(curKey.value(), *this); if (!status.ok()) return status; curKey = nextKey; } @@ -263,15 +264,15 @@ base::Result<void> BpfMap<Key, Value>::iterate( } template <class Key, class Value> -base::Result<void> BpfMap<Key, Value>::iterateWithValue( - const std::function<base::Result<void>(const Key& key, const Value& value, - const BpfMap<Key, Value>& map)>& filter) const { - base::Result<Key> curKey = getFirstKey(); +Result<void> BpfMap<Key, Value>::iterateWithValue( + const function<Result<void>(const Key& key, const Value& value, + const BpfMap<Key, Value>& map)>& filter) const { + Result<Key> curKey = getFirstKey(); while (curKey.ok()) { - const base::Result<Key>& nextKey = getNextKey(curKey.value()); - base::Result<Value> curValue = readValue(curKey.value()); + const Result<Key>& nextKey = getNextKey(curKey.value()); + Result<Value> curValue = readValue(curKey.value()); if (!curValue.ok()) return curValue.error(); - base::Result<void> status = filter(curKey.value(), curValue.value(), *this); + Result<void> status = filter(curKey.value(), curValue.value(), *this); if (!status.ok()) return status; curKey = nextKey; } @@ -280,12 +281,13 @@ base::Result<void> BpfMap<Key, Value>::iterateWithValue( } template <class Key, class Value> -base::Result<void> BpfMap<Key, Value>::iterate( - const std::function<base::Result<void>(const Key& key, BpfMap<Key, Value>& map)>& filter) { - base::Result<Key> curKey = getFirstKey(); +Result<void> BpfMap<Key, Value>::iterate( + const function<Result<void>(const Key& key, + BpfMap<Key, Value>& map)>& filter) { + Result<Key> curKey = getFirstKey(); while (curKey.ok()) { - const base::Result<Key>& nextKey = getNextKey(curKey.value()); - base::Result<void> status = filter(curKey.value(), *this); + const Result<Key>& nextKey = getNextKey(curKey.value()); + Result<void> status = filter(curKey.value(), *this); if (!status.ok()) return status; curKey = nextKey; } @@ -294,15 +296,15 @@ base::Result<void> BpfMap<Key, Value>::iterate( } template <class Key, class Value> -base::Result<void> BpfMap<Key, Value>::iterateWithValue( - const std::function<base::Result<void>(const Key& key, const Value& value, - BpfMap<Key, Value>& map)>& filter) { - base::Result<Key> curKey = getFirstKey(); +Result<void> BpfMap<Key, Value>::iterateWithValue( + const function<Result<void>(const Key& key, const Value& value, + BpfMap<Key, Value>& map)>& filter) { + Result<Key> curKey = getFirstKey(); while (curKey.ok()) { - const base::Result<Key>& nextKey = getNextKey(curKey.value()); - base::Result<Value> curValue = readValue(curKey.value()); + const Result<Key>& nextKey = getNextKey(curKey.value()); + Result<Value> curValue = readValue(curKey.value()); if (!curValue.ok()) return curValue.error(); - base::Result<void> status = filter(curKey.value(), curValue.value(), *this); + Result<void> status = filter(curKey.value(), curValue.value(), *this); if (!status.ok()) return status; curKey = nextKey; } @@ -319,7 +321,7 @@ class BpfMapRO : public BpfMap<Key, Value> { : BpfMap<Key, Value>(pathname, BPF_F_RDONLY) {} // Function that tries to get map from a pinned path. - [[clang::reinitializes]] base::Result<void> init(const char* path) { + [[clang::reinitializes]] Result<void> init(const char* path) { return BpfMap<Key,Value>::init(path, mapRetrieveRO(path)); } }; diff --git a/common/netd/libnetdutils/Android.bp b/common/netd/libnetdutils/Android.bp index 08d54121..31690331 100644 --- a/common/netd/libnetdutils/Android.bp +++ b/common/netd/libnetdutils/Android.bp @@ -66,3 +66,8 @@ cc_test { "libbase", ], } + +cc_library_headers { + name: "libnetd_utils_headers", + export_include_dirs: ["include"], +} diff --git a/common/tests/unit/Android.bp b/common/tests/unit/Android.bp index 8c49954e..40371e63 100644 --- a/common/tests/unit/Android.bp +++ b/common/tests/unit/Android.bp @@ -22,7 +22,6 @@ android_library { "net-utils-device-common-bpf", "net-utils-device-common-ip", "net-utils-device-common-wear", - "truth", ], libs: [ "android.test.runner", diff --git a/common/tests/unit/src/com/android/net/module/util/ArpPacketTest.java b/common/tests/unit/src/com/android/net/module/util/ArpPacketTest.java new file mode 100644 index 00000000..e25d5548 --- /dev/null +++ b/common/tests/unit/src/com/android/net/module/util/ArpPacketTest.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2019 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 com.android.net.module.util.NetworkStackConstants.ARP_REQUEST; +import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN; +import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST; +import static com.android.testutils.MiscAsserts.assertThrows; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import android.net.InetAddresses; +import android.net.MacAddress; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.net.module.util.arp.ArpPacket; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet4Address; +import java.nio.ByteBuffer; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public final class ArpPacketTest { + + private static final Inet4Address TEST_IPV4_ADDR = + (Inet4Address) InetAddresses.parseNumericAddress("192.168.1.2"); + private static final Inet4Address INADDR_ANY = + (Inet4Address) InetAddresses.parseNumericAddress("0.0.0.0"); + private static final byte[] TEST_SENDER_MAC_ADDR = new byte[] { + 0x00, 0x1a, 0x11, 0x22, 0x33, 0x33 }; + private static final byte[] TEST_TARGET_MAC_ADDR = new byte[] { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + private static final byte[] TEST_ARP_PROBE = new byte[] { + // dst mac address + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + // src mac address + (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33, + // ether type + (byte) 0x08, (byte) 0x06, + // hardware type + (byte) 0x00, (byte) 0x01, + // protocol type + (byte) 0x08, (byte) 0x00, + // hardware address size + (byte) 0x06, + // protocol address size + (byte) 0x04, + // opcode + (byte) 0x00, (byte) 0x01, + // sender mac address + (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33, + // sender IP address + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // target mac address + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // target IP address + (byte) 0xc0, (byte) 0xa8, (byte) 0x01, (byte) 0x02, + }; + + private static final byte[] TEST_ARP_ANNOUNCE = new byte[] { + // dst mac address + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + // src mac address + (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33, + // ether type + (byte) 0x08, (byte) 0x06, + // hardware type + (byte) 0x00, (byte) 0x01, + // protocol type + (byte) 0x08, (byte) 0x00, + // hardware address size + (byte) 0x06, + // protocol address size + (byte) 0x04, + // opcode + (byte) 0x00, (byte) 0x01, + // sender mac address + (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33, + // sender IP address + (byte) 0xc0, (byte) 0xa8, (byte) 0x01, (byte) 0x02, + // target mac address + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // target IP address + (byte) 0xc0, (byte) 0xa8, (byte) 0x01, (byte) 0x02, + }; + + private static final byte[] TEST_ARP_PROBE_TRUNCATED = new byte[] { + // dst mac address + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + // src mac address + (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33, + // ether type + (byte) 0x08, (byte) 0x06, + // hardware type + (byte) 0x00, (byte) 0x01, + // protocol type + (byte) 0x08, (byte) 0x00, + // hardware address size + (byte) 0x06, + // protocol address size + (byte) 0x04, + // opcode + (byte) 0x00, + }; + + private static final byte[] TEST_ARP_PROBE_TRUNCATED_MAC = new byte[] { + // dst mac address + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + // src mac address + (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33, + // ether type + (byte) 0x08, (byte) 0x06, + // hardware type + (byte) 0x00, (byte) 0x01, + // protocol type + (byte) 0x08, (byte) 0x00, + // hardware address size + (byte) 0x06, + // protocol address size + (byte) 0x04, + // opcode + (byte) 0x00, (byte) 0x01, + // sender mac address + (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, + }; + + @Test + public void testBuildArpProbePacket() throws Exception { + final ByteBuffer arpProbe = ArpPacket.buildArpPacket(ETHER_BROADCAST, + TEST_SENDER_MAC_ADDR, TEST_IPV4_ADDR.getAddress(), new byte[ETHER_ADDR_LEN], + INADDR_ANY.getAddress(), (short) ARP_REQUEST); + assertArrayEquals(arpProbe.array(), TEST_ARP_PROBE); + } + + @Test + public void testBuildArpAnnouncePacket() throws Exception { + final ByteBuffer arpAnnounce = ArpPacket.buildArpPacket(ETHER_BROADCAST, + TEST_SENDER_MAC_ADDR, TEST_IPV4_ADDR.getAddress(), new byte[ETHER_ADDR_LEN], + TEST_IPV4_ADDR.getAddress(), (short) ARP_REQUEST); + assertArrayEquals(arpAnnounce.array(), TEST_ARP_ANNOUNCE); + } + + @Test + public void testParseArpProbePacket() throws Exception { + final ArpPacket packet = ArpPacket.parseArpPacket(TEST_ARP_PROBE, TEST_ARP_PROBE.length); + assertEquals(packet.opCode, ARP_REQUEST); + assertEquals(packet.senderHwAddress, MacAddress.fromBytes(TEST_SENDER_MAC_ADDR)); + assertEquals(packet.targetHwAddress, MacAddress.fromBytes(TEST_TARGET_MAC_ADDR)); + assertEquals(packet.senderIp, INADDR_ANY); + assertEquals(packet.targetIp, TEST_IPV4_ADDR); + } + + @Test + public void testParseArpAnnouncePacket() throws Exception { + final ArpPacket packet = ArpPacket.parseArpPacket(TEST_ARP_ANNOUNCE, + TEST_ARP_ANNOUNCE.length); + assertEquals(packet.opCode, ARP_REQUEST); + assertEquals(packet.senderHwAddress, MacAddress.fromBytes(TEST_SENDER_MAC_ADDR)); + assertEquals(packet.targetHwAddress, MacAddress.fromBytes(TEST_TARGET_MAC_ADDR)); + assertEquals(packet.senderIp, TEST_IPV4_ADDR); + assertEquals(packet.targetIp, TEST_IPV4_ADDR); + } + + @Test + public void testParseArpPacket_invalidByteBufferParameters() throws Exception { + assertThrows(ArpPacket.ParseException.class, () -> ArpPacket.parseArpPacket( + TEST_ARP_PROBE, 0)); + } + + @Test + public void testParseArpPacket_truncatedPacket() throws Exception { + assertThrows(ArpPacket.ParseException.class, () -> ArpPacket.parseArpPacket( + TEST_ARP_PROBE_TRUNCATED, TEST_ARP_PROBE_TRUNCATED.length)); + } + + @Test + public void testParseArpPacket_truncatedMacAddress() throws Exception { + assertThrows(ArpPacket.ParseException.class, () -> ArpPacket.parseArpPacket( + TEST_ARP_PROBE_TRUNCATED_MAC, TEST_ARP_PROBE_TRUNCATED.length)); + } +} diff --git a/common/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt b/common/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt index 9fb025b3..e23f9991 100644 --- a/common/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt +++ b/common/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt @@ -63,9 +63,6 @@ class CollectionUtilsTest { assertEquals(0, CollectionUtils.indexOfSubArray(haystack, byteArrayOf())) assertEquals(-1, CollectionUtils.indexOfSubArray(byteArrayOf(), byteArrayOf(3, 2))) assertEquals(0, CollectionUtils.indexOfSubArray(byteArrayOf(), byteArrayOf())) - assertThrows(NullPointerException::class.java) { - CollectionUtils.indexOfSubArray(haystack, null) - } } @Test diff --git a/common/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java b/common/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java index 328c39a4..f259e687 100644 --- a/common/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java +++ b/common/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java @@ -17,12 +17,17 @@ package com.android.net.module.util; import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; +import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; +import static android.provider.DeviceConfig.NAMESPACE_TETHERING; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.net.module.util.FeatureVersions.CONNECTIVITY_MODULE_ID; +import static com.android.net.module.util.FeatureVersions.NETWORK_STACK_MODULE_ID; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.anyInt; @@ -84,6 +89,8 @@ public class DeviceConfigUtilsTest { private static final String TEST_GO_APEX_PACKAGE_NAME = "com.prefix.android.go.tethering"; private static final String TEST_CONNRES_PACKAGE_NAME = "com.prefix.android.connectivity.resources"; + private static final String TEST_NETWORKSTACK_NAME = "com.prefix.android.networkstack"; + private static final String TEST_GO_NETWORKSTACK_NAME = "com.prefix.android.go.networkstack"; private final PackageInfo mPackageInfo = new PackageInfo(); private final PackageInfo mApexPackageInfo = new PackageInfo(); private MockitoSession mSession; @@ -227,7 +234,7 @@ public class DeviceConfigUtilsTest { eq(TEST_EXPERIMENT_FLAG))); assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG)); - assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE, + assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */)); } @@ -237,9 +244,9 @@ public class DeviceConfigUtilsTest { eq(TEST_EXPERIMENT_FLAG))); assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG)); - assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE, + assertFalse(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */)); - assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE, + assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, true /* defaultEnabled */)); } @@ -252,7 +259,7 @@ public class DeviceConfigUtilsTest { eq(TEST_EXPERIMENT_FLAG))); assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG)); - assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE, + assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */)); // Feature should be disabled by flag value "999999999". @@ -260,7 +267,7 @@ public class DeviceConfigUtilsTest { eq(TEST_EXPERIMENT_FLAG))); assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG)); - assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE, + assertFalse(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */)); // Follow defaultEnabled if the flag is not set @@ -270,9 +277,9 @@ public class DeviceConfigUtilsTest { TEST_EXPERIMENT_FLAG, false /* defaultEnabled */)); assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, true /* defaultEnabled */)); - assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE, + assertFalse(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */)); - assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE, + assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, true /* defaultEnabled */)); } @@ -287,14 +294,14 @@ public class DeviceConfigUtilsTest { assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG)); - assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE, + assertFalse(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */)); - assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE, + assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, true /* defaultEnabled */)); doReturn(TEST_FLAG_VALUE_STRING).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE), eq(TEST_EXPERIMENT_FLAG))); - assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE, + assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */)); } @@ -317,9 +324,9 @@ public class DeviceConfigUtilsTest { public void testFeatureIsEnabledCaching_APEX() throws Exception { doReturn(TEST_FLAG_VALUE_STRING).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE), eq(TEST_EXPERIMENT_FLAG))); - assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE, + assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */)); - assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE, + assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */)); // Package info is only queried once @@ -346,4 +353,94 @@ public class DeviceConfigUtilsTest { doThrow(new Resources.NotFoundException()).when(mResources).getInteger(someResId); assertEquals(2098, DeviceConfigUtils.getResIntegerConfig(mContext, someResId, 2098)); } + + @Test + public void testGetNetworkStackModuleVersionCaching() throws Exception { + final PackageInfo networkStackPackageInfo = new PackageInfo(); + networkStackPackageInfo.setLongVersionCode(TEST_PACKAGE_VERSION); + doReturn(networkStackPackageInfo).when(mPm).getPackageInfo( + eq(TEST_NETWORKSTACK_NAME), anyInt()); + assertEquals(TEST_PACKAGE_VERSION, + DeviceConfigUtils.getNetworkStackModuleVersion(mContext)); + + assertEquals(TEST_PACKAGE_VERSION, + DeviceConfigUtils.getNetworkStackModuleVersion(mContext)); + // Package info is only queried once + verify(mPm, times(1)).getPackageInfo(anyString(), anyInt()); + verify(mContext, never()).getPackageName(); + } + + @Test + public void testGetNetworkStackModuleVersionOnNonMainline() { + assertEquals(DeviceConfigUtils.DEFAULT_PACKAGE_VERSION, + DeviceConfigUtils.getNetworkStackModuleVersion(mContext)); + } + + @Test + public void testGetNetworkStackModuleVersion() throws Exception { + final PackageInfo networkStackPackageInfo = new PackageInfo(); + final PackageInfo goNetworkStackPackageInfo = new PackageInfo(); + networkStackPackageInfo.setLongVersionCode(TEST_PACKAGE_VERSION); + goNetworkStackPackageInfo.setLongVersionCode(TEST_PACKAGE_VERSION + 1); + doReturn(goNetworkStackPackageInfo).when(mPm).getPackageInfo( + eq(TEST_NETWORKSTACK_NAME), anyInt()); + // Verify the returned value is go module version. + assertEquals(TEST_PACKAGE_VERSION + 1, + DeviceConfigUtils.getNetworkStackModuleVersion(mContext)); + } + + @Test + public void testIsFeatureSupported_networkStackFeature() throws Exception { + // Supported for DEFAULT_PACKAGE_VERSION + assertTrue(DeviceConfigUtils.isFeatureSupported( + mContext, TEST_PACKAGE_VERSION + NETWORK_STACK_MODULE_ID)); + + final PackageInfo networkStackPackageInfo = new PackageInfo(); + networkStackPackageInfo.setLongVersionCode(TEST_PACKAGE_VERSION); + doReturn(networkStackPackageInfo).when(mPm).getPackageInfo( + eq(TEST_NETWORKSTACK_NAME), anyInt()); + + assertTrue(DeviceConfigUtils.isFeatureSupported( + mContext, TEST_PACKAGE_VERSION + NETWORK_STACK_MODULE_ID)); + assertFalse(DeviceConfigUtils.isFeatureSupported( + mContext, TEST_PACKAGE_VERSION + NETWORK_STACK_MODULE_ID + 1)); + } + + @Test + public void testIsFeatureSupported_tetheringFeature() throws Exception { + assertTrue(DeviceConfigUtils.isFeatureSupported( + mContext, TEST_PACKAGE_VERSION + CONNECTIVITY_MODULE_ID)); + // Return false because feature requires a future version. + assertFalse(DeviceConfigUtils.isFeatureSupported( + mContext, 889900000L + CONNECTIVITY_MODULE_ID)); + } + + @Test + public void testIsFeatureSupported_illegalModule() throws Exception { + assertThrows(IllegalArgumentException.class, + () -> DeviceConfigUtils.isFeatureSupported(mContext, TEST_PACKAGE_VERSION)); + } + + @Test + public void testIsTetheringFeatureNotChickenedOut() throws Exception { + doReturn("0").when(() -> DeviceConfig.getProperty( + eq(NAMESPACE_TETHERING), eq(TEST_EXPERIMENT_FLAG))); + assertTrue(DeviceConfigUtils.isTetheringFeatureNotChickenedOut(TEST_EXPERIMENT_FLAG)); + + doReturn(TEST_FLAG_VALUE_STRING).when( + () -> DeviceConfig.getProperty(eq(NAMESPACE_TETHERING), eq(TEST_EXPERIMENT_FLAG))); + assertFalse(DeviceConfigUtils.isTetheringFeatureNotChickenedOut(TEST_EXPERIMENT_FLAG)); + } + + @Test + public void testIsNetworkStackFeatureNotChickenedOut() throws Exception { + doReturn("0").when(() -> DeviceConfig.getProperty( + eq(NAMESPACE_CONNECTIVITY), eq(TEST_EXPERIMENT_FLAG))); + assertTrue(DeviceConfigUtils.isNetworkStackFeatureNotChickenedOut(TEST_EXPERIMENT_FLAG)); + + doReturn(TEST_FLAG_VALUE_STRING).when( + () -> DeviceConfig.getProperty(eq(NAMESPACE_CONNECTIVITY), + eq(TEST_EXPERIMENT_FLAG))); + assertFalse(DeviceConfigUtils.isNetworkStackFeatureNotChickenedOut(TEST_EXPERIMENT_FLAG)); + } } diff --git a/common/tests/unit/src/com/android/net/module/util/DomainUtilsTest.java b/common/tests/unit/src/com/android/net/module/util/DomainUtilsTest.java new file mode 100644 index 00000000..5eaf2add --- /dev/null +++ b/common/tests/unit/src/com/android/net/module/util/DomainUtilsTest.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2023 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 org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import libcore.util.HexEncoding; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class DomainUtilsTest { + @Test + public void testEncodeInvalidDomain() { + byte[] buffer = DomainUtils.encode(".google.com"); + assertNull(buffer); + + buffer = DomainUtils.encode("google.com."); + assertNull(buffer); + + buffer = DomainUtils.encode("-google.com"); + assertNull(buffer); + + buffer = DomainUtils.encode("google.com-"); + assertNull(buffer); + + buffer = DomainUtils.encode("google..com"); + assertNull(buffer); + + buffer = DomainUtils.encode("google!.com"); + assertNull(buffer); + + buffer = DomainUtils.encode("google.o"); + assertNull(buffer); + + buffer = DomainUtils.encode("google,com"); + assertNull(buffer); + } + + @Test + public void testEncodeValidDomainNamesWithoutCompression() { + // Single domain: "google.com" + String suffix = "06676F6F676C6503636F6D00"; + byte[] buffer = DomainUtils.encode("google.com"); + //assertNotNull(buffer); + assertEquals(suffix, HexEncoding.encodeToString(buffer)); + + // Single domain: "google-guest.com" + suffix = "0C676F6F676C652D677565737403636F6D00"; + buffer = DomainUtils.encode("google-guest.com"); + assertNotNull(buffer); + assertEquals(suffix, HexEncoding.encodeToString(buffer)); + + // domain search list: "example.corp.google.com", "corp.google.com", "google.com" + suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com + + "04636F727006676F6F676C6503636F6D00" // corp.google.com + + "06676F6F676C6503636F6D00"; // google.com + buffer = DomainUtils.encode(new String[] { + "example.corp.google.com", "corp.google.com", "google.com"}, + false /* compression */); + assertNotNull(buffer); + assertEquals(suffix, HexEncoding.encodeToString(buffer)); + + + // domain search list: "example.corp.google.com", "corp..google.com"(invalid domain), + // "google.com" + suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com + + "06676F6F676C6503636F6D00"; // google.com + buffer = DomainUtils.encode(new String[] { + "example.corp.google.com", "corp..google.com", "google.com"}, + false /* compression */); + assertNotNull(buffer); + assertEquals(suffix, HexEncoding.encodeToString(buffer)); + + // Invalid domain search list: "corp..google.com", "..google.com" + buffer = DomainUtils.encode(new String[] {"corp..google.com", "..google.com"}, + false /* compression */); + assertEquals(0, buffer.length); + } + + @Test + public void testEncodeValidDomainNamesWithCompression() { + // domain search list: "example.corp.google.com", "corp.google.com", "google.com" + String suffix = + "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com + + "C008" // corp.google.com + + "C00D"; // google.com + byte[] buffer = DomainUtils.encode(new String[] { + "example.corp.google.com", "corp.google.com", "google.com"}, true); + assertNotNull(buffer); + assertEquals(suffix, HexEncoding.encodeToString(buffer)); + + // domain search list: "example.corp.google.com", "a.example.corp.google.com", "google.com" + suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com + + "0161C000" // a.example.corp.google.com + + "C00D"; // google.com + buffer = DomainUtils.encode(new String[] { + "example.corp.google.com", "a.example.corp.google.com", "google.com"}, true); + assertNotNull(buffer); + assertEquals(suffix, HexEncoding.encodeToString(buffer)); + + // domain search list: "example.corp.google.com", "google.com", "gle.com" + suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com + + "C00D" // google.com + + "03676C65C014"; // gle.com + buffer = DomainUtils.encode(new String[] { + "example.corp.google.com", "google.com", "gle.com"}, true); + assertNotNull(buffer); + assertEquals(suffix, HexEncoding.encodeToString(buffer)); + + // domain search list: "example.corp.google.com", "google.com", "google" + suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com + + "C00D"; // google.com + buffer = DomainUtils.encode(new String[] { + "example.corp.google.com", "google.com", "google"}, true); + assertNotNull(buffer); + assertEquals(suffix, HexEncoding.encodeToString(buffer)); + + // domain search list: "example.corp.google.com", "..google.com"(invalid domain), "google" + suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00"; // example.corp.google.com + buffer = DomainUtils.encode(new String[] { + "example.corp.google.com", "..google.com", "google"}, true); + assertNotNull(buffer); + assertEquals(suffix, HexEncoding.encodeToString(buffer)); + + // domain search list: "example.corp.google.com", "suffix.example.edu.cn", "edu.cn" + suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com + + "06737566666978076578616D706C650365647502636E00" // suffix.example.edu.cn + + "C028"; // edu.cn + buffer = DomainUtils.encode(new String[] { + "example.corp.google.com", "suffix.example.edu.cn", "edu.cn"}, true); + assertNotNull(buffer); + assertEquals(suffix, HexEncoding.encodeToString(buffer)); + + // domain search list: "google.com", "example.com", "sub.example.com" + suffix = "06676F6F676C6503636F6D00" // google.com + + "076578616D706C65C007" // example.com + + "03737562C00C"; // sub.example.com + buffer = DomainUtils.encode(new String[] { + "google.com", "example.com", "sub.example.com"}, true); + assertNotNull(buffer); + assertEquals(suffix, HexEncoding.encodeToString(buffer)); + } + + @Test + public void testDecodeDomainNames() { + ArrayList<String> suffixStringList; + String suffixes = "06676F6F676C6503636F6D00" // google.com + + "076578616D706C6503636F6D00" // example.com + + "06676F6F676C6500"; // google + List<String> expected = Arrays.asList("google.com", "example.com"); + ByteBuffer buffer = ByteBuffer.wrap(HexEncoding.decode(suffixes)); + suffixStringList = DomainUtils.decode(buffer, false /* compression */); + assertEquals(expected, suffixStringList); + + // include suffix with invalid length: 64 + suffixes = "06676F6F676C6503636F6D00" // google.com + + "406578616D706C6503636F6D00" // example.com(length=64) + + "06676F6F676C6500"; // google + expected = Arrays.asList("google.com"); + buffer = ByteBuffer.wrap(HexEncoding.decode(suffixes)); + suffixStringList = DomainUtils.decode(buffer, false /* compression */); + assertEquals(expected, suffixStringList); + + // include suffix with invalid length: 0 + suffixes = "06676F6F676C6503636F6D00" // google.com + + "076578616D706C6503636F6D00" // example.com + + "00676F6F676C6500"; // google(length=0) + expected = Arrays.asList("google.com", "example.com"); + buffer = ByteBuffer.wrap(HexEncoding.decode(suffixes)); + suffixStringList = DomainUtils.decode(buffer, false /* compression */); + assertEquals(expected, suffixStringList); + + suffixes = + "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com + + "C008" // corp.google.com + + "C00D"; // google.com + expected = Arrays.asList("example.corp.google.com", "corp.google.com", "google.com"); + buffer = ByteBuffer.wrap(HexEncoding.decode(suffixes)); + suffixStringList = DomainUtils.decode(buffer, true /* compression */); + assertEquals(expected, suffixStringList); + } +} diff --git a/common/tests/unit/src/com/android/net/module/util/StructTest.java b/common/tests/unit/src/com/android/net/module/util/StructTest.java index 4e462106..b4da0439 100644 --- a/common/tests/unit/src/com/android/net/module/util/StructTest.java +++ b/common/tests/unit/src/com/android/net/module/util/StructTest.java @@ -89,20 +89,20 @@ public class StructTest { return Struct.parse(clazz, buf); } - static class HeaderMsgWithConstructor extends Struct { + public static class HeaderMsgWithConstructor extends Struct { static int sType; static int sLength; @Field(order = 0, type = Type.U8, padding = 1) - final short mFamily; + public final short mFamily; @Field(order = 1, type = Type.U16) - final int mLen; + public final int mLen; @Field(order = 2, type = Type.S32) - final int mIfindex; + public final int mIfindex; @Field(order = 3, type = Type.U8) - final short mIcmpType; + public final short mIcmpType; @Field(order = 4, type = Type.U8, padding = 6) - final short mIcmpCode; + public final short mIcmpCode; HeaderMsgWithConstructor(final short family, final int len, final int ifindex, final short type, final short code) { @@ -133,20 +133,20 @@ public class StructTest { verifyHeaderParsing(msg); } - static class HeaderMsgWithoutConstructor extends Struct { + public static class HeaderMsgWithoutConstructor extends Struct { static int sType; static int sLength; @Field(order = 0, type = Type.U8, padding = 1) - short mFamily; + public short mFamily; @Field(order = 1, type = Type.U16) - int mLen; + public int mLen; @Field(order = 2, type = Type.S32) - int mIfindex; + public int mIfindex; @Field(order = 3, type = Type.U8) - short mIcmpType; + public short mIcmpType; @Field(order = 4, type = Type.U8, padding = 6) - short mIcmpCode; + public short mIcmpCode; } @Test @@ -164,7 +164,7 @@ public class StructTest { msg.writeToBytes(ByteOrder.LITTLE_ENDIAN)); } - static class HeaderMessage { + public static class HeaderMessage { @Field(order = 0, type = Type.U8, padding = 1) short mFamily; @Field(order = 1, type = Type.U16) @@ -183,7 +183,7 @@ public class StructTest { assertThrows(IllegalArgumentException.class, () -> Struct.parse(HeaderMessage.class, buf)); } - static class HeaderMessageMissingAnnotation extends Struct { + public static class HeaderMessageMissingAnnotation extends Struct { @Field(order = 0, type = Type.U8, padding = 1) short mFamily; @Field(order = 1, type = Type.U16) @@ -202,15 +202,15 @@ public class StructTest { () -> Struct.parse(HeaderMessageMissingAnnotation.class, buf)); } - static class NetworkOrderMessage extends Struct { + public static class NetworkOrderMessage extends Struct { @Field(order = 0, type = Type.UBE16) - final int mUBE16; + public final int mUBE16; @Field(order = 1, type = Type.UBE32) - final long mUBE32; + public final long mUBE32; @Field(order = 2, type = Type.UBE64) - final BigInteger mUBE64; + public final BigInteger mUBE64; @Field(order = 3, type = Type.UBE63) - final long mUBE63; + public final long mUBE63; NetworkOrderMessage(final int be16, final long be32, final BigInteger be64, final long be63) { @@ -235,19 +235,19 @@ public class StructTest { msg.writeToBytes(ByteOrder.LITTLE_ENDIAN)); } - static class UnsignedDataMessage extends Struct { + public static class UnsignedDataMessage extends Struct { @Field(order = 0, type = Type.U8) - final short mU8; + public final short mU8; @Field(order = 1, type = Type.U16) - final int mU16; + public final int mU16; @Field(order = 2, type = Type.U32) - final long mU32; + public final long mU32; @Field(order = 3, type = Type.U64) - final BigInteger mU64; + public final BigInteger mU64; @Field(order = 4, type = Type.U63) - final long mU63; + public final long mU63; @Field(order = 5, type = Type.U63) - final long mLU64; // represent U64 data with U63 type + public final long mLU64; // represent U64 data with U63 type UnsignedDataMessage(final short u8, final int u16, final long u32, final BigInteger u64, final long u63, final long lu64) { @@ -276,7 +276,7 @@ public class StructTest { msg.writeToBytes(ByteOrder.LITTLE_ENDIAN)); } - static class U64DataMessage extends Struct { + public static class U64DataMessage extends Struct { @Field(order = 0, type = Type.U64) long mU64; } @@ -290,10 +290,10 @@ public class StructTest { private static final String SMALL_VALUE_BIGINTEGER = "3412000000000000" + "0000000000001234" + "0000000000000000"; - static class SmallValueBigInteger extends Struct { - @Field(order = 0, type = Type.U64) final BigInteger mSmallValue; - @Field(order = 1, type = Type.UBE64) final BigInteger mBSmallValue; - @Field(order = 2, type = Type.U64) final BigInteger mZero; + public static class SmallValueBigInteger extends Struct { + @Field(order = 0, type = Type.U64) public final BigInteger mSmallValue; + @Field(order = 1, type = Type.UBE64) public final BigInteger mBSmallValue; + @Field(order = 2, type = Type.U64) public final BigInteger mZero; SmallValueBigInteger(final BigInteger smallValue, final BigInteger bSmallValue, final BigInteger zero) { @@ -316,15 +316,15 @@ public class StructTest { msg.writeToBytes(ByteOrder.LITTLE_ENDIAN)); } - static class SignedDataMessage extends Struct { + public static class SignedDataMessage extends Struct { @Field(order = 0, type = Type.S8) - final byte mS8; + public final byte mS8; @Field(order = 1, type = Type.S16) - final short mS16; + public final short mS16; @Field(order = 2, type = Type.S32) - final int mS32; + public final int mS32; @Field(order = 3, type = Type.S64) - final long mS64; + public final long mS64; SignedDataMessage(final byte s8, final short s16, final int s32, final long s64) { mS8 = s8; @@ -362,7 +362,7 @@ public class StructTest { msg.writeToBytes(ByteOrder.LITTLE_ENDIAN)); } - static class HeaderMessageWithDuplicateOrder extends Struct { + public static class HeaderMessageWithDuplicateOrder extends Struct { @Field(order = 0, type = Type.U8, padding = 1) short mFamily; @Field(order = 1, type = Type.U16) @@ -382,7 +382,7 @@ public class StructTest { () -> Struct.parse(HeaderMessageWithDuplicateOrder.class, buf)); } - static class HeaderMessageWithNegativeOrder extends Struct { + public static class HeaderMessageWithNegativeOrder extends Struct { @Field(order = 0, type = Type.U8, padding = 1) short mFamily; @Field(order = 1, type = Type.U16) @@ -402,7 +402,7 @@ public class StructTest { () -> Struct.parse(HeaderMessageWithNegativeOrder.class, buf)); } - static class HeaderMessageOutOfIndexBounds extends Struct { + public static class HeaderMessageOutOfIndexBounds extends Struct { @Field(order = 0, type = Type.U8, padding = 1) short mFamily; @Field(order = 1, type = Type.U16) @@ -422,7 +422,7 @@ public class StructTest { () -> Struct.parse(HeaderMessageOutOfIndexBounds.class, buf)); } - static class HeaderMessageMismatchedPrimitiveType extends Struct { + public static class HeaderMessageMismatchedPrimitiveType extends Struct { @Field(order = 0, type = Type.U8, padding = 1) short mFamily; @Field(order = 1, type = Type.U16) @@ -442,11 +442,11 @@ public class StructTest { () -> Struct.parse(HeaderMessageMismatchedPrimitiveType.class, buf)); } - static class PrefixMessage extends Struct { + public static class PrefixMessage extends Struct { @Field(order = 0, type = Type.UBE16) - final int mLifetime; + public final int mLifetime; @Field(order = 1, type = Type.ByteArray, arraysize = 12) - final byte[] mPrefix; + public final byte[] mPrefix; PrefixMessage(final int lifetime, final byte[] prefix) { mLifetime = lifetime; @@ -469,7 +469,7 @@ public class StructTest { msg.writeToBytes(ByteOrder.LITTLE_ENDIAN)); } - static class PrefixMessageWithZeroLengthArray extends Struct { + public static class PrefixMessageWithZeroLengthArray extends Struct { @Field(order = 0, type = Type.UBE16) final int mLifetime; @Field(order = 1, type = Type.ByteArray, arraysize = 0) @@ -495,7 +495,7 @@ public class StructTest { verifyPrefixByteArrayParsing(msg); } - static class HeaderMessageWithMutableField extends Struct { + public static class HeaderMessageWithMutableField extends Struct { @Field(order = 0, type = Type.U8, padding = 1) final short mFamily; @Field(order = 1, type = Type.U16) @@ -521,20 +521,20 @@ public class StructTest { () -> Struct.parse(HeaderMessageWithMutableField.class, buf)); } - static class HeaderMsgWithStaticConstant extends Struct { + public static class HeaderMsgWithStaticConstant extends Struct { private static final String TAG = "HeaderMessage"; private static final int FIELD_COUNT = 5; @Field(order = 0, type = Type.U8, padding = 1) - final short mFamily; + public final short mFamily; @Field(order = 1, type = Type.U16) - final int mLen; + public final int mLen; @Field(order = 2, type = Type.S32) - final int mIfindex; + public final int mIfindex; @Field(order = 3, type = Type.U8) - final short mIcmpType; + public final short mIcmpType; @Field(order = 4, type = Type.U8, padding = 6) - final short mIcmpCode; + public final short mIcmpCode; HeaderMsgWithStaticConstant(final short family, final int len, final int ifindex, final short type, final short code) { @@ -561,7 +561,7 @@ public class StructTest { msg.writeToBytes(ByteOrder.LITTLE_ENDIAN)); } - static class MismatchedConstructor extends Struct { + public static class MismatchedConstructor extends Struct { @Field(order = 0, type = Type.U16) final int mInt1; @Field(order = 1, type = Type.U16) final int mInt2; MismatchedConstructor(String int1, String int2) { @@ -577,9 +577,9 @@ public class StructTest { () -> Struct.parse(MismatchedConstructor.class, buf)); } - static class ClassWithTwoConstructors extends Struct { - @Field(order = 0, type = Type.U16) final int mInt1; - @Field(order = 1, type = Type.U16) final int mInt2; + public static class ClassWithTwoConstructors extends Struct { + @Field(order = 0, type = Type.U16) public final int mInt1; + @Field(order = 1, type = Type.U16) public final int mInt2; ClassWithTwoConstructors(String int1, String int2) { mInt1 = Integer.valueOf(int1); mInt2 = Integer.valueOf(int2); @@ -645,14 +645,14 @@ public class StructTest { } } - static class BigEndianDataMessage extends Struct { - @Field(order = 0, type = Type.S32) int mInt1; - @Field(order = 1, type = Type.S32) int mInt2; - @Field(order = 2, type = Type.UBE16) int mInt3; - @Field(order = 3, type = Type.U16) int mInt4; - @Field(order = 4, type = Type.U64) BigInteger mBigInteger1; - @Field(order = 5, type = Type.UBE64) BigInteger mBigInteger2; - @Field(order = 6, type = Type.S64) long mLong; + public static class BigEndianDataMessage extends Struct { + @Field(order = 0, type = Type.S32) public int mInt1; + @Field(order = 1, type = Type.S32) public int mInt2; + @Field(order = 2, type = Type.UBE16) public int mInt3; + @Field(order = 3, type = Type.U16) public int mInt4; + @Field(order = 4, type = Type.U64) public BigInteger mBigInteger1; + @Field(order = 5, type = Type.UBE64) public BigInteger mBigInteger2; + @Field(order = 6, type = Type.S64) public long mLong; } private static final String BIG_ENDIAN_DATA = "00000001" + "fffffffe" + "fffe" + "fffe" @@ -680,9 +680,9 @@ public class StructTest { return ByteBuffer.wrap(HexDump.hexStringToByteArray(hexString)); } - static class MacAddressMessage extends Struct { - @Field(order = 0, type = Type.EUI48) final MacAddress mMac1; - @Field(order = 1, type = Type.EUI48) final MacAddress mMac2; + public static class MacAddressMessage extends Struct { + public @Field(order = 0, type = Type.EUI48) final MacAddress mMac1; + public @Field(order = 1, type = Type.EUI48) final MacAddress mMac2; MacAddressMessage(final MacAddress mac1, final MacAddress mac2) { this.mMac1 = mac1; @@ -703,7 +703,7 @@ public class StructTest { msg.writeToBytes(ByteOrder.BIG_ENDIAN)); } - static class BadMacAddressType extends Struct { + public static class BadMacAddressType extends Struct { @Field(order = 0, type = Type.EUI48) byte[] mMac; } @@ -742,7 +742,7 @@ public class StructTest { assertArrayEquals(toByteBuffer(hexString).array(), msg.writeToBytes()); } - static class IpAddressMessage extends Struct { + public static class IpAddressMessage extends Struct { @Field(order = 0, type = Type.Ipv4Address) public final Inet4Address ipv4Address; @Field(order = 1, type = Type.Ipv6Address) public final Inet6Address ipv6Address; @@ -765,7 +765,7 @@ public class StructTest { msg.writeToBytes(ByteOrder.BIG_ENDIAN)); } - static class WrongIpAddressType extends Struct { + public static class WrongIpAddressType extends Struct { @Field(order = 0, type = Type.Ipv4Address) public byte[] ipv4Address; @Field(order = 1, type = Type.Ipv6Address) public byte[] ipv6Address; } @@ -777,7 +777,7 @@ public class StructTest { toByteBuffer("c0a86401" + "20010db8000300040005000600070008"))); } - static class FullTypeMessage extends Struct { + public static class FullTypeMessage extends Struct { @Field(order = 0, type = Type.U8) public final short u8; @Field(order = 1, type = Type.U16) public final int u16; @Field(order = 2, type = Type.U32) public final long u32; @@ -886,7 +886,7 @@ public class StructTest { assertTrue(msg.equals(msg1)); } - static class FullTypeMessageWithDupType extends Struct { + public static class FullTypeMessageWithDupType extends Struct { @Field(order = 0, type = Type.U8) public final short u8; @Field(order = 1, type = Type.U16) public final int u16; @Field(order = 2, type = Type.U32) public final long u32; @@ -1029,7 +1029,7 @@ public class StructTest { assertEquals(msg.hashCode(), msg1.hashCode()); } - static class InvalidByteArray extends Struct { + public static class InvalidByteArray extends Struct { @Field(order = 0, type = Type.ByteArray, arraysize = 12) public byte[] bytes; } diff --git a/common/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java b/common/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java index dac5911a..3a72dd1b 100644 --- a/common/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java +++ b/common/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java @@ -26,8 +26,6 @@ import static com.android.net.module.util.netlink.NetlinkUtils.DEFAULT_RECV_BUFS import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP; import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST; -import static com.google.common.truth.Truth.assertThat; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -193,7 +191,9 @@ public class NetlinkUtilsTest { // and IPv6 loopback address: 127.0.0.1 and ::1. final StructIfaddrMsg ifaMsg = ((RtNetlinkAddressMessage) msg).getIfaddrHeader(); final InetAddress ipAddress = ((RtNetlinkAddressMessage) msg).getIpAddress(); - assertThat((int) ifaMsg.family).isAnyOf(AF_INET, AF_INET6); + assertTrue( + "Non-IP address family: " + ifaMsg.family, + ifaMsg.family == AF_INET || ifaMsg.family == AF_INET6); assertTrue(ipAddress.isLoopbackAddress()); } diff --git a/common/testutils/Android.bp b/common/testutils/Android.bp index be4ccfed..d88cb0b4 100644 --- a/common/testutils/Android.bp +++ b/common/testutils/Android.bp @@ -57,6 +57,7 @@ java_library { visibility: [ "//frameworks/libs/net/common/tests:__subpackages__", "//frameworks/libs/net/client-libs/tests:__subpackages__", + "//packages/modules/Connectivity/tests/cts/hostside", ], // There are downstream branches using an old version of Kotlin // that used to reserve the right to make breaking changes to the diff --git a/common/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt b/common/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt index 6bcb8fc1..f1f09754 100644 --- a/common/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt +++ b/common/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt @@ -72,7 +72,7 @@ class ConnectivityCheckTest { val cb = TestableNetworkCallback() val cm = context.getSystemService(ConnectivityManager::class.java) ?: fail("Could not get ConnectivityManager") - cm.registerNetworkCallback( + cm.requestNetwork( NetworkRequest.Builder() .addTransportType(TRANSPORT_CELLULAR) .addCapability(NET_CAPABILITY_INTERNET).build(), cb) diff --git a/common/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt b/common/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt index 21e84daf..35f22b9e 100644 --- a/common/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt +++ b/common/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt @@ -19,11 +19,11 @@ package com.android.testutils import android.os.Build import androidx.test.InstrumentationRegistry import com.android.modules.utils.build.UnboundedSdkLevel +import java.util.regex.Pattern import org.junit.Assume.assumeTrue import org.junit.rules.TestRule import org.junit.runner.Description import org.junit.runners.model.Statement -import java.util.regex.Pattern @Deprecated("Use Build.VERSION_CODES", ReplaceWith("Build.VERSION_CODES.S_V2")) const val SC_V2 = Build.VERSION_CODES.S_V2 @@ -32,8 +32,22 @@ private val MAX_TARGET_SDK_ANNOTATION_RE = Pattern.compile("MaxTargetSdk([0-9]+) private val targetSdk = InstrumentationRegistry.getContext().applicationInfo.targetSdkVersion private fun isDevSdkInRange(minExclusive: String?, maxInclusive: String?): Boolean { - return (minExclusive == null || !UnboundedSdkLevel.isAtMost(minExclusive)) && - (maxInclusive == null || UnboundedSdkLevel.isAtMost(maxInclusive)) + return (minExclusive == null || !isAtMost(minExclusive)) && + (maxInclusive == null || isAtMost(maxInclusive)) +} + +private fun isAtMost(sdkVersionOrCodename: String): Boolean { + // UnboundedSdkLevel does not support builds < Q, and may stop supporting Q as well since it + // is intended for mainline modules that are now R+. + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) { + // Assume that any codename passed as argument from current code is a more recent build than + // Q: this util did not exist before Q, and codenames are only used before the corresponding + // build is finalized. This util could list 28 older codenames to check against (as per + // ro.build.version.known_codenames in more recent builds), but this does not seem valuable. + val intVersion = sdkVersionOrCodename.toIntOrNull() ?: return true + return Build.VERSION.SDK_INT <= intVersion + } + return UnboundedSdkLevel.isAtMost(sdkVersionOrCodename) } /** diff --git a/common/testutils/devicetests/com/android/testutils/FakeDns.kt b/common/testutils/devicetests/com/android/testutils/FakeDns.kt index 825d7487..1f82a35d 100644 --- a/common/testutils/devicetests/com/android/testutils/FakeDns.kt +++ b/common/testutils/devicetests/com/android/testutils/FakeDns.kt @@ -81,9 +81,9 @@ class FakeDns(val mockResolver: DnsResolver) { var type = if (posType != -1) it.arguments[posType] as Int else TYPE_UNSPECIFIED val answer = getAnswer(hostname, type) - if (!answer?.addresses.isNullOrEmpty()) { + if (answer != null && !answer.addresses.isNullOrEmpty()) { Handler(Looper.getMainLooper()).post({ executor.execute({ - callback.onAnswer(answer?.addresses, 0); }) }) + callback.onAnswer(answer.addresses, 0); }) }) } } diff --git a/common/testutils/devicetests/com/android/testutils/NonNullTestUtils.java b/common/testutils/devicetests/com/android/testutils/NonNullTestUtils.java new file mode 100644 index 00000000..463c4706 --- /dev/null +++ b/common/testutils/devicetests/com/android/testutils/NonNullTestUtils.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 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.testutils; + +import android.annotation.NonNull; + +/** + * Utilities to help Kotlin test to verify java @NonNull code + */ +public class NonNullTestUtils { + /** + * This method allows Kotlin to pass nullable to @NonNull java code for testing. + * For Foo(@NonNull arg) java method, Kotlin can pass nullable variable by + * Foo(NonNullTestUtils.nullUnsafe(nullableVar)). + */ + @NonNull public static <T> T nullUnsafe(T v) { + return v; + } +} diff --git a/common/testutils/devicetests/com/android/testutils/PacketBridge.kt b/common/testutils/devicetests/com/android/testutils/PacketBridge.kt index da3508de..d50f78a1 100644 --- a/common/testutils/devicetests/com/android/testutils/PacketBridge.kt +++ b/common/testutils/devicetests/com/android/testutils/PacketBridge.kt @@ -48,8 +48,8 @@ class PacketBridge( private val natMap = NatMap() private val binder = Binder() - private val cm = context.getSystemService(ConnectivityManager::class.java) - private val tnm = context.getSystemService(TestNetworkManager::class.java) + private val cm = context.getSystemService(ConnectivityManager::class.java)!! + private val tnm = context.getSystemService(TestNetworkManager::class.java)!! // Create test networks. private val internalIface = tnm.createTunInterface(listOf(internalAddr)) diff --git a/common/testutils/devicetests/com/android/testutils/TestNetworkTracker.kt b/common/testutils/devicetests/com/android/testutils/TestNetworkTracker.kt index b743b6c9..84fb47bc 100644 --- a/common/testutils/devicetests/com/android/testutils/TestNetworkTracker.kt +++ b/common/testutils/devicetests/com/android/testutils/TestNetworkTracker.kt @@ -91,7 +91,7 @@ private fun initTestNetwork( lp: LinkProperties?, setupTimeoutMs: Long = 10_000L ): TestNetworkTracker { - val tnm = context.getSystemService(TestNetworkManager::class.java) + val tnm = context.getSystemService(TestNetworkManager::class.java)!! val iface = if (isAtLeastS()) tnm.createTunInterface(linkAddrs) else tnm.createTunInterface(linkAddrs.toTypedArray()) val lpWithIface = if (lp == null) null else LinkProperties(lp).apply { @@ -112,7 +112,7 @@ class TestNetworkTracker internal constructor( val lp: LinkProperties?, setupTimeoutMs: Long ) : TestableNetworkCallback.HasNetwork { - private val cm = context.getSystemService(ConnectivityManager::class.java) + private val cm = context.getSystemService(ConnectivityManager::class.java)!! private val binder = Binder() private val networkCallback: NetworkCallback diff --git a/common/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt b/common/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt index 4b6aea2d..df9c61ae 100644 --- a/common/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt +++ b/common/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt @@ -389,7 +389,8 @@ open class TestableNetworkCallback private constructor( from: Int = mark, crossinline predicate: (T) -> Boolean = { true } ): T = history.poll(timeoutMs, from) { it is T && predicate(it) }.also { - assertNotNull(it, "Callback ${T::class} not received within ${timeoutMs}ms") + assertNotNull(it, "Callback ${T::class} not received within ${timeoutMs}ms. " + + "Got ${history.backtrace()}") } as T @JvmOverloads @@ -398,7 +399,8 @@ open class TestableNetworkCallback private constructor( timeoutMs: Long = defaultTimeoutMs, predicate: (cb: T) -> Boolean = { true } ) = history.poll(timeoutMs) { type.java.isInstance(it) && predicate(it as T) }.also { - assertNotNull(it, "Callback ${type.java} not received within ${timeoutMs}ms") + assertNotNull(it, "Callback ${type.java} not received within ${timeoutMs}ms. " + + "Got ${history.backtrace()}") } as T fun <T : CallbackEntry> eventuallyExpect( @@ -407,7 +409,8 @@ open class TestableNetworkCallback private constructor( from: Int = mark, predicate: (cb: T) -> Boolean = { true } ) = history.poll(timeoutMs, from) { type.java.isInstance(it) && predicate(it as T) }.also { - assertNotNull(it, "Callback ${type.java} not received within ${timeoutMs}ms") + assertNotNull(it, "Callback ${type.java} not received within ${timeoutMs}ms. " + + "Got ${history.backtrace()}") } as T // Expects onAvailable and the callbacks that follow it. These are: diff --git a/common/testutils/hostdevice/com/android/net/module/util/TrackRecord.kt b/common/testutils/hostdevice/com/android/net/module/util/TrackRecord.kt index efd77d1b..f24e4f18 100644 --- a/common/testutils/hostdevice/com/android/net/module/util/TrackRecord.kt +++ b/common/testutils/hostdevice/com/android/net/module/util/TrackRecord.kt @@ -213,6 +213,9 @@ class ArrayTrackRecord<E> : TrackRecord<E> { private val slock = StampedLock() private var readHead = 0 + // A special mark used to track the start of the last poll() operation. + private var pollMark = 0 + /** * @return the current value of the mark. */ @@ -223,6 +226,7 @@ class ArrayTrackRecord<E> : TrackRecord<E> { val stamp = slock.tryWriteLock() if (0L == stamp) concurrentAccessDetected() readHead = v + pollMark = v slock.unlockWrite(stamp) } @@ -261,6 +265,7 @@ class ArrayTrackRecord<E> : TrackRecord<E> { fun poll(timeoutMs: Long, predicate: (E) -> Boolean = { true }): E? { val stamp = slock.tryWriteLock() if (0L == stamp) concurrentAccessDetected() + pollMark = readHead try { lock.withLock { val index = pollForIndexReadLocked(timeoutMs, readHead, predicate) @@ -273,6 +278,25 @@ class ArrayTrackRecord<E> : TrackRecord<E> { } /** + * Returns a list of events that were observed since the last time poll() was called on this + * ReadHead. + * + * @return list of events since poll() was called. + */ + fun backtrace(): List<E> { + val stamp = slock.tryReadLock() + if (0L == stamp) concurrentAccessDetected() + + try { + lock.withLock { + return ArrayList(subList(pollMark, mark)) + } + } finally { + slock.unlockRead(stamp) + } + } + + /** * Returns the first element after the mark or null. This never blocks. * * This method is subject to threading restrictions. It can be used concurrently on diff --git a/common/testutils/hostdevice/com/android/testutils/DnsResolverModuleTest.kt b/common/testutils/hostdevice/com/android/testutils/DnsResolverModuleTest.kt new file mode 100644 index 00000000..9e97d51e --- /dev/null +++ b/common/testutils/hostdevice/com/android/testutils/DnsResolverModuleTest.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 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.testutils + +/** + * Indicates that the test covers functionality that was rolled out in a resolv module update. + */ +annotation class DnsResolverModuleTest diff --git a/common/testutils/hostdevice/com/android/testutils/SkipMainlinePresubmit.kt b/common/testutils/hostdevice/com/android/testutils/SkipMainlinePresubmit.kt new file mode 100644 index 00000000..59523651 --- /dev/null +++ b/common/testutils/hostdevice/com/android/testutils/SkipMainlinePresubmit.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 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.testutils + +/** + * Skip the test in presubmit runs for the reason specified in [reason]. + * + * This annotation is typically used to document limitations that prevent a test from being + * executed in presubmit on older builds. + */ +annotation class SkipMainlinePresubmit(val reason: String) |