Snap for 12583547 from 26624616f1c77833b2e95b5b0481b5f9f5c1787a to mainline-cellbroadcast-release Change-Id: Ia02da96c4b6c04f926f2d8b4a2cfe3ed4450d272
diff --git a/Android.bp b/Android.bp index 1228079..734797a 100644 --- a/Android.bp +++ b/Android.bp
@@ -43,12 +43,12 @@ defaults: ["NetworkStackReleaseTargetSdk"], sdk_version: "module_current", libs: [ - "framework-configinfrastructure", + "framework-configinfrastructure.stubs.module_lib", "framework-connectivity.stubs.module_lib", - "framework-connectivity-t", - "framework-statsd", - "framework-tethering", - "framework-wifi", + "framework-connectivity-t.stubs.module_lib", + "framework-statsd.stubs.module_lib", + "framework-tethering.stubs.module_lib", + "framework-wifi.stubs.module_lib", ], } @@ -129,8 +129,8 @@ "NetworkStackShimsCommon", "NetworkStackApi29Shims", "NetworkStackApi30Shims", - "framework-connectivity", - "framework-wifi", + "framework-connectivity.impl", + "sdk_module-lib_31_framework-wifi", ], sdk_version: "module_31", visibility: ["//visibility:private"], @@ -147,11 +147,11 @@ "NetworkStackApi29Shims", "NetworkStackApi30Shims", "NetworkStackApi31Shims", - "framework-bluetooth", - "framework-connectivity", + "sdk_module-lib_33_framework-bluetooth", + "framework-connectivity.impl", "framework-connectivity-t.stubs.module_lib", - "framework-tethering", - "framework-wifi", + "framework-tethering.impl", + "sdk_module-lib_33_framework-wifi", ], sdk_version: "module_33", visibility: ["//visibility:private"], @@ -169,11 +169,11 @@ "NetworkStackApi30Shims", "NetworkStackApi31Shims", "NetworkStackApi33Shims", - "framework-bluetooth", - "framework-connectivity", + "sdk_module-lib_34_framework-bluetooth", + "framework-connectivity.impl", "framework-connectivity-t.stubs.module_lib", - "framework-tethering", - "framework-wifi", + "framework-tethering.impl", + "sdk_module-lib_34_framework-wifi", ], sdk_version: "module_34", visibility: ["//visibility:private"], @@ -202,11 +202,11 @@ "NetworkStackApi31Shims", "NetworkStackApi33Shims", "NetworkStackApi34Shims", - "framework-bluetooth", - "framework-connectivity", + "framework-bluetooth.stubs.module_lib", + "framework-connectivity.impl", "framework-connectivity-t.stubs.module_lib", - "framework-tethering", - "framework-wifi", + "framework-tethering.impl", + "framework-wifi.stubs.module_lib", "android.net.ipsec.ike.stubs.module_lib", ], sdk_version: "module_current", @@ -275,11 +275,13 @@ ], libs: [ "error_prone_annotations", + "framework-annotations-lib", "unsupportedappusage", ], static_libs: [ "androidx.annotation_annotation", "modules-utils-build_system", + "modules-utils-expresslog", "modules-utils-preconditions", "modules-utils-shell-command-handler", "modules-utils-statemachine",
diff --git a/apishim/29/com/android/networkstack/apishim/api29/CaptivePortalDataShimImpl.java b/apishim/29/com/android/networkstack/apishim/api29/CaptivePortalDataShimImpl.java deleted file mode 100644 index 1b2cc78..0000000 --- a/apishim/29/com/android/networkstack/apishim/api29/CaptivePortalDataShimImpl.java +++ /dev/null
@@ -1,104 +0,0 @@ -/* - * 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.networkstack.apishim.api29; - -import android.net.Uri; -import android.os.Build; - -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; -import androidx.annotation.VisibleForTesting; - -import com.android.networkstack.apishim.common.CaptivePortalDataShim; -import com.android.networkstack.apishim.common.UnsupportedApiLevelException; - -import org.json.JSONException; -import org.json.JSONObject; - -/** - * Compatibility implementation of {@link CaptivePortalData}. - * - * <p>Use {@link com.android.networkstack.apishim.CaptivePortalDataShimImpl} instead of this - * fallback implementation. - */ -@RequiresApi(Build.VERSION_CODES.Q) -public abstract class CaptivePortalDataShimImpl implements CaptivePortalDataShim { - protected CaptivePortalDataShimImpl() {} - - /** - * Parse a {@link android.net.CaptivePortalDataShim} from JSON. - * - * <p>Use - * {@link com.android.networkstack.apishim.CaptivePortalDataShimImpl#fromJson(JSONObject)} - * instead of this API 29 compatibility version. - */ - @NonNull - public static CaptivePortalDataShim fromJson(JSONObject object) throws JSONException, - UnsupportedApiLevelException { - // Data class not supported in API 29 - throw new UnsupportedApiLevelException("CaptivePortalData not supported on API 29"); - } - - @Override - public CharSequence getVenueFriendlyName() { - // Not supported in API level 29 - return null; - } - - @Override - public int getUserPortalUrlSource() { - // Not supported in API level 29 - return ConstantsShim.CAPTIVE_PORTAL_DATA_SOURCE_OTHER; - } - - @VisibleForTesting - public static boolean isSupported() { - return false; - } - - /** - * Generate a {@link CaptivePortalDataShim} object with a friendly name set - * - * @param friendlyName The friendly name to set - * @return a {@link CaptivePortalData} object with a friendly name set - */ - @Override - public CaptivePortalDataShim withVenueFriendlyName(String friendlyName) - throws UnsupportedApiLevelException { - // Not supported in API level 29 - throw new UnsupportedApiLevelException("CaptivePortalData not supported on API 29"); - } - - /** - * Generate a {@link CaptivePortalDataShim} object with a friendly name and Passpoint external - * URLs set - * - * @param friendlyName The friendly name to set - * @param venueInfoUrl Venue information URL - * @param termsAndConditionsUrl Terms and conditions URL - * - * @return a {@link CaptivePortalDataShim} object with friendly name, venue info URL and terms - * and conditions URL set - */ - @Override - public CaptivePortalDataShim withPasspointInfo(@NonNull String friendlyName, - @NonNull Uri venueInfoUrl, @NonNull Uri termsAndConditionsUrl) - throws UnsupportedApiLevelException { - // Not supported in API level 29 - throw new UnsupportedApiLevelException("CaptivePortalData not supported on API 29"); - } -}
diff --git a/apishim/29/com/android/networkstack/apishim/api29/BroadcastOptionsShimImpl.java b/apishim/30/com/android/networkstack/apishim/api30/BroadcastOptionsShimImpl.java similarity index 96% rename from apishim/29/com/android/networkstack/apishim/api29/BroadcastOptionsShimImpl.java rename to apishim/30/com/android/networkstack/apishim/api30/BroadcastOptionsShimImpl.java index ab58dc2..2db73c0 100644 --- a/apishim/29/com/android/networkstack/apishim/api29/BroadcastOptionsShimImpl.java +++ b/apishim/30/com/android/networkstack/apishim/api30/BroadcastOptionsShimImpl.java
@@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.networkstack.apishim.api29; +package com.android.networkstack.apishim.api30; import android.app.BroadcastOptions; import android.os.Build;
diff --git a/apishim/30/com/android/networkstack/apishim/api30/CaptivePortalDataShimImpl.java b/apishim/30/com/android/networkstack/apishim/api30/CaptivePortalDataShimImpl.java index 8dce170..d84bb52 100644 --- a/apishim/30/com/android/networkstack/apishim/api30/CaptivePortalDataShimImpl.java +++ b/apishim/30/com/android/networkstack/apishim/api30/CaptivePortalDataShimImpl.java
@@ -22,12 +22,10 @@ import android.os.Build; import android.os.RemoteException; -import androidx.annotation.ChecksSdkIntAtLeast; import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import com.android.networkstack.apishim.common.CaptivePortalDataShim; -import com.android.networkstack.apishim.common.ShimUtils; import com.android.networkstack.apishim.common.UnsupportedApiLevelException; import org.json.JSONException; @@ -37,8 +35,7 @@ * Compatibility implementation of {@link CaptivePortalDataShim}. */ @RequiresApi(Build.VERSION_CODES.R) -public class CaptivePortalDataShimImpl - extends com.android.networkstack.apishim.api29.CaptivePortalDataShimImpl { +public class CaptivePortalDataShimImpl implements CaptivePortalDataShim { @NonNull protected final CaptivePortalData mData; @@ -53,16 +50,9 @@ /** * Parse a {@link CaptivePortalDataShim} from a JSON object. * @throws JSONException The JSON is not a representation of correct captive portal data. - * @throws UnsupportedApiLevelException CaptivePortalData is not available on this API level. */ - @RequiresApi(Build.VERSION_CODES.Q) @NonNull - public static CaptivePortalDataShim fromJson(JSONObject obj) throws JSONException, - UnsupportedApiLevelException { - if (!isSupported()) { - return com.android.networkstack.apishim.api29.CaptivePortalDataShimImpl.fromJson(obj); - } - + public static CaptivePortalDataShim fromJson(JSONObject obj) throws JSONException { final long refreshTimeMs = System.currentTimeMillis(); final long secondsRemaining = getLongOrDefault(obj, "seconds-remaining", -1L); final long millisRemaining = secondsRemaining <= Long.MAX_VALUE / 1000 @@ -81,10 +71,8 @@ .build()); } - @RequiresApi(Build.VERSION_CODES.Q) - @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.R) public static boolean isSupported() { - return ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q); + return true; } private static long getLongOrDefault(JSONObject o, String key, long def) throws JSONException { @@ -123,6 +111,18 @@ } @Override + public CharSequence getVenueFriendlyName() { + // Not supported in API level 30 + return null; + } + + @Override + public int getUserPortalUrlSource() { + // Not supported in API level 30 + return ConstantsShim.CAPTIVE_PORTAL_DATA_SOURCE_OTHER; + } + + @Override public void notifyChanged(INetworkMonitorCallbacks cb) throws RemoteException { cb.notifyCaptivePortalDataChanged(mData); }
diff --git a/apishim/33/com/android/networkstack/apishim/api33/BroadcastOptionsShimImpl.java b/apishim/33/com/android/networkstack/apishim/api33/BroadcastOptionsShimImpl.java index 5e38766..87c8e94 100644 --- a/apishim/33/com/android/networkstack/apishim/api33/BroadcastOptionsShimImpl.java +++ b/apishim/33/com/android/networkstack/apishim/api33/BroadcastOptionsShimImpl.java
@@ -31,7 +31,7 @@ */ @RequiresApi(Build.VERSION_CODES.TIRAMISU) public class BroadcastOptionsShimImpl - extends com.android.networkstack.apishim.api29.BroadcastOptionsShimImpl { + extends com.android.networkstack.apishim.api30.BroadcastOptionsShimImpl { protected BroadcastOptionsShimImpl(@NonNull BroadcastOptions options) { super(options); } @@ -42,7 +42,7 @@ @RequiresApi(Build.VERSION_CODES.TIRAMISU) public static BroadcastOptionsShim newInstance(@NonNull BroadcastOptions options) { if (!isAtLeastT()) { - return com.android.networkstack.apishim.api29.BroadcastOptionsShimImpl.newInstance( + return com.android.networkstack.apishim.api30.BroadcastOptionsShimImpl.newInstance( options); } return new BroadcastOptionsShimImpl(options);
diff --git a/apishim/common/com/android/networkstack/apishim/common/ShimUtils.java b/apishim/common/com/android/networkstack/apishim/common/ShimUtils.java index 648751b..a7bf921 100644 --- a/apishim/common/com/android/networkstack/apishim/common/ShimUtils.java +++ b/apishim/common/com/android/networkstack/apishim/common/ShimUtils.java
@@ -44,13 +44,6 @@ } /** - * Check whether the device supports in-development or final R networking APIs. - */ - public static boolean isAtLeastR() { - return isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q); - } - - /** * Check whether the device supports in-development or final S networking APIs. */ public static boolean isAtLeastS() {
diff --git a/common/networkstackclient/Android.bp b/common/networkstackclient/Android.bp index 004ec09..983f1b7 100644 --- a/common/networkstackclient/Android.bp +++ b/common/networkstackclient/Android.bp
@@ -260,9 +260,9 @@ "src/android/net/util/**/*.java", ], libs: [ - // Since this library is sdk_version: "module_current", "framework-connectivity" is just + // Since this library is sdk_version: "module_current", "framework-connectivity.stubs.module_lib" is just // the module_current API stubs of framework-connectivity - "framework-connectivity", + "framework-connectivity.stubs.module_lib", "framework-annotations-lib", ], static_libs: [
diff --git a/res/values/config.xml b/res/values/config.xml index 16364d6..8d6b64e 100644 --- a/res/values/config.xml +++ b/res/values/config.xml
@@ -61,6 +61,10 @@ <!-- Configuration for including DHCP client hostname option --> <bool name="config_dhcp_client_hostname">false</bool> + <!-- Customized preferred properties for filling DHCP client hostname option, + replacing the default device name (Dependent on config_dhcp_client_hostname is true).--> + <string-array name="config_dhcp_client_hostname_preferred_props" translatable="false"> + </string-array> <!-- Customized neighbor unreachable probe parameters. --> <integer name="config_nud_steadystate_solicit_num">10</integer>
diff --git a/res/values/overlayable.xml b/res/values/overlayable.xml index 08a2778..0aeaaec 100644 --- a/res/values/overlayable.xml +++ b/res/values/overlayable.xml
@@ -46,7 +46,8 @@ <!-- Configuration value for DhcpResults --> <item type="array" name="config_default_dns_servers"/> <!-- Configuration for including DHCP client hostname option. - If this option is true, client hostname set in Settings.Global.DEVICE_NAME will be + If this option is true, client hostname set in Settings.Global.DEVICE_NAME + (default value, if config_dhcp_client_hostname_preferred_props is not set) will be included in DHCPDISCOVER/DHCPREQUEST, otherwise, the DHCP hostname option will not be sent. RFC952 and RFC1123 stipulates an valid hostname should be only comprised of 'a-z', 'A-Z' and '-', and the length should be up to 63 octets or less (RFC1035#2.3.4), @@ -55,6 +56,17 @@ random number and etc. --> <item type="bool" name="config_dhcp_client_hostname"/> + <!-- Customized preferred properties for filling DHCP client hostname option, + replacing the default device name (Dependent on config_dhcp_client_hostname is true). + If this value is set, the DHCP hostname option will be filled in with the value of + the first property in the list that is not empty. Otherwise, the DHCP hostname option + will be filled in with the device name set in Settings.Global.DEVICE_NAME. + For example: + <item>ro.product.model</item> + <item>ro.product.name</item> + --> + <item type="array" name="config_dhcp_client_hostname_preferred_props"/> + <!-- Customized neighbor unreachable probe parameters. Legal config_*_num value should be in the range of 5-15; and config_*_interval value should be in the range of 750-1000ms.
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java index 90bd832..08a370d 100644 --- a/src/android/net/apf/ApfFilter.java +++ b/src/android/net/apf/ApfFilter.java
@@ -93,6 +93,7 @@ import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_NO_SLLA_OPTION; import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_TENTATIVE; import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_NO_ADDRESS; +import static android.net.apf.ApfCounterTracker.getCounterValue; import static android.net.apf.BaseApfGenerator.MemorySlot; import static android.net.apf.BaseApfGenerator.Register.R0; import static android.net.apf.BaseApfGenerator.Register.R1; @@ -161,7 +162,6 @@ import android.util.Pair; import android.util.SparseArray; -import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.HexDump; import com.android.internal.util.IndentingPrintWriter; @@ -260,29 +260,18 @@ public final byte[] mHardwareAddress; private final RaPacketReader mRaPacketReader; private final Handler mHandler; - @GuardedBy("this") - private long mUniqueCounter; - @GuardedBy("this") private boolean mMulticastFilter; - @GuardedBy("this") private boolean mInDozeMode; private final boolean mDrop802_3Frames; private final int[] mEthTypeBlackList; private final ApfCounterTracker mApfCounterTracker = new ApfCounterTracker(); - @GuardedBy("this") private final long mSessionStartMs; - @GuardedBy("this") private int mNumParseErrorRas = 0; - @GuardedBy("this") private int mNumZeroLifetimeRas = 0; - @GuardedBy("this") private int mLowestRouterLifetimeSeconds = Integer.MAX_VALUE; - @GuardedBy("this") private long mLowestPioValidLifetimeSeconds = Long.MAX_VALUE; - @GuardedBy("this") private long mLowestRioRouteLifetimeSeconds = Long.MAX_VALUE; - @GuardedBy("this") private long mLowestRdnssLifetimeSeconds = Long.MAX_VALUE; // Ignore non-zero RDNSS lifetimes below this value. @@ -351,22 +340,17 @@ private boolean mIsApfShutdown; // Our IPv4 address, if we have just one, otherwise null. - @GuardedBy("this") private byte[] mIPv4Address; // The subnet prefix length of our IPv4 network. Only valid if mIPv4Address is not null. - @GuardedBy("this") private int mIPv4PrefixLength; // Our IPv6 non-tentative addresses - @GuardedBy("this") private Set<Inet6Address> mIPv6NonTentativeAddresses = new ArraySet<>(); // Our tentative IPv6 addresses - @GuardedBy("this") private Set<Inet6Address> mIPv6TentativeAddresses = new ArraySet<>(); // Whether CLAT is enabled. - @GuardedBy("this") private boolean mHasClat; // mIsRunning is reflects the state of the ApfFilter during integration tests. ApfFilter can be @@ -417,7 +401,7 @@ new Dependencies(context)); } - private synchronized void maybeCleanUpApfRam() { + private void maybeCleanUpApfRam() { // Clear the APF memory to reset all counters upon connecting to the first AP // in an SSID. This is limited to APFv3 devices because this large write triggers // a crash on some older devices (b/78905546). @@ -480,11 +464,9 @@ mHardwareAddress = mInterfaceParams.macAddr.toByteArray(); // TODO: ApfFilter should not generate programs until IpClient sends provisioning success. - synchronized (this) { - maybeCleanUpApfRam(); - // Install basic filters - installNewProgramLocked(); - } + maybeCleanUpApfRam(); + // Install basic filters + installNewProgram(); mRaPacketReader = new RaPacketReader(mHandler, mInterfaceParams.index); // The class constructor must be called from the IpClient's handler thread @@ -624,7 +606,7 @@ } @Override - public synchronized String setDataSnapshot(byte[] data) { + public String setDataSnapshot(byte[] data) { mDataSnapshot = data; if (mIsRunning) { mApfCounterTracker.updateCountersFromData(data); @@ -636,11 +618,6 @@ Log.d(TAG, "(" + mInterfaceParams.name + "): " + s); } - @GuardedBy("this") - private long getUniqueNumberLocked() { - return mUniqueCounter++; - } - private static int[] filterEthTypeBlackList(int[] ethTypeBlackList) { ArrayList<Integer> bl = new ArrayList<>(); @@ -670,8 +647,7 @@ } // Returns seconds since device boot. - @VisibleForTesting - protected int secondsSinceBoot() { + private int secondsSinceBoot() { return (int) (mDependencies.elapsedRealtime() / DateUtils.SECOND_IN_MILLIS); } @@ -1113,7 +1089,7 @@ case 4: lft = getUint32(newRa.mPacket, section.start); break; } - // WARNING: keep this in sync with Ra#generateFilterLocked()! + // WARNING: keep this in sync with Ra#generateFilter()! if (section.lifetime == 0) { // Case 1) old lft == 0 if (section.min > 0) { @@ -1215,8 +1191,7 @@ // Append a filter for this RA to {@code gen}. Jump to DROP_LABEL if it should be dropped. // Jump to the next filter if packet doesn't match this RA. - @GuardedBy("ApfFilter.this") - void generateFilterLocked(ApfV4GeneratorBase<?> gen, int timeSeconds) + void generateFilter(ApfV4GeneratorBase<?> gen, int timeSeconds) throws IllegalInstructionException { String nextFilterLabel = gen.getUniqueLabel(); // Skip if packet is not the right size @@ -1316,7 +1291,7 @@ // Append a filter for this keepalive ack to {@code gen}. // Jump to drop if it matches the keepalive ack. // Jump to the next filter if packet doesn't match the keepalive ack. - abstract void generateFilterLocked(ApfV4GeneratorBase<?> gen) + abstract void generateFilter(ApfV4GeneratorBase<?> gen) throws IllegalInstructionException; } @@ -1359,8 +1334,7 @@ } @Override - @GuardedBy("ApfFilter.this") - void generateFilterLocked(ApfV4GeneratorBase<?> gen) throws IllegalInstructionException { + void generateFilter(ApfV4GeneratorBase<?> gen) throws IllegalInstructionException { final String nextFilterLabel = gen.getUniqueLabel(); gen.addLoadImmediate(R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET); @@ -1461,7 +1435,7 @@ // Append a filter for this keepalive ack to {@code gen}. // Jump to drop if it matches the keepalive ack. // Jump to the next filter if packet doesn't match the keepalive ack. - abstract void generateFilterLocked(ApfV4GeneratorBase<?> gen) + abstract void generateFilter(ApfV4GeneratorBase<?> gen) throws IllegalInstructionException; } @@ -1475,8 +1449,7 @@ } @Override - @GuardedBy("ApfFilter.this") - void generateFilterLocked(ApfV4GeneratorBase<?> gen) throws IllegalInstructionException { + void generateFilter(ApfV4GeneratorBase<?> gen) throws IllegalInstructionException { final String nextFilterLabel = gen.getUniqueLabel(); gen.addLoadImmediate(R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET); @@ -1518,7 +1491,7 @@ } @Override - void generateFilterLocked(ApfV4GeneratorBase<?> gen) { + void generateFilter(ApfV4GeneratorBase<?> gen) { throw new UnsupportedOperationException("IPv6 TCP Keepalive is not supported yet"); } } @@ -1526,11 +1499,8 @@ // Maximum number of RAs to filter for. private static final int MAX_RAS = 10; - @GuardedBy("this") private final ArrayList<Ra> mRas = new ArrayList<>(); - @GuardedBy("this") private final SparseArray<KeepalivePacket> mKeepalivePackets = new SparseArray<>(); - @GuardedBy("this") // TODO: change the mMdnsAllowList to proper type for APFv6 based mDNS offload private final List<String[]> mMdnsAllowList = new ArrayList<>(); @@ -1540,14 +1510,11 @@ private static final int FRACTION_OF_LIFETIME_TO_FILTER = 6; // When did we last install a filter program? In seconds since Unix Epoch. - @GuardedBy("this") private int mLastTimeInstalledProgram; // How long should the last installed filter program live for? In seconds. - @GuardedBy("this") private int mLastInstalledProgramMinLifetime; // For debugging only. The last program installed. - @GuardedBy("this") private byte[] mLastInstalledProgram; /** @@ -1557,17 +1524,14 @@ * IWifiStaIface#readApfPacketFilterData(), and the APF interpreter advertised support for * the opcodes to access the data buffer (LDDW and STDW). */ - @GuardedBy("this") @Nullable + @Nullable private byte[] mDataSnapshot; // How many times the program was updated since we started. - @GuardedBy("this") private int mNumProgramUpdates = 0; // The maximum program size that updated since we started. - @GuardedBy("this") private int mMaxProgramSize = 0; // The maximum number of distinct RAs - @GuardedBy("this") private int mMaxDistinctRas = 0; private ApfV6Generator tryToConvertToApfV6Generator(ApfV4GeneratorBase<?> gen) { @@ -1583,8 +1547,7 @@ * Preconditions: * - Packet being filtered is ARP */ - @GuardedBy("this") - private void generateArpFilterLocked(ApfV4GeneratorBase<?> gen) + private void generateArpFilter(ApfV4GeneratorBase<?> gen) throws IllegalInstructionException { // Here's a basic summary of what the ARP filter program does: // @@ -1695,8 +1658,7 @@ * Preconditions: * - Packet being filtered is IPv4 */ - @GuardedBy("this") - private void generateIPv4FilterLocked(ApfV4GeneratorBase<?> gen) + private void generateIPv4Filter(ApfV4GeneratorBase<?> gen) throws IllegalInstructionException { // Here's a basic summary of what the IPv4 filter program does: // @@ -1788,7 +1750,7 @@ generateV4NattKeepaliveFilters(gen); // If TCP unicast on port 7, drop - generateV4TcpPort7FilterLocked(gen); + generateV4TcpPort7Filter(gen); if (mMulticastFilter) { // Otherwise, this is an IPv4 unicast, pass @@ -1803,7 +1765,6 @@ gen.addCountAndPass(Counter.PASSED_IPV4); } - @GuardedBy("this") private void generateKeepaliveFilters(ApfV4GeneratorBase<?> gen, Class<?> filterType, int proto, int offset, String label) throws IllegalInstructionException { final boolean haveKeepaliveResponses = CollectionUtils.any(mKeepalivePackets, @@ -1819,20 +1780,18 @@ // Drop Keepalive responses for (int i = 0; i < mKeepalivePackets.size(); ++i) { final KeepalivePacket response = mKeepalivePackets.valueAt(i); - if (filterType.isInstance(response)) response.generateFilterLocked(gen); + if (filterType.isInstance(response)) response.generateFilter(gen); } gen.defineLabel(label); } - @GuardedBy("this") private void generateV4KeepaliveFilters(ApfV4GeneratorBase<?> gen) throws IllegalInstructionException { generateKeepaliveFilters(gen, TcpKeepaliveAckV4.class, IPPROTO_TCP, IPV4_PROTOCOL_OFFSET, gen.getUniqueLabel()); } - @GuardedBy("this") private void generateV4NattKeepaliveFilters(ApfV4GeneratorBase<?> gen) throws IllegalInstructionException { generateKeepaliveFilters(gen, NattKeepaliveResponse.class, @@ -1848,7 +1807,6 @@ return suffixes; } - @GuardedBy("this") private List<byte[]> getIpv6Addresses( boolean includeNonTentative, boolean includeTentative, boolean includeAnycast) { final List<byte[]> addresses = new ArrayList<>(); @@ -1870,7 +1828,6 @@ return addresses; } - @GuardedBy("this") private List<byte[]> getKnownMacAddresses() { final List<byte[]> addresses = new ArrayList<>(); addresses.addAll(mDependencies.getEtherMulticastAddresses(mInterfaceParams.name)); @@ -1882,8 +1839,7 @@ /** * Generate allocate and transmit code to send ICMPv6 non-DAD NA packets. */ - @GuardedBy("this") - private void generateNonDadNaTransmitLocked(ApfV6GeneratorBase<?> gen) + private void generateNonDadNaTransmit(ApfV6GeneratorBase<?> gen) throws IllegalInstructionException { final int ipv6PayloadLen = ICMPV6_NA_HEADER_LEN + ICMPV6_ND_OPTION_TLLA_LEN; final int pktLen = ETH_HEADER_LEN + IPV6_HEADER_LEN + ipv6PayloadLen; @@ -1927,8 +1883,7 @@ ); } - @GuardedBy("this") - private void generateNsFilterLocked(ApfV6Generator v6Gen) + private void generateNsFilter(ApfV6Generator v6Gen) throws IllegalInstructionException { final List<byte[]> allIPv6Addrs = getIpv6Addresses( true /* includeNonTentative */, @@ -2028,7 +1983,7 @@ // if multicast MAC in SLLA option -> drop v6Gen.addLoad8(R0, ICMP6_NS_OPTION_TYPE_OFFSET + 2) .addCountAndDropIfR0AnyBitsSet(1, DROPPED_IPV6_NS_INVALID); - generateNonDadNaTransmitLocked(v6Gen); + generateNonDadNaTransmit(v6Gen); v6Gen.addCountAndDrop(Counter.DROPPED_IPV6_NS_REPLIED_NON_DAD); } @@ -2038,8 +1993,7 @@ * Preconditions: * - Packet being filtered is IPv6 */ - @GuardedBy("this") - private void generateIPv6FilterLocked(ApfV4GeneratorBase<?> gen) + private void generateIPv6Filter(ApfV4GeneratorBase<?> gen) throws IllegalInstructionException { // Here's a basic summary of what the IPv6 filter program does: // @@ -2133,9 +2087,9 @@ if (v6Gen != null && mShouldHandleNdOffload) { final String skipNsPacketFilter = v6Gen.getUniqueLabel(); v6Gen.addJumpIfR0NotEquals(ICMPV6_NEIGHBOR_SOLICITATION, skipNsPacketFilter); - generateNsFilterLocked(v6Gen); - // End of NS filter. generateNsFilterLocked() method is terminal, so NS packet will be - // either dropped or passed inside generateNsFilterLocked(). + generateNsFilter(v6Gen); + // End of NS filter. generateNsFilter() method is terminal, so NS packet will be + // either dropped or passed inside generateNsFilter(). v6Gen.defineLabel(skipNsPacketFilter); } @@ -2161,8 +2115,7 @@ * Generate filter code to process mDNS packets. Execution of this code ends in * DROP_LABEL * or PASS_LABEL if the packet is mDNS packets. Otherwise, skip this check. */ - @GuardedBy("this") - private void generateMdnsFilterLocked(ApfV4GeneratorBase<?> gen) + private void generateMdnsFilter(ApfV4GeneratorBase<?> gen) throws IllegalInstructionException { final String skipMdnsv4Filter = gen.getUniqueLabel(); final String skipMdnsFilter = gen.getUniqueLabel(); @@ -2237,8 +2190,7 @@ * On entry, we know it is IPv4 ethertype, but don't know anything else. * R0/R1 have nothing useful in them, and can be clobbered. */ - @GuardedBy("this") - private void generateV4TcpPort7FilterLocked(ApfV4GeneratorBase<?> gen) + private void generateV4TcpPort7Filter(ApfV4GeneratorBase<?> gen) throws IllegalInstructionException { final String skipPort7V4Filter = gen.getUniqueLabel(); @@ -2262,7 +2214,6 @@ gen.defineLabel(skipPort7V4Filter); } - @GuardedBy("this") private void generateV6KeepaliveFilters(ApfV4GeneratorBase<?> gen) throws IllegalInstructionException { generateKeepaliveFilters(gen, TcpKeepaliveAckV6.class, IPPROTO_TCP, IPV6_NEXT_HEADER_OFFSET, @@ -2283,15 +2234,13 @@ * <li>Pass all non-IPv4 and non-IPv6 packets, * <li>Drop IPv6 ICMPv6 NAs to anything in ff02::/120. * <li>Drop IPv6 ICMPv6 RSs. - * <li>Filter IPv4 packets (see generateIPv4FilterLocked()) - * <li>Filter IPv6 packets (see generateIPv6FilterLocked()) + * <li>Filter IPv4 packets (see generateIPv4Filter()) + * <li>Filter IPv6 packets (see generateIPv6Filter()) * <li>Let execution continue off the end of the program for IPv6 ICMPv6 packets. This allows * insertion of RA filters here, or if there aren't any, just passes the packets. * </ul> */ - @GuardedBy("this") - @VisibleForTesting - public ApfV4GeneratorBase<?> emitPrologueLocked() throws IllegalInstructionException { + private ApfV4GeneratorBase<?> emitPrologue() throws IllegalInstructionException { // This is guaranteed to succeed because of the check in maybeCreate. ApfV4GeneratorBase<?> gen; if (shouldUseApfV6Generator()) { @@ -2371,17 +2320,17 @@ // Add ARP filters: String skipArpFiltersLabel = gen.getUniqueLabel(); gen.addJumpIfR0NotEquals(ETH_P_ARP, skipArpFiltersLabel); - generateArpFilterLocked(gen); + generateArpFilter(gen); gen.defineLabel(skipArpFiltersLabel); // Add mDNS filter: - generateMdnsFilterLocked(gen); + generateMdnsFilter(gen); gen.addLoad16(R0, ETH_ETHERTYPE_OFFSET); // Add IPv4 filters: String skipIPv4FiltersLabel = gen.getUniqueLabel(); gen.addJumpIfR0NotEquals(ETH_P_IP, skipIPv4FiltersLabel); - generateIPv4FilterLocked(gen); + generateIPv4Filter(gen); gen.defineLabel(skipIPv4FiltersLabel); // Check for IPv6: @@ -2398,7 +2347,7 @@ // Add IPv6 filters: gen.defineLabel(ipv6FilterLabel); - generateIPv6FilterLocked(gen); + generateIPv6Filter(gen); return gen; } @@ -2408,7 +2357,6 @@ * Currently, the epilogue consists of two trampolines which count passed and dropped packets * before jumping to the actual PASS and DROP labels. */ - @GuardedBy("this") private void emitEpilogue(ApfV4GeneratorBase<?> gen) throws IllegalInstructionException { // Execution will reach here if none of the filters match, which will pass the packet to // the application processor. @@ -2421,10 +2369,8 @@ /** * Generate and install a new filter program. */ - @GuardedBy("this") - @SuppressWarnings("GuardedBy") // errorprone false positive on ra#generateFilterLocked @VisibleForTesting - public void installNewProgramLocked() { + public void installNewProgram() { ArrayList<Ra> rasToFilter = new ArrayList<>(); final byte[] program; int programMinLft = Integer.MAX_VALUE; @@ -2434,7 +2380,7 @@ final int timeSeconds = secondsSinceBoot(); mLastTimeInstalledProgram = timeSeconds; // Step 1: Determine how many RA filters we can fit in the program. - ApfV4GeneratorBase<?> gen = emitPrologueLocked(); + ApfV4GeneratorBase<?> gen = emitPrologue(); // The epilogue normally goes after the RA filters, but add it early to include its // length when estimating the total. @@ -2450,7 +2396,7 @@ for (Ra ra : mRas) { // skip filter if it has expired. if (ra.getRemainingFilterLft(timeSeconds) <= 0) continue; - ra.generateFilterLocked(gen, timeSeconds); + ra.generateFilter(gen, timeSeconds); // Stop if we get too big. if (gen.programLengthOverEstimate() > mMaximumApfProgramSize) { if (VDBG) Log.d(TAG, "Past maximum program size, skipping RAs"); @@ -2461,10 +2407,14 @@ rasToFilter.add(ra); } + // Increase the counter before we generate the program. + // This keeps the APF_PROGRAM_ID counter in sync with the program. + mNumProgramUpdates++; + // Step 2: Actually generate the program - gen = emitPrologueLocked(); + gen = emitPrologue(); for (Ra ra : rasToFilter) { - ra.generateFilterLocked(gen, timeSeconds); + ra.generateFilter(gen, timeSeconds); programMinLft = Math.min(programMinLft, ra.getRemainingFilterLft(timeSeconds)); } emitEpilogue(gen); @@ -2475,15 +2425,12 @@ return; } if (mIsRunning) { - // Update data snapshot every time we install a new program - mIpClientCallback.startReadPacketFilter("new program install"); if (!mIpClientCallback.installPacketFilter(program)) { sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE); } } mLastInstalledProgramMinLifetime = programMinLft; mLastInstalledProgram = program; - mNumProgramUpdates++; mMaxProgramSize = Math.max(mMaxProgramSize, program.length); if (VDBG) { @@ -2514,7 +2461,7 @@ * if the current APF program should be updated. */ @VisibleForTesting - public synchronized void processRa(byte[] packet, int length) { + public void processRa(byte[] packet, int length) { if (VDBG) hexDump("Read packet = ", packet, length); final Ra ra; @@ -2561,7 +2508,7 @@ // Rate limit program installation if (mTokenBucket.get()) { - installNewProgramLocked(); + installNewProgram(); } else { Log.e(TAG, "Failed to install prog for tracked RA, too many updates. " + ra); } @@ -2580,7 +2527,7 @@ mRas.add(0, ra); // Rate limit program installation if (mTokenBucket.get()) { - installNewProgramLocked(); + installNewProgram(); } else { Log.e(TAG, "Failed to install prog for new RA, too many updates. " + ra); } @@ -2606,7 +2553,7 @@ networkQuirkMetrics); } - private synchronized void collectAndSendMetrics() { + private void collectAndSendMetrics() { if (mIpClientRaInfoMetrics == null || mApfSessionInfoMetrics == null) return; final long sessionDurationMs = mDependencies.elapsedRealtime() - mSessionStartMs; if (sessionDurationMs < mMinMetricsSessionDurationMs) return; @@ -2636,7 +2583,7 @@ mApfSessionInfoMetrics.statsWrite(); } - public synchronized void shutdown() { + public void shutdown() { collectAndSendMetrics(); // The shutdown() must be called from the IpClient's handler thread mRaPacketReader.stop(); @@ -2648,22 +2595,17 @@ } } - public synchronized void setMulticastFilter(boolean isEnabled) { + public void setMulticastFilter(boolean isEnabled) { if (mMulticastFilter == isEnabled) return; mMulticastFilter = isEnabled; - installNewProgramLocked(); + installNewProgram(); } @VisibleForTesting - public synchronized void setDozeMode(boolean isEnabled) { + public void setDozeMode(boolean isEnabled) { if (mInDozeMode == isEnabled) return; mInDozeMode = isEnabled; - installNewProgramLocked(); - } - - @VisibleForTesting - public synchronized boolean isInDozeMode() { - return mInDozeMode; + installNewProgram(); } /** Retrieve the single IPv4 LinkAddress if there is one, otherwise return null. */ @@ -2706,7 +2648,7 @@ return new Pair<>(tentativeAddrs, nonTentativeAddrs); } - public synchronized void setLinkProperties(LinkProperties lp) { + public void setLinkProperties(LinkProperties lp) { // NOTE: Do not keep a copy of LinkProperties as it would further duplicate state. final LinkAddress ipv4Address = retrieveIPv4LinkAddress(lp); final byte[] addr = (ipv4Address != null) ? ipv4Address.getAddress().getAddress() : null; @@ -2726,16 +2668,16 @@ mIPv6TentativeAddresses = ipv6Addresses.first; mIPv6NonTentativeAddresses = ipv6Addresses.second; - installNewProgramLocked(); + installNewProgram(); } @Override - public synchronized void updateClatInterfaceState(boolean add) { + public void updateClatInterfaceState(boolean add) { if (mHasClat == add) { return; } mHasClat = add; - installNewProgramLocked(); + installNewProgram(); } @Override @@ -2761,7 +2703,7 @@ * @param slot The index used to access the filter. * @param sentKeepalivePacket The attributes of the sent keepalive packet. */ - public synchronized void addTcpKeepalivePacketFilter(final int slot, + public void addTcpKeepalivePacketFilter(final int slot, final TcpKeepalivePacketDataParcelable sentKeepalivePacket) { log("Adding keepalive ack(" + slot + ")"); if (null != mKeepalivePackets.get(slot)) { @@ -2771,7 +2713,7 @@ mKeepalivePackets.put(slot, (ipVersion == 4) ? new TcpKeepaliveAckV4(sentKeepalivePacket) : new TcpKeepaliveAckV6(sentKeepalivePacket)); - installNewProgramLocked(); + installNewProgram(); } /** @@ -2781,7 +2723,7 @@ * @param slot The index used to access the filter. * @param sentKeepalivePacket The attributes of the sent keepalive packet. */ - public synchronized void addNattKeepalivePacketFilter(final int slot, + public void addNattKeepalivePacketFilter(final int slot, final NattKeepalivePacketDataParcelable sentKeepalivePacket) { log("Adding NAT-T keepalive packet(" + slot + ")"); if (null != mKeepalivePackets.get(slot)) { @@ -2794,7 +2736,7 @@ } mKeepalivePackets.put(slot, new NattKeepaliveResponse(sentKeepalivePacket)); - installNewProgramLocked(); + installNewProgram(); } /** @@ -2802,13 +2744,13 @@ * * @param slot The index used to access the filter. */ - public synchronized void removeKeepalivePacketFilter(int slot) { + public void removeKeepalivePacketFilter(int slot) { log("Removing keepalive packet(" + slot + ")"); mKeepalivePackets.remove(slot); - installNewProgramLocked(); + installNewProgram(); } - public synchronized void dump(IndentingPrintWriter pw) { + public void dump(IndentingPrintWriter pw) { // TODO: use HandlerUtils.runWithScissors() to dump APF on the handler thread. pw.println(String.format( "Capabilities: { apfVersionSupported: %d, maximumApfProgramSize: %d }", @@ -2861,14 +2803,19 @@ return; } pw.println("Program updates: " + mNumProgramUpdates); + int filterAgeSeconds = secondsSinceBoot() - mLastTimeInstalledProgram; pw.println(String.format( "Last program length %d, installed %ds ago, lifetime %ds", - mLastInstalledProgram.length, secondsSinceBoot() - mLastTimeInstalledProgram, + mLastInstalledProgram.length, filterAgeSeconds, mLastInstalledProgramMinLifetime)); - - pw.print("Denylisted Ethertypes:"); - for (int p : mEthTypeBlackList) { - pw.print(String.format(" %04x", p)); + if (SdkLevel.isAtLeastV()) { + pw.print("Hardcoded Allowlisted Ethertypes:"); + pw.println(" 0800(IPv4) 0806(ARP) 86DD(IPv6) 888E(EAPOL) 88B4(WAPI)"); + } else { + pw.print("Denylisted Ethertypes:"); + for (int p : mEthTypeBlackList) { + pw.print(String.format(" %04x", p)); + } } pw.println(); pw.println("RA filters:"); @@ -2930,16 +2877,61 @@ } else { try { Counter[] counters = Counter.class.getEnumConstants(); + long counterFilterAgeSeconds = + getCounterValue(mDataSnapshot, Counter.FILTER_AGE_SECONDS); + long counterApfProgramId = + getCounterValue(mDataSnapshot, Counter.APF_PROGRAM_ID); for (Counter c : Arrays.asList(counters).subList(1, counters.length)) { - long value = ApfCounterTracker.getCounterValue(mDataSnapshot, c); - // Only print non-zero counters - if (value != 0) { - pw.println(c.toString() + ": " + value); + long value = getCounterValue(mDataSnapshot, c); + + String note = ""; + boolean checkValueIncreases = true; + switch (c) { + case FILTER_AGE_SECONDS: + checkValueIncreases = false; + if (value != counterFilterAgeSeconds) { + note = " [ERROR: impossible]"; + } else if (counterApfProgramId < mNumProgramUpdates) { + note = " [IGNORE: obsolete program]"; + } else if (value > filterAgeSeconds) { + long offset = value - filterAgeSeconds; + note = " [ERROR: in the future by " + offset + "s]"; + } + break; + case FILTER_AGE_16384THS: + if (mApfVersionSupported > BaseApfGenerator.APF_VERSION_4) { + checkValueIncreases = false; + if (value % 16384 == 0) { + // valid, but unlikely + note = " [INFO: zero fractional portion]"; + } + if (value / 16384 != counterFilterAgeSeconds) { + // should not be able to happen + note = " [ERROR: mismatch with FILTER_AGE_SECONDS]"; + } + } else if (value != 0) { + note = " [UNEXPECTED: APF<=4, yet non-zero]"; + } + break; + case APF_PROGRAM_ID: + if (value != counterApfProgramId) { + note = " [ERROR: impossible]"; + } else if (value < mNumProgramUpdates) { + note = " [WARNING: OBSOLETE PROGRAM]"; + } else if (value > mNumProgramUpdates) { + note = " [ERROR: INVALID FUTURE ID]"; + } + break; + default: + break; } - final Set<Counter> skipCheckCounters = Set.of(FILTER_AGE_SECONDS, - FILTER_AGE_16384THS); - if (!skipCheckCounters.contains(c)) { + // Only print non-zero counters (or those with a note) + if (value != 0 || !note.equals("")) { + pw.println(c.toString() + ": " + value + note); + } + + if (checkValueIncreases) { // If the counter's value decreases, it may have been cleaned up or there // may be a bug. long oldValue = mApfCounterTracker.getCounters().getOrDefault(c, 0L); @@ -2982,7 +2974,7 @@ } /** Return data snapshot as hex string for testing purposes. */ - public synchronized @Nullable String getDataSnapshotHexString() { + public @Nullable String getDataSnapshotHexString() { if (mDataSnapshot == null) { return null; }
diff --git a/src/android/net/dhcp/DhcpClient.java b/src/android/net/dhcp/DhcpClient.java index 4b94968..7ef6364 100644 --- a/src/android/net/dhcp/DhcpClient.java +++ b/src/android/net/dhcp/DhcpClient.java
@@ -81,10 +81,12 @@ import android.os.Message; import android.os.PowerManager; import android.os.SystemClock; +import android.os.SystemProperties; import android.provider.Settings; import android.stats.connectivity.DhcpFeature; import android.system.ErrnoException; import android.system.Os; +import android.text.TextUtils; import android.util.EventLog; import android.util.Log; import android.util.SparseArray; @@ -104,7 +106,6 @@ import com.android.net.module.util.PacketReader; import com.android.net.module.util.arp.ArpPacket; import com.android.networkstack.R; -import com.android.networkstack.apishim.CaptivePortalDataShimImpl; import com.android.networkstack.apishim.SocketUtilsShimImpl; import com.android.networkstack.metrics.IpProvisioningMetrics; import com.android.networkstack.util.NetworkStackUtils; @@ -308,9 +309,7 @@ final ByteArrayOutputStream params = new ByteArrayOutputStream(DEFAULT_REQUESTED_PARAMS.length + numOptionalParams); params.write(DEFAULT_REQUESTED_PARAMS, 0, DEFAULT_REQUESTED_PARAMS.length); - if (isCapportApiEnabled()) { - params.write(DHCP_CAPTIVE_PORTAL); - } + params.write(DHCP_CAPTIVE_PORTAL); params.write(DHCP_IPV6_ONLY_PREFERRED); // Customized DHCP options to be put in PRL. for (DhcpOption option : mConfiguration.options) { @@ -323,10 +322,6 @@ return params.toByteArray(); } - private static boolean isCapportApiEnabled() { - return CaptivePortalDataShimImpl.isSupported(); - } - // DHCP flag that means "yes, we support unicast." private static final boolean DO_UNICAST = false; @@ -431,6 +426,31 @@ return context.getResources().getBoolean(R.bool.config_dhcp_client_hostname); } + private boolean isValidCustomHostnameProperty(String prop) { + return "ro.product.model".equals(prop) + || "ro.product.name".equals(prop) + || prop.startsWith("ro.vendor."); + } + + /** + * Get the customized hostname from RRO to fill hostname option. + */ + public String getCustomHostname(final Context context) { + final String[] prefHostnameProps = context.getResources().getStringArray( + R.array.config_dhcp_client_hostname_preferred_props); + if (prefHostnameProps == null || prefHostnameProps.length == 0) { + return getDeviceName(context); + } + for (final String prop : prefHostnameProps) { + if (!isValidCustomHostnameProperty(prop)) continue; + String prefHostname = getSystemProperty(prop); + if (!TextUtils.isEmpty(prefHostname)) { + return prefHostname; + } + } + return getDeviceName(context); + } + /** * Get the device name from system settings. */ @@ -440,6 +460,13 @@ } /** + * Read a system property. + */ + public String getSystemProperty(String name) { + return SystemProperties.get(name, "" /* default*/); + } + + /** * Get a IpMemoryStore instance. */ public NetworkStackIpMemoryStore getIpMemoryStore() { @@ -544,7 +571,7 @@ mRebindAlarm = makeWakeupMessage("REBIND", CMD_REBIND_DHCP); mExpiryAlarm = makeWakeupMessage("EXPIRY", CMD_EXPIRE_DHCP); - mHostname = new HostnameTransliterator().transliterate(deps.getDeviceName(mContext)); + mHostname = new HostnameTransliterator().transliterate(deps.getCustomHostname(mContext)); mMetrics.setHostnameTransinfo(deps.getSendHostnameOverlaySetting(context), mHostname != null); } @@ -657,7 +684,6 @@ private byte[] getOptionsToSkip() { final ByteArrayOutputStream optionsToSkip = new ByteArrayOutputStream(2); - if (!isCapportApiEnabled()) optionsToSkip.write(DHCP_CAPTIVE_PORTAL); if (!mConfiguration.isWifiManagedProfile) { optionsToSkip.write(DHCP_DOMAIN_SEARCHLIST); }
diff --git a/src/android/net/dhcp/DhcpPacket.java b/src/android/net/dhcp/DhcpPacket.java index 595c63a..8e327e1 100644 --- a/src/android/net/dhcp/DhcpPacket.java +++ b/src/android/net/dhcp/DhcpPacket.java
@@ -16,7 +16,6 @@ package android.net.dhcp; -import static com.android.modules.utils.build.SdkLevel.isAtLeastR; import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ALL; import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ANY; @@ -25,7 +24,6 @@ import android.net.metrics.DhcpErrorEvent; import android.net.networkstack.aidl.dhcp.DhcpOption; import android.os.Build; -import android.os.SystemProperties; import android.system.OsConstants; import android.text.TextUtils; @@ -807,9 +805,6 @@ */ @VisibleForTesting public String getHostname() { - if (mHostName == null && !isAtLeastR()) { - return SystemProperties.get("net.hostname"); - } return mHostName; }
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java index d0dda68..493f36f 100644 --- a/src/android/net/ip/IpClient.java +++ b/src/android/net/ip/IpClient.java
@@ -16,6 +16,7 @@ package android.net.ip; +import static android.content.pm.PackageManager.FEATURE_WATCH; import static android.net.IIpMemoryStore.NETWORK_EVENT_NUD_FAILURE_ROAM; import static android.net.IIpMemoryStore.NETWORK_EVENT_NUD_FAILURE_CONFIRM; import static android.net.IIpMemoryStore.NETWORK_EVENT_NUD_FAILURE_ORGANIC; @@ -44,7 +45,6 @@ import static android.net.ip.IpClient.IpClientCommands.CMD_UPDATE_APF_DATA_SNAPSHOT; import static android.net.ip.IpClient.IpClientCommands.CMD_UPDATE_HTTP_PROXY; import static android.net.ip.IpClient.IpClientCommands.CMD_UPDATE_L2INFORMATION; -import static android.net.ip.IpClient.IpClientCommands.CMD_UPDATE_L2KEY_CLUSTER; import static android.net.ip.IpClient.IpClientCommands.CMD_UPDATE_TCP_BUFFER_SIZES; import static android.net.ip.IpClient.IpClientCommands.EVENT_DHCPACTION_TIMEOUT; import static android.net.ip.IpClient.IpClientCommands.EVENT_IPV6_AUTOCONF_TIMEOUT; @@ -134,7 +134,6 @@ import android.net.shared.ProvisioningConfiguration; import android.net.shared.ProvisioningConfiguration.ScanResultInfo; import android.net.shared.ProvisioningConfiguration.ScanResultInfo.InformationElement; -import android.os.Build; import android.os.ConditionVariable; import android.os.Handler; import android.os.IBinder; @@ -152,7 +151,6 @@ import android.text.format.DateUtils; import android.util.LocalLog; import android.util.Log; -import android.util.Pair; import android.util.SparseArray; import androidx.annotation.NonNull; @@ -166,6 +164,7 @@ import com.android.internal.util.State; import com.android.internal.util.StateMachine; import com.android.internal.util.WakeupMessage; +import com.android.modules.expresslog.Counter; import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.CollectionUtils; import com.android.net.module.util.ConnectivityUtils; @@ -600,16 +599,15 @@ static final int EVENT_READ_PACKET_FILTER_COMPLETE = 12; static final int CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF = 13; static final int CMD_REMOVE_KEEPALIVE_PACKET_FILTER_FROM_APF = 14; - static final int CMD_UPDATE_L2KEY_CLUSTER = 15; - static final int CMD_COMPLETE_PRECONNECTION = 16; - static final int CMD_UPDATE_L2INFORMATION = 17; - static final int CMD_SET_DTIM_MULTIPLIER_AFTER_DELAY = 18; - static final int CMD_UPDATE_APF_CAPABILITIES = 19; - static final int EVENT_IPV6_AUTOCONF_TIMEOUT = 20; - static final int CMD_UPDATE_APF_DATA_SNAPSHOT = 21; - static final int EVENT_NUD_FAILURE_QUERY_TIMEOUT = 22; - static final int EVENT_NUD_FAILURE_QUERY_SUCCESS = 23; - static final int EVENT_NUD_FAILURE_QUERY_FAILURE = 24; + static final int CMD_COMPLETE_PRECONNECTION = 15; + static final int CMD_UPDATE_L2INFORMATION = 16; + static final int CMD_SET_DTIM_MULTIPLIER_AFTER_DELAY = 17; + static final int CMD_UPDATE_APF_CAPABILITIES = 18; + static final int EVENT_IPV6_AUTOCONF_TIMEOUT = 19; + static final int CMD_UPDATE_APF_DATA_SNAPSHOT = 20; + static final int EVENT_NUD_FAILURE_QUERY_TIMEOUT = 21; + static final int EVENT_NUD_FAILURE_QUERY_SUCCESS = 22; + static final int EVENT_NUD_FAILURE_QUERY_FAILURE = 23; // Internal commands to use instead of trying to call transitionTo() inside // a given State's enter() method. Calling transitionTo() from enter/exit // encounters a Log.wtf() that can cause trouble on eng builds. @@ -634,20 +632,13 @@ private static final int MAX_PACKET_RECORDS = 100; @VisibleForTesting - static final String CONFIG_MIN_RDNSS_LIFETIME = "ipclient_min_rdnss_lifetime"; - private static final int DEFAULT_MIN_RDNSS_LIFETIME = - ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q) ? 120 : 0; - - @VisibleForTesting static final String CONFIG_ACCEPT_RA_MIN_LFT = "ipclient_accept_ra_min_lft"; - @VisibleForTesting - static final int DEFAULT_ACCEPT_RA_MIN_LFT = 180; @VisibleForTesting static final String CONFIG_APF_COUNTER_POLLING_INTERVAL_SECS = "ipclient_apf_counter_polling_interval_secs"; @VisibleForTesting - static final int DEFAULT_APF_COUNTER_POLLING_INTERVAL_SECS = 1800; + static final int DEFAULT_APF_COUNTER_POLLING_INTERVAL_SECS = 300; // Used to wait for the provisioning to complete eventually and then decide the target // network type, which gives the accurate hint to set DTIM multiplier. Per current IPv6 @@ -798,9 +789,6 @@ @Nullable private final DevicePolicyManager mDevicePolicyManager; - // Ignore nonzero RDNSS option lifetimes below this value. 0 = disabled. - private final int mMinRdnssLifetimeSec; - // Ignore any nonzero RA section with lifetime below this value. private final int mAcceptRaMinLft; @@ -848,16 +836,25 @@ private ApfCapabilities mCurrentApfCapabilities; private WakeupMessage mIpv6AutoconfTimeoutAlarm = null; private boolean mIgnoreNudFailure; - // An array of NUD failure event count associated with the query database since the timestamps - // in the past, and is always initialized to null in StoppedState. Currently supported array - // elements are as follows: - // element 0: failures in the past week - // element 1: failures in the past day - // element 2: failures in the past 6h + /** + * An array of NUD failure event counts retrieved from the memory store since the timestamps + * in the past, and is always initialized to null in StoppedState. Currently supported array + * elements are as follows: + * element 0: failures in the past week + * element 1: failures in the past day + * element 2: failures in the past 6h + */ @Nullable private int[] mNudFailureEventCounts = null; /** + * The number of NUD failure events that were stored in the memory store since this IpClient + * was last started. Always set to zero in StoppedState. Used to prevent writing excessive NUD + * failure events to the memory store. + */ + private int mNudFailuresStoredSinceStart = 0; + + /** * Reading the snapshot is an asynchronous operation initiated by invoking * Callback.startReadPacketFilter() and completed when the WiFi Service responds with an * EVENT_READ_PACKET_FILTER_COMPLETE message. The mApfDataSnapshotComplete condition variable @@ -1062,14 +1059,13 @@ mDhcp6PrefixDelegationEnabled = mDependencies.isFeatureEnabled(mContext, IPCLIENT_DHCPV6_PREFIX_DELEGATION_VERSION); - mMinRdnssLifetimeSec = mDependencies.getDeviceConfigPropertyInt( - CONFIG_MIN_RDNSS_LIFETIME, DEFAULT_MIN_RDNSS_LIFETIME); + final boolean isWatch = mContext.getPackageManager().hasSystemFeature(FEATURE_WATCH); mAcceptRaMinLft = mDependencies.getDeviceConfigPropertyInt(CONFIG_ACCEPT_RA_MIN_LFT, - DEFAULT_ACCEPT_RA_MIN_LFT); + isWatch ? 900 : 180); mApfCounterPollingIntervalMs = mDependencies.getDeviceConfigPropertyInt( CONFIG_APF_COUNTER_POLLING_INTERVAL_SECS, DEFAULT_APF_COUNTER_POLLING_INTERVAL_SECS) * DateUtils.SECOND_IN_MILLIS; - mUseNewApfFilter = SdkLevel.isAtLeastV() || mDependencies.isFeatureEnabled(context, + mUseNewApfFilter = SdkLevel.isAtLeastV() || mDependencies.isFeatureNotChickenedOut(context, APF_NEW_RA_FILTER_VERSION); mEnableApfPollingCounters = mDependencies.isFeatureEnabled(context, APF_POLLING_COUNTERS_VERSION); @@ -1094,7 +1090,7 @@ DEFAULT_NUD_FAILURE_COUNT_WEEKLY_THRESHOLD); IpClientLinkObserver.Configuration config = new IpClientLinkObserver.Configuration( - mMinRdnssLifetimeSec, mPopulateLinkAddressLifetime); + mAcceptRaMinLft, mPopulateLinkAddressLifetime); mLinkObserver = new IpClientLinkObserver( mContext, getHandler(), @@ -1204,7 +1200,8 @@ @Override public void setL2KeyAndGroupHint(String l2Key, String cluster) { enforceNetworkStackCallingPermission(); - IpClient.this.setL2KeyAndCluster(l2Key, cluster); + // This method is not supported anymore. The caller should call + // #updateLayer2Information() instead. } @Override public void setTcpBufferSizes(String tcpBufferSizes) { @@ -1398,18 +1395,6 @@ } /** - * Set the L2 key and cluster for storing info into the memory store. - * - * This method is only supported on Q devices. For R or above releases, - * caller should call #updateLayer2Information() instead. - */ - public void setL2KeyAndCluster(String l2Key, String cluster) { - if (!ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q)) { - sendMessage(CMD_UPDATE_L2KEY_CLUSTER, new Pair<>(l2Key, cluster)); - } - } - - /** * Set the HTTP Proxy configuration to use. * * This may be called, repeatedly, at any time before or after a call to @@ -2543,11 +2528,24 @@ // In order to avoid overflowing the database (the maximum is 10MB) in case of a NUD failure // happens frequently (e.g, every 30s in a broken network), we stop writing the NUD failure - // event to database if the event count in past 6h has exceeded the daily threshold. + // event to database if the total event count in past 6h, plus the number of events written + // since IpClient was started, has exceeded the daily threshold. + // + // The code also counts the number of events written since this IpClient was last started. + // Otherwise, if NUD failures are already being ignored due to a (daily or weekly) threshold + // being hit by events that happened more than 6 hours ago, but there have been no failures in + // the last 6 hours, the code would never stop logging failures (filling up the memory store) + // until IpClient is restarted and queries the memory store again. + // + // The 6-hour count is still useful, even though the code looks at the number of NUD failures + // since IpClient was last started, because it ensures that even if the network disconnects and + // reconnects frequently for any other reason, the code will never store more than 10 NUD + // failures every 6 hours. private boolean shouldStopWritingNudFailureEventToDatabase() { // NUD failure query has not completed yet. if (mNudFailureEventCounts == null) return true; - return mNudFailureEventCounts[2] >= mNudFailureCountDailyThreshold; + return mNudFailureEventCounts[2] + mNudFailuresStoredSinceStart + >= mNudFailureCountDailyThreshold; } private void maybeStoreNudFailureToDatabase(final NudEventType type) { @@ -2566,6 +2564,7 @@ Log.e(TAG, "Failed to store NUD failure event"); } }); + mNudFailuresStoredSinceStart++; if (DBG) { Log.d(TAG, "store network event " + type + " at " + now @@ -2585,7 +2584,10 @@ @Override public void notifyLost(String logMsg, NudEventType type) { maybeStoreNudFailureToDatabase(type); - if (mIgnoreNudFailure) return; + if (mIgnoreNudFailure) { + Counter.logIncrement("core_networking.value_nud_failure_ignored"); + return; + } final int version = mCallback.getInterfaceVersion(); if (version >= VERSION_ADDED_REACHABILITY_FAILURE) { final int reason = nudEventTypeToInt(type); @@ -2744,7 +2746,7 @@ apfConfig.multicastFilter = mMulticastFiltering; // Get the Configuration for ApfFilter from Context // Resource settings were moved from ApfCapabilities APIs to NetworkStack resources in S - if (ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.R)) { + if (ShimUtils.isAtLeastS()) { final Resources res = mContext.getResources(); apfConfig.ieee802_3Filter = res.getBoolean(R.bool.config_apfDrop802_3Frames); apfConfig.ethTypeBlackList = res.getIntArray(R.array.config_apfEthTypeDenyList); @@ -2753,7 +2755,9 @@ apfConfig.ethTypeBlackList = ApfCapabilities.getApfEtherTypeBlackList(); } - apfConfig.minRdnssLifetimeSec = mMinRdnssLifetimeSec; + // The RDNSS option is not processed by the kernel, so lifetime filtering + // can occur independent of kernel support for accept_ra_min_lft. + apfConfig.minRdnssLifetimeSec = mAcceptRaMinLft; // Check the feature flag first before reading IPv6 sysctl, which can prevent from // triggering a potential kernel bug about the sysctl. // TODO: add unit test to check if the setIpv6Sysctl() is called or not. @@ -2801,6 +2805,7 @@ mMulticastNsSourceAddresses.clear(); mDelegatedPrefixes.clear(); mNudFailureEventCounts = null; + mNudFailuresStoredSinceStart = 0; resetLinkProperties(); if (mStartTimeMillis > 0) { @@ -2844,13 +2849,6 @@ handleLinkPropertiesUpdate(NO_CALLBACKS); break; - case CMD_UPDATE_L2KEY_CLUSTER: { - final Pair<String, String> args = (Pair<String, String>) msg.obj; - mL2Key = args.first; - mCluster = args.second; - break; - } - case CMD_SET_MULTICAST_FILTER: mMulticastFiltering = (boolean) msg.obj; break; @@ -3208,20 +3206,6 @@ transitionToStoppingState(DisconnectCode.forNumber(msg.arg1)); break; - case CMD_UPDATE_L2KEY_CLUSTER: { - final Pair<String, String> args = (Pair<String, String>) msg.obj; - mL2Key = args.first; - mCluster = args.second; - // TODO : attributes should be saved to the memory store with - // these new values if they differ from the previous ones. - // If the state machine is in pure StartedState, then the values to input - // are not known yet and should be updated when the LinkProperties are updated. - // If the state machine is in RunningState (which is a child of StartedState) - // then the next NUD check should be used to store the new values to avoid - // inputting current values for what may be a different L3 network. - break; - } - case CMD_UPDATE_L2INFORMATION: handleUpdateL2Information((Layer2InformationParcelable) msg.obj); break; @@ -3296,6 +3280,7 @@ sinceTimes[2] = now - SIX_HOURS_IN_MS; mIpMemoryStore.retrieveNetworkEventCount(mCluster, sinceTimes, NETWORK_EVENT_NUD_FAILURE_TYPES, mListener); + Counter.logIncrement("core_networking.value_nud_failure_queried"); } @Override
diff --git a/src/android/net/util/RawPacketTracker.java b/src/android/net/util/RawPacketTracker.java new file mode 100644 index 0000000..e73834b --- /dev/null +++ b/src/android/net/util/RawPacketTracker.java
@@ -0,0 +1,242 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.util; + +import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; + +import android.net.ip.ConnectivityPacketTracker; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.util.ArrayMap; +import android.util.LocalLog; +import android.util.Log; + +import androidx.annotation.NonNull; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.net.module.util.InterfaceParams; + +import java.util.Objects; + +/** + * Tracks and manages raw packet captures on a network interface. + * + * <p>This class is not a thread-safe and should be only run on the handler thread. + * It utilizes a dedicated {@link HandlerThread} to perform capture operations, allowing + * the caller to interact with it asynchronously through methods like + * {@link #startCapture(String, long)}, {@link #stopCapture(String)}, + * and {@link #getMatchedPacketCount(String, String)}.</p> + * + */ +public class RawPacketTracker { + /** + * Dependencies class for testing. + */ + @VisibleForTesting(visibility = PRIVATE) + static class Dependencies { + public @NonNull ConnectivityPacketTracker createPacketTracker( + Handler handler, InterfaceParams ifParams, int maxPktRecords) { + return new ConnectivityPacketTracker( + handler, ifParams, new LocalLog(maxPktRecords)); + } + + public @NonNull HandlerThread createHandlerThread() { + final HandlerThread handlerThread = new HandlerThread(TAG + "-handler"); + handlerThread.start(); + return handlerThread; + } + + public @NonNull Looper getLooper(HandlerThread handlerThread) { + return handlerThread.getLooper(); + } + } + + // Maximum number of packet records to store. + private static final int MAX_PACKET_RECORDS = 100; + // Maximum duration for a packet capture session in milliseconds. + public static final long MAX_CAPTURE_TIME_MS = 300_000; + @VisibleForTesting(visibility = PRIVATE) + public static final int CMD_STOP_CAPTURE = 1; + private static final String TAG = RawPacketTracker.class.getSimpleName(); + + private final @NonNull HandlerThread mHandlerThread; + private final @NonNull Dependencies mDeps; + private final @NonNull Handler mHandler; + + /** + * A map that stores ConnectivityPacketTracker objects, keyed by their associated + * network interface name, e.g: wlan0. This allows for tracking connectivity + * packets on a per-interface basis. This is only accessed by handler thread. + */ + private final ArrayMap<String, ConnectivityPacketTracker> mTrackerMap = new ArrayMap<>(); + + public RawPacketTracker() { + this(new Dependencies()); + } + + @VisibleForTesting(visibility = PRIVATE) + public RawPacketTracker( + @NonNull Dependencies deps + ) { + mDeps = deps; + mHandlerThread = deps.createHandlerThread(); + mHandler = new RawPacketTrackerHandler(deps.getLooper(mHandlerThread), this); + } + + private static class RawPacketTrackerHandler extends Handler { + private final RawPacketTracker mRawPacketTracker; + private RawPacketTrackerHandler( + @NonNull Looper looper, + @NonNull RawPacketTracker rawPacketTracker) { + super(looper); + mRawPacketTracker = rawPacketTracker; + } + + @Override + public void handleMessage(Message msg) { + final String ifaceName; + switch (msg.what) { + case CMD_STOP_CAPTURE: + ifaceName = (String) msg.obj; + mRawPacketTracker.processStopCapture(ifaceName); + break; + default: + Log.e(TAG, "unrecognized message: " + msg.what); + } + } + } + + /** + * Starts capturing packets on the specified network interface. + * + * <p>Initiates a packet capture session if one is not already running for the given interface. + * A capture timeout is set to automatically stop the capture after {@code maxCaptureTimeMs} + * milliseconds. If a previous stop capture event was scheduled, it is canceled.</p> + * + * @param ifaceName The name of the network interface to capture packets on. + * @param maxCaptureTimeMs The maximum capture duration in milliseconds. + * @throws IllegalArgumentException If {@code maxCaptureTimeMs} is less than or equal to 0. + * @throws RuntimeException If a capture is already running on the specified interface. + * @throws IllegalStateException If this method is not running on handler thread + */ + public void startCapture( + String ifaceName, long maxCaptureTimeMs + ) throws IllegalArgumentException, RuntimeException, IllegalStateException { + ensureRunOnHandlerThread(); + if (maxCaptureTimeMs <= 0) { + throw new IllegalArgumentException("maxCaptureTimeMs " + maxCaptureTimeMs + " <= 0"); + } + + if (mTrackerMap.containsKey(ifaceName)) { + throw new RuntimeException(ifaceName + " is already capturing"); + } + + final InterfaceParams ifParams = InterfaceParams.getByName(ifaceName); + Objects.requireNonNull(ifParams, "invalid interface " + ifaceName); + + final ConnectivityPacketTracker tracker = + mDeps.createPacketTracker(mHandler, ifParams, MAX_PACKET_RECORDS); + tracker.start(TAG + "." + ifaceName); + mTrackerMap.putIfAbsent(ifaceName, tracker); + tracker.setCapture(true); + + // remove scheduled stop events if it already in the queue + mHandler.removeMessages(CMD_STOP_CAPTURE, ifaceName); + + // capture up to configured capture time and stop capturing + final Message stopMsg = mHandler.obtainMessage(CMD_STOP_CAPTURE, ifaceName); + mHandler.sendMessageDelayed(stopMsg, maxCaptureTimeMs); + } + + /** + * Stops capturing packets on the specified network interface. + * + * <p>Terminates the packet capture session if one is active for the given interface. + * Any pending stop capture events for the interface are canceled.</p> + * + * @param ifaceName The name of the network interface to stop capturing on. + * @throws RuntimeException If no capture is running on the specified interface. + * @throws IllegalStateException If this method is not running on handler thread + */ + public void stopCapture(String ifaceName) throws RuntimeException, IllegalStateException { + ensureRunOnHandlerThread(); + if (!mTrackerMap.containsKey(ifaceName)) { + throw new RuntimeException(ifaceName + " is already stopped"); + } + + final Message msg = mHandler.obtainMessage(CMD_STOP_CAPTURE, ifaceName); + // remove scheduled stop events if it already in the queue + mHandler.removeMessages(CMD_STOP_CAPTURE, ifaceName); + mHandler.sendMessage(msg); + } + + /** + * Returns the {@link Handler} associated with this RawTracker. + * + * <p>This handler is used for posting tasks to the RawTracker's internal thread. + * You can use it to execute code that needs to interact with the RawTracker + * in a thread-safe manner. + * + * @return The non-null {@link Handler} instance. + */ + public @NonNull Handler getHandler() { + return mHandler; + } + + /** + * Retrieves the number of captured packets matching a specific pattern. + * + * <p>Queries the packet capture data for the specified interface and counts the occurrences + * of packets that match the provided {@code packet} string. The count is performed + * asynchronously on the capture thread.</p> + * + * @param ifaceName The name of the network interface. + * @param packetPattern The packet pattern to match. + * @return The number of matched packets, or 0 if an error occurs or no matching packets are + * found. + * @throws RuntimeException If no capture is running on the specified interface. + * @throws IllegalStateException If this method is not running on handler thread + */ + public int getMatchedPacketCount( + String ifaceName, String packetPattern + ) throws RuntimeException, IllegalStateException { + ensureRunOnHandlerThread(); + final ConnectivityPacketTracker tracker; + tracker = mTrackerMap.getOrDefault(ifaceName, null); + if (tracker == null) { + throw new RuntimeException(ifaceName + " is not capturing"); + } + + return tracker.getMatchedPacketCount(packetPattern); + } + + private void processStopCapture(String ifaceName) { + final ConnectivityPacketTracker tracker = mTrackerMap.get(ifaceName); + mTrackerMap.remove(ifaceName); + tracker.setCapture(false); + } + + private void ensureRunOnHandlerThread() { + if (mHandler.getLooper() != Looper.myLooper()) { + throw new IllegalStateException( + "Not running on Handler thread: " + Thread.currentThread().getName() + ); + } + } +}
diff --git a/src/android/net/util/RawSocketUtils.java b/src/android/net/util/RawSocketUtils.java index a6c8a40..5823dc4 100644 --- a/src/android/net/util/RawSocketUtils.java +++ b/src/android/net/util/RawSocketUtils.java
@@ -92,9 +92,9 @@ } @RequiresPermission(NETWORK_SETTINGS) - private static void enforceTetheredInterface(@NonNull Context context, + public static void enforceTetheredInterface(@NonNull Context context, @NonNull String interfaceName) - throws ExecutionException, InterruptedException, TimeoutException { + throws ExecutionException, InterruptedException, TimeoutException, SecurityException { final TetheringManager tm = context.getSystemService(TetheringManager.class); final CompletableFuture<List<String>> tetheredInterfaces = new CompletableFuture<>(); final TetheringManager.TetheringEventCallback callback =
diff --git a/src/com/android/server/NetworkStackService.java b/src/com/android/server/NetworkStackService.java index 4d10c3b..686a399 100644 --- a/src/com/android/server/NetworkStackService.java +++ b/src/com/android/server/NetworkStackService.java
@@ -19,6 +19,7 @@ import static android.net.dhcp.IDhcpServer.STATUS_INVALID_ARGUMENT; import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; import static android.net.dhcp.IDhcpServer.STATUS_UNKNOWN_ERROR; +import static android.net.util.RawPacketTracker.MAX_CAPTURE_TIME_MS; import static android.net.util.RawSocketUtils.sendRawPacketDownStream; import static com.android.net.module.util.DeviceConfigUtils.getResBooleanConfig; @@ -50,7 +51,8 @@ import android.net.ip.IpClient; import android.net.networkstack.aidl.NetworkMonitorParameters; import android.net.shared.PrivateDnsConfig; -import android.os.Build; +import android.net.util.RawPacketTracker; +import android.net.util.RawSocketUtils; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; @@ -67,10 +69,10 @@ import com.android.internal.util.IndentingPrintWriter; import com.android.modules.utils.BasicShellCommandHandler; import com.android.net.module.util.DeviceConfigUtils; +import com.android.net.module.util.HandlerUtils; import com.android.net.module.util.SharedLog; import com.android.networkstack.NetworkStackNotifier; import com.android.networkstack.R; -import com.android.networkstack.apishim.common.ShimUtils; import com.android.networkstack.ipmemorystore.IpMemoryStoreService; import com.android.server.connectivity.NetworkMonitor; import com.android.server.util.PermissionUtil; @@ -90,6 +92,8 @@ import java.util.Objects; import java.util.SortedSet; import java.util.TreeSet; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; /** * Android service used to start the network stack when bound to via an intent. @@ -99,6 +103,7 @@ public class NetworkStackService extends Service { private static final String TAG = NetworkStackService.class.getSimpleName(); private static NetworkStackConnector sConnector; + private static final RawPacketTracker sRawPacketTracker = new RawPacketTracker(); /** * Create a binder connector for the system server to communicate with the network stack. @@ -201,7 +206,6 @@ @GuardedBy("mIpClients") private final ArrayList<WeakReference<IpClient>> mIpClients = new ArrayList<>(); private final IpMemoryStoreService mIpMemoryStoreService; - @Nullable private final NetworkStackNotifier mNotifier; private static final int MAX_VALIDATION_LOGS = 10; @@ -296,15 +300,10 @@ mNetd = INetd.Stub.asInterface( (IBinder) context.getSystemService(Context.NETD_SERVICE)); mIpMemoryStoreService = mDeps.makeIpMemoryStoreService(context); - // NetworkStackNotifier only shows notifications relevant for API level > Q - if (ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q)) { - final HandlerThread notifierThread = new HandlerThread( - NetworkStackNotifier.class.getSimpleName()); - notifierThread.start(); - mNotifier = mDeps.makeNotifier(context, notifierThread.getLooper()); - } else { - mNotifier = null; - } + final HandlerThread notifierThread = new HandlerThread( + NetworkStackNotifier.class.getSimpleName()); + notifierThread.start(); + mNotifier = mDeps.makeNotifier(context, notifierThread.getLooper()); int netdVersion; String netdHash; @@ -523,6 +522,8 @@ } private class ShellCmd extends BasicShellCommandHandler { + private static final long MAX_CAPTURE_CMD_WAITING_TIMEOUT_MS = 30_000L; + @Override public int onCommand(String cmd) { if (cmd == null) { @@ -567,6 +568,14 @@ } return 0; } + case "capture": + // Usage: cmd network_stack capture <cmd> + HandlerUtils.runWithScissorsForDump( + sRawPacketTracker.getHandler(), + () -> captureShellCommand(mContext, peekRemainingArgs()), + MAX_CAPTURE_CMD_WAITING_TIMEOUT_MS + ); + return 0; case "apf": // Usage: cmd network_stack apf <iface> <cmd> final String iface = getNextArg(); @@ -609,6 +618,18 @@ pw.println(" to tethering downstream for security considerations."); pw.println(" <packet_in_hex>: A valid hexadecimal representation of "); pw.println(" a packet starting from L2 header."); + pw.println(" capture <cmd>"); + pw.println(" APF utility commands for multi-devices tests."); + pw.println(" start <interface>"); + pw.println(" start capture packets in the received buffer."); + pw.println(" The capture is up to 300 sec, then it will stop."); + pw.println(" <interface>: Target interface name, note that this is limited"); + pw.println(" to tethering downstream for security considerations."); + pw.println(" stop <interface>"); + pw.println(" stop capture packets and clear the received buffer."); + pw.println(" matched-packet-counts <interface> <pkt-hex-string>"); + pw.println(" the <pkt-hex-string> starts from ether header."); + pw.println(" Expect to do full packet match."); pw.println(" apf <iface> <cmd>"); pw.println(" APF utility commands for integration tests."); pw.println(" <iface>: the network interface the provided command operates on."); @@ -628,17 +649,79 @@ pw.println(" read"); pw.println(" reads and returns the current state of APF memory."); } + + private void captureShellCommand( + @NonNull Context context, + @NonNull String[] args + ) { + if (args.length < 2) { + throw new IllegalArgumentException("Incorrect number of arguments"); + } + + final String cmd = args[0]; + final String ifaceName = args[1]; + try { + RawSocketUtils.enforceTetheredInterface(context, ifaceName); + } catch (ExecutionException + | InterruptedException + | TimeoutException + | SecurityException e) { + throw new RuntimeException(e.getMessage()); + } + + final PrintWriter pw = getOutPrintWriter(); + switch(cmd) { + case "start": + // Usage : cmd network_stack capture start <interface> + if (args.length != 2) { + throw new IllegalArgumentException("Incorrect number of arguments"); + } + + sRawPacketTracker.startCapture(ifaceName, MAX_CAPTURE_TIME_MS); + pw.println("success"); + break; + case "matched-packet-counts": + // Usage : cmd network_stack capture matched-packet-counts + // <interface> <packet-in-hex> + // for example, there is an usage to get matched arp reply packet count + // in hex string format on the wlan0 interface + // cmd network_stack capture matched-packet-counts wlan0 \ + // "00010203040501020304050608060001080006040002010203040506c0a80101" + + // "000102030405c0a80102" + if (args.length != 3) { + throw new IllegalArgumentException("Incorrect number of arguments"); + } + + final String packetInHex = args[2]; + + // limit the input hex string up to 3000 (1500 bytes) + if (packetInHex.length() > 3000) { + throw new IllegalArgumentException("Packet Hex String over the limit"); + } + + final int pktCnt = + sRawPacketTracker.getMatchedPacketCount(ifaceName, packetInHex); + pw.println(pktCnt); + break; + case "stop": + // Usage : cmd network_stack capture stop <interface> + if (args.length != 2) { + throw new IllegalArgumentException("Incorrect number of arguments"); + } + + sRawPacketTracker.stopCapture(ifaceName); + pw.println("success"); + break; + default: + throw new IllegalArgumentException("Invalid apf command: " + cmd); + } + } } /** * Dump version information of the module and detected system version. */ private void dumpVersion(@NonNull PrintWriter fout) { - if (!ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q)) { - dumpVersionNumberOnly(fout); - return; - } - fout.println("LocalInterface:" + this.VERSION + ":" + this.HASH); synchronized (mAidlVersions) { // Sort versions for deterministic order in output
diff --git a/src/com/android/server/connectivity/NetworkMonitor.java b/src/com/android/server/connectivity/NetworkMonitor.java index 60bb927..659e911 100755 --- a/src/com/android/server/connectivity/NetworkMonitor.java +++ b/src/com/android/server/connectivity/NetworkMonitor.java
@@ -125,7 +125,6 @@ import android.net.util.Stopwatch; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; -import android.os.Build; import android.os.Bundle; import android.os.CancellationSignal; import android.os.Message; @@ -177,7 +176,6 @@ import com.android.networkstack.apishim.common.NetworkAgentConfigShim; import com.android.networkstack.apishim.common.NetworkInformationShim; import com.android.networkstack.apishim.common.ShimUtils; -import com.android.networkstack.apishim.common.UnsupportedApiLevelException; import com.android.networkstack.metrics.DataStallDetectionStats; import com.android.networkstack.metrics.DataStallStatsUtils; import com.android.networkstack.metrics.NetworkValidationMetrics; @@ -477,7 +475,6 @@ private final TelephonyManager mTelephonyManager; private final WifiManager mWifiManager; private final ConnectivityManager mCm; - @Nullable private final NetworkStackNotifier mNotifier; private final IpConnectivityLog mMetricsLog; private final Dependencies mDependencies; @@ -611,13 +608,6 @@ } catch (RemoteException e) { version = 0; } - // The AIDL was freezed from Q beta 5 but it's unfreezing from R before releasing. In order - // to distinguish the behavior between R and Q beta 5 and before Q beta 5, add SDK and - // CODENAME check here. Basically, it's only expected to return 0 for Q beta 4 and below - // because the test result has changed. - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q - && Build.VERSION.CODENAME.equals("REL") - && version == Build.VERSION_CODES.CUR_DEVELOPMENT) version = 0; return version; } @@ -1447,7 +1437,7 @@ final CaptivePortalProbeResult probeRes = mLastPortalProbeResult; // Use redirect URL from AP if exists. final String portalUrl = - (useRedirectUrlForPortal() && makeURL(probeRes.redirectUrl) != null) + (makeURL(probeRes.redirectUrl) != null) ? probeRes.redirectUrl : probeRes.detectUrl; appExtras.putString(EXTRA_CAPTIVE_PORTAL_URL, portalUrl); if (probeRes.probeSpec != null) { @@ -1456,9 +1446,7 @@ } appExtras.putString(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_USER_AGENT, mCaptivePortalUserAgent); - if (mNotifier != null) { - mNotifier.notifyCaptivePortalValidationPending(network); - } + mNotifier.notifyCaptivePortalValidationPending(network); mCm.startCaptivePortalApp(network, appExtras); return HANDLED; default: @@ -1466,12 +1454,6 @@ } } - private boolean useRedirectUrlForPortal() { - // It must match the conditions in CaptivePortalLogin in which the redirect URL is not - // used to validate that the portal is gone. - return ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q); - } - @Override public void exit() { if (mLaunchCaptivePortalAppBroadcastReceiver != null) { @@ -3359,11 +3341,6 @@ } catch (JSONException e) { validationLog("Could not parse capport API JSON: " + e.getMessage()); return null; - } catch (UnsupportedApiLevelException e) { - // This should never happen because LinkProperties would not have a capport URL - // before R. - validationLog("Platform API too low to support capport API"); - return null; } }
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp index 65a94f3..d728c6b 100644 --- a/tests/integration/Android.bp +++ b/tests/integration/Android.bp
@@ -51,9 +51,9 @@ "testables", ], libs: [ - "android.test.runner", - "android.test.base", - "android.test.mock", + "android.test.runner.stubs", + "android.test.base.stubs", + "android.test.mock.stubs", ], visibility: ["//visibility:private"], }
diff --git a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java index f7e1c4d..ee23c99 100644 --- a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java +++ b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
@@ -36,14 +36,12 @@ import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST; import static android.net.dhcp.DhcpPacket.INFINITE_LEASE; import static android.net.dhcp.DhcpPacket.MIN_V6ONLY_WAIT_MS; -import static android.net.dhcp6.Dhcp6Packet.PrefixDelegation; import static android.net.ip.IIpClientCallbacks.DTIM_MULTIPLIER_RESET; import static android.net.ip.IpClient.CONFIG_IPV6_AUTOCONF_TIMEOUT; import static android.net.ip.IpClient.CONFIG_ACCEPT_RA_MIN_LFT; import static android.net.ip.IpClient.CONFIG_APF_COUNTER_POLLING_INTERVAL_SECS; import static android.net.ip.IpClient.CONFIG_NUD_FAILURE_COUNT_DAILY_THRESHOLD; import static android.net.ip.IpClient.CONFIG_NUD_FAILURE_COUNT_WEEKLY_THRESHOLD; -import static android.net.ip.IpClient.DEFAULT_ACCEPT_RA_MIN_LFT; import static android.net.ip.IpClient.DEFAULT_APF_COUNTER_POLLING_INTERVAL_SECS; import static android.net.ip.IpClient.DEFAULT_NUD_FAILURE_COUNT_DAILY_THRESHOLD; import static android.net.ip.IpClient.DEFAULT_NUD_FAILURE_COUNT_WEEKLY_THRESHOLD; @@ -128,6 +126,7 @@ import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.app.AlarmManager; @@ -194,7 +193,6 @@ import android.os.PowerManager; import android.os.RemoteException; import android.os.SystemClock; -import android.os.SystemProperties; import android.provider.Settings; import android.stats.connectivity.NudEventType; import android.system.ErrnoException; @@ -228,8 +226,6 @@ import com.android.net.module.util.structs.RdnssOption; import com.android.networkstack.R; import com.android.networkstack.apishim.CaptivePortalDataShimImpl; -import com.android.networkstack.apishim.ConstantsShim; -import com.android.networkstack.apishim.common.ShimUtils; import com.android.networkstack.ipmemorystore.IpMemoryStoreService; import com.android.networkstack.metrics.IpProvisioningMetrics; import com.android.networkstack.metrics.IpReachabilityMonitorMetrics; @@ -242,7 +238,7 @@ import com.android.testutils.DevSdkIgnoreRule; import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; import com.android.testutils.HandlerUtils; -import com.android.testutils.TapPacketReader; +import com.android.testutils.PollPacketReader; import com.android.testutils.TestableNetworkAgent; import com.android.testutils.TestableNetworkCallback; @@ -392,7 +388,7 @@ private String mIfaceName; private HandlerThread mPacketReaderThread; private Handler mHandler; - private TapPacketReader mPacketReader; + private PollPacketReader mPacketReader; private FileDescriptor mTapFd; private byte[] mClientMac; private InetAddress mClientIpAddress; @@ -851,7 +847,6 @@ return null; }).when(mIpMemoryStore).retrieveNetworkEventCount(eq(TEST_CLUSTER), any(), any(), any()); - setDeviceConfigProperty(IpClient.CONFIG_MIN_RDNSS_LIFETIME, 67); setDeviceConfigProperty(DhcpClient.DHCP_RESTART_CONFIG_DELAY, 10); setDeviceConfigProperty(DhcpClient.ARP_FIRST_PROBE_DELAY_MS, 10); setDeviceConfigProperty(DhcpClient.ARP_PROBE_MIN_MS, 10); @@ -869,7 +864,7 @@ // Set the minimal RA lifetime value, any RA section with liftime below this value will be // ignored. - setDeviceConfigProperty(CONFIG_ACCEPT_RA_MIN_LFT, DEFAULT_ACCEPT_RA_MIN_LFT); + setDeviceConfigProperty(CONFIG_ACCEPT_RA_MIN_LFT, 67); // Set the polling interval to update APF data snapshot. setDeviceConfigProperty(CONFIG_APF_COUNTER_POLLING_INTERVAL_SECS, @@ -929,7 +924,7 @@ // go out of scope. mTapFd = new FileDescriptor(); mTapFd.setInt$(iface.getFileDescriptor().detachFd()); - mPacketReader = new TapPacketReader(mHandler, mTapFd, DATA_BUFFER_LEN); + mPacketReader = new PollPacketReader(mHandler, mTapFd, DATA_BUFFER_LEN); mHandler.post(() -> mPacketReader.start()); } @@ -1300,23 +1295,13 @@ final List<DhcpPacket> packetList) throws Exception { for (DhcpPacket packet : packetList) { if (!expectSendHostname || hostname == null) { - assertNoHostname(packet.getHostname()); + assertNull(packet.getHostname()); } else { assertEquals(hostnameAfterTransliteration, packet.getHostname()); } } } - private void assertNoHostname(String hostname) { - if (ShimUtils.isAtLeastR()) { - assertNull(hostname); - } else { - // Until Q, if no hostname is set, the device falls back to the hostname set via - // system property, to avoid breaking Q devices already launched with that setup. - assertEquals(SystemProperties.get("net.hostname"), hostname); - } - } - // Helper method to complete DHCP 2-way or 4-way handshake private List<DhcpPacket> performDhcpHandshake(final boolean isSuccessLease, final Integer leaseTimeSec, final boolean shouldReplyRapidCommitAck, final int mtu, @@ -1767,7 +1752,7 @@ assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU); } - @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + @Test public void testRollbackFromRapidCommitOption() throws Exception { startIpClientProvisioning(true /* isDhcpRapidCommitEnabled */, false /* isPreConnectionEnabled */, @@ -1853,10 +1838,8 @@ assertTrue(packet instanceof DhcpDiscoverPacket); } - @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + @Test public void testDhcpServerInLinkProperties() throws Exception { - assumeTrue(ConstantsShim.VERSION > Build.VERSION_CODES.Q); - performDhcpHandshake(); ArgumentCaptor<LinkProperties> captor = ArgumentCaptor.forClass(LinkProperties.class); verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningSuccess(captor.capture()); @@ -2264,13 +2247,13 @@ LinkProperties lp = doIpv6OnlyProvisioning(inOrder, ra); - // Expect that DNS servers with lifetimes below CONFIG_MIN_RDNSS_LIFETIME are not accepted. + // Expect that DNS servers with lifetimes below CONFIG_ACCEPT_RA_MIN_LFT are not accepted. assertNotNull(lp); assertEquals(1, lp.getDnsServers().size()); assertTrue(lp.getDnsServers().contains(InetAddress.getByName(dnsServer))); // If the RDNSS lifetime is above the minimum, the DNS server is accepted. - rdnss1 = buildRdnssOption(68, lowlifeDnsServer); + rdnss1 = buildRdnssOption(67, lowlifeDnsServer); ra = buildRaPacket(pio, rdnss1, rdnss2); mPacketReader.sendResponse(ra); inOrder.verify(mCb, timeout(TEST_TIMEOUT_MS)).onLinkPropertiesChange(captor.capture()); @@ -2335,11 +2318,9 @@ } - @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required") public void testPref64Option() throws Exception { - assumeTrue(ConstantsShim.VERSION > Build.VERSION_CODES.Q); - ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() .withoutIpReachabilityMonitor() .withoutIPv4() @@ -2812,8 +2793,6 @@ argThat(lp -> lp.getMtu() == testMtu)); // Ensure that the URL was set as expected in the callbacks. - // Can't verify the URL up to Q as there is no such attribute in LinkProperties. - if (!ShimUtils.isAtLeastR()) return null; verify(mCb, atLeastOnce()).onLinkPropertiesChange(captor.capture()); final LinkProperties expectedLp = captor.getAllValues().stream().findFirst().get(); assertNotNull(expectedLp); @@ -4110,6 +4089,7 @@ } @Test + @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = false) public void testIpReachabilityMonitor_probeFailed() throws Exception { runIpReachabilityMonitorProbeFailedTest(); assertNotifyNeighborLost(ROUTER_LINK_LOCAL /* targetIp */, @@ -4158,6 +4138,7 @@ } @Test + @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = false) public void testIpReachabilityMonitor_mcastResolicitProbeFailed() throws Exception { runIpReachabilityMonitorMcastResolicitProbeFailedTest(); assertNotifyNeighborLost(ROUTER_LINK_LOCAL /* targetIp */, @@ -4300,6 +4281,7 @@ } @Test + @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = false) @Flag(name = IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION, enabled = false) public void testIpReachabilityMonitor_ignoreIpv4DefaultRouterOrganicNudFailure_flagoff() throws Exception { @@ -4362,21 +4344,24 @@ final Inet6Address dnsServerIp = ipv6Addr(dnsServer); final LinkProperties lp = performDualStackProvisioning(ra, dnsServerIp); runAsShell(MANAGE_TEST_NETWORKS, () -> createTestNetworkAgentAndRegister(lp)); + } - // Send a UDP packet to IPv6 DNS server to trigger address resolution process for IPv6 - // on-link DNS server or default router(if the target is default router, we should pass - // in an IPv6 off-link DNS server such as 2001:db8:4860:4860::64). + /** + * Send a UDP packet to dstIp to trigger address resolution for targetIp, and possibly expect a + * neighbor lost callback. + * If dstIp is on-link, then dstIp and targetIp should be the same. + * If dstIp is off-link, then targetIp should be the IPv6 default router. + * The ND cache should not have an entry for targetIp. + */ + private void sendPacketToUnreachableNeighbor(Inet6Address dstIp) throws Exception { final Random random = new Random(); final byte[] data = new byte[100]; random.nextBytes(data); - sendUdpPacketToNetwork(mNetworkAgent.getNetwork(), dnsServerIp, 1234 /* port */, data); + sendUdpPacketToNetwork(mNetworkAgent.getNetwork(), dstIp, 1234 /* port */, data); } - private void runIpReachabilityMonitorAddressResolutionTest(final String dnsServer, - final Inet6Address targetIp, - final boolean expectNeighborLost) throws Exception { - prepareIpReachabilityMonitorAddressResolutionTest(dnsServer, targetIp); - + private void expectAndDropMulticastNses(Inet6Address targetIp, boolean expectNeighborLost) + throws Exception { // Wait for the multicast NSes but never respond to them, that results in the on-link // DNS gets lost and onReachabilityLost callback will be invoked. final List<NeighborSolicitation> nsList = new ArrayList<NeighborSolicitation>(); @@ -4400,6 +4385,14 @@ } } + private void runIpReachabilityMonitorAddressResolutionTest(final String dnsServer, + final Inet6Address targetIp, + final boolean expectNeighborLost) throws Exception { + prepareIpReachabilityMonitorAddressResolutionTest(dnsServer, targetIp); + sendPacketToUnreachableNeighbor(ipv6Addr(dnsServer)); + expectAndDropMulticastNses(targetIp, expectNeighborLost); + } + @Test @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION, enabled = true) @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION, enabled = false) @@ -4414,6 +4407,7 @@ @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION, enabled = false) @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION, enabled = false) @Flag(name = IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION, enabled = false) + @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = false) public void testIpReachabilityMonitor_incompleteIpv6DnsServerInDualStack_flagoff() throws Exception { final Inet6Address targetIp = ipv6Addr(IPV6_ON_LINK_DNS_SERVER); @@ -4436,6 +4430,7 @@ @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION, enabled = false) @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION, enabled = false) @Flag(name = IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION, enabled = false) + @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = false) public void testIpReachabilityMonitor_incompleteIpv6DefaultRouterInDualStack_flagoff() throws Exception { runIpReachabilityMonitorAddressResolutionTest(IPV6_OFF_LINK_DNS_SERVER, @@ -4458,6 +4453,7 @@ @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION, enabled = false) @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION, enabled = false) @Flag(name = IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION, enabled = false) + @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = false) public void testIpReachabilityMonitor_ignoreOnLinkIpv6DnsOrganicNudFailure_flagoff() throws Exception { final Inet6Address targetIp = ipv6Addr(IPV6_ON_LINK_DNS_SERVER); @@ -4480,6 +4476,7 @@ @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION, enabled = false) @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION, enabled = false) @Flag(name = IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION, enabled = false) + @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = false) public void testIpReachabilityMonitor_ignoreIpv6DefaultRouterOrganicNudFailure_flagoff() throws Exception { runIpReachabilityMonitorAddressResolutionTest(IPV6_OFF_LINK_DNS_SERVER, @@ -4490,6 +4487,7 @@ private void runIpReachabilityMonitorEverReachableIpv6NeighborTest(final String dnsServer, final Inet6Address targetIp) throws Exception { prepareIpReachabilityMonitorAddressResolutionTest(dnsServer, targetIp); + sendPacketToUnreachableNeighbor(ipv6Addr(dnsServer)); // Simulate the default router/DNS was reachable by responding to multicast NS(not for DAD). NeighborSolicitation ns; @@ -6049,6 +6047,7 @@ } @Test + @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = false) @Flag(name = IP_REACHABILITY_IGNORE_NUD_FAILURE_VERSION, enabled = false) @SignatureRequiredTest(reason = "need to delete cluster from real db in tearDown") public void testIgnoreNudFailuresIfTooManyInPastDay_flagOff() throws Exception { @@ -6063,6 +6062,7 @@ } @Test + @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = false) @Flag(name = IP_REACHABILITY_IGNORE_NUD_FAILURE_VERSION, enabled = true) @SignatureRequiredTest(reason = "need to delete cluster from real db in tearDown") public void testIgnoreNudFailuresIfTooManyInPastDay_notUpToThreshold() @@ -6096,6 +6096,7 @@ } @Test + @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = false) @Flag(name = IP_REACHABILITY_IGNORE_NUD_FAILURE_VERSION, enabled = false) @SignatureRequiredTest(reason = "need to delete cluster from real db in tearDown") public void testIgnoreNudFailuresIfTooManyInPastWeek_flagOff() throws Exception { @@ -6148,6 +6149,41 @@ } @Test + @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION, enabled = false) + @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION, enabled = false) + @Flag(name = IP_REACHABILITY_IGNORE_NUD_FAILURE_VERSION, enabled = true) + @SignatureRequiredTest(reason = "need to delete cluster from real db in tearDown") + public void testIgnoreNudFailuresStopWritingEvents() throws Exception { + // Add enough failures that NUD failures are ignored. + long when = (long) (System.currentTimeMillis() - SIX_HOURS_IN_MS * 1.1); + long expiry = when + ONE_WEEK_IN_MS; + storeNudFailureEvents(when, expiry, 10, IIpMemoryStore.NETWORK_EVENT_NUD_FAILURE_ORGANIC); + + // Add enough recent failures to almost, but not quite reach the 6-hour threshold. + when = (long) (System.currentTimeMillis() - SIX_HOURS_IN_MS * 0.1); + expiry = when + ONE_WEEK_IN_MS; + storeNudFailureEvents(when, expiry, 9, IIpMemoryStore.NETWORK_EVENT_NUD_FAILURE_ORGANIC); + + prepareIpReachabilityMonitorAddressResolutionTest(IPV6_ON_LINK_DNS_SERVER, + ROUTER_LINK_LOCAL); + + // The first new failure is ignored and written to the database. + // The total is 10 failures in the last 6 hours. + sendPacketToUnreachableNeighbor(ipv6Addr(IPV6_OFF_LINK_DNS_SERVER)); + expectAndDropMulticastNses(ROUTER_LINK_LOCAL, false /* expectNeighborLost */); + verify(mIpMemoryStore).storeNetworkEvent(any(), anyLong(), anyLong(), + eq(IIpMemoryStore.NETWORK_EVENT_NUD_FAILURE_ORGANIC), any()); + + // The second new failure is ignored, but not written. + reset(mIpMemoryStore); + sendPacketToUnreachableNeighbor(ipv6Addr(IPV6_ON_LINK_DNS_SERVER)); + expectAndDropMulticastNses(ipv6Addr(IPV6_ON_LINK_DNS_SERVER), + false /* expectNeighborLost */); + verifyNoMoreInteractions(mIpMemoryStore); + } + + @Test + @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = false) @Flag(name = IP_REACHABILITY_IGNORE_NUD_FAILURE_VERSION, enabled = false) @SignatureRequiredTest(reason = "need to delete cluster from real db in tearDown") public void testIgnoreNudFailuresIfTooManyInPastWeek_stopWritingEvent_flagOff() @@ -6164,6 +6200,7 @@ } @Test + @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = false) @Flag(name = IP_REACHABILITY_IGNORE_NUD_FAILURE_VERSION, enabled = true) @SignatureRequiredTest(reason = "need to delete cluster from real db in tearDown") public void testIgnoreNudFailuresIfTooManyInPastWeek_stopWritingEvent_notUpToThreshold()
diff --git a/tests/integration/signature/android/net/netlink/InetDiagSocketIntegrationTest.java b/tests/integration/signature/android/net/netlink/InetDiagSocketIntegrationTest.java index 0329fab..eb7b123 100644 --- a/tests/integration/signature/android/net/netlink/InetDiagSocketIntegrationTest.java +++ b/tests/integration/signature/android/net/netlink/InetDiagSocketIntegrationTest.java
@@ -181,8 +181,6 @@ @Test public void testGetConnectionOwnerUid() throws Exception { - // Skip the test for API <= Q, as b/141603906 this was only fixed in Q-QPR2 - assumeTrue(ShimUtils.isAtLeastR()); checkGetConnectionOwnerUid("::", null); checkGetConnectionOwnerUid("::", "::"); checkGetConnectionOwnerUid("0.0.0.0", null); @@ -196,8 +194,6 @@ /* Verify fix for b/141603906 */ @Test public void testB141603906() throws Exception { - // Skip the test for API <= Q, as b/141603906 this was only fixed in Q-QPR2 - assumeTrue(ShimUtils.isAtLeastR()); final InetSocketAddress src = new InetSocketAddress(0); final InetSocketAddress dst = new InetSocketAddress(0); final int numThreads = 8;
diff --git a/tests/integration/signature/android/net/util/NetworkStackUtilsIntegrationTest.kt b/tests/integration/signature/android/net/util/NetworkStackUtilsIntegrationTest.kt index f5c06a1..29e6237 100644 --- a/tests/integration/signature/android/net/util/NetworkStackUtilsIntegrationTest.kt +++ b/tests/integration/signature/android/net/util/NetworkStackUtilsIntegrationTest.kt
@@ -58,7 +58,7 @@ import com.android.networkstack.util.NetworkStackUtils import com.android.testutils.ArpRequestFilter import com.android.testutils.IPv4UdpFilter -import com.android.testutils.TapPacketReader +import com.android.testutils.PollPacketReader import java.io.FileDescriptor import java.net.Inet4Address import java.net.Inet6Address @@ -94,7 +94,7 @@ private val readerHandler = HandlerThread( NetworkStackUtilsIntegrationTest::class.java.simpleName) private lateinit var iface: TestNetworkInterface - private lateinit var reader: TapPacketReader + private lateinit var reader: PollPacketReader @Before fun setUp() { @@ -106,7 +106,7 @@ inst.uiAutomation.dropShellPermissionIdentity() } readerHandler.start() - reader = TapPacketReader(readerHandler.threadHandler, iface.fileDescriptor.fileDescriptor, + reader = PollPacketReader(readerHandler.threadHandler, iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */) readerHandler.threadHandler.post { reader.start() } }
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp index e26ea28..7e6de1a 100644 --- a/tests/unit/Android.bp +++ b/tests/unit/Android.bp
@@ -36,9 +36,9 @@ "testables", ], libs: [ - "android.test.runner", - "android.test.base", - "android.test.mock", + "android.test.runner.stubs", + "android.test.base.stubs", + "android.test.mock.stubs", ], defaults: [ "framework-connectivity-test-defaults",
diff --git a/tests/unit/src/android/net/apf/ApfTest.java b/tests/unit/src/android/net/apf/ApfTest.java index 14e2122..9a4a224 100644 --- a/tests/unit/src/android/net/apf/ApfTest.java +++ b/tests/unit/src/android/net/apf/ApfTest.java
@@ -2355,7 +2355,7 @@ mCurrentTimeMs += timePassedSeconds * DateUtils.SECOND_IN_MILLIS; doReturn(mCurrentTimeMs).when(mDependencies).elapsedRealtime(); synchronized (apfFilter) { - apfFilter.installNewProgramLocked(); + apfFilter.installNewProgram(); } byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); verifyRaLifetime(program, basePacket, routerLifetime, timePassedSeconds); @@ -2365,7 +2365,7 @@ ((routerLifetime / 6) - timePassedSeconds - 1) * DateUtils.SECOND_IN_MILLIS; doReturn(mCurrentTimeMs).when(mDependencies).elapsedRealtime(); synchronized (apfFilter) { - apfFilter.installNewProgramLocked(); + apfFilter.installNewProgram(); } program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); assertDrop(program, basePacket.array()); @@ -2373,7 +2373,7 @@ mCurrentTimeMs += DateUtils.SECOND_IN_MILLIS; doReturn(mCurrentTimeMs).when(mDependencies).elapsedRealtime(); synchronized (apfFilter) { - apfFilter.installNewProgramLocked(); + apfFilter.installNewProgram(); } program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); assertPass(program, basePacket.array()); @@ -2810,7 +2810,7 @@ verify(mNetworkQuirkMetrics).statsWrite(); reset(mNetworkQuirkMetrics); synchronized (apfFilter) { - apfFilter.installNewProgramLocked(); + apfFilter.installNewProgram(); } verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE); verify(mNetworkQuirkMetrics).statsWrite(); @@ -2836,12 +2836,12 @@ public void testGenerateApfProgramException() { final ApfConfiguration config = getDefaultConfig(); ApfFilter apfFilter = getApfFilter(config); - // Simulate exception during installNewProgramLocked() by mocking + // Simulate exception during installNewProgram() by mocking // mDependencies.elapsedRealtime() to throw an exception (this method doesn't throw in // real-world scenarios). doThrow(new IllegalStateException("test exception")).when(mDependencies).elapsedRealtime(); synchronized (apfFilter) { - apfFilter.installNewProgramLocked(); + apfFilter.installNewProgram(); } verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_GENERATE_FILTER_EXCEPTION); verify(mNetworkQuirkMetrics).statsWrite();
diff --git a/tests/unit/src/android/net/ip/DhcpClientTest.kt b/tests/unit/src/android/net/ip/DhcpClientTest.kt new file mode 100644 index 0000000..6210bc5 --- /dev/null +++ b/tests/unit/src/android/net/ip/DhcpClientTest.kt
@@ -0,0 +1,102 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.ip + +import android.content.Context +import android.content.res.Resources +import android.net.NetworkStackIpMemoryStore +import android.net.dhcp.DhcpClient +import androidx.test.filters.SmallTest +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import com.android.networkstack.R +import com.android.networkstack.metrics.IpProvisioningMetrics +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.any +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.mock +import org.mockito.Mockito.spy +import kotlin.test.assertEquals + +const val HOSTNAME = "myhostname" +const val HOSTNAME1 = "myhostname1" +const val HOSTNAME2 = "myhostname2" +const val HOSTNAME3 = "myhostname3" +const val PROP1 = "ro.product.model" +const val PROP2 = "ro.product.name" +const val PROP3 = "ro.vendor.specialname" +const val PROP_EMPTY = "ro.product.name_empty" +const val PROP_INVALID = "ro.notproduct.and.notvendor" + +/** + * Unit tests for DhcpClient (currently only for its Dependencies class). Note that most of + * DhcpClient's functionality is (and should be) tested in the IpClient integration tests and in the + * DhcpPacket unit tests, not here. This test class is mostly intended to test small bits of + * functionality that would be difficult to exercise in those larger tests. + */ +@RunWith(AndroidJUnit4::class) +@SmallTest +class DhcpClientTest { + private val context = mock(Context::class.java) + private val resources = mock(Resources::class.java) + + // This is a spy because DhcpClient.Dependencies is the actual class under test. + // The tests mock some of the class's methods, exercise certain methods that end up calling + // the mocked methods, and checks the results. + private val deps = spy(DhcpClient.Dependencies( + mock(NetworkStackIpMemoryStore::class.java), + mock(IpProvisioningMetrics::class.java))) + + @Before + fun setUp() { + doReturn(resources).`when`(context).resources + doReturn(HOSTNAME).`when`(deps).getDeviceName(any()) + doReturn(HOSTNAME1).`when`(deps).getSystemProperty(PROP1) + doReturn(HOSTNAME2).`when`(deps).getSystemProperty(PROP2) + doReturn(HOSTNAME2).`when`(deps).getSystemProperty(PROP_INVALID) + doReturn(HOSTNAME3).`when`(deps).getSystemProperty(PROP3) + doReturn("").`when`(deps).getSystemProperty(PROP_EMPTY) + } + + private fun setHostnameProps(props: Array<String>?) { + doReturn(props).`when`(resources).getStringArray( + R.array.config_dhcp_client_hostname_preferred_props) + } + + @Test + fun testGetHostname_PropsSet() { + setHostnameProps(null) + assertEquals(HOSTNAME, deps.getCustomHostname(context)) + + setHostnameProps(emptyArray()) + assertEquals(HOSTNAME, deps.getCustomHostname(context)) + + setHostnameProps(arrayOf(PROP1, PROP2)) + assertEquals(HOSTNAME1, deps.getCustomHostname(context)) + + setHostnameProps(arrayOf(PROP_INVALID, PROP1, PROP2)) + assertEquals(HOSTNAME1, deps.getCustomHostname(context)) + + setHostnameProps(arrayOf(PROP_EMPTY, PROP2)) + assertEquals(HOSTNAME2, deps.getCustomHostname(context)) + + setHostnameProps(arrayOf(PROP_EMPTY, PROP3)) + assertEquals(HOSTNAME3, deps.getCustomHostname(context)) + } +}
diff --git a/tests/unit/src/android/net/ip/IpClientTest.java b/tests/unit/src/android/net/ip/IpClientTest.java index 0df8a31..f5fd22b 100644 --- a/tests/unit/src/android/net/ip/IpClientTest.java +++ b/tests/unit/src/android/net/ip/IpClientTest.java
@@ -65,6 +65,7 @@ import android.app.AlarmManager; import android.content.ContentResolver; import android.content.Context; +import android.content.pm.PackageManager; import android.content.res.Resources; import android.net.ConnectivityManager; import android.net.INetd; @@ -191,6 +192,7 @@ @Mock private PrintWriter mWriter; @Mock private IpClientNetlinkMonitor mNetlinkMonitor; @Mock private AndroidPacketFilter mApfFilter; + @Mock private PackageManager mPackageManager; private InterfaceParams mIfParams; private INetlinkMessageProcessor mNetlinkMessageProcessor; @@ -216,6 +218,8 @@ when(mDependencies.makeIpClientNetlinkMonitor( any(), any(), any(), anyInt(), any())).thenReturn(mNetlinkMonitor); when(mNetlinkMonitor.start()).thenReturn(true); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + when(mPackageManager.hasSystemFeature(eq(PackageManager.FEATURE_WATCH))).thenReturn(false); mIfParams = null; } @@ -323,7 +327,7 @@ 0 /* flags */, 0xffffffffL /* change */); - return new RtNetlinkLinkMessage(nlmsghdr, 0 /* mtu */, ifInfoMsg, TEST_MAC, ifaceName); + return RtNetlinkLinkMessage.build(nlmsghdr, ifInfoMsg, 0 /* mtu */, TEST_MAC, ifaceName); } private void onInterfaceAddressUpdated(final LinkAddress la, int flags) { @@ -531,19 +535,20 @@ final IpClient ipc = makeIpClient(iface); final String l2Key = TEST_L2KEY; final String cluster = TEST_CLUSTER; + final MacAddress bssid = MacAddress.fromString(TEST_BSSID); ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() .withoutIPv4() .withoutIpReachabilityMonitor() .withInitialConfiguration( conf(links(TEST_LOCAL_ADDRESSES), prefixes(TEST_PREFIXES), ips())) + .withLayer2Information(new Layer2Information(l2Key, cluster, bssid)) .build(); ipc.startProvisioning(config); verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).setNeighborDiscoveryOffload(true); verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).setFallbackMulticastFilter(false); verify(mCb, never()).onProvisioningFailure(any()); - ipc.setL2KeyAndCluster(l2Key, cluster); for (String addr : TEST_LOCAL_ADDRESSES) { String[] parts = addr.split("/");
diff --git a/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt b/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt index 98dbd64..518cec7 100644 --- a/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt +++ b/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt
@@ -290,24 +290,15 @@ }.`when`(dependencies).makeIpNeighborMonitor(any(), any(), any()) doReturn(mIpReachabilityMonitorMetrics) .`when`(dependencies).getIpReachabilityMonitorMetrics() - doReturn(true).`when`(dependencies).isFeatureNotChickenedOut( - any(), - eq(IP_REACHABILITY_MCAST_RESOLICIT_VERSION) - ) - - // TODO: test with non-default flag combinations. - // Note: because dependencies is a mock, all features that are not specified here are - // neither enabled nor chickened out. - doReturn(true).`when`(dependencies).isFeatureNotChickenedOut( - any(), - eq(IP_REACHABILITY_ROUTER_MAC_CHANGE_FAILURE_ONLY_AFTER_ROAM_VERSION) - ) // Set flags based on test method annotations. + // Note: because dependencies is a mock, all features that are not specified in flag + // annotations are either disabled or chickened out. var testMethod = this::class.java.getMethod(mTestName.methodName) val flags = testMethod.getAnnotationsByType(Flag::class.java) - for (flag in flags) { - doReturn(flag.enabled).`when`(dependencies).isFeatureEnabled(any(), eq(flag.name)) + for (f in flags) { + doReturn(f.enabled).`when`(dependencies).isFeatureEnabled(any(), eq(f.name)) + doReturn(f.enabled).`when`(dependencies).isFeatureNotChickenedOut(any(), eq(f.name)) } val monitorFuture = CompletableFuture<IpReachabilityMonitor>() @@ -1032,6 +1023,8 @@ } @Test + @Flag(name = IP_REACHABILITY_MCAST_RESOLICIT_VERSION, true) + @Flag(name = IP_REACHABILITY_ROUTER_MAC_CHANGE_FAILURE_ONLY_AFTER_ROAM_VERSION, enabled = true) fun testNudProbeFailedMetrics_defaultIPv6GatewayMacAddrChangedAfterRoaming() { prepareNeighborReachableButMacAddrChangedTest( TEST_LINK_PROPERTIES, @@ -1043,6 +1036,8 @@ } @Test + @Flag(name = IP_REACHABILITY_MCAST_RESOLICIT_VERSION, true) + @Flag(name = IP_REACHABILITY_ROUTER_MAC_CHANGE_FAILURE_ONLY_AFTER_ROAM_VERSION, enabled = true) fun testNudProbeFailedMetrics_defaultIPv4GatewayMacAddrChangedAfterRoaming() { prepareNeighborReachableButMacAddrChangedTest( TEST_LINK_PROPERTIES, @@ -1055,6 +1050,8 @@ } @Test + @Flag(name = IP_REACHABILITY_MCAST_RESOLICIT_VERSION, true) + @Flag(name = IP_REACHABILITY_ROUTER_MAC_CHANGE_FAILURE_ONLY_AFTER_ROAM_VERSION, enabled = true) fun testNudProbeFailedMetrics_defaultIPv6GatewayMacAddrChangedAfterConfirm() { prepareNeighborReachableButMacAddrChangedTest( TEST_LINK_PROPERTIES, @@ -1067,6 +1064,8 @@ } @Test + @Flag(name = IP_REACHABILITY_MCAST_RESOLICIT_VERSION, true) + @Flag(name = IP_REACHABILITY_ROUTER_MAC_CHANGE_FAILURE_ONLY_AFTER_ROAM_VERSION, enabled = true) fun testNudProbeFailedMetrics_defaultIPv6GatewayMacAddrChangedAfterOrganic() { prepareNeighborReachableButMacAddrChangedTest( TEST_LINK_PROPERTIES,
diff --git a/tests/unit/src/android/net/util/RawPacketTrackerTest.kt b/tests/unit/src/android/net/util/RawPacketTrackerTest.kt new file mode 100644 index 0000000..cecd7a0 --- /dev/null +++ b/tests/unit/src/android/net/util/RawPacketTrackerTest.kt
@@ -0,0 +1,195 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.net.util + +import android.net.ip.ConnectivityPacketTracker +import android.os.HandlerThread +import androidx.test.filters.SmallTest +import com.android.testutils.DevSdkIgnoreRunner +import com.android.testutils.FunctionalUtils.ThrowingSupplier +import com.android.testutils.assertThrows +import com.android.testutils.visibleOnHandlerThread +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mockito +import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.mock +import org.mockito.Mockito.timeout +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyZeroInteractions + +/** + * Test for RawPacketTracker. + */ +@SmallTest +@DevSdkIgnoreRunner.MonitorThreadLeak +class RawPacketTrackerTest { + companion object { + private const val TEST_TIMEOUT_MS: Long = 1000 + private const val TEST_MAX_CAPTURE_TIME_MS: Long = 1000 + private const val TAG = "RawPacketTrackerTest" + } + + private val deps = mock(RawPacketTracker.Dependencies::class.java) + private val tracker = mock(ConnectivityPacketTracker::class.java) + private val ifaceName = "lo" + private val handlerThread by lazy { + HandlerThread("$TAG-handler-thread").apply { start() } + } + private lateinit var rawTracker: RawPacketTracker + + @Before + fun setUp() { + doReturn(handlerThread).`when`(deps).createHandlerThread() + doReturn(handlerThread.looper).`when`(deps).getLooper(any()) + doReturn(tracker).`when`(deps).createPacketTracker(any(), any(), anyInt()) + rawTracker = RawPacketTracker(deps) + } + + @After + fun tearDown() { + Mockito.framework().clearInlineMocks() + handlerThread.quitSafely() + handlerThread.join() + } + + @Test + fun testStartCapture() { + // start capturing + startCaptureOnHandler(ifaceName) + verifySetCapture(true, 1) + + assertTrue(rawTracker.handler.hasMessages(RawPacketTracker.CMD_STOP_CAPTURE)) + } + + @Test + fun testInvalidStartCapture() { + // start capturing with negative timeout + assertThrows(IllegalArgumentException::class.java) { + startCaptureOnHandler(ifaceName, -1) + } + } + + @Test + fun testStopCapture() { + // start capturing + startCaptureOnHandler(ifaceName) + // simulate capture status for stop capturing + verifySetCapture(true, 1) + + // stop capturing + stopCaptureOnHandler(ifaceName) + verifySetCapture(false, 1) + verifyZeroInteractions(tracker) + } + + @Test + fun testDuplicatedStartAndStop() { + // start capture with a long timeout + startCaptureOnHandler(ifaceName, 10_000) + verifySetCapture(true, 1) + + // start capturing for multiple times + for (i in 1..10) { + assertThrows(RuntimeException::class.java) { + startCaptureOnHandler(ifaceName) + } + } + + // expect no duplicated start capture + verifySetCapture(true, 0) + + // stop capturing for multiple times + stopCaptureOnHandler(ifaceName) + verifySetCapture(false, 1) + for (i in 1..10) { + assertThrows(RuntimeException::class.java) { + stopCaptureOnHandler(ifaceName) + } + } + + verifySetCapture(false, 0) + verifyZeroInteractions(tracker) + } + + @Test + fun testMatchedPacketCount() { + val matchedPkt = "12345" + val notMatchedPkt = "54321" + + // simulate get matched packet count + doReturn(1).`when`(tracker).getMatchedPacketCount(matchedPkt) + // simulate get not matched packet count + doReturn(0).`when`(tracker).getMatchedPacketCount(notMatchedPkt) + + // start capture + startCaptureOnHandler(ifaceName) + + assertEquals(1, getMatchedPktCntOnHandler(ifaceName, matchedPkt)) + assertEquals(0, getMatchedPktCntOnHandler(ifaceName, notMatchedPkt)) + + // for non-existed interface + val nonExistedIface = "non-existed-iface" + assertThrows(RuntimeException::class.java) { + getMatchedPktCntOnHandler(nonExistedIface, matchedPkt) + getMatchedPktCntOnHandler(nonExistedIface, notMatchedPkt) + } + + // stop capture + stopCaptureOnHandler(ifaceName) + + // expect no matched packet after stop capturing + assertThrows(RuntimeException::class.java) { + getMatchedPktCntOnHandler(ifaceName, matchedPkt) + getMatchedPktCntOnHandler(ifaceName, notMatchedPkt) + } + } + + private fun startCaptureOnHandler( + ifaceName: String, maxCaptureTime: Long = TEST_MAX_CAPTURE_TIME_MS + ) { + visibleOnHandlerThread(rawTracker.handler) { + rawTracker.startCapture(ifaceName, maxCaptureTime) + } + } + + private fun stopCaptureOnHandler(ifaceName: String) { + visibleOnHandlerThread(rawTracker.handler) { + rawTracker.stopCapture(ifaceName) + } + } + + private fun getMatchedPktCntOnHandler(ifaceName: String, packetPattern: String): Int { + return visibleOnHandlerThread(rawTracker.handler, ThrowingSupplier { + rawTracker.getMatchedPacketCount(ifaceName, packetPattern) + }) + } + + private fun verifySetCapture( + isCapture: Boolean, + receiveCnt: Int + ) { + verify(tracker, timeout(TEST_TIMEOUT_MS).times(receiveCnt)).setCapture(eq(isCapture)) + clearInvocations<Any>(tracker) + } +} \ No newline at end of file
diff --git a/tests/unit/src/com/android/networkstack/NetworkStackNotifierTest.kt b/tests/unit/src/com/android/networkstack/NetworkStackNotifierTest.kt index 9fda189..efd4069 100644 --- a/tests/unit/src/com/android/networkstack/NetworkStackNotifierTest.kt +++ b/tests/unit/src/com/android/networkstack/NetworkStackNotifierTest.kt
@@ -50,7 +50,6 @@ import com.android.networkstack.NetworkStackNotifier.CONNECTED_NOTIFICATION_TIMEOUT_MS import com.android.networkstack.NetworkStackNotifier.Dependencies import com.android.networkstack.apishim.NetworkInformationShimImpl -import com.android.modules.utils.build.SdkLevel.isAtLeastR import com.android.modules.utils.build.SdkLevel.isAtLeastS import org.junit.Assume.assumeTrue import org.junit.Before @@ -236,8 +235,6 @@ @Test fun testConnectedNotification_WithSsid() { - // NetworkCapabilities#getSSID is not available for API <= Q - assumeTrue(isAtLeastR()) val capabilities = NetworkCapabilities(VALIDATED_CAPABILITIES).setSSID(TEST_SSID) onCapabilitiesChanged(EMPTY_CAPABILITIES) @@ -253,8 +250,6 @@ @Test fun testConnectedVenueInfoNotification() { - // Venue info (CaptivePortalData) is not available for API <= Q - assumeTrue(isAtLeastR()) mNotifier.notifyCaptivePortalValidationPending(TEST_NETWORK) onLinkPropertiesChanged(mTestCapportLp) onDefaultNetworkAvailable(TEST_NETWORK) @@ -271,8 +266,6 @@ @Test fun testConnectedVenueInfoNotification_VenueInfoDisabled() { - // Venue info (CaptivePortalData) is not available for API <= Q - assumeTrue(isAtLeastR()) val channel = NotificationChannel(CHANNEL_VENUE_INFO, "test channel", IMPORTANCE_NONE) doReturn(channel).`when`(mNotificationChannelsNm).getNotificationChannel(CHANNEL_VENUE_INFO) mNotifier.notifyCaptivePortalValidationPending(TEST_NETWORK) @@ -290,8 +283,6 @@ @Test fun testVenueInfoNotification() { - // Venue info (CaptivePortalData) is not available for API <= Q - assumeTrue(isAtLeastR()) onLinkPropertiesChanged(mTestCapportLp) onDefaultNetworkAvailable(TEST_NETWORK) val capabilities = NetworkCapabilities(VALIDATED_CAPABILITIES).setSSID(TEST_SSID) @@ -308,8 +299,6 @@ @Test fun testVenueInfoNotification_VenueInfoDisabled() { - // Venue info (CaptivePortalData) is not available for API <= Q - assumeTrue(isAtLeastR()) doReturn(null).`when`(mNm).getNotificationChannel(CHANNEL_VENUE_INFO) onLinkPropertiesChanged(mTestCapportLp) onDefaultNetworkAvailable(TEST_NETWORK) @@ -321,8 +310,6 @@ @Test fun testNonDefaultVenueInfoNotification() { - // Venue info (CaptivePortalData) is not available for API <= Q - assumeTrue(isAtLeastR()) onLinkPropertiesChanged(mTestCapportLp) onCapabilitiesChanged(VALIDATED_CAPABILITIES) mLooper.processAllMessages() @@ -332,8 +319,6 @@ @Test fun testEmptyCaptivePortalDataVenueInfoNotification() { - // Venue info (CaptivePortalData) is not available for API <= Q - assumeTrue(isAtLeastR()) onLinkPropertiesChanged(EMPTY_CAPPORT_LP) onCapabilitiesChanged(VALIDATED_CAPABILITIES) mLooper.processAllMessages() @@ -343,8 +328,6 @@ @Test fun testUnvalidatedNetworkVenueInfoNotification() { - // Venue info (CaptivePortalData) is not available for API <= Q - assumeTrue(isAtLeastR()) onLinkPropertiesChanged(mTestCapportLp) onCapabilitiesChanged(EMPTY_CAPABILITIES) mLooper.processAllMessages()
diff --git a/tests/unit/src/com/android/networkstack/util/DnsUtilsTest.kt b/tests/unit/src/com/android/networkstack/util/DnsUtilsTest.kt index 59d96be..e65d69e 100644 --- a/tests/unit/src/com/android/networkstack/util/DnsUtilsTest.kt +++ b/tests/unit/src/com/android/networkstack/util/DnsUtilsTest.kt
@@ -21,12 +21,12 @@ import android.net.DnsResolver.TYPE_A import android.net.DnsResolver.TYPE_AAAA import android.net.Network -import com.android.testutils.FakeDns import androidx.test.filters.SmallTest import androidx.test.runner.AndroidJUnit4 import com.android.networkstack.util.DnsUtils import com.android.networkstack.util.DnsUtils.TYPE_ADDRCONFIG import com.android.server.connectivity.NetworkMonitor.DnsLogFunc +import com.android.server.connectivity.FakeDns import java.net.InetAddress import java.net.UnknownHostException import kotlin.test.assertFailsWith @@ -43,7 +43,8 @@ @RunWith(AndroidJUnit4::class) @SmallTest class DnsUtilsTest { - val fakeNetwork: Network = Network(1234) + @Mock + lateinit var mockNetwork: Network @Mock lateinit var mockLogger: DnsLogFunc @Mock @@ -53,7 +54,7 @@ @Before fun setup() { MockitoAnnotations.initMocks(this) - fakeDns = FakeDns(mockResolver) + fakeDns = FakeDns(mockNetwork, mockResolver) fakeDns.startMocking() } @@ -71,8 +72,13 @@ } private fun verifyGetAllByName(name: String, expected: Array<String>, type: Int) { - fakeDns.setAnswer(name, expected, type) - DnsUtils.getAllByName(mockResolver, fakeNetwork, name, type, FLAG_EMPTY, DEFAULT_TIMEOUT_MS, + if (type == TYPE_ADDRCONFIG) { + fakeDns.setAnswer(name, expected.filter({":" in it}).toTypedArray(), TYPE_AAAA) + fakeDns.setAnswer(name, expected.filter({"." in it}).toTypedArray(), TYPE_A) + } else { + fakeDns.setAnswer(name, expected, type) + } + DnsUtils.getAllByName(mockResolver, mockNetwork, name, type, FLAG_EMPTY, DEFAULT_TIMEOUT_MS, mockLogger).let { assertIpAddressArrayEquals(expected, it) } } @@ -85,7 +91,7 @@ private fun verifyGetAllByNameFails(name: String, type: Int) { assertFailsWith<UnknownHostException> { - DnsUtils.getAllByName(mockResolver, fakeNetwork, name, type, + DnsUtils.getAllByName(mockResolver, mockNetwork, name, type, FLAG_EMPTY, SHORT_TIMEOUT_MS, mockLogger) } }
diff --git a/tests/unit/src/com/android/server/connectivity/FakeDns.java b/tests/unit/src/com/android/server/connectivity/FakeDns.java index e689f49..2f16e23 100644 --- a/tests/unit/src/com/android/server/connectivity/FakeDns.java +++ b/tests/unit/src/com/android/server/connectivity/FakeDns.java
@@ -20,6 +20,8 @@ import static android.net.DnsResolver.TYPE_AAAA; import static android.net.InetAddresses.parseNumericAddress; +import static com.android.net.module.util.DnsPacket.TYPE_SVCB; + import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.doAnswer; @@ -30,15 +32,18 @@ import android.os.Looper; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.testutils.DnsSvcbUtils; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; +import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; @@ -64,7 +69,6 @@ private boolean mNonBypassPrivateDnsWorking = true; public FakeDns(@NonNull Network network, @NonNull DnsResolver dnsResolver) { - mNetwork = Objects.requireNonNull(network); mDnsResolver = Objects.requireNonNull(dnsResolver); } @@ -88,16 +92,18 @@ public interface AnswerSupplier { /** Supplies the answer to one DnsResolver query method call. */ - List<String> get() throws DnsResolver.DnsException; + @Nullable + String[] get() throws DnsResolver.DnsException; } private static class InstantAnswerSupplier implements AnswerSupplier { - private final List<String> mAnswers; - InstantAnswerSupplier(List<String> answers) { + private final String[] mAnswers; + InstantAnswerSupplier(String[] answers) { mAnswers = answers; } @Override - public List<String> get() { + @Nullable + public String[] get() { return mAnswers; } } @@ -115,7 +121,7 @@ } /** Returns the answer for a given name and type on the given mock network. */ - private CompletableFuture<List<String>> getAnswer(Network mockNetwork, String hostname, + private CompletableFuture<String[]> getAnswer(Network mockNetwork, String hostname, int type) { if (mNetwork.equals(mockNetwork) && !mNonBypassPrivateDnsWorking) { return CompletableFuture.completedFuture(null); @@ -137,7 +143,7 @@ return CompletableFuture.completedFuture( ((InstantAnswerSupplier) answerSupplier).get()); } - final CompletableFuture<List<String>> answerFuture = new CompletableFuture<>(); + final CompletableFuture<String[]> answerFuture = new CompletableFuture<>(); new Thread(() -> { try { answerFuture.complete(answerSupplier.get()); @@ -150,8 +156,7 @@ /** Sets the answer for a given name and type. */ public void setAnswer(String hostname, String[] answer, int type) { - setAnswer(hostname, new InstantAnswerSupplier( - (answer == null) ? null : Arrays.asList(answer)), type); + setAnswer(hostname, new InstantAnswerSupplier(answer), type); } /** Sets the answer for a given name and type. */ @@ -165,6 +170,15 @@ } } + private byte[] makeSvcbResponse(String hostname, String[] answer) { + try { + return DnsSvcbUtils.makeSvcbResponse(hostname, answer); + } catch (IOException e) { + throw new AssertionError("Invalid test data building SVCB response for: " + + Arrays.toString(answer)); + } + } + /** Simulates a getAllByName call for the specified name on the specified mock network. */ private InetAddress[] getAllByName(Network mockNetwork, String hostname) throws UnknownHostException { @@ -183,25 +197,25 @@ // Regardless of the type, depends on what the responses contained in the network. @SuppressWarnings("FutureReturnValueIgnored") - private CompletableFuture<List<String>> queryAllTypes( + private CompletableFuture<String[]> queryAllTypes( Network mockNetwork, String hostname) { if (mNetwork.equals(mockNetwork) && !mNonBypassPrivateDnsWorking) { return CompletableFuture.completedFuture(null); } - final CompletableFuture<List<String>> aFuture = + final CompletableFuture<String[]> aFuture = getAnswer(mockNetwork, hostname, TYPE_A) - .exceptionally(e -> Collections.emptyList()); - final CompletableFuture<List<String>> aaaaFuture = + .exceptionally(e -> new String[0]); + final CompletableFuture<String[]> aaaaFuture = getAnswer(mockNetwork, hostname, TYPE_AAAA) - .exceptionally(e -> Collections.emptyList()); + .exceptionally(e -> new String[0]); - final CompletableFuture<List<String>> combinedFuture = new CompletableFuture<>(); + final CompletableFuture<String[]> combinedFuture = new CompletableFuture<>(); aFuture.thenAcceptBoth(aaaaFuture, (res1, res2) -> { - final List<String> answer = new ArrayList<>(); - if (res1 != null) answer.addAll(res1); - if (res2 != null) answer.addAll(res2); - combinedFuture.complete(answer); + final List<String> answerList = new ArrayList<>(); + if (res1 != null) answerList.addAll(Arrays.asList(res1)); + if (res2 != null) answerList.addAll(Arrays.asList(res2)); + combinedFuture.complete(answerList.toArray(new String[0])); }); return combinedFuture; } @@ -224,9 +238,16 @@ return mockQuery(invocation, 0 /* posNetwork */, 1 /* posHostname */, 4 /* posExecutor */, 6 /* posCallback */, 2 /* posType */); }).when(mDnsResolver).query(any(), any(), anyInt(), anyInt(), any(), any(), any()); + + // Queries using rawQuery. Currently, mockQuery only supports TYPE_SVCB. + doAnswer(invocation -> { + return mockQuery(invocation, 0 /* posNetwork */, 1 /* posHostname */, + 5 /* posExecutor */, 7 /* posCallback */, 3 /* posType */); + }).when(mDnsResolver).rawQuery(any(), any(), anyInt(), anyInt(), anyInt(), any(), + any(), any()); } - private List<InetAddress> stringsToInetAddresses(List<String> addrs) { + private List<InetAddress> stringsToInetAddresses(String[] addrs) { if (addrs == null) return null; final List<InetAddress> out = new ArrayList<>(); for (String addr : addrs) { @@ -244,7 +265,7 @@ Network network = invocation.getArgument(posNetwork); DnsResolver.Callback callback = invocation.getArgument(posCallback); - final CompletableFuture<List<String>> answerFuture = (posType != -1) + final CompletableFuture<String[]> answerFuture = (posType != -1) ? getAnswer(network, hostname, invocation.getArgument(posType)) : queryAllTypes(network, hostname); @@ -257,7 +278,7 @@ callback.onError((DnsResolver.DnsException) exception); return; } - if (answer != null && answer.size() > 0) { + if (answer != null && answer.length > 0) { final int qtype = (posType != -1) ? invocation.getArgument(posType) : TYPE_AAAA; switch (qtype) { @@ -266,6 +287,9 @@ case TYPE_AAAA: callback.onAnswer(stringsToInetAddresses(answer), 0); break; + case TYPE_SVCB: + callback.onAnswer(makeSvcbResponse(hostname, answer), 0); + break; default: throw new UnsupportedOperationException( "Unsupported qtype: " + qtype + ", update this fake");
diff --git a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java index 64196dd..4273d95 100644 --- a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java +++ b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
@@ -19,6 +19,7 @@ import static android.content.Intent.ACTION_CONFIGURATION_CHANGED; import static android.net.CaptivePortal.APP_RETURN_DISMISSED; import static android.net.CaptivePortal.APP_RETURN_WANTED_AS_IS; +import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; import static android.net.DnsResolver.TYPE_A; import static android.net.DnsResolver.TYPE_AAAA; @@ -53,6 +54,9 @@ import static android.os.Build.VERSION_CODES.S_V2; import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; +import static com.android.net.module.util.DnsPacket.TYPE_SVCB; +import static com.android.net.module.util.FeatureVersions.FEATURE_DDR_IN_CONNECTIVITY; +import static com.android.net.module.util.FeatureVersions.FEATURE_DDR_IN_DNSRESOLVER; import static com.android.net.module.util.NetworkStackConstants.TEST_CAPTIVE_PORTAL_HTTPS_URL; import static com.android.net.module.util.NetworkStackConstants.TEST_CAPTIVE_PORTAL_HTTP_URL; import static com.android.net.module.util.NetworkStackConstants.TEST_URL_EXPIRATION_TIME; @@ -64,7 +68,9 @@ import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS; import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_USE_HTTPS; import static com.android.networkstack.util.NetworkStackUtils.DEFAULT_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT; +import static com.android.networkstack.util.NetworkStackUtils.DNS_DDR_VERSION; import static com.android.networkstack.util.NetworkStackUtils.DNS_PROBE_PRIVATE_IP_NO_INTERNET_VERSION; +import static com.android.networkstack.util.NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION; import static com.android.networkstack.util.NetworkStackUtils.REEVALUATE_WHEN_RESUME; import static com.android.server.connectivity.NetworkMonitor.CONFIG_ASYNC_PRIVDNS_PROBE_TIMEOUT_MS; import static com.android.server.connectivity.NetworkMonitor.INITIAL_REEVALUATE_DELAY_MS; @@ -150,6 +156,7 @@ import android.telephony.CellInfoLte; import android.telephony.CellSignalStrength; import android.telephony.TelephonyManager; +import android.text.TextUtils; import android.util.ArrayMap; import androidx.test.filters.SmallTest; @@ -171,7 +178,6 @@ import com.android.networkstack.metrics.DataStallDetectionStats; import com.android.networkstack.metrics.DataStallStatsUtils; import com.android.networkstack.netlink.TcpSocketTracker; -import com.android.networkstack.util.NetworkStackUtils; import com.android.server.NetworkStackService.NetworkStackServiceManager; import com.android.server.connectivity.nano.CellularData; import com.android.server.connectivity.nano.DnsEvent; @@ -400,6 +406,7 @@ } return null; }).when(mDependencies).onExecutorServiceCreated(any()); + doReturn(mValidationLogger).when(mValidationLogger).forSubComponent(any()); doReturn(mCleartextDnsNetwork).when(mNetwork).getPrivateDnsBypassingCopy(); @@ -1084,33 +1091,18 @@ private static CellIdentityGsm makeCellIdentityGsm(int lac, int cid, int arfcn, int bsic, String mccStr, String mncStr, String alphal, String alphas) throws ReflectiveOperationException { - if (ShimUtils.isAtLeastR()) { - return new CellIdentityGsm(lac, cid, arfcn, bsic, mccStr, mncStr, alphal, alphas, - Collections.emptyList() /* additionalPlmns */); - } else { - // API <= Q does not have the additionalPlmns parameter - final Constructor<CellIdentityGsm> constructor = CellIdentityGsm.class.getConstructor( - int.class, int.class, int.class, int.class, String.class, String.class, - String.class, String.class); - return constructor.newInstance(lac, cid, arfcn, bsic, mccStr, mncStr, alphal, alphas); - } + // TODO: inline this call. + return new CellIdentityGsm(lac, cid, arfcn, bsic, mccStr, mncStr, alphal, alphas, + Collections.emptyList() /* additionalPlmns */); } private static CellIdentityLte makeCellIdentityLte(int ci, int pci, int tac, int earfcn, int bandwidth, String mccStr, String mncStr, String alphal, String alphas) throws ReflectiveOperationException { - if (ShimUtils.isAtLeastR()) { - return new CellIdentityLte(ci, pci, tac, earfcn, new int[] {} /* bands */, - bandwidth, mccStr, mncStr, alphal, alphas, - Collections.emptyList() /* additionalPlmns */, null /* csgInfo */); - } else { - // API <= Q does not have the additionalPlmns and csgInfo parameters - final Constructor<CellIdentityLte> constructor = CellIdentityLte.class.getConstructor( - int.class, int.class, int.class, int.class, int.class, String.class, - String.class, String.class, String.class); - return constructor.newInstance(ci, pci, tac, earfcn, bandwidth, mccStr, mncStr, alphal, - alphas); - } + // TODO: inline this call. + return new CellIdentityLte(ci, pci, tac, earfcn, new int[] {} /* bands */, + bandwidth, mccStr, mncStr, alphal, alphas, + Collections.emptyList() /* additionalPlmns */, null /* csgInfo */); } @Test @@ -2084,22 +2076,10 @@ assertEquals(expectedUrl, redirectUrl); } - @Test - public void testCaptivePortalLogin_beforeR() throws Exception { - assumeFalse(ShimUtils.isAtLeastR()); - testCaptivePortalLogin(TEST_HTTP_URL); - } - - @Test - public void testCaptivePortalLogin_AfterR() throws Exception { - assumeTrue(ShimUtils.isAtLeastR()); - testCaptivePortalLogin(TEST_LOGIN_URL); - } - - private void testCaptivePortalLogin(String expectedUrl) throws Exception { + public void testCaptivePortalLogin() throws Exception { final NetworkMonitor nm = makeMonitor(CELL_METERED_CAPABILITIES); - setupAndLaunchCaptivePortalApp(nm, expectedUrl); + setupAndLaunchCaptivePortalApp(nm, TEST_LOGIN_URL); // Have the app report that the captive portal is dismissed, and check that we revalidate. setStatus(mHttpsConnection, 204); @@ -2114,20 +2094,9 @@ } @Test - public void testCaptivePortalUseAsIs_beforeR() throws Exception { - assumeFalse(ShimUtils.isAtLeastR()); - testCaptivePortalUseAsIs(TEST_HTTP_URL); - } - - @Test - public void testCaptivePortalUseAsIs_AfterR() throws Exception { - assumeTrue(ShimUtils.isAtLeastR()); - testCaptivePortalUseAsIs(TEST_LOGIN_URL); - } - - private void testCaptivePortalUseAsIs(String expectedUrl) throws Exception { + public void testCaptivePortalUseAsIs() throws Exception { final NetworkMonitor nm = makeMonitor(CELL_METERED_CAPABILITIES); - setupAndLaunchCaptivePortalApp(nm, expectedUrl); + setupAndLaunchCaptivePortalApp(nm, TEST_LOGIN_URL); // The user decides this network is wanted as is, either by encountering an SSL error or // encountering an unknown scheme and then deciding to continue through the browser, or by @@ -2182,14 +2151,14 @@ @Test public void testPrivateDnsSuccess_SyncDns() throws Exception { doReturn(false).when(mDependencies).isFeatureEnabled( - any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); + any(), eq(NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); runPrivateDnsSuccessTest(); } @Test public void testPrivateDnsSuccess_AsyncDns() throws Exception { doReturn(true).when(mDependencies).isFeatureEnabled( - any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); + any(), eq(NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); runPrivateDnsSuccessTest(); } @@ -2218,14 +2187,14 @@ @Test public void testProbeStatusChanged_SyncDns() throws Exception { doReturn(false).when(mDependencies).isFeatureEnabled( - any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); + any(), eq(NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); runProbeStatusChangedTest(); } @Test public void testProbeStatusChanged_AsyncDns() throws Exception { doReturn(true).when(mDependencies).isFeatureEnabled( - any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); + any(), eq(NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); runProbeStatusChangedTest(); } @@ -2276,21 +2245,21 @@ @Test public void testPrivateDnsResolutionRetryUpdate_SyncDns() throws Exception { doReturn(false).when(mDependencies).isFeatureEnabled( - any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); + any(), eq(NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); runPrivateDnsResolutionRetryUpdateTest(); } @Test public void testPrivateDnsResolutionRetryUpdate_AsyncDns() throws Exception { doReturn(true).when(mDependencies).isFeatureEnabled( - any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); + any(), eq(NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); runPrivateDnsResolutionRetryUpdateTest(); } @Test public void testAsyncPrivateDnsResolution_PartialTimeout() throws Exception { doReturn(true).when(mDependencies).isFeatureEnabled( - any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); + any(), eq(NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); setStatus(mHttpsConnection, 204); setStatus(mHttpConnection, 204); @@ -2314,7 +2283,7 @@ @Test public void testAsyncPrivateDnsResolution_PartialFailure() throws Exception { doReturn(true).when(mDependencies).isFeatureEnabled( - any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); + any(), eq(NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); setStatus(mHttpsConnection, 204); setStatus(mHttpConnection, 204); @@ -2343,7 +2312,7 @@ public void testAsyncPrivateDnsResolution_AQuerySucceedsFirst_PrioritizeAAAA() throws Exception { doReturn(true).when(mDependencies).isFeatureEnabled( - any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); + any(), eq(NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); setStatus(mHttpsConnection, 204); setStatus(mHttpConnection, 204); @@ -2353,7 +2322,7 @@ final ConditionVariable v4Queried = new ConditionVariable(); mFakeDns.setAnswer("dns.google", () -> { v4Queried.open(); - return List.of("192.0.2.123"); + return new String[]{"192.0.2.123"}; }, TYPE_A); mFakeDns.setAnswer("dns.google", () -> { // Make sure the v6 query processing is a bit slower than the v6 one. The small delay @@ -2362,7 +2331,7 @@ // not, the test should pass. v4Queried.block(HANDLER_TIMEOUT_MS); SystemClock.sleep(10L); - return List.of("2001:db8::1", "2001:db8::2"); + return new String[]{"2001:db8::1", "2001:db8::2"}; }, TYPE_AAAA); notifyNetworkConnected(wnm, CELL_NOT_METERED_CAPABILITIES); @@ -2381,7 +2350,7 @@ public void testAsyncPrivateDnsResolution_ConfigChange_RestartsWithNewConfig() throws Exception { doReturn(true).when(mDependencies).isFeatureEnabled( - any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); + any(), eq(NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); setStatus(mHttpsConnection, 204); setStatus(mHttpConnection, 204); @@ -2393,12 +2362,12 @@ mFakeDns.setAnswer("v1.google", () -> { queriedLatch.countDown(); blockReplies.block(HANDLER_TIMEOUT_MS); - return List.of("192.0.2.123"); + return new String[]{"192.0.2.123"}; }, TYPE_A); mFakeDns.setAnswer("v1.google", () -> { queriedLatch.countDown(); blockReplies.block(HANDLER_TIMEOUT_MS); - return List.of("2001:db8::1"); + return new String[]{"2001:db8::1"}; }, TYPE_AAAA); notifyNetworkConnected(wnm, CELL_NOT_METERED_CAPABILITIES); @@ -2429,7 +2398,7 @@ public void testAsyncPrivateDnsResolution_TurnOffStrictMode_SkipsDnsValidation() throws Exception { doReturn(true).when(mDependencies).isFeatureEnabled( - any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); + any(), eq(NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); setStatus(mHttpsConnection, 204); setStatus(mHttpConnection, 204); @@ -2441,12 +2410,12 @@ mFakeDns.setAnswer("v1.google", () -> { queriedLatch.countDown(); blockReplies.block(HANDLER_TIMEOUT_MS); - return List.of("192.0.2.123"); + return new String[]{"192.0.2.123"}; }, TYPE_A); mFakeDns.setAnswer("v1.google", () -> { queriedLatch.countDown(); blockReplies.block(HANDLER_TIMEOUT_MS); - return List.of("2001:db8::1"); + return new String[]{"2001:db8::1"}; }, TYPE_AAAA); notifyNetworkConnected(wnm, CELL_NOT_METERED_CAPABILITIES); @@ -2464,6 +2433,210 @@ verify(mCallbacks, never()).notifyPrivateDnsConfigResolved(any()); } + private void setDdrEnabledForTest() { + doReturn(true).when(mDependencies).isFeatureEnabled(any(), + eq(NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); + doReturn(true).when(mDependencies).isFeatureEnabled(any(), eq(DNS_DDR_VERSION)); + doReturn(true).when(mDependencies).isFeatureSupported(any(), + eq(FEATURE_DDR_IN_CONNECTIVITY)); + doReturn(true).when(mDependencies).isFeatureSupported(any(), + eq(FEATURE_DDR_IN_DNSRESOLVER)); + } + + @Test + public void testPrivateDnsDiscoveryWithDdr_dnsServerChange() throws Exception { + setDdrEnabledForTest(); + LinkProperties lp = new LinkProperties(TEST_LINK_PROPERTIES); + final String svcb1 = "1 dot.google alpn=dot ipv4hint=192.0.2.1"; + final String svcb2 = "2 doh.google alpn=h2,h3 port=443 ipv4hint=192.0.2.100 " + + "ipv6hint=2001:db8::100 dohpath=/dns-query{?dns}"; + setStatus(mHttpsConnection, 204); + setStatus(mHttpConnection, 204); + mFakeDns.setAnswer("_dns.resolver.arpa", new String[] { svcb1, svcb2 }, TYPE_SVCB); + + WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor(); + wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig(true)); + notifyNetworkConnected(wnm, CELL_NOT_METERED_CAPABILITIES); + verifyNetworkTestedValidFromHttps(1); + // The network just got connected. Verify the callback. + // Expect that `dohIps` is empty since there's no DNS on the network. + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyPrivateDnsConfigResolved( + matchPrivateDnsConfigParcelWithDohOnly("doh.google" /* dohName */, + new String[0] /* dohIps */, "/dns-query{?dns}" /* dohPath */, + 443 /* dohPort */)); + + // Add some DNS servers. Verify the callback. + assertTrue(lp.addDnsServer(InetAddress.parseNumericAddress("192.0.2.100"))); + assertTrue(lp.addDnsServer(InetAddress.parseNumericAddress("2001:db8::100"))); + wnm.notifyLinkPropertiesChanged(lp); + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyPrivateDnsConfigResolved( + matchPrivateDnsConfigParcelWithDohOnly("doh.google" /* dohName */, + new String[] { "192.0.2.100", "2001:db8::100" } /* dohIps */, + "/dns-query{?dns}" /* dohPath */, 443 /* dohPort */)); + + // Verify that the callback is not fired if there is no DNS servers change. + // The number of the invoke callbacks remains 2. + wnm.notifyLinkPropertiesChanged(lp); + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(2)) + .notifyPrivateDnsConfigResolved(any()); + + // Remove a DNS server. Verify the callback. + assertTrue(lp.removeDnsServer(InetAddress.parseNumericAddress("2001:db8::100"))); + wnm.notifyLinkPropertiesChanged(lp); + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyPrivateDnsConfigResolved( + matchPrivateDnsConfigParcelWithDohOnly("doh.google" /* dohName */, + new String[] { "192.0.2.100" } /* dohIps */, + "/dns-query{?dns}" /* dohPath */, 443 /* DohPort */)); + } + + @Test + public void testPrivateDnsDiscoveryWithDdr_privateDnsModeChange() throws Exception { + setDdrEnabledForTest(); + final String svcb1 = "1 some.dot.name alpn=dot ipv4hint=192.0.1.100"; + final String svcb2 = "1 some.doh.name alpn=h3 port=443 ipv4hint=192.0.2.1,192.0.2.100 " + + "ipv6hint=2001:db8::1,2001:db8::100 dohpath=/dns-query{?dns}"; + setStatus(mHttpsConnection, 204); + setStatus(mHttpConnection, 204); + mFakeDns.setAnswer("_dns.resolver.arpa", new String[] { svcb2 }, TYPE_SVCB); + mFakeDns.setAnswer("_dns.dot.google", new String[] { svcb1 }, TYPE_SVCB); + mFakeDns.setAnswer("_dns.doh.google", new String[] { svcb2 }, TYPE_SVCB); + mFakeDns.setAnswer("dot.google", new String[] { "2001:db8::853" }, TYPE_AAAA); + mFakeDns.setAnswer("doh.google", new String[] { "2001:db8::854" }, TYPE_AAAA); + + WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor(); + wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig(true)); + notifyNetworkConnected(wnm, CELL_NOT_METERED_CAPABILITIES); + verifyNetworkTestedValidFromHttps(1); + // The network just got connected. Verify the callback. + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyPrivateDnsConfigResolved( + matchPrivateDnsConfigParcelWithDohOnly("some.doh.name" /* dohName */, + new String[0] /* dohIps */, "/dns-query{?dns}" /* dohPath */, + 443 /* dohPort */)); + + // Change the mode to off mode. The callback is not fired. + // The number of invoked callbacks remains 1. + wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig(false)); + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)) + .notifyPrivateDnsConfigResolved(any()); + + // Change the mode to opportunistic mode. Verify the callback. + wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig(true)); + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyPrivateDnsConfigResolved( + matchPrivateDnsConfigParcelWithDohOnly("some.doh.name" /* dohName */, + new String[0] /* dohIps */, "/dns-query{?dns}" /* dohPath */, + 443 /* dohPort */)); + + // Change the mode to strict mode. Verify the callback. + wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dot.google", new InetAddress[0])); + verifyNetworkTestedValidFromPrivateDns(1); + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).atLeast(1)).notifyPrivateDnsConfigResolved( + matchPrivateDnsConfigParcelWithDotOnly("dot.google" /* hostname */, + new String[] { "2001:db8::853" } /* dotIps */)); + + // Change the hostname of the setting. Verify the callback. + wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("doh.google", new InetAddress[0])); + verifyNetworkTestedValidFromPrivateDns(2); + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).atLeast(1)).notifyPrivateDnsConfigResolved( + matchPrivateDnsConfigParcel("doh.google" /* hostname */, + new String[] { "2001:db8::854" } /* dotIps */, + "doh.google" /* dohName */, + new String[] { "192.0.2.1", "192.0.2.100", "2001:db8::1", + "2001:db8::100" } /* dohIps */, + "/dns-query{?dns}" /* dohPath */, 443 /* dohPort */)); + } + + @Test + public void testPrivateDnsDiscoveryWithDdr_h3NotSupported() throws Exception { + setDdrEnabledForTest(); + final String svcb = "1 doh.google alpn=h2 ipv4hint=192.0.2.100 dohpath=/dns-query{?dns}"; + setStatus(mHttpsConnection, 204); + setStatus(mHttpConnection, 204); + mFakeDns.setAnswer("_dns.resolver.arpa", new String[] { svcb }, TYPE_SVCB); + mFakeDns.setAnswer("_dns.dns.google", new String[] { svcb }, TYPE_SVCB); + mFakeDns.setAnswer("dns.google", new String[] { "2001:db8::853" }, TYPE_AAAA); + + WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor(); + wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0])); + notifyNetworkConnected(wnm, CELL_NOT_METERED_CAPABILITIES); + verifyNetworkTestedValidFromPrivateDns(1); + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).atLeast(1)).notifyPrivateDnsConfigResolved( + matchPrivateDnsConfigParcelWithDotOnly("dns.google" /* hostname */, + new String[] { "2001:db8::853" } /* dotIps */)); + } + + @Test + public void testPrivateDnsDiscoveryWithDdr_svcbLookupError() throws Exception { + setDdrEnabledForTest(); + setStatus(mHttpsConnection, 204); + setStatus(mHttpConnection, 204); + mFakeDns.setAnswer("dns.google", new String[] { "2001:db8::1" }, TYPE_AAAA); + mFakeDns.setAnswer("_dns.dns.google", () -> { + throw mock(DnsResolver.DnsException.class); }, TYPE_SVCB); + mFakeDns.setAnswer("_dns.resolver.arpa", () -> { + throw mock(DnsResolver.DnsException.class); }, TYPE_SVCB); + + // In opportunistic mode, DoH is not used if the SVCB lookup fails or times out. + WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor(); + wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig(true)); + notifyNetworkConnected(wnm, CELL_NOT_METERED_CAPABILITIES); + verifyNetworkTestedValidFromHttps(1); + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS)).notifyPrivateDnsConfigResolved( + matchPrivateDnsConfigParcelWithDotOnly("" /* hostname */, + new String[] {} /* dotIps */)); + + // In strict mode, DoH not used if the SVCB lookup fails or times out. + wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0])); + verifyNetworkTestedValidFromPrivateDns(1); + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyPrivateDnsConfigResolved( + matchPrivateDnsConfigParcelWithDotOnly("dns.google" /* hostname */, + new String[] { "2001:db8::1" } /* dotIps */)); + } + + @Test + public void testPrivateDnsDiscoveryWithDdr_retryForReevaluation() throws Exception { + setDdrEnabledForTest(); + final String svcb = "1 doh.google alpn=h3 ipv4hint=192.0.2.100 dohpath=/dns-query{?dns}"; + setStatus(mHttpsConnection, 204); + setStatus(mHttpConnection, 204); + mFakeDns.setAnswer("_dns.resolver.arpa", new String[] { svcb }, TYPE_SVCB); + mFakeDns.setAnswer("_dns.dns.google", new String[] { svcb }, TYPE_SVCB); + mFakeDns.setAnswer("dns.google", new String[] { "2001:db8::853" }, TYPE_AAAA); + + // Verify the callback for opportunistic mode. + WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor(); + wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig(true)); + notifyNetworkConnected(wnm, CELL_NOT_METERED_CAPABILITIES); + verifyNetworkTestedValidFromHttps(1); + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyPrivateDnsConfigResolved( + matchPrivateDnsConfigParcelWithDohOnly("doh.google" /* dohName */, + new String[0] /* dohIps */, "/dns-query{?dns}" /* dohPath */, + -1 /* dohPort */)); + + // Re-evaluation triggers DDR even in opportunistic mode. + wnm.forceReevaluation(Process.myUid()); + verifyNetworkTestedValidFromHttps(2); + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(2)) + .notifyPrivateDnsConfigResolved(any()); + + // Verify the callback for strict mode. + wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0])); + verifyNetworkTestedValidFromPrivateDns(1); + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).atLeast(1)).notifyPrivateDnsConfigResolved( + matchPrivateDnsConfigParcel("dns.google" /* hostname */, + new String[] { "2001:db8::853" } /* dotIps */, "dns.google" /* dohName */, + new String[] { "192.0.2.100" } /* dohIps */, + "/dns-query{?dns}" /* dohPath */, -1 /* dohPort */)); + + // Reevaluation triggers DDR. + wnm.forceReevaluation(Process.myUid()); + verifyNetworkTestedValidFromPrivateDns(2); + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).atLeastOnce()) + .notifyPrivateDnsConfigResolved(matchPrivateDnsConfigParcel( + "dns.google" /* hostname */, new String[] { "2001:db8::853" } /* dotIps */, + "dns.google" /* dohName */, new String[] { "192.0.2.100" } /* dohIps */, + "/dns-query{?dns}" /* dohPath */, -1 /* dohPort */)); + } + @Test public void testReevaluationInterval_networkResume() throws Exception { // Setup nothing and expect validation to fail. @@ -2933,30 +3106,21 @@ @Test public void testDismissPortalInValidatedNetworkEnabledOsSupported() throws Exception { - assumeTrue(ShimUtils.isAtLeastR()); testDismissPortalInValidatedNetworkEnabled(TEST_LOGIN_URL, TEST_LOGIN_URL); } @Test public void testDismissPortalInValidatedNetworkEnabledOsSupported_NullLocationUrl() throws Exception { - assumeTrue(ShimUtils.isAtLeastR()); testDismissPortalInValidatedNetworkEnabled(TEST_HTTP_URL, null /* locationUrl */); } @Test public void testDismissPortalInValidatedNetworkEnabledOsSupported_InvalidLocationUrl() throws Exception { - assumeTrue(ShimUtils.isAtLeastR()); testDismissPortalInValidatedNetworkEnabled(TEST_HTTP_URL, TEST_RELATIVE_URL); } - @Test - public void testDismissPortalInValidatedNetworkEnabledOsNotSupported() throws Exception { - assumeFalse(ShimUtils.isAtLeastR()); - testDismissPortalInValidatedNetworkEnabled(TEST_HTTP_URL, TEST_LOGIN_URL); - } - private void testDismissPortalInValidatedNetworkEnabled(String expectedUrl, String locationUrl) throws Exception { setSslException(mHttpsConnection); @@ -3054,11 +3218,11 @@ mFakeDns.setAnswer("www.google.com", () -> { // Make sure the DNS probes take at least 1ms SystemClock.sleep(1); - return List.of("2001:db8::443"); + return new String[]{"2001:db8::443"}; }, TYPE_AAAA); mFakeDns.setAnswer(PRIVATE_DNS_PROBE_HOST_SUFFIX, () -> { SystemClock.sleep(1); - return List.of("2001:db8::444"); + return new String[]{"2001:db8::444"}; }, TYPE_AAAA); setStatus(mHttpsConnection, 204); setStatus(mHttpConnection, 204); @@ -3134,14 +3298,14 @@ @Test public void testLegacyConnectivityLog_SyncDns() throws Exception { doReturn(false).when(mDependencies).isFeatureEnabled( - any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); + any(), eq(NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); doLegacyConnectivityLogTest(); } @Test public void testLegacyConnectivityLog_AsyncDns() throws Exception { doReturn(true).when(mDependencies).isFeatureEnabled( - any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); + any(), eq(NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); doLegacyConnectivityLogTest(); } @@ -3659,6 +3823,26 @@ return argThat(p -> (p.detectionMethod & ConstantsShim.DETECTION_METHOD_TCP_METRICS) != 0); } + private PrivateDnsConfigParcel matchPrivateDnsConfigParcelWithDohOnly(String dohName, + String[] dohIps, String dohPath, int dohPort) { + return matchPrivateDnsConfigParcel("", new String[0], dohName, dohIps, dohPath, dohPort); + } + + private PrivateDnsConfigParcel matchPrivateDnsConfigParcelWithDotOnly(String hostname, + String[] dotIps) { + return matchPrivateDnsConfigParcel(hostname, dotIps, "", new String[0], "", -1); + } + + private PrivateDnsConfigParcel matchPrivateDnsConfigParcel(String hostname, + String[] dotIps, String dohName, String[] dohIps, String dohPath, int dohPort) { + final int mode = TextUtils.isEmpty(hostname) + ? PRIVATE_DNS_MODE_OPPORTUNISTIC : PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; + return argThat(p -> (p.privateDnsMode == mode && p.hostname.equals(hostname) + && Arrays.equals(p.ips, dotIps) && p.dohName.equals(dohName) + && p.dohPath.equals(dohPath) && Arrays.equals(p.dohIps, dohIps) + && p.dohPort == dohPort)); + } + private void assertCaptivePortalAppReceiverRegistered(boolean isPortal) { // There will be configuration change receiver registered after NetworkMonitor being // started. If captive portal app receiver is registered, then the size of the registered