Snap for 11285496 from ae8ff033eaec89acf21d9641357d91bbd37227f6 to mainline-configinfrastructure-release Change-Id: I5ed4b5f30a04a129e105ee4bd9061851c598a0c4
diff --git a/Android.bp b/Android.bp index 40bf3ce..a7f57ae 100644 --- a/Android.bp +++ b/Android.bp
@@ -58,6 +58,7 @@ name: "NetworkStackNextEnableDefaults", enabled: true, } + // This is a placeholder comment to avoid merge conflicts // as the above target may have different "enabled" values // depending on the branch @@ -72,7 +73,7 @@ "framework-connectivity-t", "framework-statsd", "framework-wifi", - ] + ], } // Common defaults for NetworkStack integration tests, root tests and coverage tests @@ -85,15 +86,15 @@ java_defaults { name: "NetworkStackReleaseApiLevel", - defaults:["NetworkStackReleaseTargetSdk"], + defaults: ["NetworkStackReleaseTargetSdk"], sdk_version: module_34_version, libs: [ "framework-configinfrastructure", - "framework-connectivity", + "framework-connectivity.stubs.module_lib", "framework-connectivity-t", "framework-statsd", "framework-wifi", - ] + ], } // Libraries for the API shims @@ -103,12 +104,12 @@ "androidx.annotation_annotation", "networkstack-aidl-latest", ], - static_libs : [ - "modules-utils-build_system" + static_libs: [ + "modules-utils-build_system", ], apex_available: [ "com.android.tethering", - "//apex_available:platform", // For InProcessNetworkStack + "//apex_available:platform", // For InProcessNetworkStack ], min_sdk_version: "30", } @@ -124,6 +125,9 @@ srcs: ["apishim/common/**/*.java"], sdk_version: "system_current", visibility: ["//visibility:private"], + lint: { + baseline_filename: "lint-baseline.xml", + }, } // Each level of the shims (29, 30, ...) is its own java_library compiled against the corresponding @@ -137,6 +141,9 @@ ], sdk_version: "system_29", visibility: ["//visibility:private"], + lint: { + baseline_filename: "lint-baseline.xml", + }, } java_library { @@ -153,6 +160,7 @@ visibility: ["//visibility:private"], lint: { strict_updatability_linting: true, + baseline_filename: "lint-baseline.xml", }, } @@ -176,6 +184,9 @@ ], sdk_version: "module_31", visibility: ["//visibility:private"], + lint: { + baseline_filename: "lint-baseline.xml", + }, } java_library { @@ -193,10 +204,12 @@ "framework-connectivity", "framework-connectivity-t.stubs.module_lib", "framework-tethering", - "android.net.ipsec.ike.stubs.module_lib", ], sdk_version: "module_33", visibility: ["//visibility:private"], + lint: { + baseline_filename: "lint-baseline.xml", + }, } java_library { @@ -215,10 +228,12 @@ "framework-connectivity", "framework-connectivity-t.stubs.module_lib", "framework-tethering", - "android.net.ipsec.ike.stubs.module_lib" ], sdk_version: module_34_version, visibility: ["//visibility:private"], + lint: { + baseline_filename: "lint-baseline.xml", + }, } // Shims for APIs being added to the current development version of Android. These APIs are not @@ -230,7 +245,10 @@ // are part of the stable shims and scanned when generating jarjar rules. java_library { name: "NetworkStackApi35Shims", - defaults: ["NetworkStackShimsDefaults", "ConnectivityNextEnableDefaults"], + defaults: [ + "NetworkStackShimsDefaults", + "ConnectivityNextEnableDefaults", + ], srcs: [ "apishim/35/**/*.java", ], @@ -245,10 +263,13 @@ "framework-connectivity", "framework-connectivity-t.stubs.module_lib", "framework-tethering", - "android.net.ipsec.ike.stubs.module_lib" + "android.net.ipsec.ike.stubs.module_lib", ], sdk_version: "module_current", visibility: ["//visibility:private"], + lint: { + baseline_filename: "lint-baseline.xml", + }, } // API current uses the API current shims directly. @@ -276,6 +297,9 @@ "//packages/modules/Connectivity/service-t", "//packages/modules/Connectivity/tests:__subpackages__", ], + lint: { + baseline_filename: "lint-baseline.xml", + }, } // API stable uses jarjar to rename the latest stable apishim package from @@ -283,7 +307,10 @@ // the networkstack code. java_library { name: "NetworkStackApiStableShims", - defaults: ["NetworkStackShimsDefaults", "NetworkStackReleaseApiLevel"], + defaults: [ + "NetworkStackShimsDefaults", + "NetworkStackReleaseApiLevel", + ], static_libs: [ "NetworkStackShimsCommon", "NetworkStackApi29Shims", @@ -299,6 +326,9 @@ "//packages/modules/Connectivity/service-t", "//packages/modules/Connectivity/tests:__subpackages__", ], + lint: { + baseline_filename: "lint-baseline.xml", + }, } // Common defaults for android libraries containing network stack code, used to compile variants of @@ -307,13 +337,13 @@ name: "NetworkStackAndroidLibraryDefaults", srcs: [ ":framework-networkstack-shared-srcs", - ":services-connectivity-shared-srcs", ], libs: ["unsupportedappusage"], static_libs: [ "androidx.annotation_annotation", "modules-utils-build_system", "modules-utils-preconditions", + "modules-utils-shell-command-handler", "modules-utils-statemachine", "netd_aidl_interface-lateststable-java", "networkstack-client", @@ -322,9 +352,9 @@ "datastallprotosnano", "statsprotos", "captiveportal-lib", - "net-utils-device-common", "net-utils-device-common-ip", "net-utils-device-common-netlink", + "net-utils-device-common-struct", ], } @@ -338,9 +368,11 @@ ], srcs: [ "src/**/*.java", - ":statslog-networkstack-java-gen-current" + ":statslog-networkstack-java-gen-current", ], - static_libs: ["NetworkStackApiCurrentShims"], + static_libs: [ + "NetworkStackApiCurrentShims", + ], manifest: "AndroidManifestBase.xml", visibility: [ "//frameworks/base/tests/net/integration", @@ -348,17 +380,25 @@ "//packages/modules/NetworkStack/tests/unit", "//packages/modules/NetworkStack/tests/integration", ], - lint: { strict_updatability_linting: true }, + lint: { + strict_updatability_linting: true, + baseline_filename: "lint-baseline.xml", + }, } android_library { name: "NetworkStackApiStableLib", - defaults: ["NetworkStackReleaseApiLevel", "NetworkStackAndroidLibraryDefaults"], + defaults: [ + "NetworkStackReleaseApiLevel", + "NetworkStackAndroidLibraryDefaults", + ], srcs: [ "src/**/*.java", ":statslog-networkstack-java-gen-stable", ], - static_libs: ["NetworkStackApiStableShims"], + static_libs: [ + "NetworkStackApiStableShims", + ], manifest: "AndroidManifestBase.xml", visibility: [ "//frameworks/base/packages/Connectivity/tests/integration", @@ -368,7 +408,10 @@ "//packages/modules/NetworkStack/tests/unit", "//packages/modules/NetworkStack/tests/integration", ], - lint: { strict_updatability_linting: true }, + lint: { + strict_updatability_linting: true, + baseline_filename: "lint-baseline.xml", + }, } java_library { @@ -386,10 +429,17 @@ "modules-utils-build", "net-utils-framework-common", "networkstack-client", + "net-utils-device-common", ], // If this library is ever used outside of tests, it should not use "Dhcp*Packet", and specify // its contents explicitly. - visibility: ["//packages/modules/Connectivity/tests/cts/net"], + visibility: [ + "//packages/modules/Connectivity/Tethering/tests/integration", + "//packages/modules/Connectivity/tests/cts/net", + ], + lint: { + baseline_filename: "lint-baseline.xml", + }, } java_genrule { @@ -449,12 +499,18 @@ certificate: "platform", manifest: "AndroidManifest_InProcess.xml", // InProcessNetworkStack is a replacement for NetworkStack - overrides: ["NetworkStack", "NetworkStackNext"], + overrides: [ + "NetworkStack", + "NetworkStackNext", + ], // The InProcessNetworkStack goes together with the PlatformCaptivePortalLogin, which replaces // the default CaptivePortalLogin. required: [ "PlatformCaptivePortalLogin", ], + lint: { + baseline_filename: "lint-baseline.xml", + }, } // Pre-merge the AndroidManifest for NetworkStackNext, so that its manifest can be merged on top @@ -466,7 +522,10 @@ "ConnectivityNextEnableDefaults", ], static_libs: ["NetworkStackApiCurrentLib"], - manifest: "AndroidManifest.xml" + manifest: "AndroidManifest.xml", + lint: { + baseline_filename: "lint-baseline.xml", + }, } // NetworkStack build targeting the current API release, for testing on in-development SDK @@ -484,13 +543,19 @@ "privapp_whitelist_com.android.networkstack", ], updatable: true, - lint: { strict_updatability_linting: true }, + lint: { + strict_updatability_linting: true, + baseline_filename: "lint-baseline.xml", + }, } // Updatable network stack for finalized API android_app { name: "NetworkStack", - defaults: ["NetworkStackAppDefaults", "NetworkStackReleaseApiLevel"], + defaults: [ + "NetworkStackAppDefaults", + "NetworkStackReleaseApiLevel", + ], static_libs: ["NetworkStackApiStableLib"], certificate: "networkstack", manifest: "AndroidManifest.xml", @@ -498,13 +563,19 @@ "privapp_whitelist_com.android.networkstack", ], updatable: true, - lint: { strict_updatability_linting: true }, + lint: { + strict_updatability_linting: true, + baseline_filename: "lint-baseline.xml", + }, } cc_library_shared { name: "libnetworkstackutilsjni", srcs: [ - "jni/network_stack_utils_jni.cpp" + "jni/network_stack_utils_jni.cpp", + ], + header_libs: [ + "bpf_headers", ], sdk_version: "30", min_sdk_version: "30", @@ -539,8 +610,8 @@ name: "statslog-networkstack-java-gen-current", tools: ["stats-log-api-gen"], cmd: "$(location stats-log-api-gen) --java $(out) --module network_stack" + - " --javaPackage com.android.networkstack.metrics --javaClass NetworkStackStatsLog" + - " --minApiLevel 30", + " --javaPackage com.android.networkstack.metrics --javaClass NetworkStackStatsLog" + + " --minApiLevel 30", out: ["com/android/networkstack/metrics/NetworkStackStatsLog.java"], } @@ -548,12 +619,11 @@ name: "statslog-networkstack-java-gen-stable", tools: ["stats-log-api-gen"], cmd: "$(location stats-log-api-gen) --java $(out) --module network_stack" + - " --javaPackage com.android.networkstack.metrics --javaClass NetworkStackStatsLog" + - " --minApiLevel 30 --compileApiLevel 30", + " --javaPackage com.android.networkstack.metrics --javaClass NetworkStackStatsLog" + + " --minApiLevel 30 --compileApiLevel 30", out: ["com/android/networkstack/metrics/NetworkStackStatsLog.java"], } - version_code_networkstack_next = "300000000" version_code_networkstack_test = "999999999" @@ -561,21 +631,27 @@ name: "NetworkStackTestAndroidManifest", srcs: ["AndroidManifest.xml"], out: ["TestAndroidManifest.xml"], - cmd: "sed -E 's/versionCode=\"[0-9]+\"/versionCode=\"" - + version_code_networkstack_test - + "\"/' $(in) > $(out)", + cmd: "sed -E 's/versionCode=\"[0-9]+\"/versionCode=\"" + + version_code_networkstack_test + + "\"/' $(in) > $(out)", visibility: ["//visibility:private"], } android_app { name: "TestNetworkStack", - defaults: ["NetworkStackAppDefaults", "NetworkStackReleaseApiLevel"], + defaults: [ + "NetworkStackAppDefaults", + "NetworkStackReleaseApiLevel", + ], static_libs: ["NetworkStackApiStableLib"], certificate: "networkstack", manifest: ":NetworkStackTestAndroidManifest", required: [ "privapp_whitelist_com.android.networkstack", ], + lint: { + baseline_filename: "lint-baseline.xml", + }, } // When adding or modifying protos, the jarjar rules and possibly proguard rules need @@ -592,4 +668,7 @@ "networkstackprotos", ], defaults: ["NetworkStackReleaseApiLevel"], + lint: { + baseline_filename: "lint-baseline.xml", + }, }
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 259a403..a65d0d5 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml
@@ -45,6 +45,10 @@ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> <!-- Signature permission defined in NetworkStackStub --> <uses-permission android:name="android.permission.MAINLINE_NETWORK_STACK" /> + <!-- Crash while reading deviceownerName security exception --> + <uses-permission android:name="android.permission.MANAGE_USERS"/> + <!-- ends here --> + <application android:extractNativeLibs="false" android:persistent="true"
diff --git a/OWNERS b/OWNERS index 62c5737..b0e134e 100644 --- a/OWNERS +++ b/OWNERS
@@ -1,2 +1,3 @@ +# Bug component: 31808 set noparent -file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking +file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking
diff --git a/TEST_MAPPING b/TEST_MAPPING index 3869fc9..46ade58 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING
@@ -7,7 +7,12 @@ "name": "NetworkStackNextTests" }, { - "name": "NetworkStackIntegrationTests" + "name": "NetworkStackIntegrationTests", + "options": [ + { + "exclude-annotation": "com.android.testutils.SkipPresubmit" + } + ] }, { "name": "NetworkStackRootTests" @@ -32,6 +37,11 @@ "name": "NetworkStackRootTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk]" } ], + "postsubmit": [ + { + "name": "NetworkStackIntegrationTests" + } + ], "imports": [ { "path": "packages/modules/Connectivity"
diff --git a/apishim/29/com/android/networkstack/apishim/api29/NsdShimImpl.java b/apishim/29/com/android/networkstack/apishim/api29/NsdShimImpl.java deleted file mode 100644 index ac0c69a..0000000 --- a/apishim/29/com/android/networkstack/apishim/api29/NsdShimImpl.java +++ /dev/null
@@ -1,96 +0,0 @@ -/* - * Copyright (C) 2022 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.Network; -import android.net.NetworkRequest; -import android.net.nsd.NsdManager; -import android.net.nsd.NsdServiceInfo; -import android.os.Build; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; - -import com.android.networkstack.apishim.common.NsdShim; -import com.android.networkstack.apishim.common.UnsupportedApiLevelException; - -import java.util.concurrent.Executor; - -/** - * Implementation of {@link NsdShim}. - */ -@RequiresApi(Build.VERSION_CODES.Q) -public class NsdShimImpl implements NsdShim { - - /** - * Get a new instance of {@link NsdShim}. - */ - public static NsdShim newInstance() { - return new NsdShimImpl(); - } - - @Nullable - @Override - public Network getNetwork(@NonNull NsdServiceInfo serviceInfo) { - // NsdServiceInfo has no Network before T - return null; - } - - @Override - public void setNetwork(@NonNull NsdServiceInfo serviceInfo, @Nullable Network network) { - // No-op: NsdServiceInfo has no Network before T - } - - @Override - public void registerService(@NonNull NsdManager nsdManager, @NonNull NsdServiceInfo serviceInfo, - int protocolType, @NonNull Executor executor, - @NonNull NsdManager.RegistrationListener listener) throws UnsupportedApiLevelException { - throw new UnsupportedApiLevelException("Register with an executor is only supported on T+"); - } - - @Override - public void discoverServices(@NonNull NsdManager nsdManager, @NonNull String serviceType, - int protocolType, @Nullable Network network, - @NonNull Executor executor, @NonNull NsdManager.DiscoveryListener listener) - throws UnsupportedApiLevelException { - throw new UnsupportedApiLevelException("Discover on network is only supported on T+"); - } - - @Override - public void discoverServices(@NonNull NsdManager nsdManager, @NonNull String serviceType, - int protocolType, @Nullable NetworkRequest request, - @NonNull Executor executor, @NonNull NsdManager.DiscoveryListener listener) - throws UnsupportedApiLevelException { - throw new UnsupportedApiLevelException( - "Discover with NetworkRequest is only supported on T+"); - } - - @Override - public void resolveService(@NonNull NsdManager nsdManager, @NonNull NsdServiceInfo serviceInfo, - @NonNull Executor executor, @NonNull NsdManager.ResolveListener resolveListener) - throws UnsupportedApiLevelException { - throw new UnsupportedApiLevelException("Resolve with executor is only supported on T+"); - } - - @Override - public void stopServiceResolution(@NonNull NsdManager nsdManager, - @NonNull NsdManager.ResolveListener resolveListener) - throws UnsupportedApiLevelException { - throw new UnsupportedApiLevelException("Stop service resolution is only supported on U+"); - } -}
diff --git a/apishim/30/com/android/networkstack/apishim/api30/Ikev2VpnProfileBuilderShimImpl.java b/apishim/30/com/android/networkstack/apishim/api30/Ikev2VpnProfileBuilderShimImpl.java deleted file mode 100644 index 2597840..0000000 --- a/apishim/30/com/android/networkstack/apishim/api30/Ikev2VpnProfileBuilderShimImpl.java +++ /dev/null
@@ -1,122 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.networkstack.apishim.api30; - -import android.net.Ikev2VpnProfile; -import android.net.ProxyInfo; -import android.os.Build; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; - -import com.android.networkstack.apishim.common.Ikev2VpnProfileBuilderShim; -import com.android.networkstack.apishim.common.Ikev2VpnProfileShim; -import com.android.networkstack.apishim.common.UnsupportedApiLevelException; - -import java.security.PrivateKey; -import java.security.cert.X509Certificate; -import java.util.List; - -/** - * Implementation of Ikev2VpnProfileBuilderShim for API 30. - */ -@RequiresApi(Build.VERSION_CODES.R) -public class Ikev2VpnProfileBuilderShimImpl - implements Ikev2VpnProfileBuilderShim<Ikev2VpnProfile.Builder> { - protected final Ikev2VpnProfile.Builder mBuilder; - - protected Ikev2VpnProfileBuilderShimImpl(@NonNull Ikev2VpnProfile.Builder builder) { - mBuilder = builder; - } - - protected Ikev2VpnProfileBuilderShimImpl(@NonNull String serverAddr, - @NonNull String identity) { - mBuilder = new Ikev2VpnProfile.Builder(serverAddr, identity); - } - - /** - * Returns a new instance of this shim impl. - */ - public static Ikev2VpnProfileBuilderShim<Ikev2VpnProfile.Builder> newInstance( - @NonNull String serverAddr, @NonNull String identity) { - return new Ikev2VpnProfileBuilderShimImpl(serverAddr, identity); - } - - @Override - public Ikev2VpnProfileBuilderShim<Ikev2VpnProfile.Builder> setAuthPsk(@NonNull byte[] psk) { - mBuilder.setAuthPsk(psk); - return this; - } - - @Override - public Ikev2VpnProfileBuilderShim<Ikev2VpnProfile.Builder> setAuthUsernamePassword( - @NonNull String user, @NonNull String pass, @Nullable X509Certificate serverRootCa) - throws UnsupportedApiLevelException { - mBuilder.setAuthUsernamePassword(user, pass, serverRootCa); - return this; - } - - @Override - public Ikev2VpnProfileBuilderShim<Ikev2VpnProfile.Builder> setAuthDigitalSignature( - @NonNull X509Certificate userCert, @NonNull PrivateKey key, - @Nullable X509Certificate serverRootCa) { - mBuilder.setAuthDigitalSignature(userCert, key, serverRootCa); - return this; - } - - @Override - public Ikev2VpnProfileBuilderShim<Ikev2VpnProfile.Builder> setBypassable(boolean isBypassable) { - mBuilder.setBypassable(true); - return this; - } - - @Override - public Ikev2VpnProfileBuilderShim<Ikev2VpnProfile.Builder> setProxy(@Nullable ProxyInfo proxy) { - mBuilder.setProxy(proxy); - return this; - } - - @Override - public Ikev2VpnProfileBuilderShim<Ikev2VpnProfile.Builder> setMaxMtu(int mtu) { - mBuilder.setMaxMtu(mtu); - return this; - } - - @Override - public Ikev2VpnProfileBuilderShim<Ikev2VpnProfile.Builder> setMetered(boolean isMetered) { - mBuilder.setMetered(isMetered); - return this; - } - - @Override - public Ikev2VpnProfileBuilderShim<Ikev2VpnProfile.Builder> setAllowedAlgorithms( - @NonNull List<String> algorithmNames) { - mBuilder.setAllowedAlgorithms(algorithmNames); - return this; - } - - @Override - public Ikev2VpnProfile.Builder getBuilder() { - return mBuilder; - } - - @Override - public Ikev2VpnProfileShim build() { - return Ikev2VpnProfileShimImpl.newInstance(mBuilder.build()); - } -}
diff --git a/apishim/30/com/android/networkstack/apishim/api30/Ikev2VpnProfileShimImpl.java b/apishim/30/com/android/networkstack/apishim/api30/Ikev2VpnProfileShimImpl.java deleted file mode 100644 index 54a2a35..0000000 --- a/apishim/30/com/android/networkstack/apishim/api30/Ikev2VpnProfileShimImpl.java +++ /dev/null
@@ -1,46 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.networkstack.apishim.api30; - -import android.net.Ikev2VpnProfile; - -import androidx.annotation.NonNull; - -import com.android.networkstack.apishim.common.Ikev2VpnProfileShim; -/** - * Implementation of Ikev2VpnProfileShim for API 30. - */ -// TODO : when API29 is no longer supported, remove the type argument -public class Ikev2VpnProfileShimImpl implements Ikev2VpnProfileShim<Ikev2VpnProfile> { - protected final Ikev2VpnProfile mProfile; - - protected Ikev2VpnProfileShimImpl(Ikev2VpnProfile profile) { - mProfile = profile; - } - - /** - * Returns a new instance of this shim impl. - */ - public static Ikev2VpnProfileShim<Ikev2VpnProfile> newInstance( - @NonNull Ikev2VpnProfile profile) { - return new Ikev2VpnProfileShimImpl(profile); - } - - public Ikev2VpnProfile getProfile() { - return mProfile; - } -}
diff --git a/apishim/31/com/android/networkstack/apishim/api31/NsdShimImpl.java b/apishim/31/com/android/networkstack/apishim/api31/NsdShimImpl.java deleted file mode 100644 index 0e89b59..0000000 --- a/apishim/31/com/android/networkstack/apishim/api31/NsdShimImpl.java +++ /dev/null
@@ -1,24 +0,0 @@ -/* - * Copyright (C) 2022 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.api31; - -/** - * Implementation of {@link com.android.networkstack.apishim.common.NsdShim}. - */ -public class NsdShimImpl extends com.android.networkstack.apishim.api29.NsdShimImpl { - // Inherit everything from API29 shim -}
diff --git a/apishim/33/com/android/networkstack/apishim/api33/ConstantsShim.java b/apishim/33/com/android/networkstack/apishim/api33/ConstantsShim.java index c8b4edd..8b9d872 100644 --- a/apishim/33/com/android/networkstack/apishim/api33/ConstantsShim.java +++ b/apishim/33/com/android/networkstack/apishim/api33/ConstantsShim.java
@@ -42,4 +42,7 @@ public static final int DEFERRAL_POLICY_NONE = 1; // Constant defined in android.app.BroadcastOptions. public static final int DEFERRAL_POLICY_UNTIL_ACTIVE = 2; + // Const defined in android.Manifest.permission + public static final String REGISTER_NSD_OFFLOAD_ENGINE = + "android.permission.REGISTER_NSD_OFFLOAD_ENGINE"; }
diff --git a/apishim/33/com/android/networkstack/apishim/api33/Ikev2VpnProfileBuilderShimImpl.java b/apishim/33/com/android/networkstack/apishim/api33/Ikev2VpnProfileBuilderShimImpl.java deleted file mode 100644 index 308407b..0000000 --- a/apishim/33/com/android/networkstack/apishim/api33/Ikev2VpnProfileBuilderShimImpl.java +++ /dev/null
@@ -1,90 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.networkstack.apishim.api33; - -import android.net.Ikev2VpnProfile; -import android.net.ipsec.ike.IkeTunnelConnectionParams; -import android.os.Build; - -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; - -import com.android.modules.utils.build.SdkLevel; -import com.android.networkstack.apishim.common.Ikev2VpnProfileBuilderShim; -import com.android.networkstack.apishim.common.UnsupportedApiLevelException; - -/** - * A shim for Ikev2VpnProfile.Builder - */ -@RequiresApi(Build.VERSION_CODES.TIRAMISU) -public class Ikev2VpnProfileBuilderShimImpl - extends com.android.networkstack.apishim.api30.Ikev2VpnProfileBuilderShimImpl { - protected Ikev2VpnProfileBuilderShimImpl(@NonNull IkeTunnelConnectionParams params) { - super(new Ikev2VpnProfile.Builder(params)); - } - - protected Ikev2VpnProfileBuilderShimImpl(@NonNull String serverAddr, - @NonNull String identity) { - super(serverAddr, identity); - } - - /** - * Returns a new instance of this shim impl. - */ - @RequiresApi(Build.VERSION_CODES.R) - public static Ikev2VpnProfileBuilderShim<Ikev2VpnProfile.Builder> newInstance( - @NonNull String serverAddr, @NonNull String identity) { - if (SdkLevel.isAtLeastT()) { - return new Ikev2VpnProfileBuilderShimImpl(serverAddr, identity); - } - return com.android.networkstack.apishim.api30.Ikev2VpnProfileBuilderShimImpl - .newInstance(serverAddr, identity); - } - - /** - * Returns a new instance of this shim impl. - */ - @RequiresApi(Build.VERSION_CODES.TIRAMISU) - public static Ikev2VpnProfileBuilderShim<Ikev2VpnProfile.Builder> newInstance( - @NonNull IkeTunnelConnectionParams params) throws UnsupportedApiLevelException { - if (SdkLevel.isAtLeastT()) { - return new Ikev2VpnProfileBuilderShimImpl(params); - } else { - throw new UnsupportedApiLevelException("Only supported from API 33"); - } - } - - /** - * @see Ikev2VpnProfile.Builder#setRequiresInternetValidation(boolean) - */ - @Override - public Ikev2VpnProfileBuilderShim<Ikev2VpnProfile.Builder> setRequiresInternetValidation( - boolean requiresInternetValidation) { - mBuilder.setRequiresInternetValidation(requiresInternetValidation); - return this; - } - - /** - * @see Ikev2VpnProfile.Builder#setLocalRoutesExcluded(boolean) - */ - @Override - public Ikev2VpnProfileBuilderShim<Ikev2VpnProfile.Builder> setLocalRoutesExcluded( - boolean excludeLocalRoutes) { - mBuilder.setLocalRoutesExcluded(excludeLocalRoutes); - return this; - } -}
diff --git a/apishim/33/com/android/networkstack/apishim/api33/Ikev2VpnProfileShimImpl.java b/apishim/33/com/android/networkstack/apishim/api33/Ikev2VpnProfileShimImpl.java deleted file mode 100644 index 8e82218..0000000 --- a/apishim/33/com/android/networkstack/apishim/api33/Ikev2VpnProfileShimImpl.java +++ /dev/null
@@ -1,29 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.networkstack.apishim.api33; - -import android.net.Ikev2VpnProfile; - -/** - * A shim for Ikev2VpnProfile - */ -public class Ikev2VpnProfileShimImpl - extends com.android.networkstack.apishim.api30.Ikev2VpnProfileShimImpl { - protected Ikev2VpnProfileShimImpl(Ikev2VpnProfile profile) { - super(profile); - } -}
diff --git a/apishim/33/com/android/networkstack/apishim/api33/NsdShimImpl.java b/apishim/33/com/android/networkstack/apishim/api33/NsdShimImpl.java deleted file mode 100644 index 1f0907f..0000000 --- a/apishim/33/com/android/networkstack/apishim/api33/NsdShimImpl.java +++ /dev/null
@@ -1,93 +0,0 @@ -/* - * Copyright (C) 2022 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.api33; - -import android.net.Network; -import android.net.NetworkRequest; -import android.net.nsd.NsdManager; -import android.net.nsd.NsdServiceInfo; -import android.os.Build; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; - -import com.android.modules.utils.build.SdkLevel; -import com.android.networkstack.apishim.common.NsdShim; -import com.android.networkstack.apishim.common.UnsupportedApiLevelException; - -import java.util.concurrent.Executor; - -/** - * Implementation of {@link com.android.networkstack.apishim.common.NsdShim}. - */ -@RequiresApi(Build.VERSION_CODES.TIRAMISU) -public class NsdShimImpl extends com.android.networkstack.apishim.api31.NsdShimImpl { - - /** - * Get a new instance of {@link NsdShim}. - */ - @RequiresApi(Build.VERSION_CODES.Q) - public static NsdShim newInstance() { - if (SdkLevel.isAtLeastT()) { - return new NsdShimImpl(); - } else { - return new com.android.networkstack.apishim.api31.NsdShimImpl(); - } - } - - @Nullable - @Override - public Network getNetwork(@NonNull NsdServiceInfo serviceInfo) { - return serviceInfo.getNetwork(); - } - - @Override - public void setNetwork(@NonNull NsdServiceInfo serviceInfo, @Nullable Network network) { - serviceInfo.setNetwork(network); - } - - @Override - public void registerService(@NonNull NsdManager nsdManager, @NonNull NsdServiceInfo serviceInfo, - int protocolType, @NonNull Executor executor, - @NonNull NsdManager.RegistrationListener listener) { - nsdManager.registerService(serviceInfo, protocolType, executor, listener); - } - - @Override - public void discoverServices(@NonNull NsdManager nsdManager, @NonNull String serviceType, - int protocolType, @Nullable Network network, - @NonNull Executor executor, @NonNull NsdManager.DiscoveryListener listener) - throws UnsupportedApiLevelException { - nsdManager.discoverServices(serviceType, protocolType, network, executor, listener); - } - - @Override - public void discoverServices(@NonNull NsdManager nsdManager, @NonNull String serviceType, - int protocolType, @Nullable NetworkRequest request, - @NonNull Executor executor, @NonNull NsdManager.DiscoveryListener listener) - throws UnsupportedApiLevelException { - nsdManager.discoverServices(serviceType, protocolType, request, executor, listener); - } - - @Override - public void resolveService(@NonNull NsdManager nsdManager, @NonNull NsdServiceInfo serviceInfo, - @NonNull Executor executor, @NonNull NsdManager.ResolveListener resolveListener) - throws UnsupportedApiLevelException { - nsdManager.resolveService(serviceInfo, executor, resolveListener); - } -}
diff --git a/apishim/34/com/android/networkstack/apishim/api34/Ikev2VpnProfileBuilderShimImpl.java b/apishim/34/com/android/networkstack/apishim/api34/Ikev2VpnProfileBuilderShimImpl.java deleted file mode 100644 index ac74028..0000000 --- a/apishim/34/com/android/networkstack/apishim/api34/Ikev2VpnProfileBuilderShimImpl.java +++ /dev/null
@@ -1,92 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.networkstack.apishim.api34; - -import android.net.Ikev2VpnProfile; -import android.net.ipsec.ike.IkeTunnelConnectionParams; -import android.os.Build; - -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; - -import com.android.modules.utils.build.SdkLevel; -import com.android.networkstack.apishim.common.Ikev2VpnProfileBuilderShim; -import com.android.networkstack.apishim.common.UnsupportedApiLevelException; - -/** - * A shim for Ikev2VpnProfile.Builder - */ -@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) -public class Ikev2VpnProfileBuilderShimImpl - extends com.android.networkstack.apishim.api33.Ikev2VpnProfileBuilderShimImpl { - - protected Ikev2VpnProfileBuilderShimImpl(@NonNull IkeTunnelConnectionParams params) { - super(params); - } - - protected Ikev2VpnProfileBuilderShimImpl(@NonNull String serverAddr, - @NonNull String identity) { - super(serverAddr, identity); - } - - /** - * Returns a new instance of this shim impl. - */ - @RequiresApi(Build.VERSION_CODES.R) - public static Ikev2VpnProfileBuilderShim<Ikev2VpnProfile.Builder> newInstance( - @NonNull String serverAddr, @NonNull String identity) { - if (SdkLevel.isAtLeastU()) { - return new Ikev2VpnProfileBuilderShimImpl(serverAddr, identity); - } - return com.android.networkstack.apishim.api33.Ikev2VpnProfileBuilderShimImpl - .newInstance(serverAddr, identity); - } - - /** - * Returns a new instance of this shim impl. - */ - @RequiresApi(Build.VERSION_CODES.TIRAMISU) - public static Ikev2VpnProfileBuilderShim<Ikev2VpnProfile.Builder> newInstance( - @NonNull IkeTunnelConnectionParams params) throws UnsupportedApiLevelException { - if (SdkLevel.isAtLeastU()) { - return new Ikev2VpnProfileBuilderShimImpl(params); - } else { - return com.android.networkstack.apishim.api33.Ikev2VpnProfileBuilderShimImpl - .newInstance(params); - } - } - - /** - * @see Ikev2VpnProfile.Builder#setAutomaticIpVersionSelectionEnabled(boolean) - */ - @Override - public Ikev2VpnProfileBuilderShim<Ikev2VpnProfile.Builder> - setAutomaticIpVersionSelectionEnabled(boolean isEnabled) { - mBuilder.setAutomaticIpVersionSelectionEnabled(isEnabled); - return this; - } - - /** - * @see Ikev2VpnProfile.Builder#setAutomaticNattKeepaliveTimerEnabled(boolean) - */ - @Override - public Ikev2VpnProfileBuilderShim<Ikev2VpnProfile.Builder> - setAutomaticNattKeepaliveTimerEnabled(boolean isEnabled) { - mBuilder.setAutomaticNattKeepaliveTimerEnabled(isEnabled); - return this; - } -}
diff --git a/apishim/34/com/android/networkstack/apishim/api34/Ikev2VpnProfileShimImpl.java b/apishim/34/com/android/networkstack/apishim/api34/Ikev2VpnProfileShimImpl.java deleted file mode 100644 index 848bbd1..0000000 --- a/apishim/34/com/android/networkstack/apishim/api34/Ikev2VpnProfileShimImpl.java +++ /dev/null
@@ -1,62 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.networkstack.apishim.api34; - -import android.net.Ikev2VpnProfile; -import android.os.Build; - -import androidx.annotation.RequiresApi; - -import com.android.modules.utils.build.SdkLevel; -import com.android.networkstack.apishim.common.Ikev2VpnProfileShim; - -/** - * A shim for Ikev2VpnProfile - */ -@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) -public class Ikev2VpnProfileShimImpl - extends com.android.networkstack.apishim.api33.Ikev2VpnProfileShimImpl { - protected Ikev2VpnProfileShimImpl(Ikev2VpnProfile profile) { - super(profile); - } - /** - * Returns a new instance of this shim impl. - */ - @RequiresApi(Build.VERSION_CODES.R) - public static Ikev2VpnProfileShim<Ikev2VpnProfile> newInstance(Ikev2VpnProfile profile) { - if (SdkLevel.isAtLeastU()) { - return new Ikev2VpnProfileShimImpl(profile); - } else { - return com.android.networkstack.apishim.api33.Ikev2VpnProfileShimImpl - .newInstance(profile); - } - } - - /** - * @see Ikev2VpnProfile#isAutomaticIpVersionSelectionEnabled() - */ - public boolean isAutomaticIpVersionSelectionEnabled() { - return mProfile.isAutomaticIpVersionSelectionEnabled(); - } - - /** - * @see Ikev2VpnProfile#isAutomaticNattKeepaliveTimerEnabled() - */ - public boolean isAutomaticNattKeepaliveTimerEnabled() { - return mProfile.isAutomaticNattKeepaliveTimerEnabled(); - } -}
diff --git a/apishim/34/com/android/networkstack/apishim/api34/NsdShimImpl.java b/apishim/34/com/android/networkstack/apishim/api34/NsdShimImpl.java deleted file mode 100644 index fc37b30..0000000 --- a/apishim/34/com/android/networkstack/apishim/api34/NsdShimImpl.java +++ /dev/null
@@ -1,127 +0,0 @@ -/* - * Copyright (C) 2022 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.api34; - -import android.net.nsd.NsdManager; -import android.net.nsd.NsdServiceInfo; -import android.os.Build; -import android.util.ArrayMap; - -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; - -import com.android.modules.utils.build.SdkLevel; -import com.android.networkstack.apishim.common.NsdShim; -import com.android.networkstack.apishim.common.UnsupportedApiLevelException; - -import java.net.InetAddress; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.Executor; - -/** - * Implementation of {@link NsdShim}. - */ -@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) -public class NsdShimImpl extends com.android.networkstack.apishim.api33.NsdShimImpl { - private final Map<ServiceInfoCallbackShim, ServiceInfoCallbackWrapper> mCbWrappers = - Collections.synchronizedMap(new ArrayMap<>()); - - /** - * Get a new instance of {@link NsdShim}. - */ - @RequiresApi(Build.VERSION_CODES.Q) - public static NsdShim newInstance() { - if (SdkLevel.isAtLeastU()) { - return new NsdShimImpl(); - } else { - return new com.android.networkstack.apishim.api33.NsdShimImpl(); - } - } - - @Override - public void stopServiceResolution(@NonNull NsdManager nsdManager, - @NonNull NsdManager.ResolveListener resolveListener) - throws UnsupportedApiLevelException { - nsdManager.stopServiceResolution(resolveListener); - } - - private static class ServiceInfoCallbackWrapper implements NsdManager.ServiceInfoCallback { - @NonNull - final ServiceInfoCallbackShim mListener; - - ServiceInfoCallbackWrapper(@NonNull ServiceInfoCallbackShim listener) { - mListener = listener; - } - - @Override - public void onServiceInfoCallbackRegistrationFailed(int errorCode) { - mListener.onServiceInfoCallbackRegistrationFailed(errorCode); - } - - @Override - public void onServiceUpdated(@NonNull NsdServiceInfo serviceInfo) { - mListener.onServiceUpdated(serviceInfo); - } - - @Override - public void onServiceLost() { - mListener.onServiceLost(); - } - - @Override - public void onServiceInfoCallbackUnregistered() { - mListener.onServiceInfoCallbackUnregistered(); - } - }; - - @Override - public void registerServiceInfoCallback(@NonNull NsdManager nsdManager, - @NonNull NsdServiceInfo serviceInfo, @NonNull Executor executor, - @NonNull ServiceInfoCallbackShim listener) throws UnsupportedApiLevelException { - Objects.requireNonNull(listener); - final ServiceInfoCallbackWrapper wrapper = new ServiceInfoCallbackWrapper(listener); - if (null != mCbWrappers.put(listener, wrapper)) { - throw new IllegalArgumentException("Listener shims must not be reused"); - } - nsdManager.registerServiceInfoCallback(serviceInfo, executor, wrapper); - } - - @Override - public void unregisterServiceInfoCallback(@NonNull NsdManager nsdManager, - @NonNull ServiceInfoCallbackShim listener) throws UnsupportedApiLevelException { - final ServiceInfoCallbackWrapper wrapper = mCbWrappers.remove(listener); - if (wrapper == null) { - throw new IllegalArgumentException("Listener was not registered"); - } - nsdManager.unregisterServiceInfoCallback(wrapper); - } - - @NonNull - @Override - public List<InetAddress> getHostAddresses(@NonNull NsdServiceInfo serviceInfo) { - return serviceInfo.getHostAddresses(); - } - - @Override - public void setHostAddresses(@NonNull NsdServiceInfo serviceInfo, - @NonNull List<InetAddress> addresses) { - serviceInfo.setHostAddresses(addresses); - } -}
diff --git a/apishim/35/com/android/networkstack/apishim/ConstantsShim.java b/apishim/35/com/android/networkstack/apishim/ConstantsShim.java index 212352d..3f6a385 100644 --- a/apishim/35/com/android/networkstack/apishim/ConstantsShim.java +++ b/apishim/35/com/android/networkstack/apishim/ConstantsShim.java
@@ -21,6 +21,8 @@ import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; +import com.android.modules.utils.build.SdkLevel; + /** * Utility class for defining and importing constants from the Android platform. */ @@ -35,4 +37,11 @@ */ @VisibleForTesting public static final int VERSION = 35; + + // When building against the latest shims but running on U (for example building from main + // and running mainline tests), this shim class will be used. The linter wouldn't be happy + // about the newer constant being used without a SDK check. + public static final String REGISTER_NSD_OFFLOAD_ENGINE = + SdkLevel.isAtLeastV() ? android.Manifest.permission.REGISTER_NSD_OFFLOAD_ENGINE + : com.android.networkstack.apishim.api34.ConstantsShim.REGISTER_NSD_OFFLOAD_ENGINE; }
diff --git a/apishim/35/com/android/networkstack/apishim/Ikev2VpnProfileBuilderShimImpl.java b/apishim/35/com/android/networkstack/apishim/Ikev2VpnProfileBuilderShimImpl.java deleted file mode 100644 index 486063f..0000000 --- a/apishim/35/com/android/networkstack/apishim/Ikev2VpnProfileBuilderShimImpl.java +++ /dev/null
@@ -1,36 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.networkstack.apishim; - -import android.net.ipsec.ike.IkeTunnelConnectionParams; -import android.os.Build; - -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; - -/** - * A shim for Ikev2VpnProfile.Builder - */ -// TODO: when available in all active branches: @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) -@RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT) -public class Ikev2VpnProfileBuilderShimImpl - extends com.android.networkstack.apishim.api34.Ikev2VpnProfileBuilderShimImpl { - // Currently identical to the API 34 shim, so inherit everything - protected Ikev2VpnProfileBuilderShimImpl(@NonNull IkeTunnelConnectionParams params) { - super(params); - } -}
diff --git a/apishim/35/com/android/networkstack/apishim/Ikev2VpnProfileShimImpl.java b/apishim/35/com/android/networkstack/apishim/Ikev2VpnProfileShimImpl.java deleted file mode 100644 index bc40e16..0000000 --- a/apishim/35/com/android/networkstack/apishim/Ikev2VpnProfileShimImpl.java +++ /dev/null
@@ -1,35 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.networkstack.apishim; - -import android.net.Ikev2VpnProfile; -import android.os.Build; - -import androidx.annotation.RequiresApi; - -/** - * A shim for Ikev2VpnProfile - */ -// TODO: when available in all active branches: @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) -@RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT) -public class Ikev2VpnProfileShimImpl - extends com.android.networkstack.apishim.api34.Ikev2VpnProfileShimImpl { - // Currently identical to the API 34 shim, so inherit everything - protected Ikev2VpnProfileShimImpl(Ikev2VpnProfile profile) { - super(profile); - } -}
diff --git a/apishim/35/com/android/networkstack/apishim/NsdShimImpl.java b/apishim/35/com/android/networkstack/apishim/NsdShimImpl.java deleted file mode 100644 index 4b992ee..0000000 --- a/apishim/35/com/android/networkstack/apishim/NsdShimImpl.java +++ /dev/null
@@ -1,32 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.networkstack.apishim; - -import android.os.Build; - -import androidx.annotation.RequiresApi; - -import com.android.networkstack.apishim.common.NsdShim; - -/** - * Implementation of {@link NsdShim}. - */ -// TODO: when available in all active branches: @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) -@RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT) -public class NsdShimImpl extends com.android.networkstack.apishim.api34.NsdShimImpl { - // Inherit everything from API34 shim -}
diff --git a/apishim/common/com/android/networkstack/apishim/common/Ikev2VpnProfileBuilderShim.java b/apishim/common/com/android/networkstack/apishim/common/Ikev2VpnProfileBuilderShim.java deleted file mode 100644 index 4232885..0000000 --- a/apishim/common/com/android/networkstack/apishim/common/Ikev2VpnProfileBuilderShim.java +++ /dev/null
@@ -1,148 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.networkstack.apishim.common; - -import android.net.ProxyInfo; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.security.PrivateKey; -import java.security.cert.X509Certificate; -import java.util.List; - -/** - * A shim for Ikev2VpnProfile.Builder. - * - * T should extend Ikev2VpnProfile.Builder, but this can't be written here as that class is not - * available in API29. - * @param <T> type of builder, typically Ikev2VpnProfile.Builder - */ -// TODO : when API29 is no longer supported, remove the type argument -public interface Ikev2VpnProfileBuilderShim<T> { - /** - * @see Ikev2VpnProfile.Builder#setRequiresInternetValidation(boolean) - */ - default Ikev2VpnProfileBuilderShim<T> setRequiresInternetValidation( - boolean requiresInternetValidation) throws UnsupportedApiLevelException { - throw new UnsupportedApiLevelException("Only supported from API 33"); - } - - /** - * @see Ikev2VpnProfile.Builder#setAuthPsk(byte[]) - */ - default Ikev2VpnProfileBuilderShim<T> setAuthPsk(@NonNull byte[] psk) - throws UnsupportedApiLevelException { - throw new UnsupportedApiLevelException("Only supported from API 30"); - } - - /** - * @see Ikev2VpnProfile.Builder#setAuthUsernamePassword(String, String, X509Certificate) - */ - default Ikev2VpnProfileBuilderShim<T> setAuthUsernamePassword(@NonNull String user, - @NonNull String pass, @Nullable X509Certificate serverRootCa) - throws UnsupportedApiLevelException { - throw new UnsupportedApiLevelException("Only supported from API 30"); - } - - /** - * @see Ikev2VpnProfile.Builder#setAuthDigitalSignature(X509Certificate, PrivateKey, - * X509Certificate) - */ - default Ikev2VpnProfileBuilderShim<T> setAuthDigitalSignature(@NonNull X509Certificate userCert, - @NonNull PrivateKey key, @Nullable X509Certificate serverRootCa) - throws UnsupportedApiLevelException { - throw new UnsupportedApiLevelException("Only supported from API 30"); - } - - /** - * @see Ikev2VpnProfile.Builder#setBypassable(boolean) - */ - default Ikev2VpnProfileBuilderShim<T> setBypassable(boolean isBypassable) - throws UnsupportedApiLevelException { - throw new UnsupportedApiLevelException("Only supported from API 30"); - } - - /** - * @see Ikev2VpnProfile.Builder#setProxy(ProxyInfo) - */ - default Ikev2VpnProfileBuilderShim<T> setProxy(@Nullable ProxyInfo proxy) - throws UnsupportedApiLevelException { - throw new UnsupportedApiLevelException("Only supported from API 30"); - } - - /** - * @see Ikev2VpnProfile.Builder#setMaxMtu(int) - */ - default Ikev2VpnProfileBuilderShim<T> setMaxMtu(int mtu) throws UnsupportedApiLevelException { - throw new UnsupportedApiLevelException("Only supported from API 30"); - } - - /** - * @see Ikev2VpnProfile.Builder#setMetered(boolean) - */ - default Ikev2VpnProfileBuilderShim<T> setMetered(boolean isMetered) - throws UnsupportedApiLevelException { - throw new UnsupportedApiLevelException("Only supported from API 30"); - } - - /** - * @see Ikev2VpnProfile.Builder#setAllowedAlgorithms(List<String>) - */ - default Ikev2VpnProfileBuilderShim<T> setAllowedAlgorithms(@NonNull List<String> algorithmNames) - throws UnsupportedApiLevelException { - throw new UnsupportedApiLevelException("Only supported from API 30"); - } - - /** - * @see Ikev2VpnProfile.Builder#setLocalRoutesExcluded(boolean) - */ - default Ikev2VpnProfileBuilderShim<T> setLocalRoutesExcluded(boolean excludeLocalRoutes) - throws UnsupportedApiLevelException { - throw new UnsupportedApiLevelException("Only supported from API 33"); - } - - /** - * @see Ikev2VpnProfile.Builder#setAutomaticIpVersionSelectionEnabled(boolean) - */ - default Ikev2VpnProfileBuilderShim<T> setAutomaticIpVersionSelectionEnabled(boolean isEnabled) - throws UnsupportedApiLevelException { - throw new UnsupportedApiLevelException("Only supported from API 34"); - } - - /** - * @see Ikev2VpnProfile.Builder#setAutomaticNattKeepaliveTimerEnabled(boolean) - */ - default Ikev2VpnProfileBuilderShim<T> setAutomaticNattKeepaliveTimerEnabled(boolean isEnabled) - throws UnsupportedApiLevelException { - throw new UnsupportedApiLevelException("Only supported from API 34"); - } - - /** - * Get <T> type of builder, typically Ikev2VpnProfile.Builder - */ - default T getBuilder() throws UnsupportedApiLevelException { - throw new UnsupportedApiLevelException("Only supported from API 30"); - } - - /** - * Build an Ikev2VpnProfileShim - */ - default Ikev2VpnProfileShim build() throws UnsupportedApiLevelException { - throw new UnsupportedApiLevelException("Only supported from API 30"); - } -}
diff --git a/apishim/common/com/android/networkstack/apishim/common/Ikev2VpnProfileShim.java b/apishim/common/com/android/networkstack/apishim/common/Ikev2VpnProfileShim.java deleted file mode 100644 index ad0bdcd..0000000 --- a/apishim/common/com/android/networkstack/apishim/common/Ikev2VpnProfileShim.java +++ /dev/null
@@ -1,48 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.networkstack.apishim.common; - -/** - * A shim for Ikev2VpnProfile. - * - * T should extend Ikev2VpnProfile, but this can't be written here as that class is not - * available in API29. - * @param <T> type of profile, typically Ikev2VpnProfile - */ -// TODO : when API29 is no longer supported, remove the type argument -public interface Ikev2VpnProfileShim<T> { - /** - * @see Ikev2VpnProfile#isAutomaticNattKeepaliveTimerEnabled() - */ - default boolean isAutomaticNattKeepaliveTimerEnabled() throws UnsupportedApiLevelException { - throw new UnsupportedApiLevelException("Only supported from API level 34."); - } - - /** - * @see Ikev2VpnProfile#isAutomaticIpVersionSelectionEnabled() - */ - default boolean isAutomaticIpVersionSelectionEnabled() throws UnsupportedApiLevelException { - throw new UnsupportedApiLevelException("Only supported from API level 34."); - } - /** - * Return the <T> type of profile. - * TODO: remove when Q is no longer supported. - */ - default T getProfile() throws UnsupportedApiLevelException { - throw new UnsupportedApiLevelException("Only supported from API level 30."); - } -}
diff --git a/apishim/common/com/android/networkstack/apishim/common/NetworkInformationShim.java b/apishim/common/com/android/networkstack/apishim/common/NetworkInformationShim.java index e7f7b3d..2fcd8c6 100644 --- a/apishim/common/com/android/networkstack/apishim/common/NetworkInformationShim.java +++ b/apishim/common/com/android/networkstack/apishim/common/NetworkInformationShim.java
@@ -100,7 +100,7 @@ } /** - * @see NetworkCapabilites#getUnderlyingNetworks() + * @see NetworkCapabilities#getUnderlyingNetworks() */ @Nullable default List<Network> getUnderlyingNetworks(@NonNull NetworkCapabilities nc) {
diff --git a/apishim/common/com/android/networkstack/apishim/common/NsdShim.java b/apishim/common/com/android/networkstack/apishim/common/NsdShim.java deleted file mode 100644 index 7281022..0000000 --- a/apishim/common/com/android/networkstack/apishim/common/NsdShim.java +++ /dev/null
@@ -1,129 +0,0 @@ -/* - * Copyright (C) 2022 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.common; - -import android.net.Network; -import android.net.NetworkRequest; -import android.net.nsd.NsdManager; -import android.net.nsd.NsdManager.DiscoveryListener; -import android.net.nsd.NsdManager.RegistrationListener; -import android.net.nsd.NsdManager.ResolveListener; -import android.net.nsd.NsdServiceInfo; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.net.InetAddress; -import java.util.List; -import java.util.concurrent.Executor; - -/** Shim for NSD APIs, including {@link android.net.nsd.NsdManager} and - * {@link android.net.nsd.NsdServiceInfo}. */ -public interface NsdShim { - /** - * @see NsdServiceInfo#getNetwork() - */ - @Nullable - Network getNetwork(@NonNull NsdServiceInfo serviceInfo); - - /** - * @see NsdServiceInfo#setNetwork(Network) - */ - void setNetwork(@NonNull NsdServiceInfo serviceInfo, @Nullable Network network); - - /** - * @see NsdManager#registerService(NsdServiceInfo, int, Executor, RegistrationListener) - */ - void registerService(@NonNull NsdManager nsdManager, @NonNull NsdServiceInfo serviceInfo, - int protocolType, @NonNull Executor executor, @NonNull RegistrationListener listener) - throws UnsupportedApiLevelException; - - /** - * @see NsdManager#discoverServices(String, int, Network, Executor, DiscoveryListener) - */ - void discoverServices(@NonNull NsdManager nsdManager, @NonNull String serviceType, - int protocolType, @Nullable Network network, - @NonNull Executor executor, @NonNull DiscoveryListener listener) - throws UnsupportedApiLevelException; - - /** - * @see NsdManager#resolveService(NsdServiceInfo, Executor, ResolveListener) - */ - void resolveService(@NonNull NsdManager nsdManager, @NonNull NsdServiceInfo serviceInfo, - @NonNull Executor executor, @NonNull ResolveListener resolveListener) - throws UnsupportedApiLevelException; - - /** - * @see NsdManager#discoverServices(String, int, NetworkRequest, Executor, DiscoveryListener) - */ - void discoverServices(@NonNull NsdManager nsdManager, @NonNull String serviceType, - int protocolType, @Nullable NetworkRequest request, - @NonNull Executor executor, @NonNull DiscoveryListener listener) - throws UnsupportedApiLevelException; - - /** - * @see NsdManager#stopServiceResolution(ResolveListener) - */ - void stopServiceResolution(@NonNull NsdManager nsdManager, - @NonNull ResolveListener resolveListener) throws UnsupportedApiLevelException; - - /** - * @see NsdManager#ServiceInfoCallback - */ - interface ServiceInfoCallbackShim { - default void onServiceInfoCallbackRegistrationFailed(int errorCode) {} - default void onServiceUpdated(@NonNull NsdServiceInfo serviceInfo) {} - default void onServiceLost() {} - default void onServiceInfoCallbackUnregistered() {} - } - - /** - * @see NsdManager#registerServiceInfoCallback - */ - default void registerServiceInfoCallback(@NonNull NsdManager nsdManager, - @NonNull NsdServiceInfo serviceInfo, @NonNull Executor executor, - @NonNull ServiceInfoCallbackShim listener) - throws UnsupportedApiLevelException { - throw new UnsupportedApiLevelException("Service callback is only supported on U+"); - } - - /** - * @see NsdManager#unregisterServiceInfoCallback - */ - default void unregisterServiceInfoCallback(@NonNull NsdManager nsdManager, - @NonNull ServiceInfoCallbackShim listener) - throws UnsupportedApiLevelException { - throw new UnsupportedApiLevelException("Service callback is only supported on U+"); - } - - /** - * @see NsdServiceInfo#getHostAddresses() - */ - @NonNull - default List<InetAddress> getHostAddresses(@NonNull NsdServiceInfo serviceInfo) - throws UnsupportedApiLevelException { - throw new UnsupportedApiLevelException("getHostAddresses is only supported on U+"); - } - - /** - * @see NsdServiceInfo#setHostAddresses(List<InetAddress>) - */ - default void setHostAddresses(@NonNull NsdServiceInfo serviceInfo, - @NonNull List<InetAddress> addresses) throws UnsupportedApiLevelException { - throw new UnsupportedApiLevelException("setHostAddresses is only supported on U+"); - } -}
diff --git a/common/networkstackclient/Android.bp b/common/networkstackclient/Android.bp index 0571042..060f0da 100644 --- a/common/networkstackclient/Android.bp +++ b/common/networkstackclient/Android.bp
@@ -48,21 +48,54 @@ enabled: false, }, }, - versions: [ - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "10", - ], + visibility: [ "//system/tools/aidl/build", ], + versions_with_info: [ + { + version: "1", + imports: [], + }, + { + version: "2", + imports: [], + }, + { + version: "3", + imports: [], + }, + { + version: "4", + imports: [], + }, + { + version: "5", + imports: [], + }, + { + version: "6", + imports: [], + }, + { + version: "7", + imports: [], + }, + { + version: "8", + imports: [], + }, + { + version: "9", + imports: [], + }, + { + version: "10", + imports: [], + }, + + ], + } aidl_interface { @@ -160,12 +193,17 @@ version: "18", imports: ["ipmemorystore-aidl-interfaces-V10"], }, + { + version: "19", + imports: ["ipmemorystore-aidl-interfaces-V10"], + }, + { + version: "20", + imports: ["ipmemorystore-aidl-interfaces-V10"], + }, ], - - // "frozen: true" is removed manually after each freeze, this property cannot be unrecognized - // in some downstream branches and TH will get the build error, see b/262507066 for details. - + frozen: true, } java_library { @@ -174,7 +212,7 @@ min_sdk_version: "30", static_libs: [ "ipmemorystore-aidl-interfaces-V10-java", - "networkstack-aidl-interfaces-V18-java", + "networkstack-aidl-interfaces-V20-java", ], visibility: ["//packages/modules/NetworkStack:__subpackages__"], apex_available: [ @@ -183,6 +221,9 @@ "com.android.tethering", "com.android.wifi", ], + lint: { + baseline_filename: "lint-baseline.xml", + }, } java_library { @@ -227,4 +268,7 @@ "com.android.tethering", "com.android.wifi", ], + lint: { + baseline_filename: "lint-baseline.xml", + }, }
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/.hash b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/.hash new file mode 100644 index 0000000..5e94944 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/.hash
@@ -0,0 +1 @@ +ffc74fbac5dcfe825bc16b9a3a91b43251476361
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/DataStallReportParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/DataStallReportParcelable.aidl new file mode 100644 index 0000000..771deda --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/DataStallReportParcelable.aidl
@@ -0,0 +1,42 @@ +/** + * Copyright (c) 2020, 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 perNmissions and + * limitations under the License. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net; +@JavaDerive(toString=true) +parcelable DataStallReportParcelable { + long timestampMillis = 0; + int detectionMethod = 1; + int tcpPacketFailRate = 2; + int tcpMetricsCollectionPeriodMillis = 3; + int dnsConsecutiveTimeouts = 4; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/DhcpResultsParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/DhcpResultsParcelable.aidl new file mode 100644 index 0000000..31f2194 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/DhcpResultsParcelable.aidl
@@ -0,0 +1,44 @@ +/** + * 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 perNmissions and + * limitations under the License. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net; +@JavaDerive(toString=true) +parcelable DhcpResultsParcelable { + android.net.StaticIpConfiguration baseConfiguration; + int leaseDuration; + int mtu; + String serverAddress; + String vendorInfo; + @nullable String serverHostName; + @nullable String captivePortalApiUrl; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/INetworkMonitor.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/INetworkMonitor.aidl new file mode 100644 index 0000000..fb13c0c --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/INetworkMonitor.aidl
@@ -0,0 +1,60 @@ +/** + * Copyright (c) 2018, 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 perNmissions and + * limitations under the License. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net; +/* @hide */ +interface INetworkMonitor { + oneway void start(); + oneway void launchCaptivePortalApp(); + oneway void notifyCaptivePortalAppFinished(int response); + oneway void setAcceptPartialConnectivity(); + oneway void forceReevaluation(int uid); + oneway void notifyPrivateDnsChanged(in android.net.PrivateDnsConfigParcel config); + oneway void notifyDnsResponse(int returnCode); + oneway void notifyNetworkConnected(in android.net.LinkProperties lp, in android.net.NetworkCapabilities nc); + oneway void notifyNetworkDisconnected(); + oneway void notifyLinkPropertiesChanged(in android.net.LinkProperties lp); + oneway void notifyNetworkCapabilitiesChanged(in android.net.NetworkCapabilities nc); + oneway void notifyNetworkConnectedParcel(in android.net.networkstack.aidl.NetworkMonitorParameters params); + const int NETWORK_TEST_RESULT_VALID = 0; + const int NETWORK_TEST_RESULT_INVALID = 1; + const int NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY = 2; + const int NETWORK_VALIDATION_RESULT_VALID = 0x01; + const int NETWORK_VALIDATION_RESULT_PARTIAL = 0x02; + const int NETWORK_VALIDATION_RESULT_SKIPPED = 0x04; + const int NETWORK_VALIDATION_PROBE_DNS = 0x04; + const int NETWORK_VALIDATION_PROBE_HTTP = 0x08; + const int NETWORK_VALIDATION_PROBE_HTTPS = 0x10; + const int NETWORK_VALIDATION_PROBE_FALLBACK = 0x20; + const int NETWORK_VALIDATION_PROBE_PRIVDNS = 0x40; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/INetworkMonitorCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/INetworkMonitorCallbacks.aidl new file mode 100644 index 0000000..36eda8e --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/INetworkMonitorCallbacks.aidl
@@ -0,0 +1,46 @@ +/* + * Copyright (C) 2018 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net; +/* @hide */ +interface INetworkMonitorCallbacks { + oneway void onNetworkMonitorCreated(in android.net.INetworkMonitor networkMonitor) = 0; + oneway void notifyNetworkTested(int testResult, @nullable String redirectUrl) = 1; + oneway void notifyPrivateDnsConfigResolved(in android.net.PrivateDnsConfigParcel config) = 2; + oneway void showProvisioningNotification(String action, String packageName) = 3; + oneway void hideProvisioningNotification() = 4; + oneway void notifyProbeStatusChanged(int probesCompleted, int probesSucceeded) = 5; + oneway void notifyNetworkTestedWithExtras(in android.net.NetworkTestResultParcelable result) = 6; + oneway void notifyDataStallSuspected(in android.net.DataStallReportParcelable report) = 7; + oneway void notifyCaptivePortalDataChanged(in android.net.CaptivePortalData data) = 8; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/INetworkStackConnector.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/INetworkStackConnector.aidl new file mode 100644 index 0000000..8120ffc --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/INetworkStackConnector.aidl
@@ -0,0 +1,42 @@ +/** + * Copyright (c) 2018, 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 perNmissions and + * limitations under the License. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net; +/* @hide */ +interface INetworkStackConnector { + oneway void makeDhcpServer(in String ifName, in android.net.dhcp.DhcpServingParamsParcel params, in android.net.dhcp.IDhcpServerCallbacks cb); + oneway void makeNetworkMonitor(in android.net.Network network, String name, in android.net.INetworkMonitorCallbacks cb); + oneway void makeIpClient(in String ifName, in android.net.ip.IIpClientCallbacks callbacks); + oneway void fetchIpMemoryStore(in android.net.IIpMemoryStoreCallbacks cb); + oneway void allowTestUid(int uid, in android.net.INetworkStackStatusCallback cb); +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/INetworkStackStatusCallback.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/INetworkStackStatusCallback.aidl new file mode 100644 index 0000000..0b6b778 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/INetworkStackStatusCallback.aidl
@@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net; +/* @hide */ +interface INetworkStackStatusCallback { + oneway void onStatusAvailable(int statusCode); +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/InformationElementParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/InformationElementParcelable.aidl new file mode 100644 index 0000000..6103774 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/InformationElementParcelable.aidl
@@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net; +@JavaDerive(toString=true) +parcelable InformationElementParcelable { + int id; + byte[] payload; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/InitialConfigurationParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/InitialConfigurationParcelable.aidl new file mode 100644 index 0000000..6a597e6 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/InitialConfigurationParcelable.aidl
@@ -0,0 +1,41 @@ +/* + * 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net; +@JavaDerive(toString=true) +parcelable InitialConfigurationParcelable { + android.net.LinkAddress[] ipAddresses; + android.net.IpPrefix[] directlyConnectedRoutes; + String[] dnsServers; + String gateway; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/Layer2InformationParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/Layer2InformationParcelable.aidl new file mode 100644 index 0000000..83796ee --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/Layer2InformationParcelable.aidl
@@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net; +@JavaDerive(toString=true) +parcelable Layer2InformationParcelable { + String l2Key; + String cluster; + android.net.MacAddress bssid; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/Layer2PacketParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/Layer2PacketParcelable.aidl new file mode 100644 index 0000000..4b3fff5 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/Layer2PacketParcelable.aidl
@@ -0,0 +1,39 @@ +/** + * 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 perNmissions and + * limitations under the License. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net; +@JavaDerive(toString=true) +parcelable Layer2PacketParcelable { + android.net.MacAddress dstMacAddress; + byte[] payload; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/NattKeepalivePacketDataParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/NattKeepalivePacketDataParcelable.aidl new file mode 100644 index 0000000..18cf954 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/NattKeepalivePacketDataParcelable.aidl
@@ -0,0 +1,41 @@ +/* + * 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net; +@JavaDerive(toString=true) +parcelable NattKeepalivePacketDataParcelable { + byte[] srcAddress; + int srcPort; + byte[] dstAddress; + int dstPort; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/NetworkTestResultParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/NetworkTestResultParcelable.aidl new file mode 100644 index 0000000..4d6d5a2 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/NetworkTestResultParcelable.aidl
@@ -0,0 +1,42 @@ +/** + * Copyright (c) 2020, 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 perNmissions and + * limitations under the License. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net; +@JavaDerive(toString=true) +parcelable NetworkTestResultParcelable { + long timestampMillis; + int result; + int probesSucceeded; + int probesAttempted; + String redirectUrl; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/PrivateDnsConfigParcel.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/PrivateDnsConfigParcel.aidl new file mode 100644 index 0000000..ab62fe7 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/PrivateDnsConfigParcel.aidl
@@ -0,0 +1,44 @@ +/* + * Copyright (C) 2018 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net; +@JavaDerive(equals=true, toString=true) +parcelable PrivateDnsConfigParcel { + String hostname; + String[] ips; + int privateDnsMode = (-1) /* -1 */; + String dohName = ""; + String[] dohIps = {}; + String dohPath = ""; + int dohPort = (-1) /* -1 */; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/ProvisioningConfigurationParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/ProvisioningConfigurationParcelable.aidl new file mode 100644 index 0000000..fba524b --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/ProvisioningConfigurationParcelable.aidl
@@ -0,0 +1,63 @@ +/* +** +** 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. +*/ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net; +@JavaDerive(toString=true) +parcelable ProvisioningConfigurationParcelable { + /** + * @deprecated use ipv4ProvisioningMode instead. + */ + boolean enableIPv4; + /** + * @deprecated use ipv6ProvisioningMode instead. + */ + boolean enableIPv6; + boolean usingMultinetworkPolicyTracker; + boolean usingIpReachabilityMonitor; + int requestedPreDhcpActionMs; + android.net.InitialConfigurationParcelable initialConfig; + android.net.StaticIpConfiguration staticIpConfig; + android.net.apf.ApfCapabilities apfCapabilities; + int provisioningTimeoutMs; + int ipv6AddrGenMode; + android.net.Network network; + String displayName; + boolean enablePreconnection; + @nullable android.net.ScanResultInfoParcelable scanResultInfo; + @nullable android.net.Layer2InformationParcelable layer2Info; + @nullable List<android.net.networkstack.aidl.dhcp.DhcpOption> options; + int ipv4ProvisioningMode; + int ipv6ProvisioningMode; + boolean uniqueEui64AddressesOnly; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/ScanResultInfoParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/ScanResultInfoParcelable.aidl new file mode 100644 index 0000000..94fc27f --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/ScanResultInfoParcelable.aidl
@@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net; +@JavaDerive(toString=true) +parcelable ScanResultInfoParcelable { + String ssid; + String bssid; + android.net.InformationElementParcelable[] informationElements; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/TcpKeepalivePacketDataParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/TcpKeepalivePacketDataParcelable.aidl new file mode 100644 index 0000000..0e1c21c --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/TcpKeepalivePacketDataParcelable.aidl
@@ -0,0 +1,47 @@ +/* + * 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net; +@JavaDerive(toString=true) +parcelable TcpKeepalivePacketDataParcelable { + byte[] srcAddress; + int srcPort; + byte[] dstAddress; + int dstPort; + int seq; + int ack; + int rcvWnd; + int rcvWndScale; + int tos; + int ttl; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/dhcp/DhcpLeaseParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/dhcp/DhcpLeaseParcelable.aidl new file mode 100644 index 0000000..3cd8860 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/dhcp/DhcpLeaseParcelable.aidl
@@ -0,0 +1,43 @@ +/** + * Copyright (c) 2020, 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 perNmissions and + * limitations under the License. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net.dhcp; +@JavaDerive(toString=true) +parcelable DhcpLeaseParcelable { + byte[] clientId; + byte[] hwAddr; + int netAddr; + int prefixLength; + long expTime; + String hostname; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/dhcp/DhcpServingParamsParcel.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/dhcp/DhcpServingParamsParcel.aidl new file mode 100644 index 0000000..7997936 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/dhcp/DhcpServingParamsParcel.aidl
@@ -0,0 +1,49 @@ +/** + * + * Copyright (C) 2018 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net.dhcp; +@JavaDerive(toString=true) +parcelable DhcpServingParamsParcel { + int serverAddr; + int serverAddrPrefixLength; + int[] defaultRouters; + int[] dnsServers; + int[] excludedAddrs; + long dhcpLeaseTimeSecs; + int linkMtu; + boolean metered; + int singleClientAddr = 0; + boolean changePrefixOnDecline = false; + int leasesSubnetPrefixLength = 0; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/dhcp/IDhcpEventCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/dhcp/IDhcpEventCallbacks.aidl new file mode 100644 index 0000000..9312f47 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/dhcp/IDhcpEventCallbacks.aidl
@@ -0,0 +1,38 @@ +/** + * Copyright (c) 2020, 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 perNmissions and + * limitations under the License. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net.dhcp; +interface IDhcpEventCallbacks { + oneway void onLeasesChanged(in List<android.net.dhcp.DhcpLeaseParcelable> newLeases); + oneway void onNewPrefixRequest(in android.net.IpPrefix currentPrefix); +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/dhcp/IDhcpServer.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/dhcp/IDhcpServer.aidl new file mode 100644 index 0000000..1109f35 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/dhcp/IDhcpServer.aidl
@@ -0,0 +1,45 @@ +/** + * Copyright (c) 2018, 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 perNmissions and + * limitations under the License. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net.dhcp; +/* @hide */ +interface IDhcpServer { + oneway void start(in android.net.INetworkStackStatusCallback cb) = 0; + oneway void startWithCallbacks(in android.net.INetworkStackStatusCallback statusCb, in android.net.dhcp.IDhcpEventCallbacks eventCb) = 3; + oneway void updateParams(in android.net.dhcp.DhcpServingParamsParcel params, in android.net.INetworkStackStatusCallback cb) = 1; + oneway void stop(in android.net.INetworkStackStatusCallback cb) = 2; + const int STATUS_UNKNOWN = 0; + const int STATUS_SUCCESS = 1; + const int STATUS_INVALID_ARGUMENT = 2; + const int STATUS_UNKNOWN_ERROR = 3; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/dhcp/IDhcpServerCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/dhcp/IDhcpServerCallbacks.aidl new file mode 100644 index 0000000..ab8577c --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/dhcp/IDhcpServerCallbacks.aidl
@@ -0,0 +1,38 @@ +/** + * Copyright (c) 2018, 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 perNmissions and + * limitations under the License. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net.dhcp; +/* @hide */ +interface IDhcpServerCallbacks { + oneway void onDhcpServerCreated(int statusCode, in android.net.dhcp.IDhcpServer server); +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/ip/IIpClient.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/ip/IIpClient.aidl new file mode 100644 index 0000000..b81ec20 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/ip/IIpClient.aidl
@@ -0,0 +1,59 @@ +/** + * 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 perNmissions and + * limitations under the License. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net.ip; +/* @hide */ +interface IIpClient { + oneway void completedPreDhcpAction(); + oneway void confirmConfiguration(); + oneway void readPacketFilterComplete(in byte[] data); + oneway void shutdown(); + oneway void startProvisioning(in android.net.ProvisioningConfigurationParcelable req); + oneway void stop(); + oneway void setTcpBufferSizes(in String tcpBufferSizes); + oneway void setHttpProxy(in android.net.ProxyInfo proxyInfo); + oneway void setMulticastFilter(boolean enabled); + oneway void addKeepalivePacketFilter(int slot, in android.net.TcpKeepalivePacketDataParcelable pkt); + oneway void removeKeepalivePacketFilter(int slot); + oneway void setL2KeyAndGroupHint(in String l2Key, in String cluster); + oneway void addNattKeepalivePacketFilter(int slot, in android.net.NattKeepalivePacketDataParcelable pkt); + oneway void notifyPreconnectionComplete(boolean success); + oneway void updateLayer2Information(in android.net.Layer2InformationParcelable info); + oneway void updateApfCapabilities(in android.net.apf.ApfCapabilities apfCapabilities); + const int PROV_IPV4_DISABLED = 0x00; + const int PROV_IPV4_STATIC = 0x01; + const int PROV_IPV4_DHCP = 0x02; + const int PROV_IPV6_DISABLED = 0x00; + const int PROV_IPV6_SLAAC = 0x01; + const int PROV_IPV6_LINKLOCAL = 0x02; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/ip/IIpClientCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/ip/IIpClientCallbacks.aidl new file mode 100644 index 0000000..9d36419 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/ip/IIpClientCallbacks.aidl
@@ -0,0 +1,54 @@ +/** + * 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 perNmissions and + * limitations under the License. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net.ip; +/* @hide */ +interface IIpClientCallbacks { + oneway void onIpClientCreated(in android.net.ip.IIpClient ipClient); + oneway void onPreDhcpAction(); + oneway void onPostDhcpAction(); + oneway void onNewDhcpResults(in android.net.DhcpResultsParcelable dhcpResults); + oneway void onProvisioningSuccess(in android.net.LinkProperties newLp); + oneway void onProvisioningFailure(in android.net.LinkProperties newLp); + oneway void onLinkPropertiesChange(in android.net.LinkProperties newLp); + oneway void onReachabilityLost(in String logMsg); + oneway void onQuit(); + oneway void installPacketFilter(in byte[] filter); + oneway void startReadPacketFilter(); + oneway void setFallbackMulticastFilter(boolean enabled); + oneway void setNeighborDiscoveryOffload(boolean enable); + oneway void onPreconnectionStart(in List<android.net.Layer2PacketParcelable> packets); + oneway void onReachabilityFailure(in android.net.networkstack.aidl.ip.ReachabilityLossInfoParcelable lossInfo); + oneway void setMaxDtimMultiplier(int multiplier); + const int DTIM_MULTIPLIER_RESET = 0; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/networkstack/aidl/NetworkMonitorParameters.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/networkstack/aidl/NetworkMonitorParameters.aidl new file mode 100644 index 0000000..2ab9db0 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/networkstack/aidl/NetworkMonitorParameters.aidl
@@ -0,0 +1,41 @@ +/** + * + * Copyright (C) 2022 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net.networkstack.aidl; +@JavaDerive(equals=true, toString=true) +parcelable NetworkMonitorParameters { + android.net.NetworkAgentConfig networkAgentConfig; + android.net.NetworkCapabilities networkCapabilities; + android.net.LinkProperties linkProperties; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/networkstack/aidl/dhcp/DhcpOption.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/networkstack/aidl/dhcp/DhcpOption.aidl new file mode 100644 index 0000000..eea3e0d --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/networkstack/aidl/dhcp/DhcpOption.aidl
@@ -0,0 +1,39 @@ +/** + * Copyright (c) 2020, 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 perNmissions and + * limitations under the License. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net.networkstack.aidl.dhcp; +@JavaDerive(toString=true) +parcelable DhcpOption { + byte type; + @nullable byte[] value; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/networkstack/aidl/ip/ReachabilityLossInfoParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/networkstack/aidl/ip/ReachabilityLossInfoParcelable.aidl new file mode 100644 index 0000000..bb88434 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/networkstack/aidl/ip/ReachabilityLossInfoParcelable.aidl
@@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net.networkstack.aidl.ip; +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable ReachabilityLossInfoParcelable { + String message; + android.net.networkstack.aidl.ip.ReachabilityLossReason reason; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/networkstack/aidl/ip/ReachabilityLossReason.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/networkstack/aidl/ip/ReachabilityLossReason.aidl new file mode 100644 index 0000000..f9bb3c4 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/networkstack/aidl/ip/ReachabilityLossReason.aidl
@@ -0,0 +1,40 @@ +/* + * Copyright (C) 2021 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net.networkstack.aidl.ip; +@Backing(type="int") +enum ReachabilityLossReason { + ROAM, + CONFIRM, + ORGANIC, +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/.hash b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/.hash new file mode 100644 index 0000000..e447bdd --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/.hash
@@ -0,0 +1 @@ +fdb37ac2749e155d9d5aa51541ec254dc36dc353
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/DataStallReportParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/DataStallReportParcelable.aidl new file mode 100644 index 0000000..771deda --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/DataStallReportParcelable.aidl
@@ -0,0 +1,42 @@ +/** + * Copyright (c) 2020, 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 perNmissions and + * limitations under the License. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net; +@JavaDerive(toString=true) +parcelable DataStallReportParcelable { + long timestampMillis = 0; + int detectionMethod = 1; + int tcpPacketFailRate = 2; + int tcpMetricsCollectionPeriodMillis = 3; + int dnsConsecutiveTimeouts = 4; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/DhcpResultsParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/DhcpResultsParcelable.aidl new file mode 100644 index 0000000..31f2194 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/DhcpResultsParcelable.aidl
@@ -0,0 +1,44 @@ +/** + * 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 perNmissions and + * limitations under the License. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net; +@JavaDerive(toString=true) +parcelable DhcpResultsParcelable { + android.net.StaticIpConfiguration baseConfiguration; + int leaseDuration; + int mtu; + String serverAddress; + String vendorInfo; + @nullable String serverHostName; + @nullable String captivePortalApiUrl; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/INetworkMonitor.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/INetworkMonitor.aidl new file mode 100644 index 0000000..fb13c0c --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/INetworkMonitor.aidl
@@ -0,0 +1,60 @@ +/** + * Copyright (c) 2018, 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 perNmissions and + * limitations under the License. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net; +/* @hide */ +interface INetworkMonitor { + oneway void start(); + oneway void launchCaptivePortalApp(); + oneway void notifyCaptivePortalAppFinished(int response); + oneway void setAcceptPartialConnectivity(); + oneway void forceReevaluation(int uid); + oneway void notifyPrivateDnsChanged(in android.net.PrivateDnsConfigParcel config); + oneway void notifyDnsResponse(int returnCode); + oneway void notifyNetworkConnected(in android.net.LinkProperties lp, in android.net.NetworkCapabilities nc); + oneway void notifyNetworkDisconnected(); + oneway void notifyLinkPropertiesChanged(in android.net.LinkProperties lp); + oneway void notifyNetworkCapabilitiesChanged(in android.net.NetworkCapabilities nc); + oneway void notifyNetworkConnectedParcel(in android.net.networkstack.aidl.NetworkMonitorParameters params); + const int NETWORK_TEST_RESULT_VALID = 0; + const int NETWORK_TEST_RESULT_INVALID = 1; + const int NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY = 2; + const int NETWORK_VALIDATION_RESULT_VALID = 0x01; + const int NETWORK_VALIDATION_RESULT_PARTIAL = 0x02; + const int NETWORK_VALIDATION_RESULT_SKIPPED = 0x04; + const int NETWORK_VALIDATION_PROBE_DNS = 0x04; + const int NETWORK_VALIDATION_PROBE_HTTP = 0x08; + const int NETWORK_VALIDATION_PROBE_HTTPS = 0x10; + const int NETWORK_VALIDATION_PROBE_FALLBACK = 0x20; + const int NETWORK_VALIDATION_PROBE_PRIVDNS = 0x40; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/INetworkMonitorCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/INetworkMonitorCallbacks.aidl new file mode 100644 index 0000000..36eda8e --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/INetworkMonitorCallbacks.aidl
@@ -0,0 +1,46 @@ +/* + * Copyright (C) 2018 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net; +/* @hide */ +interface INetworkMonitorCallbacks { + oneway void onNetworkMonitorCreated(in android.net.INetworkMonitor networkMonitor) = 0; + oneway void notifyNetworkTested(int testResult, @nullable String redirectUrl) = 1; + oneway void notifyPrivateDnsConfigResolved(in android.net.PrivateDnsConfigParcel config) = 2; + oneway void showProvisioningNotification(String action, String packageName) = 3; + oneway void hideProvisioningNotification() = 4; + oneway void notifyProbeStatusChanged(int probesCompleted, int probesSucceeded) = 5; + oneway void notifyNetworkTestedWithExtras(in android.net.NetworkTestResultParcelable result) = 6; + oneway void notifyDataStallSuspected(in android.net.DataStallReportParcelable report) = 7; + oneway void notifyCaptivePortalDataChanged(in android.net.CaptivePortalData data) = 8; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/INetworkStackConnector.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/INetworkStackConnector.aidl new file mode 100644 index 0000000..8120ffc --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/INetworkStackConnector.aidl
@@ -0,0 +1,42 @@ +/** + * Copyright (c) 2018, 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 perNmissions and + * limitations under the License. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net; +/* @hide */ +interface INetworkStackConnector { + oneway void makeDhcpServer(in String ifName, in android.net.dhcp.DhcpServingParamsParcel params, in android.net.dhcp.IDhcpServerCallbacks cb); + oneway void makeNetworkMonitor(in android.net.Network network, String name, in android.net.INetworkMonitorCallbacks cb); + oneway void makeIpClient(in String ifName, in android.net.ip.IIpClientCallbacks callbacks); + oneway void fetchIpMemoryStore(in android.net.IIpMemoryStoreCallbacks cb); + oneway void allowTestUid(int uid, in android.net.INetworkStackStatusCallback cb); +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/INetworkStackStatusCallback.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/INetworkStackStatusCallback.aidl new file mode 100644 index 0000000..0b6b778 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/INetworkStackStatusCallback.aidl
@@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net; +/* @hide */ +interface INetworkStackStatusCallback { + oneway void onStatusAvailable(int statusCode); +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/InformationElementParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/InformationElementParcelable.aidl new file mode 100644 index 0000000..6103774 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/InformationElementParcelable.aidl
@@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net; +@JavaDerive(toString=true) +parcelable InformationElementParcelable { + int id; + byte[] payload; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/InitialConfigurationParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/InitialConfigurationParcelable.aidl new file mode 100644 index 0000000..6a597e6 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/InitialConfigurationParcelable.aidl
@@ -0,0 +1,41 @@ +/* + * 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net; +@JavaDerive(toString=true) +parcelable InitialConfigurationParcelable { + android.net.LinkAddress[] ipAddresses; + android.net.IpPrefix[] directlyConnectedRoutes; + String[] dnsServers; + String gateway; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/Layer2InformationParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/Layer2InformationParcelable.aidl new file mode 100644 index 0000000..83796ee --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/Layer2InformationParcelable.aidl
@@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net; +@JavaDerive(toString=true) +parcelable Layer2InformationParcelable { + String l2Key; + String cluster; + android.net.MacAddress bssid; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/Layer2PacketParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/Layer2PacketParcelable.aidl new file mode 100644 index 0000000..4b3fff5 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/Layer2PacketParcelable.aidl
@@ -0,0 +1,39 @@ +/** + * 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 perNmissions and + * limitations under the License. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net; +@JavaDerive(toString=true) +parcelable Layer2PacketParcelable { + android.net.MacAddress dstMacAddress; + byte[] payload; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/NattKeepalivePacketDataParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/NattKeepalivePacketDataParcelable.aidl new file mode 100644 index 0000000..18cf954 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/NattKeepalivePacketDataParcelable.aidl
@@ -0,0 +1,41 @@ +/* + * 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net; +@JavaDerive(toString=true) +parcelable NattKeepalivePacketDataParcelable { + byte[] srcAddress; + int srcPort; + byte[] dstAddress; + int dstPort; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/NetworkTestResultParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/NetworkTestResultParcelable.aidl new file mode 100644 index 0000000..4d6d5a2 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/NetworkTestResultParcelable.aidl
@@ -0,0 +1,42 @@ +/** + * Copyright (c) 2020, 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 perNmissions and + * limitations under the License. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net; +@JavaDerive(toString=true) +parcelable NetworkTestResultParcelable { + long timestampMillis; + int result; + int probesSucceeded; + int probesAttempted; + String redirectUrl; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/PrivateDnsConfigParcel.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/PrivateDnsConfigParcel.aidl new file mode 100644 index 0000000..ab62fe7 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/PrivateDnsConfigParcel.aidl
@@ -0,0 +1,44 @@ +/* + * Copyright (C) 2018 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net; +@JavaDerive(equals=true, toString=true) +parcelable PrivateDnsConfigParcel { + String hostname; + String[] ips; + int privateDnsMode = (-1) /* -1 */; + String dohName = ""; + String[] dohIps = {}; + String dohPath = ""; + int dohPort = (-1) /* -1 */; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/ProvisioningConfigurationParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/ProvisioningConfigurationParcelable.aidl new file mode 100644 index 0000000..7061f1e --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/ProvisioningConfigurationParcelable.aidl
@@ -0,0 +1,64 @@ +/* +** +** 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. +*/ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net; +@JavaDerive(toString=true) +parcelable ProvisioningConfigurationParcelable { + /** + * @deprecated use ipv4ProvisioningMode instead. + */ + boolean enableIPv4; + /** + * @deprecated use ipv6ProvisioningMode instead. + */ + boolean enableIPv6; + boolean usingMultinetworkPolicyTracker; + boolean usingIpReachabilityMonitor; + int requestedPreDhcpActionMs; + android.net.InitialConfigurationParcelable initialConfig; + android.net.StaticIpConfiguration staticIpConfig; + android.net.apf.ApfCapabilities apfCapabilities; + int provisioningTimeoutMs; + int ipv6AddrGenMode; + android.net.Network network; + String displayName; + boolean enablePreconnection; + @nullable android.net.ScanResultInfoParcelable scanResultInfo; + @nullable android.net.Layer2InformationParcelable layer2Info; + @nullable List<android.net.networkstack.aidl.dhcp.DhcpOption> options; + int ipv4ProvisioningMode; + int ipv6ProvisioningMode; + boolean uniqueEui64AddressesOnly; + int creatorUid; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/ScanResultInfoParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/ScanResultInfoParcelable.aidl new file mode 100644 index 0000000..94fc27f --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/ScanResultInfoParcelable.aidl
@@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net; +@JavaDerive(toString=true) +parcelable ScanResultInfoParcelable { + String ssid; + String bssid; + android.net.InformationElementParcelable[] informationElements; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/TcpKeepalivePacketDataParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/TcpKeepalivePacketDataParcelable.aidl new file mode 100644 index 0000000..0e1c21c --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/TcpKeepalivePacketDataParcelable.aidl
@@ -0,0 +1,47 @@ +/* + * 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net; +@JavaDerive(toString=true) +parcelable TcpKeepalivePacketDataParcelable { + byte[] srcAddress; + int srcPort; + byte[] dstAddress; + int dstPort; + int seq; + int ack; + int rcvWnd; + int rcvWndScale; + int tos; + int ttl; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/dhcp/DhcpLeaseParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/dhcp/DhcpLeaseParcelable.aidl new file mode 100644 index 0000000..3cd8860 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/dhcp/DhcpLeaseParcelable.aidl
@@ -0,0 +1,43 @@ +/** + * Copyright (c) 2020, 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 perNmissions and + * limitations under the License. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net.dhcp; +@JavaDerive(toString=true) +parcelable DhcpLeaseParcelable { + byte[] clientId; + byte[] hwAddr; + int netAddr; + int prefixLength; + long expTime; + String hostname; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/dhcp/DhcpServingParamsParcel.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/dhcp/DhcpServingParamsParcel.aidl new file mode 100644 index 0000000..7997936 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/dhcp/DhcpServingParamsParcel.aidl
@@ -0,0 +1,49 @@ +/** + * + * Copyright (C) 2018 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net.dhcp; +@JavaDerive(toString=true) +parcelable DhcpServingParamsParcel { + int serverAddr; + int serverAddrPrefixLength; + int[] defaultRouters; + int[] dnsServers; + int[] excludedAddrs; + long dhcpLeaseTimeSecs; + int linkMtu; + boolean metered; + int singleClientAddr = 0; + boolean changePrefixOnDecline = false; + int leasesSubnetPrefixLength = 0; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/dhcp/IDhcpEventCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/dhcp/IDhcpEventCallbacks.aidl new file mode 100644 index 0000000..9312f47 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/dhcp/IDhcpEventCallbacks.aidl
@@ -0,0 +1,38 @@ +/** + * Copyright (c) 2020, 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 perNmissions and + * limitations under the License. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net.dhcp; +interface IDhcpEventCallbacks { + oneway void onLeasesChanged(in List<android.net.dhcp.DhcpLeaseParcelable> newLeases); + oneway void onNewPrefixRequest(in android.net.IpPrefix currentPrefix); +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/dhcp/IDhcpServer.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/dhcp/IDhcpServer.aidl new file mode 100644 index 0000000..1109f35 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/dhcp/IDhcpServer.aidl
@@ -0,0 +1,45 @@ +/** + * Copyright (c) 2018, 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 perNmissions and + * limitations under the License. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net.dhcp; +/* @hide */ +interface IDhcpServer { + oneway void start(in android.net.INetworkStackStatusCallback cb) = 0; + oneway void startWithCallbacks(in android.net.INetworkStackStatusCallback statusCb, in android.net.dhcp.IDhcpEventCallbacks eventCb) = 3; + oneway void updateParams(in android.net.dhcp.DhcpServingParamsParcel params, in android.net.INetworkStackStatusCallback cb) = 1; + oneway void stop(in android.net.INetworkStackStatusCallback cb) = 2; + const int STATUS_UNKNOWN = 0; + const int STATUS_SUCCESS = 1; + const int STATUS_INVALID_ARGUMENT = 2; + const int STATUS_UNKNOWN_ERROR = 3; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/dhcp/IDhcpServerCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/dhcp/IDhcpServerCallbacks.aidl new file mode 100644 index 0000000..ab8577c --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/dhcp/IDhcpServerCallbacks.aidl
@@ -0,0 +1,38 @@ +/** + * Copyright (c) 2018, 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 perNmissions and + * limitations under the License. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net.dhcp; +/* @hide */ +interface IDhcpServerCallbacks { + oneway void onDhcpServerCreated(int statusCode, in android.net.dhcp.IDhcpServer server); +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/ip/IIpClient.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/ip/IIpClient.aidl new file mode 100644 index 0000000..b81ec20 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/ip/IIpClient.aidl
@@ -0,0 +1,59 @@ +/** + * 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 perNmissions and + * limitations under the License. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net.ip; +/* @hide */ +interface IIpClient { + oneway void completedPreDhcpAction(); + oneway void confirmConfiguration(); + oneway void readPacketFilterComplete(in byte[] data); + oneway void shutdown(); + oneway void startProvisioning(in android.net.ProvisioningConfigurationParcelable req); + oneway void stop(); + oneway void setTcpBufferSizes(in String tcpBufferSizes); + oneway void setHttpProxy(in android.net.ProxyInfo proxyInfo); + oneway void setMulticastFilter(boolean enabled); + oneway void addKeepalivePacketFilter(int slot, in android.net.TcpKeepalivePacketDataParcelable pkt); + oneway void removeKeepalivePacketFilter(int slot); + oneway void setL2KeyAndGroupHint(in String l2Key, in String cluster); + oneway void addNattKeepalivePacketFilter(int slot, in android.net.NattKeepalivePacketDataParcelable pkt); + oneway void notifyPreconnectionComplete(boolean success); + oneway void updateLayer2Information(in android.net.Layer2InformationParcelable info); + oneway void updateApfCapabilities(in android.net.apf.ApfCapabilities apfCapabilities); + const int PROV_IPV4_DISABLED = 0x00; + const int PROV_IPV4_STATIC = 0x01; + const int PROV_IPV4_DHCP = 0x02; + const int PROV_IPV6_DISABLED = 0x00; + const int PROV_IPV6_SLAAC = 0x01; + const int PROV_IPV6_LINKLOCAL = 0x02; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/ip/IIpClientCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/ip/IIpClientCallbacks.aidl new file mode 100644 index 0000000..9d36419 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/ip/IIpClientCallbacks.aidl
@@ -0,0 +1,54 @@ +/** + * 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 perNmissions and + * limitations under the License. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net.ip; +/* @hide */ +interface IIpClientCallbacks { + oneway void onIpClientCreated(in android.net.ip.IIpClient ipClient); + oneway void onPreDhcpAction(); + oneway void onPostDhcpAction(); + oneway void onNewDhcpResults(in android.net.DhcpResultsParcelable dhcpResults); + oneway void onProvisioningSuccess(in android.net.LinkProperties newLp); + oneway void onProvisioningFailure(in android.net.LinkProperties newLp); + oneway void onLinkPropertiesChange(in android.net.LinkProperties newLp); + oneway void onReachabilityLost(in String logMsg); + oneway void onQuit(); + oneway void installPacketFilter(in byte[] filter); + oneway void startReadPacketFilter(); + oneway void setFallbackMulticastFilter(boolean enabled); + oneway void setNeighborDiscoveryOffload(boolean enable); + oneway void onPreconnectionStart(in List<android.net.Layer2PacketParcelable> packets); + oneway void onReachabilityFailure(in android.net.networkstack.aidl.ip.ReachabilityLossInfoParcelable lossInfo); + oneway void setMaxDtimMultiplier(int multiplier); + const int DTIM_MULTIPLIER_RESET = 0; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/networkstack/aidl/NetworkMonitorParameters.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/networkstack/aidl/NetworkMonitorParameters.aidl new file mode 100644 index 0000000..2ab9db0 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/networkstack/aidl/NetworkMonitorParameters.aidl
@@ -0,0 +1,41 @@ +/** + * + * Copyright (C) 2022 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net.networkstack.aidl; +@JavaDerive(equals=true, toString=true) +parcelable NetworkMonitorParameters { + android.net.NetworkAgentConfig networkAgentConfig; + android.net.NetworkCapabilities networkCapabilities; + android.net.LinkProperties linkProperties; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/networkstack/aidl/dhcp/DhcpOption.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/networkstack/aidl/dhcp/DhcpOption.aidl new file mode 100644 index 0000000..eea3e0d --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/networkstack/aidl/dhcp/DhcpOption.aidl
@@ -0,0 +1,39 @@ +/** + * Copyright (c) 2020, 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 perNmissions and + * limitations under the License. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net.networkstack.aidl.dhcp; +@JavaDerive(toString=true) +parcelable DhcpOption { + byte type; + @nullable byte[] value; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/networkstack/aidl/ip/ReachabilityLossInfoParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/networkstack/aidl/ip/ReachabilityLossInfoParcelable.aidl new file mode 100644 index 0000000..bb88434 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/networkstack/aidl/ip/ReachabilityLossInfoParcelable.aidl
@@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net.networkstack.aidl.ip; +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable ReachabilityLossInfoParcelable { + String message; + android.net.networkstack.aidl.ip.ReachabilityLossReason reason; +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/networkstack/aidl/ip/ReachabilityLossReason.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/networkstack/aidl/ip/ReachabilityLossReason.aidl new file mode 100644 index 0000000..f9bb3c4 --- /dev/null +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/20/android/net/networkstack/aidl/ip/ReachabilityLossReason.aidl
@@ -0,0 +1,40 @@ +/* + * Copyright (C) 2021 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.net.networkstack.aidl.ip; +@Backing(type="int") +enum ReachabilityLossReason { + ROAM, + CONFIRM, + ORGANIC, +}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/PrivateDnsConfigParcel.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/PrivateDnsConfigParcel.aidl index 1457caf..ab62fe7 100644 --- a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/PrivateDnsConfigParcel.aidl +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/PrivateDnsConfigParcel.aidl
@@ -32,8 +32,13 @@ // later when a module using the interface is updated, e.g., Mainline modules. package android.net; -@JavaDerive(toString=true) +@JavaDerive(equals=true, toString=true) parcelable PrivateDnsConfigParcel { String hostname; String[] ips; + int privateDnsMode = (-1) /* -1 */; + String dohName = ""; + String[] dohIps = {}; + String dohPath = ""; + int dohPort = (-1) /* -1 */; }
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/ProvisioningConfigurationParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/ProvisioningConfigurationParcelable.aidl index fba524b..7061f1e 100644 --- a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/ProvisioningConfigurationParcelable.aidl +++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/ProvisioningConfigurationParcelable.aidl
@@ -60,4 +60,5 @@ int ipv4ProvisioningMode; int ipv6ProvisioningMode; boolean uniqueEui64AddressesOnly; + int creatorUid; }
diff --git a/common/networkstackclient/src/android/net/PrivateDnsConfigParcel.aidl b/common/networkstackclient/src/android/net/PrivateDnsConfigParcel.aidl index 97bb697..e747d61 100644 --- a/common/networkstackclient/src/android/net/PrivateDnsConfigParcel.aidl +++ b/common/networkstackclient/src/android/net/PrivateDnsConfigParcel.aidl
@@ -16,8 +16,60 @@ package android.net; -@JavaDerive(toString=true) +@JavaDerive(equals=true, toString=true) parcelable PrivateDnsConfigParcel { + /** + * The hostname of private DNS provider. + */ String hostname; + + /** + * The DoT server IP addresses of `hostname`. They are not sorted. + */ String[] ips; + + /** + * The private DNS mode associated with this PrivateDnsConfigParcel. + * If it's set, the value must be one of the following constants defined in + * ConnectivitySettingsManager. + * - PRIVATE_DNS_MODE_OFF (1) + * - PRIVATE_DNS_MODE_OPPORTUNISTIC (2) + * - PRIVATE_DNS_MODE_PROVIDER_HOSTNAME (3) + * + * For compatibility with old PrivateDnsConfigParcel, set the default value to -1 to indicate + * that the sender is using an old version of PrivateDnsConfigParcel and that the receiver + * cannot determine the private DNS mode by reading this field. + */ + int privateDnsMode = -1; + + /** + * The following fields with the prefix "doh" store the DoH3 information discovered from + * DDR. The similar fields are defined in DnsResolver as well. Although duplicating code + * is not a good idea, it avoids the complexity and confusion of having a parcelable + * containing a nested parcelable where the client and server could have a different version + * of the nested parcelable. + */ + + /** + * The DoH server hostname derived from TargetName field of a DNS SVCB response. + */ + String dohName = ""; + + /** + * The DoH server IP addresses of `dohName`. They are not sorted. + */ + String[] dohIps = {}; + + /** + * A part of the URI template used to construct the URL for DNS resolution. + * It's derived only from DNS SVCB SvcParamKey "dohpath". + * The URI template for DNS resolution is as follows: + * https://<dohName>/<dohPath> + */ + String dohPath = ""; + + /** + * The port used to reach the DoH servers. + */ + int dohPort = -1; }
diff --git a/common/networkstackclient/src/android/net/ProvisioningConfigurationParcelable.aidl b/common/networkstackclient/src/android/net/ProvisioningConfigurationParcelable.aidl index c4d7866..7ab612f 100644 --- a/common/networkstackclient/src/android/net/ProvisioningConfigurationParcelable.aidl +++ b/common/networkstackclient/src/android/net/ProvisioningConfigurationParcelable.aidl
@@ -50,4 +50,5 @@ int ipv4ProvisioningMode; int ipv6ProvisioningMode; boolean uniqueEui64AddressesOnly; + int creatorUid; }
diff --git a/common/networkstackclient/src/android/net/shared/Layer2Information.java b/common/networkstackclient/src/android/net/shared/Layer2Information.java index 8cdd018..f384509 100644 --- a/common/networkstackclient/src/android/net/shared/Layer2Information.java +++ b/common/networkstackclient/src/android/net/shared/Layer2Information.java
@@ -30,7 +30,6 @@ public final String mCluster; @Nullable public final MacAddress mBssid; - /** * Create a Layer2Information with the specified configuration. */
diff --git a/common/networkstackclient/src/android/net/shared/PrivateDnsConfig.java b/common/networkstackclient/src/android/net/shared/PrivateDnsConfig.java index 106ca1c..632d1d6 100644 --- a/common/networkstackclient/src/android/net/shared/PrivateDnsConfig.java +++ b/common/networkstackclient/src/android/net/shared/PrivateDnsConfig.java
@@ -16,9 +16,14 @@ package android.net.shared; +import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OFF; +import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; +import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; import static android.net.shared.ParcelableUtil.fromParcelableArray; import static android.net.shared.ParcelableUtil.toParcelableArray; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.net.PrivateDnsConfigParcel; import android.text.TextUtils; @@ -27,50 +32,116 @@ /** @hide */ public class PrivateDnsConfig { - public final boolean useTls; + // These fields store the private DNS configuration from setting. + public final int mode; + @NonNull public final String hostname; + + // Stores the DoT server IP addresses resolved from A/AAAA lookups. + @NonNull public final InetAddress[] ips; + // These fields store the DoH information discovered from SVCB lookups. + @NonNull + public final String dohName; + @NonNull + public final InetAddress[] dohIps; + @NonNull + public final String dohPath; + public final int dohPort; + + /** + * A constructor for off mode private DNS configuration. + * TODO(b/261404136): Consider simplifying the constructors. One possible way is to + * use constants to represent private DNS modes: + * public static PrivateDnsConfig OFF = new PrivateDnsConfig(false); + * public static PrivateDnsConfig OPPORTUNISTIC = new PrivateDnsConfig(true); + * public static PrivateDnsConfig STRICT = new PrivateDnsConfig(String hostname); + */ public PrivateDnsConfig() { this(false); } + /** + * A constructor for off/opportunistic mode private DNS configuration depending on `useTls`. + */ public PrivateDnsConfig(boolean useTls) { - this.useTls = useTls; - this.hostname = ""; - this.ips = new InetAddress[0]; + this(useTls ? PRIVATE_DNS_MODE_OPPORTUNISTIC : PRIVATE_DNS_MODE_OFF, null /* hostname */, + null /* ips */, null /* dohName */, null /* dohIps */, null /* dohPath */, + -1 /* dohPort */); } - public PrivateDnsConfig(String hostname, InetAddress[] ips) { - this.useTls = !TextUtils.isEmpty(hostname); - this.hostname = useTls ? hostname : ""; - this.ips = (ips != null) ? ips : new InetAddress[0]; + /** + * A constructor for off/strict mode private DNS configuration depending on `hostname`. + * If `hostname` is empty or null, this constructor creates a PrivateDnsConfig for off mode; + * otherwise, it creates a PrivateDnsConfig for strict mode. + */ + public PrivateDnsConfig(@Nullable String hostname, @Nullable InetAddress[] ips) { + this(TextUtils.isEmpty(hostname) ? PRIVATE_DNS_MODE_OFF : + PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, hostname, ips, null /* dohName */, + null /* dohIps */, null /* dohPath */, -1 /* dohPort */); + } + + /** + * A constructor for all kinds of private DNS configuration with given DoH information. + * It treats both null values and empty strings as equivalent. Similarly, treats null values + * and empty arrays as equivalent. + */ + public PrivateDnsConfig(int mode, @Nullable String hostname, @Nullable InetAddress[] ips, + @Nullable String dohName, @Nullable InetAddress[] dohIps, @Nullable String dohPath, + int dohPort) { + this.mode = mode; + this.hostname = (hostname != null) ? hostname : ""; + this.ips = (ips != null) ? ips.clone() : new InetAddress[0]; + this.dohName = (dohName != null) ? dohName : ""; + this.dohIps = (dohIps != null) ? dohIps.clone() : new InetAddress[0]; + this.dohPath = (dohPath != null) ? dohPath : ""; + this.dohPort = dohPort; } public PrivateDnsConfig(PrivateDnsConfig cfg) { - useTls = cfg.useTls; + mode = cfg.mode; hostname = cfg.hostname; ips = cfg.ips; + dohName = cfg.dohName; + dohIps = cfg.dohIps; + dohPath = cfg.dohPath; + dohPort = cfg.dohPort; } /** * Indicates whether this is a strict mode private DNS configuration. */ public boolean inStrictMode() { - return useTls && !TextUtils.isEmpty(hostname); + return mode == PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; } /** * Indicates whether this is an opportunistic mode private DNS configuration. */ public boolean inOpportunisticMode() { - return useTls && TextUtils.isEmpty(hostname); + return mode == PRIVATE_DNS_MODE_OPPORTUNISTIC; } @Override public String toString() { return PrivateDnsConfig.class.getSimpleName() - + "{" + useTls + ":" + hostname + "/" + Arrays.toString(ips) + "}"; + + "{" + modeAsString(mode) + ":" + hostname + "/" + Arrays.toString(ips) + + ", dohName=" + dohName + + ", dohIps=" + Arrays.toString(dohIps) + + ", dohPath=" + dohPath + + ", dohPort=" + dohPort + + "}"; + } + + @NonNull + private static String modeAsString(int mode) { + switch (mode) { + case PRIVATE_DNS_MODE_OFF: return "off"; + case PRIVATE_DNS_MODE_OPPORTUNISTIC: return "opportunistic"; + case PRIVATE_DNS_MODE_PROVIDER_HOSTNAME: return "strict"; + default: return "unknown"; + } } /** @@ -81,7 +152,12 @@ parcel.hostname = hostname; parcel.ips = toParcelableArray( Arrays.asList(ips), IpConfigurationParcelableUtil::parcelAddress, String.class); - + parcel.privateDnsMode = mode; + parcel.dohName = dohName; + parcel.dohIps = toParcelableArray( + Arrays.asList(dohIps), IpConfigurationParcelableUtil::parcelAddress, String.class); + parcel.dohPath = dohPath; + parcel.dohPort = dohPort; return parcel; } @@ -92,6 +168,26 @@ InetAddress[] ips = new InetAddress[parcel.ips.length]; ips = fromParcelableArray(parcel.ips, IpConfigurationParcelableUtil::unparcelAddress) .toArray(ips); - return new PrivateDnsConfig(parcel.hostname, ips); + + // For compatibility. If the sender (Tethering module) is using an old version (< 19) of + // NetworkStack AIDL that `privateDnsMode` field is not present, `privateDnsMode` will be + // assigned from the default value -1. Let `privateDnsMode` assigned based on the hostname. + // In this case, there is a harmless bug that the receiver (NetworkStack module) can't + // convert the parcel to a PrivateDnsConfig that indicates opportunistic mode. + // The bug is harmless because 1) the bug exists for years without any problems and + // 2) NetworkMonitor cares PrivateDnsConfig that indicates strict/off mode only. + // If the sender is using new version (>=19) while the receiver is using an old version, + // the above mentioned harmless bug will persist. Except for that harmless bug, there + // should be no other issues. New version's toParcel() doesn't change how the pre-existing + // fields `hostname` and `ips` are assigned. + if (parcel.privateDnsMode == -1) { + return new PrivateDnsConfig(parcel.hostname, ips); + } + + InetAddress[] dohIps = new InetAddress[parcel.dohIps.length]; + dohIps = fromParcelableArray(parcel.dohIps, + IpConfigurationParcelableUtil::unparcelAddress).toArray(dohIps); + return new PrivateDnsConfig(parcel.privateDnsMode, parcel.hostname, ips, parcel.dohName, + dohIps, parcel.dohPath, parcel.dohPort); } }
diff --git a/common/networkstackclient/src/android/net/shared/ProvisioningConfiguration.java b/common/networkstackclient/src/android/net/shared/ProvisioningConfiguration.java index f3631f2..16ad201 100644 --- a/common/networkstackclient/src/android/net/shared/ProvisioningConfiguration.java +++ b/common/networkstackclient/src/android/net/shared/ProvisioningConfiguration.java
@@ -228,6 +228,14 @@ } /** + * Specify the UID of the remote entity that created this Network. + */ + public Builder withCreatorUid(int creatoruid) { + mConfig.mCreatorUid = creatoruid; + return this; + } + + /** * Specify the information elements included in wifi scan result that was obtained * prior to connecting to the access point, if this is a WiFi network. * @@ -492,6 +500,7 @@ public List<DhcpOption> mDhcpOptions; public int mIPv4ProvisioningMode = PROV_IPV4_DHCP; public int mIPv6ProvisioningMode = PROV_IPV6_SLAAC; + public int mCreatorUid; public ProvisioningConfiguration() {} // used by Builder @@ -510,6 +519,7 @@ mIPv6AddrGenMode = other.mIPv6AddrGenMode; mNetwork = other.mNetwork; mDisplayName = other.mDisplayName; + mCreatorUid = other.mCreatorUid; mScanResultInfo = other.mScanResultInfo; mLayer2Info = other.mLayer2Info; mDhcpOptions = other.mDhcpOptions; @@ -540,6 +550,7 @@ p.ipv6AddrGenMode = mIPv6AddrGenMode; p.network = mNetwork; p.displayName = mDisplayName; + p.creatorUid = mCreatorUid; p.scanResultInfo = (mScanResultInfo == null) ? null : mScanResultInfo.toStableParcelable(); p.layer2Info = (mLayer2Info == null) ? null : mLayer2Info.toStableParcelable(); p.options = (mDhcpOptions == null) ? null : new ArrayList<>(mDhcpOptions); @@ -572,6 +583,7 @@ config.mIPv6AddrGenMode = p.ipv6AddrGenMode; config.mNetwork = p.network; config.mDisplayName = p.displayName; + config.mCreatorUid = p.creatorUid; config.mScanResultInfo = ScanResultInfo.fromStableParcelable(p.scanResultInfo); config.mLayer2Info = Layer2Information.fromStableParcelable(p.layer2Info); config.mDhcpOptions = (p.options == null) ? null : new ArrayList<>(p.options); @@ -630,6 +642,7 @@ .add("mIPv6AddrGenMode: " + mIPv6AddrGenMode) .add("mNetwork: " + mNetwork) .add("mDisplayName: " + mDisplayName) + .add("mCreatorUid:" + mCreatorUid) .add("mScanResultInfo: " + mScanResultInfo) .add("mLayer2Info: " + mLayer2Info) .add("mDhcpOptions: " + mDhcpOptions) @@ -680,7 +693,8 @@ && Objects.equals(mLayer2Info, other.mLayer2Info) && dhcpOptionListEquals(mDhcpOptions, other.mDhcpOptions) && mIPv4ProvisioningMode == other.mIPv4ProvisioningMode - && mIPv6ProvisioningMode == other.mIPv6ProvisioningMode; + && mIPv6ProvisioningMode == other.mIPv6ProvisioningMode + && mCreatorUid == other.mCreatorUid; } public boolean isValid() {
diff --git a/jni/network_stack_utils_jni.cpp b/jni/network_stack_utils_jni.cpp index 5d84f57..6f47d7e 100644 --- a/jni/network_stack_utils_jni.cpp +++ b/jni/network_stack_utils_jni.cpp
@@ -36,20 +36,12 @@ #include <netjniutils/netjniutils.h> #include <android/log.h> +#include <bpf/BpfClassic.h> namespace android { constexpr const char NETWORKSTACKUTILS_PKG_NAME[] = "com/android/networkstack/util/NetworkStackUtils"; -static const uint32_t kEtherTypeOffset = offsetof(ether_header, ether_type); -static const uint32_t kEtherHeaderLen = sizeof(ether_header); -static const uint32_t kIPv4Protocol = kEtherHeaderLen + offsetof(iphdr, protocol); -static const uint32_t kIPv4FlagsOffset = kEtherHeaderLen + offsetof(iphdr, frag_off); -static const uint32_t kIPv6NextHeader = kEtherHeaderLen + offsetof(ip6_hdr, ip6_nxt); -static const uint32_t kIPv6PayloadStart = kEtherHeaderLen + sizeof(ip6_hdr); -static const uint32_t kICMPv6TypeOffset = kIPv6PayloadStart + offsetof(icmp6_hdr, icmp6_type); -static const uint32_t kUDPSrcPortIndirectOffset = kEtherHeaderLen + offsetof(udphdr, source); -static const uint32_t kUDPDstPortIndirectOffset = kEtherHeaderLen + offsetof(udphdr, dest); static const uint16_t kDhcpClientPort = 68; static bool checkLenAndCopy(JNIEnv* env, const jbyteArray& addr, int len, void* dst) { @@ -99,28 +91,26 @@ } } +// fd is a "socket(AF_PACKET, SOCK_RAW, ETH_P_IP)" +// which guarantees packets already have skb->protocol == htons(ETH_P_IP) static void network_stack_utils_attachDhcpFilter(JNIEnv *env, jclass clazz, jobject javaFd) { static sock_filter filter_code[] = { // Check the protocol is UDP. - BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kIPv4Protocol), - BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_UDP, 0, 6), + BPF_LOAD_IPV4_U8(protocol), + BPF2_REJECT_IF_NOT_EQUAL(IPPROTO_UDP), // Check this is not a fragment. - BPF_STMT(BPF_LD | BPF_H | BPF_ABS, kIPv4FlagsOffset), - BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, IP_MF | IP_OFFMASK, 4, 0), + BPF_LOAD_IPV4_BE16(frag_off), + BPF2_REJECT_IF_ANY_MASKED_BITS_SET(IP_MF | IP_OFFMASK), // Get the IP header length. - BPF_STMT(BPF_LDX | BPF_B | BPF_MSH, kEtherHeaderLen), + BPF_LOADX_NET_RELATIVE_IPV4_HLEN, // Check the destination port. - BPF_STMT(BPF_LD | BPF_H | BPF_IND, kUDPDstPortIndirectOffset), - BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, kDhcpClientPort, 0, 1), + BPF_LOAD_NETX_RELATIVE_DST_PORT, + BPF2_REJECT_IF_NOT_EQUAL(kDhcpClientPort), - // Accept. - BPF_STMT(BPF_RET | BPF_K, 0xffff), - - // Reject. - BPF_STMT(BPF_RET | BPF_K, 0) + BPF_ACCEPT, }; const sock_fprog filter = { sizeof(filter_code) / sizeof(filter_code[0]), @@ -133,28 +123,21 @@ } } -static void network_stack_utils_attachRaFilter(JNIEnv *env, jclass clazz, jobject javaFd, - jint hardwareAddressType) { - if (hardwareAddressType != ARPHRD_ETHER) { - jniThrowExceptionFmt(env, "java/net/SocketException", - "attachRaFilter only supports ARPHRD_ETHER"); - return; - } - +// fd is a "socket(AF_PACKET, SOCK_RAW, ETH_P_IPV6)" +// which guarantees packets already have skb->protocol == htons(ETH_P_IPV6) +static void network_stack_utils_attachRaFilter(JNIEnv *env, jclass clazz, jobject javaFd) { static sock_filter filter_code[] = { + BPF_LOADX_CONSTANT_IPV6_HLEN, + // Check IPv6 Next Header is ICMPv6. - BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kIPv6NextHeader), - BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 3), + BPF_LOAD_IPV6_U8(nexthdr), + BPF2_REJECT_IF_NOT_EQUAL(IPPROTO_ICMPV6), // Check ICMPv6 type is Router Advertisement. - BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kICMPv6TypeOffset), - BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_ROUTER_ADVERT, 0, 1), + BPF_LOAD_NETX_RELATIVE_ICMP_TYPE, + BPF2_REJECT_IF_NOT_EQUAL(ND_ROUTER_ADVERT), - // Accept. - BPF_STMT(BPF_RET | BPF_K, 0xffff), - - // Reject. - BPF_STMT(BPF_RET | BPF_K, 0) + BPF_ACCEPT, }; static const sock_fprog filter = { sizeof(filter_code) / sizeof(filter_code[0]), @@ -163,20 +146,14 @@ int fd = netjniutils::GetNativeFileDescriptor(env, javaFd); if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) { - jniThrowExceptionFmt(env, "java/net/SocketException", - "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno)); + jniThrowErrnoException(env, "setsockopt(SO_ATTACH_FILTER)", errno); } } // TODO: Move all this filter code into libnetutils. +// fd is a "socket(AF_PACKET, SOCK_RAW, ETH_P_ALL)" static void network_stack_utils_attachControlPacketFilter( - JNIEnv *env, jclass clazz, jobject javaFd, jint hardwareAddressType) { - if (hardwareAddressType != ARPHRD_ETHER) { - jniThrowExceptionFmt(env, "java/net/SocketException", - "attachControlPacketFilter only supports ARPHRD_ETHER"); - return; - } - + JNIEnv *env, jclass clazz, jobject javaFd) { // Capture all: // - ARPs // - DHCPv4 packets @@ -188,50 +165,50 @@ // '(ip and udp port 68)' or // '(icmp6 and ip6[40] >= 133 and ip6[40] <= 136)' static sock_filter filter_code[] = { - // Load the link layer next payload field. - BPF_STMT(BPF_LD | BPF_H | BPF_ABS, kEtherTypeOffset), + // Load the ethertype from skb->protocol + BPF_LOAD_SKB_PROTOCOL, // Accept all ARP. // TODO: Figure out how to better filter ARPs on noisy networks. - BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ETHERTYPE_ARP, 16, 0), + BPF2_ACCEPT_IF_EQUAL(ETHERTYPE_ARP), - // If IPv4: - BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ETHERTYPE_IP, 0, 9), + // If IPv4: (otherwise jump to the 'IPv6 ...' below) + BPF_JUMP_IF_NOT_EQUAL(ETHERTYPE_IP, 14), // Check the protocol is UDP. - BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kIPv4Protocol), - BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_UDP, 0, 14), + BPF_LOAD_IPV4_U8(protocol), + BPF2_REJECT_IF_NOT_EQUAL(IPPROTO_UDP), // Check this is not a fragment. - BPF_STMT(BPF_LD | BPF_H | BPF_ABS, kIPv4FlagsOffset), - BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, IP_OFFMASK, 12, 0), + BPF_LOAD_IPV4_BE16(frag_off), + BPF2_REJECT_IF_ANY_MASKED_BITS_SET(IP_MF | IP_OFFMASK), // Get the IP header length. - BPF_STMT(BPF_LDX | BPF_B | BPF_MSH, kEtherHeaderLen), + BPF_LOADX_NET_RELATIVE_IPV4_HLEN, // Check the source port. - BPF_STMT(BPF_LD | BPF_H | BPF_IND, kUDPSrcPortIndirectOffset), - BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, kDhcpClientPort, 8, 0), + BPF_LOAD_NETX_RELATIVE_SRC_PORT, + BPF2_ACCEPT_IF_EQUAL(kDhcpClientPort), // Check the destination port. - BPF_STMT(BPF_LD | BPF_H | BPF_IND, kUDPDstPortIndirectOffset), - BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, kDhcpClientPort, 6, 7), + BPF_LOAD_NETX_RELATIVE_DST_PORT, + BPF2_ACCEPT_IF_EQUAL(kDhcpClientPort), + + // Reject any other UDPv4 + BPF_REJECT, // IPv6 ... - BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ETHERTYPE_IPV6, 0, 6), + BPF2_REJECT_IF_NOT_EQUAL(ETHERTYPE_IPV6), + // Assume standard, 40-byte, extension header-less ipv6 packet + BPF_LOADX_CONSTANT_IPV6_HLEN, // ... check IPv6 Next Header is ICMPv6 (ignore fragments), ... - BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kIPv6NextHeader), - BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 4), + BPF_LOAD_IPV6_U8(nexthdr), + BPF2_REJECT_IF_NOT_EQUAL(IPPROTO_ICMPV6), // ... and check the ICMPv6 type is one of RS/RA/NS/NA. - BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kICMPv6TypeOffset), - BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, ND_ROUTER_SOLICIT, 0, 2), - BPF_JUMP(BPF_JMP | BPF_JGT | BPF_K, ND_NEIGHBOR_ADVERT, 1, 0), + BPF_LOAD_NETX_RELATIVE_ICMP_TYPE, + BPF3_REJECT_IF_NOT_IN_RANGE(ND_ROUTER_SOLICIT, ND_NEIGHBOR_ADVERT), - // Accept. - BPF_STMT(BPF_RET | BPF_K, 0xffff), - - // Reject. - BPF_STMT(BPF_RET | BPF_K, 0) + BPF_ACCEPT, }; static const sock_fprog filter = { sizeof(filter_code) / sizeof(filter_code[0]), @@ -240,8 +217,7 @@ int fd = netjniutils::GetNativeFileDescriptor(env, javaFd); if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) { - jniThrowExceptionFmt(env, "java/net/SocketException", - "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno)); + jniThrowErrnoException(env, "setsockopt(SO_ATTACH_FILTER)", errno); } } @@ -252,8 +228,8 @@ /* name, signature, funcPtr */ { "addArpEntry", "([B[BLjava/lang/String;Ljava/io/FileDescriptor;)V", (void*) network_stack_utils_addArpEntry }, { "attachDhcpFilter", "(Ljava/io/FileDescriptor;)V", (void*) network_stack_utils_attachDhcpFilter }, - { "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) network_stack_utils_attachRaFilter }, - { "attachControlPacketFilter", "(Ljava/io/FileDescriptor;I)V", (void*) network_stack_utils_attachControlPacketFilter }, + { "attachRaFilter", "(Ljava/io/FileDescriptor;)V", (void*) network_stack_utils_attachRaFilter }, + { "attachControlPacketFilter", "(Ljava/io/FileDescriptor;)V", (void*) network_stack_utils_attachControlPacketFilter }, }; extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml index a590ee6..f254610 100644 --- a/res/values-sq/strings.xml +++ b/res/values-sq/strings.xml
@@ -21,6 +21,6 @@ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Informacionet për vendin e rrjetit"</string> <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Njoftimet e shfaqura për të treguar se rrjeti ka një faqe me informacione për vendin"</string> <string name="connected" msgid="4563643884927480998">"U lidh"</string> - <string name="tap_for_info" msgid="6849746325626883711">"Lidhur / Trokit për të parë faqen e internetit"</string> + <string name="tap_for_info" msgid="6849746325626883711">"Lidhur / Trokit për të parë uebsajtin"</string> <string name="application_label" msgid="1322847171305285454">"Menaxheri i rrjetit"</string> </resources>
diff --git a/res/values/config.xml b/res/values/config.xml index 15152e3..aed375c 100644 --- a/res/values/config.xml +++ b/res/values/config.xml
@@ -125,4 +125,7 @@ <!-- Whether to validate DUN networks. This is unused and always true on U+. --> <bool name="config_validate_dun_networks">false</bool> + + <!-- Configuration for including DHCP domain search list option --> + <bool name="config_dhcp_client_domain_search_list">false</bool> </resources>
diff --git a/res/values/overlayable.xml b/res/values/overlayable.xml index 20ee593..9c2172a 100644 --- a/res/values/overlayable.xml +++ b/res/values/overlayable.xml
@@ -90,6 +90,8 @@ <item type="drawable" name="icon_wifi"/> <!-- Whether to validate DUN networks. This is unused and always true on U+. --> <item type="bool" name="config_validate_dun_networks"/> + <!-- Configuration for including DHCP client domain search list option. --> + <item type="bool" name="config_dhcp_client_domain_search_list"/> </policy> </overlayable> </resources>
diff --git a/src/android/net/DhcpResults.java b/src/android/net/DhcpResults.java index 7c271c3..d590c07 100644 --- a/src/android/net/DhcpResults.java +++ b/src/android/net/DhcpResults.java
@@ -63,6 +63,8 @@ @Nullable public String captivePortalApiUrl; + public ArrayList<String> dmnsrchList = new ArrayList<>(); + public DhcpResults() { super(); } @@ -100,6 +102,7 @@ mtu = source.mtu; serverHostName = source.serverHostName; captivePortalApiUrl = source.captivePortalApiUrl; + dmnsrchList = source.dmnsrchList; } } @@ -135,6 +138,7 @@ mtu = 0; serverHostName = null; captivePortalApiUrl = null; + dmnsrchList.clear(); } @Override @@ -167,13 +171,14 @@ && Objects.equals(serverHostName, target.serverHostName) && leaseDuration == target.leaseDuration && mtu == target.mtu - && Objects.equals(captivePortalApiUrl, target.captivePortalApiUrl); + && Objects.equals(captivePortalApiUrl, target.captivePortalApiUrl) + && dmnsrchList.equals(target.dmnsrchList); } @Override public int hashCode() { return Objects.hash(ipAddress, gateway, dnsServers, domains, serverAddress, vendorInfo, - serverHostName, captivePortalApiUrl) + 43 * leaseDuration + 67 * mtu; + serverHostName, captivePortalApiUrl, dmnsrchList) + 43 * leaseDuration + 67 * mtu; } /** @@ -302,6 +307,15 @@ return domains; } + /** + * Append the domain search list strings separated by space to domain string. + */ + public String appendDomainsSearchList() { + final String domainsPrefix = domains == null ? "" : domains; + final String separator = domains != null && dmnsrchList.size() > 0 ? " " : ""; + return domainsPrefix + separator + TextUtils.join(" ", dmnsrchList); + } + public void setDomains(String domains) { this.domains = domains; }
diff --git a/src/android/net/apf/AndroidPacketFilter.java b/src/android/net/apf/AndroidPacketFilter.java new file mode 100644 index 0000000..18c704e --- /dev/null +++ b/src/android/net/apf/AndroidPacketFilter.java
@@ -0,0 +1,80 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.net.apf; + +import android.net.LinkProperties; +import android.net.NattKeepalivePacketDataParcelable; +import android.net.TcpKeepalivePacketDataParcelable; + +import com.android.internal.util.IndentingPrintWriter; + +/** + * The interface for AndroidPacketFilter + */ +public interface AndroidPacketFilter { + /** + * Update the LinkProperties that will be used by APF. + */ + void setLinkProperties(LinkProperties lp); + + /** + * Shutdown the APF. + */ + void shutdown(); + + /** + * Switch for the multicast filter. + * @param isEnabled if the multicast filter should be enabled or not. + */ + void setMulticastFilter(boolean isEnabled); + + /** + * Set the APF data snapshot. + */ + void setDataSnapshot(byte[] data); + + /** + * Add TCP keepalive ack packet filter. + * This will add a filter to drop acks to the keepalive packet passed as an argument. + * + * @param slot The index used to access the filter. + * @param sentKeepalivePacket The attributes of the sent keepalive packet. + */ + void addTcpKeepalivePacketFilter(int slot, + TcpKeepalivePacketDataParcelable sentKeepalivePacket); + + /** + * Add NAT-T keepalive packet filter. + * This will add a filter to drop NAT-T keepalive packet which is passed as an argument. + * + * @param slot The index used to access the filter. + * @param sentKeepalivePacket The attributes of the sent keepalive packet. + */ + void addNattKeepalivePacketFilter(int slot, + NattKeepalivePacketDataParcelable sentKeepalivePacket); + + /** + * Remove keepalive packet filter. + * + * @param slot The index used to access the filter. + */ + void removeKeepalivePacketFilter(int slot); + + /** + * Dump the status of APF. + */ + void dump(IndentingPrintWriter pw); +}
diff --git a/src/android/net/apf/ApfCounterTracker.java b/src/android/net/apf/ApfCounterTracker.java new file mode 100644 index 0000000..0527666 --- /dev/null +++ b/src/android/net/apf/ApfCounterTracker.java
@@ -0,0 +1,151 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.apf; + +import android.util.ArrayMap; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +/** + * Common counter class for {@code ApfFilter} and {@code LegacyApfFilter}. + * + * @hide + */ +public class ApfCounterTracker { + /** + * APF packet counters. + * + * Packet counters are 32bit big-endian values, and allocated near the end of the APF data + * buffer, using negative byte offsets, where -4 is equivalent to maximumApfProgramSize - 4, + * the last writable 32bit word. + */ + @VisibleForTesting + public enum Counter { + RESERVED_OOB, // Points to offset 0 from the end of the buffer (out-of-bounds) + TOTAL_PACKETS, + PASSED_ARP, + PASSED_DHCP, + PASSED_IPV4, + PASSED_IPV6_NON_ICMP, + PASSED_IPV4_UNICAST, + PASSED_IPV6_ICMP, + PASSED_IPV6_UNICAST_NON_ICMP, + PASSED_ARP_NON_IPV4, + PASSED_ARP_UNKNOWN, + PASSED_ARP_UNICAST_REPLY, + PASSED_NON_IP_UNICAST, + PASSED_MDNS, + DROPPED_ETH_BROADCAST, + DROPPED_RA, + DROPPED_GARP_REPLY, + DROPPED_ARP_OTHER_HOST, + DROPPED_IPV4_L2_BROADCAST, + DROPPED_IPV4_BROADCAST_ADDR, + DROPPED_IPV4_BROADCAST_NET, + DROPPED_IPV4_MULTICAST, + DROPPED_IPV6_ROUTER_SOLICITATION, + DROPPED_IPV6_MULTICAST_NA, + DROPPED_IPV6_MULTICAST, + DROPPED_IPV6_MULTICAST_PING, + DROPPED_IPV6_NON_ICMP_MULTICAST, + DROPPED_802_3_FRAME, + DROPPED_ETHERTYPE_DENYLISTED, + DROPPED_ARP_REPLY_SPA_NO_HOST, + DROPPED_IPV4_KEEPALIVE_ACK, + DROPPED_IPV6_KEEPALIVE_ACK, + DROPPED_IPV4_NATT_KEEPALIVE, + DROPPED_MDNS, + DROPPED_IPV4_TCP_PORT7_UNICAST, + DROPPED_ARP_NON_IPV4, + DROPPED_ARP_UNKNOWN; + + /** + * Returns the negative byte offset from the end of the APF data segment for + * a given counter. + */ + public int offset() { + return -this.ordinal() * 4; // Currently, all counters are 32bit long. + } + + /** + * Returns the total size of the data segment in bytes. + */ + public static int totalSize() { + return (Counter.class.getEnumConstants().length - 1) * 4; + } + } + + private final List<Counter> mCounterList; + // Store the counters' value + private final Map<Counter, Long> mCounters = new ArrayMap<>(); + + public ApfCounterTracker() { + Counter[] counters = Counter.class.getEnumConstants(); + mCounterList = Arrays.asList(counters).subList(1, counters.length); + } + + /** + * Get the value of a counter from APF data. + */ + public static long getCounterValue(byte[] data, Counter counter) + throws ArrayIndexOutOfBoundsException { + // Follow the same wrap-around addressing scheme of the interpreter. + int offset = counter.offset(); + if (offset < 0) { + offset = data.length + offset; + } + + // Decode 32bit big-endian integer into a long so we can count up beyond 2^31. + long value = 0; + for (int i = 0; i < 4; i++) { + value = value << 8 | (data[offset] & 0xFF); + offset++; + } + return value; + } + + /** + * Update counters from APF data. + */ + public void updateCountersFromData(byte[] data) { + if (data == null) return; + for (Counter counter : mCounterList) { + long value; + try { + value = getCounterValue(data, counter); + } catch (ArrayIndexOutOfBoundsException e) { + value = 0; + } + long oldValue = mCounters.getOrDefault(counter, 0L); + // All counters are increamental + if (value > oldValue) { + mCounters.put(counter, value); + } + } + } + + /** + * Get counters map. + */ + public Map<Counter, Long> getCounters() { + return mCounters; + } +}
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java index 3689cee..ee2990b 100644 --- a/src/android/net/apf/ApfFilter.java +++ b/src/android/net/apf/ApfFilter.java
@@ -17,7 +17,8 @@ package android.net.apf; import static android.net.util.SocketUtils.makePacketSocketAddress; -import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; +import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED; +import static android.os.PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED; import static android.system.OsConstants.AF_PACKET; import static android.system.OsConstants.ARPHRD_ETHER; import static android.system.OsConstants.ETH_P_ARP; @@ -26,6 +27,7 @@ import static android.system.OsConstants.IPPROTO_ICMPV6; import static android.system.OsConstants.IPPROTO_TCP; import static android.system.OsConstants.IPPROTO_UDP; +import static android.system.OsConstants.SOCK_CLOEXEC; import static android.system.OsConstants.SOCK_RAW; import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST; @@ -34,7 +36,6 @@ import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT; import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION; import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_LEN; -import static com.android.networkstack.util.NetworkStackUtils.APF_USE_RA_LIFETIME_CALCULATION_FIX_VERSION; import android.content.BroadcastReceiver; import android.content.Context; @@ -44,32 +45,35 @@ import android.net.LinkProperties; import android.net.NattKeepalivePacketDataParcelable; import android.net.TcpKeepalivePacketDataParcelable; +import android.net.apf.ApfCounterTracker.Counter; import android.net.apf.ApfGenerator.IllegalInstructionException; import android.net.apf.ApfGenerator.Register; import android.net.ip.IpClient.IpClientCallbacksWrapper; -import android.net.metrics.ApfProgramEvent; -import android.net.metrics.ApfStats; -import android.net.metrics.IpConnectivityLog; -import android.net.metrics.RaEvent; import android.os.PowerManager; import android.os.SystemClock; +import android.stats.connectivity.NetworkQuirkEvent; import android.system.ErrnoException; import android.system.Os; import android.text.format.DateUtils; import android.util.Log; import android.util.SparseArray; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.HexDump; import com.android.internal.util.IndentingPrintWriter; +import com.android.internal.util.TokenBucket; +import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.CollectionUtils; import com.android.net.module.util.ConnectivityUtils; -import com.android.net.module.util.DeviceConfigUtils; import com.android.net.module.util.InterfaceParams; import com.android.net.module.util.SocketUtils; +import com.android.networkstack.metrics.ApfSessionInfoMetrics; +import com.android.networkstack.metrics.IpClientRaInfoMetrics; +import com.android.networkstack.metrics.NetworkQuirkMetrics; import com.android.networkstack.util.NetworkStackUtils; import java.io.ByteArrayOutputStream; @@ -88,6 +92,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; /** * For networks that support packet filtering via APF programs, {@code ApfFilter} @@ -107,7 +112,7 @@ * * @hide */ -public class ApfFilter { +public class ApfFilter implements AndroidPacketFilter { // Helper class for specifying functional filter parameters. public static class ApfConfiguration { @@ -116,71 +121,18 @@ public boolean ieee802_3Filter; public int[] ethTypeBlackList; public int minRdnssLifetimeSec; + public int acceptRaMinLft; + public boolean shouldHandleLightDoze; + public long minMetricsSessionDurationMs; } - // Enums describing the outcome of receiving an RA packet. - private static enum ProcessRaResult { - MATCH, // Received RA matched a known RA - DROPPED, // Received RA ignored due to MAX_RAS - PARSE_ERROR, // Received RA could not be parsed - ZERO_LIFETIME, // Received RA had 0 lifetime - UPDATE_NEW_RA, // APF program updated for new RA - UPDATE_EXPIRY // APF program updated for expiry - } - - /** - * APF packet counters. - * - * Packet counters are 32bit big-endian values, and allocated near the end of the APF data - * buffer, using negative byte offsets, where -4 is equivalent to maximumApfProgramSize - 4, - * the last writable 32bit word. - */ - @VisibleForTesting - public static enum Counter { - RESERVED_OOB, // Points to offset 0 from the end of the buffer (out-of-bounds) - TOTAL_PACKETS, - PASSED_ARP, - PASSED_DHCP, - PASSED_IPV4, - PASSED_IPV6_NON_ICMP, - PASSED_IPV4_UNICAST, - PASSED_IPV6_ICMP, - PASSED_IPV6_UNICAST_NON_ICMP, - PASSED_ARP_NON_IPV4, - PASSED_ARP_UNKNOWN, - PASSED_ARP_UNICAST_REPLY, - PASSED_NON_IP_UNICAST, - PASSED_MDNS, - DROPPED_ETH_BROADCAST, - DROPPED_RA, - DROPPED_GARP_REPLY, - DROPPED_ARP_OTHER_HOST, - DROPPED_IPV4_L2_BROADCAST, - DROPPED_IPV4_BROADCAST_ADDR, - DROPPED_IPV4_BROADCAST_NET, - DROPPED_IPV4_MULTICAST, - DROPPED_IPV6_ROUTER_SOLICITATION, - DROPPED_IPV6_MULTICAST_NA, - DROPPED_IPV6_MULTICAST, - DROPPED_IPV6_MULTICAST_PING, - DROPPED_IPV6_NON_ICMP_MULTICAST, - DROPPED_802_3_FRAME, - DROPPED_ETHERTYPE_BLACKLISTED, - DROPPED_ARP_REPLY_SPA_NO_HOST, - DROPPED_IPV4_KEEPALIVE_ACK, - DROPPED_IPV6_KEEPALIVE_ACK, - DROPPED_IPV4_NATT_KEEPALIVE, - DROPPED_MDNS; - - // Returns the negative byte offset from the end of the APF data segment for - // a given counter. - public int offset() { - return - this.ordinal() * 4; // Currently, all counters are 32bit long. - } - - // Returns the total size of the data segment in bytes. - public static int totalSize() { - return (Counter.class.getEnumConstants().length - 1) * 4; + /** A wrapper class of {@link SystemClock} to be mocked in unit tests. */ + public static class Clock { + /** + * @see SystemClock#elapsedRealtime + */ + public long elapsedRealtime() { + return SystemClock.elapsedRealtime(); } } @@ -203,14 +155,6 @@ public class ReceiveThread extends Thread { private final byte[] mPacket = new byte[1514]; private final FileDescriptor mSocket; - private final long mStart = SystemClock.elapsedRealtime(); - - private int mReceivedRas = 0; - private int mMatchingRas = 0; - private int mDroppedRas = 0; - private int mParseErrors = 0; - private int mZeroLifetimeRas = 0; - private int mProgramUpdates = 0; private volatile boolean mStopped; @@ -230,59 +174,13 @@ while (!mStopped) { try { int length = Os.read(mSocket, mPacket, 0, mPacket.length); - updateStats(processRa(mPacket, length)); + processRa(mPacket, length); } catch (IOException|ErrnoException e) { if (!mStopped) { Log.e(TAG, "Read error", e); } } } - logStats(); - } - - private void updateStats(ProcessRaResult result) { - mReceivedRas++; - switch(result) { - case MATCH: - mMatchingRas++; - return; - case DROPPED: - mDroppedRas++; - return; - case PARSE_ERROR: - mParseErrors++; - return; - case ZERO_LIFETIME: - mZeroLifetimeRas++; - return; - case UPDATE_EXPIRY: - mMatchingRas++; - mProgramUpdates++; - return; - case UPDATE_NEW_RA: - mProgramUpdates++; - return; - } - } - - private void logStats() { - final long nowMs = SystemClock.elapsedRealtime(); - synchronized (this) { - final ApfStats stats = new ApfStats.Builder() - .setReceivedRas(mReceivedRas) - .setMatchingRas(mMatchingRas) - .setDroppedRas(mDroppedRas) - .setParseErrors(mParseErrors) - .setZeroLifetimeRas(mZeroLifetimeRas) - .setProgramUpdates(mProgramUpdates) - .setDurationMs(nowMs - mStart) - .setMaxProgramSize(mApfCapabilities.maximumApfProgramSize) - .setProgramUpdatesAll(mNumProgramUpdates) - .setProgramUpdatesAllowingMulticast(mNumProgramUpdatesAllowingMulticast) - .build(); - mMetricsLog.log(stats); - logApfProgramEventLocked(nowMs / DateUtils.SECOND_IN_MILLIS); - } } } @@ -301,6 +199,7 @@ // Endianness is not an issue for this constant because the APF interpreter always operates in // network byte order. private static final int IPV4_FRAGMENT_OFFSET_MASK = 0x1fff; + private static final int IPV4_FRAGMENT_MORE_FRAGS_MASK = 0x2000; private static final int IPV4_PROTOCOL_OFFSET = ETH_HEADER_LEN + 9; private static final int IPV4_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 16; private static final int IPV4_ANY_HOST_ADDRESS = 0; @@ -322,8 +221,10 @@ private static final int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN; + private static final int IPPROTO_HOPOPTS = 0; + // NOTE: this must be added to the IPv4 header length in IPV4_HEADER_SIZE_MEMORY_SLOT - private static final int UDP_DESTINATION_PORT_OFFSET = ETH_HEADER_LEN + 2; + private static final int TCP_UDP_DESTINATION_PORT_OFFSET = ETH_HEADER_LEN + 2; private static final int UDP_HEADER_LEN = 8; private static final int TCP_HEADER_SIZE_OFFSET = 12; @@ -345,8 +246,6 @@ private static final short ARP_OPCODE_REPLY = 2; private static final int ARP_SOURCE_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 14; private static final int ARP_TARGET_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 24; - // Do not log ApfProgramEvents whose actual lifetimes was less than this. - private static final int APF_PROGRAM_EVENT_LIFETIME_THRESHOLD = 2; // Limit on the Black List size to cap on program usage for this // TODO: Select a proper max length private static final int APF_MAX_ETH_TYPE_BLACK_LIST_LEN = 20; @@ -356,6 +255,8 @@ private static final byte[] ETH_MULTICAST_MDNS_V6_MAC_ADDRESS = {(byte) 0x33, (byte) 0x33, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xfb}; private static final int MDNS_PORT = 5353; + + private static final int ECHO_PORT = 7; private static final int DNS_HEADER_LEN = 12; private static final int DNS_QDCOUNT_OFFSET = 4; // NOTE: this must be added to the IPv4 header length in IPV4_HEADER_SIZE_MEMORY_SLOT, or the @@ -366,11 +267,10 @@ ETH_HEADER_LEN + UDP_HEADER_LEN + DNS_HEADER_LEN; - private final ApfCapabilities mApfCapabilities; private final IpClientCallbacksWrapper mIpClientCallback; private final InterfaceParams mInterfaceParams; - private final IpConnectivityLog mMetricsLog; + private final TokenBucket mTokenBucket; @VisibleForTesting public byte[] mHardwareAddress; @@ -385,21 +285,78 @@ private final boolean mDrop802_3Frames; private final int[] mEthTypeBlackList; + private final Clock mClock; + private final ApfCounterTracker mApfCounterTracker = new ApfCounterTracker(); + @GuardedBy("this") + private long mSessionStartMs = 0; + @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. private final int mMinRdnssLifetimeSec; - // Flag to use the RA lifetime calculation fix in aosp/2276160. - private final boolean mUseLifetimeCalculationFix; + // Minimum session time for metrics, duration less than this time will not be logged. + private final long mMinMetricsSessionDurationMs; + + // Tracks the value of /proc/sys/ipv6/conf/$iface/accept_ra_min_lft which affects router, RIO, + // and PIO valid lifetimes. + private final int mAcceptRaMinLft; + private final boolean mShouldHandleLightDoze; + + private final NetworkQuirkMetrics mNetworkQuirkMetrics; + private final IpClientRaInfoMetrics mIpClientRaInfoMetrics; + private final ApfSessionInfoMetrics mApfSessionInfoMetrics; + + private static boolean isDeviceIdleModeChangedAction(Intent intent) { + return ACTION_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction()); + } + + private boolean isDeviceLightIdleModeChangedAction(Intent intent) { + // The ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED only exist since T. For lower platform version, + // the check should return false. The explicit SDK check is needed to make linter happy + // about accessing ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED in this function. + if (!SdkLevel.isAtLeastT()) { + return false; + } + if (!mShouldHandleLightDoze) { + return false; + } + return ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED.equals(intent.getAction()); + } + + private boolean isDeviceLightIdleMode(@NonNull PowerManager powerManager) { + // The powerManager.isDeviceLightIdleMode() only exist since T. For lower platform version, + // the check should return false. The explicit SDK check is needed to make linter happy + // about accessing powerManager.isDeviceLightIdleMode() in this function. + if (!SdkLevel.isAtLeastT()) { + return false; + } + if (!mShouldHandleLightDoze) { + return false; + } + + return powerManager.isDeviceLightIdleMode(); + } // Detects doze mode state transitions. private final BroadcastReceiver mDeviceIdleReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (action.equals(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)) { - PowerManager powerManager = - (PowerManager) context.getSystemService(Context.POWER_SERVICE); - final boolean deviceIdle = powerManager.isDeviceIdleMode(); + final PowerManager powerManager = context.getSystemService(PowerManager.class); + if (isDeviceIdleModeChangedAction(intent) + || isDeviceLightIdleModeChangedAction(intent)) { + final boolean deviceIdle = powerManager.isDeviceIdleMode() + || isDeviceLightIdleMode(powerManager); setDozeMode(deviceIdle); } } @@ -413,38 +370,42 @@ @GuardedBy("this") private int mIPv4PrefixLength; - /** - * Dependencies for the ApfFilter. Useful to be mocked in tests. - */ - public static class Dependencies { - /** - * Return whether a feature guarded by a feature flag is enabled. - * @see NetworkStackUtils#isFeatureEnabled(Context, String, String) - */ - public boolean isFeatureEnabled(final Context context, final String name, - boolean defaultEnabled) { - return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name, - defaultEnabled); - } - } + private final Dependencies mDependencies; public ApfFilter(Context context, ApfConfiguration config, InterfaceParams ifParams, - IpClientCallbacksWrapper ipClientCallback, IpConnectivityLog log) { - this(context, config, ifParams, ipClientCallback, log, new Dependencies()); + IpClientCallbacksWrapper ipClientCallback, NetworkQuirkMetrics networkQuirkMetrics) { + this(context, config, ifParams, ipClientCallback, networkQuirkMetrics, + new Dependencies(context), new Clock()); } @VisibleForTesting public ApfFilter(Context context, ApfConfiguration config, InterfaceParams ifParams, - IpClientCallbacksWrapper ipClientCallback, IpConnectivityLog log, Dependencies deps) { + IpClientCallbacksWrapper ipClientCallback, NetworkQuirkMetrics networkQuirkMetrics, + Dependencies dependencies) { + this(context, config, ifParams, ipClientCallback, networkQuirkMetrics, dependencies, + new Clock()); + } + + @VisibleForTesting + public ApfFilter(Context context, ApfConfiguration config, InterfaceParams ifParams, + IpClientCallbacksWrapper ipClientCallback, NetworkQuirkMetrics networkQuirkMetrics, + Dependencies dependencies, Clock clock) { mApfCapabilities = config.apfCapabilities; mIpClientCallback = ipClientCallback; mInterfaceParams = ifParams; mMulticastFilter = config.multicastFilter; mDrop802_3Frames = config.ieee802_3Filter; mMinRdnssLifetimeSec = config.minRdnssLifetimeSec; - mUseLifetimeCalculationFix = deps.isFeatureEnabled(context, - APF_USE_RA_LIFETIME_CALCULATION_FIX_VERSION, true /* defaultEnabled */); + mAcceptRaMinLft = config.acceptRaMinLft; mContext = context; + mShouldHandleLightDoze = config.shouldHandleLightDoze; + mDependencies = dependencies; + mNetworkQuirkMetrics = networkQuirkMetrics; + mIpClientRaInfoMetrics = dependencies.getIpClientRaInfoMetrics(); + mApfSessionInfoMetrics = dependencies.getApfSessionInfoMetrics(); + mClock = clock; + mSessionStartMs = mClock.elapsedRealtime(); + mMinMetricsSessionDurationMs = config.minMetricsSessionDurationMs; if (mApfCapabilities.hasDataAccess()) { mCountAndPassLabel = "countAndPass"; @@ -459,18 +420,65 @@ // Now fill the black list from the passed array mEthTypeBlackList = filterEthTypeBlackList(config.ethTypeBlackList); - mMetricsLog = log; + // TokenBucket for rate limiting filter installation. APF filtering relies on the filter + // always being up-to-date and APF bytecode being in sync with userspace. The TokenBucket + // merely prevents illconfigured / abusive networks from impacting the system, so it does + // not need to be very restrictive. + // The TokenBucket starts with its full capacity of 20 tokens (= 20 filter updates). A new + // token is generated every 3 seconds limiting the filter update rate to at most once every + // 3 seconds. + mTokenBucket = new TokenBucket(3_000 /* deltaMs */, 20 /* capacity */, 20 /* tokens */); // TODO: ApfFilter should not generate programs until IpClient sends provisioning success. maybeStartFilter(); // Listen for doze-mode transition changes to enable/disable the IPv6 multicast filter. - mContext.registerReceiver(mDeviceIdleReceiver, - new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)); + mDependencies.addDeviceIdleReceiver(mDeviceIdleReceiver, mShouldHandleLightDoze); + } + + /** + * Dependencies class for testing. + */ + @VisibleForTesting + public static class Dependencies { + private final Context mContext; + public Dependencies(final Context context) { + mContext = context; + } + + /** Add receiver for detecting doze mode change */ + public void addDeviceIdleReceiver(@NonNull final BroadcastReceiver receiver, + boolean shouldHandleLightDoze) { + final IntentFilter intentFilter = new IntentFilter(ACTION_DEVICE_IDLE_MODE_CHANGED); + if (SdkLevel.isAtLeastT() && shouldHandleLightDoze) { + intentFilter.addAction(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED); + } + mContext.registerReceiver(receiver, intentFilter); + } + + /** Remove broadcast receiver. */ + public void removeBroadcastReceiver(@NonNull final BroadcastReceiver receiver) { + mContext.unregisterReceiver(receiver); + } + + /** + * Get a ApfSessionInfoMetrics instance. + */ + public ApfSessionInfoMetrics getApfSessionInfoMetrics() { + return new ApfSessionInfoMetrics(); + } + + /** + * Get a IpClientRaInfoMetrics instance. + */ + public IpClientRaInfoMetrics getIpClientRaInfoMetrics() { + return new IpClientRaInfoMetrics(); + } } public synchronized void setDataSnapshot(byte[] data) { mDataSnapshot = data; + mApfCounterTracker.updateCountersFromData(data); } private void log(String s) { @@ -526,16 +534,18 @@ // a crash on some older devices (b/78905546). if (mApfCapabilities.hasDataAccess()) { byte[] zeroes = new byte[mApfCapabilities.maximumApfProgramSize]; - mIpClientCallback.installPacketFilter(zeroes); + if (!mIpClientCallback.installPacketFilter(zeroes)) { + sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE); + } } // Install basic filters installNewProgramLocked(); } - socket = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IPV6); + socket = Os.socket(AF_PACKET, SOCK_RAW | SOCK_CLOEXEC, 0); + NetworkStackUtils.attachRaFilter(socket); SocketAddress addr = makePacketSocketAddress(ETH_P_IPV6, mInterfaceParams.index); Os.bind(socket, addr); - NetworkStackUtils.attachRaFilter(socket, mApfCapabilities.apfPacketFormat); } catch(SocketException|ErrnoException e) { Log.e(TAG, "Error starting filter", e); return; @@ -546,8 +556,8 @@ // Returns seconds since device boot. @VisibleForTesting - protected long currentTimeSeconds() { - return SystemClock.elapsedRealtime() / DateUtils.SECOND_IN_MILLIS; + protected int secondsSinceBoot() { + return (int) (mClock.elapsedRealtime() / DateUtils.SECOND_IN_MILLIS); } public static class InvalidRaException extends Exception { @@ -562,8 +572,7 @@ private static class PacketSection { public enum Type { MATCH, // A field that should be matched (e.g., the router IP address). - IGNORE, // An ignored field such as the checksum of the flow label. Not matched. - LIFETIME, // A lifetime. Not matched, and generally counts toward minimum RA lifetime. + LIFETIME, // A lifetime. Not matched, and counts toward minimum RA lifetime if >= min. } /** The type of section. */ @@ -572,22 +581,35 @@ public final int start; /** Length of this section in bytes. */ public final int length; - /** If this is a lifetime, the ICMP option that defined it. 0 for router lifetime. */ - public final int option; /** If this is a lifetime, the lifetime value. */ public final long lifetime; + /** If this is a lifetime, the value below which the lifetime is ignored */ + public final int min; - PacketSection(int start, int length, Type type, int option, long lifetime) { + PacketSection(int start, int length, Type type, long lifetime, int min) { this.start = start; + + if (type == Type.LIFETIME && length != 2 && length != 4) { + throw new IllegalArgumentException("LIFETIME section length must be 2 or 4 bytes"); + } this.length = length; this.type = type; - this.option = option; + + if (type == Type.MATCH && (lifetime != 0 || min != 0)) { + throw new IllegalArgumentException("lifetime, min must be 0 for MATCH sections"); + } this.lifetime = lifetime; + + // It has already been asserted that min is 0 for MATCH sections. + if (min < 0) { + throw new IllegalArgumentException("min must be >= 0 for LIFETIME sections"); + } + this.min = min; } public String toString() { if (type == Type.LIFETIME) { - return String.format("%s: (%d, %d) %d %d", type, start, length, option, lifetime); + return String.format("%s: (%d, %d) %d %d", type, start, length, lifetime, min); } else { return String.format("%s: (%d, %d)", type, start, length); } @@ -615,10 +637,18 @@ private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET = 8; private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_LEN = 4; + // From RFC4861: source link-layer address + private static final int ICMP6_SOURCE_LL_ADDRESS_OPTION_TYPE = 1; + // From RFC4861: mtu size option + private static final int ICMP6_MTU_OPTION_TYPE = 5; // From RFC6106: Recursive DNS Server option private static final int ICMP6_RDNSS_OPTION_TYPE = 25; // From RFC6106: DNS Search List option private static final int ICMP6_DNSSL_OPTION_TYPE = 31; + // From RFC8910: Captive-Portal option + private static final int ICMP6_CAPTIVE_PORTAL_OPTION_TYPE = 37; + // From RFC8781: PREF64 option + private static final int ICMP6_PREF64_OPTION_TYPE = 38; // From RFC4191: Route Information option private static final int ICMP6_ROUTE_INFO_OPTION_TYPE = 24; @@ -632,10 +662,18 @@ // List of sections in the packet. private final ArrayList<PacketSection> mPacketSections = new ArrayList<>(); + // Router lifetime in packet + private final int mRouterLifetime; + // Minimum valid lifetime of PIOs in packet, Long.MAX_VALUE means not seen. + private long mMinPioValidLifetime = Long.MAX_VALUE; + // Minimum route lifetime of RIOs in packet, Long.MAX_VALUE means not seen. + private long mMinRioRouteLifetime = Long.MAX_VALUE; + // Minimum lifetime of RDNSSs in packet, Long.MAX_VALUE means not seen. + private long mMinRdnssLifetime = Long.MAX_VALUE; // Minimum lifetime in packet - long mMinLifetime; + private final int mMinLifetime; // When the packet was last captured, in seconds since Unix Epoch - long mLastSeen; + private final int mLastSeen; // For debugging only. Offsets into the packet where PIOs are. private final ArrayList<Integer> mPrefixOptionOffsets = new ArrayList<>(); @@ -646,9 +684,6 @@ // For debugging only. Offsets into the packet where RIO options are. private final ArrayList<Integer> mRioOptionOffsets = new ArrayList<>(); - // For debugging only. How many times this RA was seen. - int seenCount = 0; - // For debugging only. Returns the hex representation of the last matching packet. String getLastMatchingPacket() { return HexDump.toHexString(mPacket.array(), 0, mPacket.capacity(), @@ -754,9 +789,25 @@ // check to prevent doing so in the presence of bugs or malformed or // truncated packets. if (length == 0) return; - mPacketSections.add( - new PacketSection(mPacket.position(), length, PacketSection.Type.MATCH, 0, 0)); - mPacket.position(mPacket.position() + length); + + // we need to add a MATCH section 'from, length, MATCH, 0, 0' + int from = mPacket.position(); + + // if possible try to increase the length of the previous match section + int lastIdx = mPacketSections.size() - 1; + if (lastIdx >= 0) { // there had to be a previous section + PacketSection prev = mPacketSections.get(lastIdx); + if (prev.type == PacketSection.Type.MATCH) { // of type match + if (prev.start + prev.length == from) { // ending where we start + from -= prev.length; + length += prev.length; + mPacketSections.remove(lastIdx); + } + } + } + + mPacketSections.add(new PacketSection(from, length, PacketSection.Type.MATCH, 0, 0)); + mPacket.position(from + length); } /** @@ -772,49 +823,62 @@ * @param length the length of the section in bytes */ private void addIgnoreSection(int length) { - mPacketSections.add( - new PacketSection(mPacket.position(), length, PacketSection.Type.IGNORE, 0, 0)); mPacket.position(mPacket.position() + length); } /** * Add a packet section that represents a lifetime, starting from the current position. * @param length the length of the section in bytes - * @param optionType the RA option containing this lifetime, or 0 for router lifetime * @param lifetime the lifetime + * @param min the minimum acceptable lifetime */ - private void addLifetimeSection(int length, int optionType, long lifetime) { + private void addLifetimeSection(int length, long lifetime, int min) { mPacketSections.add( new PacketSection(mPacket.position(), length, PacketSection.Type.LIFETIME, - optionType, lifetime)); + lifetime, min)); mPacket.position(mPacket.position() + length); } /** * Adds packet sections for an RA option with a 4-byte lifetime 4 bytes into the option - * @param optionType the RA option that is being added * @param optionLength the length of the option in bytes + * @param min the minimum acceptable lifetime */ - private long add4ByteLifetimeOption(int optionType, int optionLength) { + private long add4ByteLifetimeOption(int optionLength, int min) { addMatchSection(ICMP6_4_BYTE_LIFETIME_OFFSET); final long lifetime = getUint32(mPacket, mPacket.position()); - addLifetimeSection(ICMP6_4_BYTE_LIFETIME_LEN, optionType, lifetime); + addLifetimeSection(ICMP6_4_BYTE_LIFETIME_LEN, lifetime, min); addMatchSection(optionLength - ICMP6_4_BYTE_LIFETIME_OFFSET - ICMP6_4_BYTE_LIFETIME_LEN); return lifetime; } - // http://b/66928272 http://b/65056012 - // DnsServerRepository ignores RDNSS servers with lifetimes that are too low. Ignore these - // lifetimes for the purpose of filter lifetime calculations. - private boolean shouldIgnoreLifetime(int optionType, long lifetime) { - return optionType == ICMP6_RDNSS_OPTION_TYPE - && lifetime != 0 && lifetime < mMinRdnssLifetimeSec; + /** + * Return the router lifetime of the RA + */ + public int routerLifetime() { + return mRouterLifetime; } - private boolean isRelevantLifetime(PacketSection section) { - return section.type == PacketSection.Type.LIFETIME - && !shouldIgnoreLifetime(section.option, section.lifetime); + /** + * Return the minimum valid lifetime in PIOs + */ + public long minPioValidLifetime() { + return mMinPioValidLifetime; + } + + /** + * Return the minimum route lifetime in RIOs + */ + public long minRioRouteLifetime() { + return mMinRioRouteLifetime; + } + + /** + * Return the minimum lifetime in RDNSSs + */ + public long minRdnssLifetime() { + return mMinRdnssLifetime; } // Note that this parses RA and may throw InvalidRaException (from @@ -828,7 +892,7 @@ } mPacket = ByteBuffer.wrap(Arrays.copyOf(packet, length)); - mLastSeen = currentTimeSeconds(); + mLastSeen = secondsSinceBoot(); // Check packet in case a packet arrives before we attach RA filter // to our packet socket. b/29586253 @@ -838,22 +902,23 @@ throw new InvalidRaException("Not an ICMP6 router advertisement"); } - - RaEvent.Builder builder = new RaEvent.Builder(); - // Ignore the flow label and low 4 bits of traffic class. addMatchUntil(IPV6_FLOW_LABEL_OFFSET); addIgnoreSection(IPV6_FLOW_LABEL_LEN); + // Ignore IPv6 destination address. + addMatchUntil(IPV6_DEST_ADDR_OFFSET); + addIgnoreSection(IPV6_ADDR_LEN); + // Ignore checksum. addMatchUntil(ICMP6_RA_CHECKSUM_OFFSET); addIgnoreSection(ICMP6_RA_CHECKSUM_LEN); // Parse router lifetime addMatchUntil(ICMP6_RA_ROUTER_LIFETIME_OFFSET); - final long routerLifetime = getUint16(mPacket, ICMP6_RA_ROUTER_LIFETIME_OFFSET); - addLifetimeSection(ICMP6_RA_ROUTER_LIFETIME_LEN, 0, routerLifetime); - builder.updateRouterLifetime(routerLifetime); + mRouterLifetime = getUint16(mPacket, ICMP6_RA_ROUTER_LIFETIME_OFFSET); + addLifetimeSection(ICMP6_RA_ROUTER_LIFETIME_LEN, mRouterLifetime, mAcceptRaMinLft); + if (mRouterLifetime == 0) mNumZeroLifetimeRas++; // Add remaining fields (reachable time and retransmission timer) to match section. addMatchUntil(ICMP6_RA_OPTION_OFFSET); @@ -862,6 +927,11 @@ final int position = mPacket.position(); final int optionType = getUint8(mPacket, position); final int optionLength = getUint8(mPacket, position + 1) * 8; + if (optionLength <= 0) { + throw new InvalidRaException(String.format( + "Invalid option length opt=%d len=%d", optionType, optionLength)); + } + long lifetime; switch (optionType) { case ICMP6_PREFIX_OPTION_TYPE: @@ -871,14 +941,17 @@ addMatchSection(ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET); lifetime = getUint32(mPacket, mPacket.position()); addLifetimeSection(ICMP6_PREFIX_OPTION_VALID_LIFETIME_LEN, - ICMP6_PREFIX_OPTION_TYPE, lifetime); - builder.updatePrefixValidLifetime(lifetime); + lifetime, mAcceptRaMinLft); + mMinPioValidLifetime = getMinForPositiveValue( + mMinPioValidLifetime, lifetime); + if (lifetime == 0) mNumZeroLifetimeRas++; // Parse preferred lifetime lifetime = getUint32(mPacket, mPacket.position()); + // The PIO preferred lifetime is not affected by accept_ra_min_lft and + // therefore does not have a minimum. addLifetimeSection(ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_LEN, - ICMP6_PREFIX_OPTION_TYPE, lifetime); - builder.updatePrefixPreferredLifetime(lifetime); + lifetime, 0 /* min lifetime */); addMatchSection(4); // Reserved bytes addMatchSection(IPV6_ADDR_LEN); // The prefix itself @@ -887,133 +960,269 @@ // are processed with the same specialized add4ByteLifetimeOption: case ICMP6_RDNSS_OPTION_TYPE: mRdnssOptionOffsets.add(position); - lifetime = add4ByteLifetimeOption(optionType, optionLength); - builder.updateRdnssLifetime(lifetime); + lifetime = add4ByteLifetimeOption(optionLength, mMinRdnssLifetimeSec); + mMinRdnssLifetime = getMinForPositiveValue(mMinRdnssLifetime, lifetime); + if (lifetime == 0) mNumZeroLifetimeRas++; break; case ICMP6_ROUTE_INFO_OPTION_TYPE: mRioOptionOffsets.add(position); - lifetime = add4ByteLifetimeOption(optionType, optionLength); - builder.updateRouteInfoLifetime(lifetime); + lifetime = add4ByteLifetimeOption(optionLength, mAcceptRaMinLft); + mMinRioRouteLifetime = getMinForPositiveValue( + mMinRioRouteLifetime, lifetime); + if (lifetime == 0) mNumZeroLifetimeRas++; break; - case ICMP6_DNSSL_OPTION_TYPE: - lifetime = add4ByteLifetimeOption(optionType, optionLength); - builder.updateDnsslLifetime(lifetime); + case ICMP6_SOURCE_LL_ADDRESS_OPTION_TYPE: + case ICMP6_MTU_OPTION_TYPE: + case ICMP6_PREF64_OPTION_TYPE: + addMatchSection(optionLength); break; + case ICMP6_CAPTIVE_PORTAL_OPTION_TYPE: // unlikely to ever change. + case ICMP6_DNSSL_OPTION_TYPE: // currently unsupported in userspace. default: // RFC4861 section 4.2 dictates we ignore unknown options for forwards // compatibility. - mPacket.position(position + optionLength); + // However, make sure the option's type and length match. + addMatchSection(2); // option type & length + // optionLength is guaranteed to be >= 8. + addIgnoreSection(optionLength - 2); break; } - if (optionLength <= 0) { - throw new InvalidRaException(String.format( - "Invalid option length opt=%d len=%d", optionType, optionLength)); - } } mMinLifetime = minLifetime(); - mMetricsLog.log(builder.build()); + } + + public enum MatchType { + NO_MATCH, // the RAs do not match + MATCH_PASS, // the RAS match, and the APF program would pass. + MATCH_DROP, // the RAs match, but the APF program would drop. } // Considering only the MATCH sections, does {@code packet} match this RA? - boolean matches(byte[] packet, int length) { - if (length != mPacket.capacity()) return false; - byte[] referencePacket = mPacket.array(); + MatchType matches(Ra newRa) { + // Does their size match? + if (newRa.mPacket.capacity() != mPacket.capacity()) return MatchType.NO_MATCH; + + // If the filter has expired, it cannot match the new RA. + if (getRemainingFilterLft(secondsSinceBoot()) <= 0) return MatchType.NO_MATCH; + + // Check if all MATCH sections are byte-identical. + final byte[] newPacket = newRa.mPacket.array(); + final byte[] oldPacket = mPacket.array(); for (PacketSection section : mPacketSections) { if (section.type != PacketSection.Type.MATCH) continue; for (int i = section.start; i < (section.start + section.length); i++) { - if (packet[i] != referencePacket[i]) return false; + if (newPacket[i] != oldPacket[i]) return MatchType.NO_MATCH; } } - return true; + + // Apply APF lifetime matching to LIFETIME sections and decide whether a packet should + // be processed (MATCH_PASS) or ignored (MATCH_DROP). This logic is needed to + // consistently process / ignore packets no matter the current state of the APF program. + // Note that userspace has no control (or knowledge) over when the APF program is + // running. + for (PacketSection section : mPacketSections) { + if (section.type != PacketSection.Type.LIFETIME) continue; + + // the lifetime of the new RA. + long lft = 0; + switch (section.length) { + // section.length is guaranteed to be 2 or 4. + case 2: lft = getUint16(newRa.mPacket, section.start); break; + case 4: lft = getUint32(newRa.mPacket, section.start); break; + } + + // WARNING: keep this in sync with Ra#generateFilterLocked()! + if (section.lifetime == 0) { + // Case 1) old lft == 0 + if (section.min > 0) { + // a) in the presence of a min value. + // if lft >= min -> PASS + // gen.addJumpIfR0GreaterThan(section.min - 1, nextFilterLabel); + if (lft >= section.min) return MatchType.MATCH_PASS; + } else { + // b) if min is 0 / there is no min value. + // if lft > 0 -> PASS + // gen.addJumpIfR0GreaterThan(0, nextFilterLabel); + if (lft > 0) return MatchType.MATCH_PASS; + } + } else if (section.min == 0) { + // Case 2b) section is not affected by any minimum. + // + // if lft < (oldLft + 2) // 3 -> PASS + // if lft > oldLft -> PASS + // gen.addJumpIfR0LessThan((int) ((section.lifetime + 2) / 3), + // nextFilterLabel); + if (lft < (section.lifetime + 2) / 3) return MatchType.MATCH_PASS; + // gen.addJumpIfR0GreaterThan((int) section.lifetime, nextFilterLabel); + if (lft > section.lifetime) return MatchType.MATCH_PASS; + } else if (section.lifetime < section.min) { + // Case 2a) 0 < old lft < min + // + // if lft == 0 -> PASS + // if lft >= min -> PASS + // gen.addJumpIfR0Equals(0, nextFilterLabel); + if (lft == 0) return MatchType.MATCH_PASS; + // gen.addJumpIfR0GreaterThan(section.min - 1, nextFilterLabel); + if (lft >= section.min) return MatchType.MATCH_PASS; + } else if (section.lifetime <= 3 * (long) section.min) { + // Case 3a) min <= old lft <= 3 * min + // Note that: + // "(old lft + 2) / 3 <= min" is equivalent to "old lft <= 3 * min" + // + // Essentially, in this range there is no "renumbering support", as the + // renumbering constant of 1/3 * old lft is smaller than the minimum + // lifetime accepted by the kernel / userspace. + // + // if lft == 0 -> PASS + // if lft > oldLft -> PASS + // gen.addJumpIfR0Equals(0, nextFilterLabel); + if (lft == 0) return MatchType.MATCH_PASS; + // gen.addJumpIfR0GreaterThan((int) section.lifetime, nextFilterLabel); + if (lft > section.lifetime) return MatchType.MATCH_PASS; + } else { + // Case 4a) otherwise + // + // if lft == 0 -> PASS + // if lft < min -> CONTINUE + // if lft < (oldLft + 2) // 3 -> PASS + // if lft > oldLft -> PASS + // gen.addJumpIfR0Equals(0, nextFilterLabel); + if (lft == 0) return MatchType.MATCH_PASS; + // gen.addJumpIfR0LessThan(section.min, continueLabel); + if (lft < section.min) continue; + // gen.addJumpIfR0LessThan((int) ((section.lifetime + 2) / 3), + // nextFilterLabel); + if (lft < (section.lifetime + 2) / 3) return MatchType.MATCH_PASS; + // gen.addJumpIfR0GreaterThan((int) section.lifetime, nextFilterLabel); + if (lft > section.lifetime) return MatchType.MATCH_PASS; + } + } + + return MatchType.MATCH_DROP; } // What is the minimum of all lifetimes within {@code packet} in seconds? // Precondition: matches(packet, length) already returned true. - long minLifetime() { - long minLifetime = Long.MAX_VALUE; + private int minLifetime() { + // While technically most lifetimes in the RA are u32s, as far as the RA filter is + // concerned, INT_MAX is still a *much* longer lifetime than any filter would ever + // reasonably be active for. + // Clamp minLifetime at INT_MAX. + int minLifetime = Integer.MAX_VALUE; for (PacketSection section : mPacketSections) { - if (isRelevantLifetime(section)) { - minLifetime = Math.min(minLifetime, section.lifetime); + if (section.type != PacketSection.Type.LIFETIME) { + continue; } + // Ignore lifetimes below section.min and always ignore 0 lifetimes. + if (section.lifetime < Math.max(section.min, 1)) { + continue; + } + + minLifetime = (int) Math.min(minLifetime, section.lifetime); } return minLifetime; } - // How many seconds does this RA's have to live, taking into account the fact - // that we might have seen it a while ago. - long currentLifetime() { - return mMinLifetime - (currentTimeSeconds() - mLastSeen); - } - - boolean isExpired() { - // TODO: We may want to handle 0 lifetime RAs differently, if they are common. We'll - // have to calculate the filter lifetime specially as a fraction of 0 is still 0. - return currentLifetime() <= 0; - } - // Filter for a fraction of the lifetime and adjust for the age of the RA. - @GuardedBy("ApfFilter.this") - int filterLifetime() { - // Use a flag from device config to toggle on/off the use of lifetime calculation fix - // in aosp/2276160. The old buggy behavior drops more RAs in some circumstances which - // probably use less battery. We can change it immediately if any OEM complains about - // additional battery usage after the fix. - if (mUseLifetimeCalculationFix) { - return (int) (mMinLifetime / FRACTION_OF_LIFETIME_TO_FILTER) - - (int) (mProgramBaseTime - mLastSeen); - } else { - // The old buggy formula, always filter a fraction of the remaining lifetime. - return (int) (currentLifetime() / FRACTION_OF_LIFETIME_TO_FILTER); - } - } - - @GuardedBy("ApfFilter.this") - boolean shouldFilter() { - return filterLifetime() > 0; + int getRemainingFilterLft(int currentTimeSeconds) { + int filterLifetime = (int) ((mMinLifetime / FRACTION_OF_LIFETIME_TO_FILTER) + - (currentTimeSeconds - mLastSeen)); + filterLifetime = Math.max(0, filterLifetime); + // Clamp filterLifetime to <= 65535, so it fits in 2 bytes. + return Math.min(65535, filterLifetime); } // 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. - // Return Long.MAX_VALUE if we don't install any filter program for this RA. As the return - // value of this function is used to calculate the program min lifetime (which corresponds - // to the smallest generated filter lifetime). Returning Long.MAX_VALUE in the case no - // filter gets generated makes sure the program lifetime stays unaffected. @GuardedBy("ApfFilter.this") - long generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException { + void generateFilterLocked(ApfGenerator gen, int timeSeconds) + throws IllegalInstructionException { String nextFilterLabel = "Ra" + getUniqueNumberLocked(); // Skip if packet is not the right size gen.addLoadFromMemory(Register.R0, gen.PACKET_SIZE_MEMORY_SLOT); gen.addJumpIfR0NotEquals(mPacket.capacity(), nextFilterLabel); // Skip filter if expired gen.addLoadFromMemory(Register.R0, gen.FILTER_AGE_MEMORY_SLOT); - gen.addJumpIfR0GreaterThan(filterLifetime(), nextFilterLabel); + gen.addJumpIfR0GreaterThan(getRemainingFilterLft(timeSeconds), nextFilterLabel); for (PacketSection section : mPacketSections) { // Generate code to match the packet bytes. if (section.type == PacketSection.Type.MATCH) { gen.addLoadImmediate(Register.R0, section.start); - gen.addJumpIfBytesNotEqual(Register.R0, + gen.addJumpIfBytesAtR0NotEqual( Arrays.copyOfRange(mPacket.array(), section.start, section.start + section.length), nextFilterLabel); - } - - // Generate code to test the lifetimes haven't gone down too far. - // The packet is accepted if any non-ignored lifetime is lower than filterLifetime. - if (isRelevantLifetime(section)) { + } else { switch (section.length) { - case 4: gen.addLoad32(Register.R0, section.start); break; + // length asserted to be either 2 or 4 on PacketSection construction case 2: gen.addLoad16(Register.R0, section.start); break; - default: - throw new IllegalStateException( - "bogus lifetime size " + section.length); + case 4: gen.addLoad32(Register.R0, section.start); break; } - gen.addJumpIfR0LessThan(filterLifetime(), nextFilterLabel); + + // WARNING: keep this in sync with matches()! + // For more information on lifetime comparisons in the APF bytecode, see + // go/apf-ra-filter. + if (section.lifetime == 0) { + // Case 1) old lft == 0 + if (section.min > 0) { + // a) in the presence of a min value. + // if lft >= min -> PASS + gen.addJumpIfR0GreaterThan(section.min - 1, nextFilterLabel); + } else { + // b) if min is 0 / there is no min value. + // if lft > 0 -> PASS + gen.addJumpIfR0GreaterThan(0, nextFilterLabel); + } + } else if (section.min == 0) { + // Case 2b) section is not affected by any minimum. + // + // if lft < (oldLft + 2) // 3 -> PASS + // if lft > oldLft -> PASS + gen.addJumpIfR0LessThan((int) ((section.lifetime + 2) / 3), + nextFilterLabel); + gen.addJumpIfR0GreaterThan((int) section.lifetime, nextFilterLabel); + } else if (section.lifetime < section.min) { + // Case 2a) 0 < old lft < min + // + // if lft == 0 -> PASS + // if lft >= min -> PASS + gen.addJumpIfR0Equals(0, nextFilterLabel); + gen.addJumpIfR0GreaterThan(section.min - 1, nextFilterLabel); + } else if (section.lifetime <= 3 * (long) section.min) { + // Case 3a) min <= old lft <= 3 * min + // Note that: + // "(old lft + 2) / 3 <= min" is equivalent to "old lft <= 3 * min" + // + // Essentially, in this range there is no "renumbering support", as the + // renumbering constant of 1/3 * old lft is smaller than the minimum + // lifetime accepted by the kernel / userspace. + // + // if lft == 0 -> PASS + // if lft > oldLft -> PASS + gen.addJumpIfR0Equals(0, nextFilterLabel); + gen.addJumpIfR0GreaterThan((int) section.lifetime, nextFilterLabel); + } else { + final String continueLabel = "Continue" + getUniqueNumberLocked(); + // Case 4a) otherwise + // + // if lft == 0 -> PASS + // if lft < min -> CONTINUE + // if lft < (oldLft + 2) // 3 -> PASS + // if lft > oldLft -> PASS + gen.addJumpIfR0Equals(0, nextFilterLabel); + gen.addJumpIfR0LessThan(section.min, continueLabel); + gen.addJumpIfR0LessThan((int) ((section.lifetime + 2) / 3), + nextFilterLabel); + gen.addJumpIfR0GreaterThan((int) section.lifetime, nextFilterLabel); + + // CONTINUE + gen.defineLabel(continueLabel); + } } } maybeSetupCounter(gen, Counter.DROPPED_RA); gen.addJump(mCountAndDropLabel); gen.defineLabel(nextFilterLabel); - return filterLifetime(); } } @@ -1074,7 +1283,7 @@ final String nextFilterLabel = "natt_keepalive_filter" + getUniqueNumberLocked(); gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET); - gen.addJumpIfBytesNotEqual(Register.R0, mSrcDstAddr, nextFilterLabel); + gen.addJumpIfBytesAtR0NotEqual(mSrcDstAddr, nextFilterLabel); // A NAT-T keepalive packet contains 1 byte payload with the value 0xff // Check payload length is 1 @@ -1089,11 +1298,11 @@ // Check that the ports match gen.addLoadFromMemory(Register.R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT); gen.addAdd(ETH_HEADER_LEN); - gen.addJumpIfBytesNotEqual(Register.R0, mPortFingerprint, nextFilterLabel); + gen.addJumpIfBytesAtR0NotEqual(mPortFingerprint, nextFilterLabel); // Payload offset = R0 + UDP header length gen.addAdd(UDP_HEADER_LEN); - gen.addJumpIfBytesNotEqual(Register.R0, mPayload, nextFilterLabel); + gen.addJumpIfBytesAtR0NotEqual(mPayload, nextFilterLabel); maybeSetupCounter(gen, Counter.DROPPED_IPV4_NATT_KEEPALIVE); gen.addJump(mCountAndDropLabel); @@ -1189,7 +1398,7 @@ final String nextFilterLabel = "keepalive_ack" + getUniqueNumberLocked(); gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET); - gen.addJumpIfBytesNotEqual(Register.R0, mSrcDstAddr, nextFilterLabel); + gen.addJumpIfBytesAtR0NotEqual(mSrcDstAddr, nextFilterLabel); // Skip to the next filter if it's not zero-sized : // TCP_HEADER_SIZE + IPV4_HEADER_SIZE - ipv4_total_length == 0 @@ -1211,7 +1420,7 @@ gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT); gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN); gen.addAddR1(); - gen.addJumpIfBytesNotEqual(Register.R0, mPortSeqAckFingerprint, nextFilterLabel); + gen.addJumpIfBytesAtR0NotEqual(mPortSeqAckFingerprint, nextFilterLabel); maybeSetupCounter(gen, Counter.DROPPED_IPV4_KEEPALIVE_ACK); gen.addJump(mCountAndDropLabel); @@ -1243,29 +1452,17 @@ @GuardedBy("this") private final List<String[]> mMdnsAllowList = new ArrayList<>(); - // There is always some marginal benefit to updating the installed APF program when an RA is - // seen because we can extend the program's lifetime slightly, but there is some cost to - // updating the program, so don't bother unless the program is going to expire soon. This - // constant defines "soon" in seconds. - private static final long MAX_PROGRAM_LIFETIME_WORTH_REFRESHING = 30; // We don't want to filter an RA for it's whole lifetime as it'll be expired by the time we ever // see a refresh. Using half the lifetime might be a good idea except for the fact that // packets may be dropped, so let's use 6. private static final int FRACTION_OF_LIFETIME_TO_FILTER = 6; - // The base time for this filter program. In seconds since Unix Epoch. - // This is the time when the APF program was generated. All filters in the program should use - // this base time as their current time for consistency purposes. - @GuardedBy("this") - private long mProgramBaseTime; // When did we last install a filter program? In seconds since Unix Epoch. @GuardedBy("this") - private long mLastTimeInstalledProgram; + private int mLastTimeInstalledProgram; // How long should the last installed filter program live for? In seconds. @GuardedBy("this") - private long mLastInstalledProgramMinLifetime; - @GuardedBy("this") - private ApfProgramEvent.Builder mLastInstallEvent; + private int mLastInstalledProgramMinLifetime; // For debugging only. The last program installed. @GuardedBy("this") @@ -1284,6 +1481,12 @@ // 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; // How many times the program was updated since we started for allowing multicast traffic. @GuardedBy("this") private int mNumProgramUpdatesAllowingMulticast = 0; @@ -1299,9 +1502,9 @@ // Here's a basic summary of what the ARP filter program does: // // if not ARP IPv4 - // pass + // drop // if not ARP IPv4 reply or request - // pass + // drop // if ARP reply source ip is 0.0.0.0 // drop // if unicast ARP reply @@ -1316,28 +1519,28 @@ final String checkTargetIPv4 = "checkTargetIPv4"; - // Pass if not ARP IPv4. + // Drop if not ARP IPv4. gen.addLoadImmediate(Register.R0, ARP_HEADER_OFFSET); - maybeSetupCounter(gen, Counter.PASSED_ARP_NON_IPV4); - gen.addJumpIfBytesNotEqual(Register.R0, ARP_IPV4_HEADER, mCountAndPassLabel); + maybeSetupCounter(gen, Counter.DROPPED_ARP_NON_IPV4); + gen.addJumpIfBytesAtR0NotEqual(ARP_IPV4_HEADER, mCountAndDropLabel); - // Pass if unknown ARP opcode. + // Drop if unknown ARP opcode. gen.addLoad16(Register.R0, ARP_OPCODE_OFFSET); gen.addJumpIfR0Equals(ARP_OPCODE_REQUEST, checkTargetIPv4); // Skip to unicast check - maybeSetupCounter(gen, Counter.PASSED_ARP_UNKNOWN); - gen.addJumpIfR0NotEquals(ARP_OPCODE_REPLY, mCountAndPassLabel); + maybeSetupCounter(gen, Counter.DROPPED_ARP_UNKNOWN); + gen.addJumpIfR0NotEquals(ARP_OPCODE_REPLY, mCountAndDropLabel); // Drop if ARP reply source IP is 0.0.0.0 gen.addLoad32(Register.R0, ARP_SOURCE_IP_ADDRESS_OFFSET); maybeSetupCounter(gen, Counter.DROPPED_ARP_REPLY_SPA_NO_HOST); gen.addJumpIfR0Equals(IPV4_ANY_HOST_ADDRESS, mCountAndDropLabel); - // Pass if unicast reply. + // Pass if non-broadcast reply. gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET); maybeSetupCounter(gen, Counter.PASSED_ARP_UNICAST_REPLY); - gen.addJumpIfBytesNotEqual(Register.R0, ETHER_BROADCAST, mCountAndPassLabel); + gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel); - // Either a unicast request, a unicast reply, or a broadcast reply. + // Either a request, or a broadcast reply. gen.defineLabel(checkTargetIPv4); if (mIPv4Address == null) { // When there is no IPv4 address, drop GARP replies (b/29404209). @@ -1349,7 +1552,7 @@ // and broadcast replies with a different target IPv4 address. gen.addLoadImmediate(Register.R0, ARP_TARGET_IP_ADDRESS_OFFSET); maybeSetupCounter(gen, Counter.DROPPED_ARP_OTHER_HOST); - gen.addJumpIfBytesNotEqual(Register.R0, mIPv4Address, mCountAndDropLabel); + gen.addJumpIfBytesAtR0NotEqual(mIPv4Address, mCountAndDropLabel); } maybeSetupCounter(gen, Counter.PASSED_ARP); @@ -1386,18 +1589,18 @@ // Check it's UDP. gen.addLoad8(Register.R0, IPV4_PROTOCOL_OFFSET); gen.addJumpIfR0NotEquals(IPPROTO_UDP, skipDhcpv4Filter); - // Check it's not a fragment. This matches the BPF filter installed by the DHCP client. + // Check it's not a fragment or is the initial fragment. gen.addLoad16(Register.R0, IPV4_FRAGMENT_OFFSET_OFFSET); gen.addJumpIfR0AnyBitsSet(IPV4_FRAGMENT_OFFSET_MASK, skipDhcpv4Filter); // Check it's addressed to DHCP client port. gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT); - gen.addLoad16Indexed(Register.R0, UDP_DESTINATION_PORT_OFFSET); + gen.addLoad16Indexed(Register.R0, TCP_UDP_DESTINATION_PORT_OFFSET); gen.addJumpIfR0NotEquals(DHCP_CLIENT_PORT, skipDhcpv4Filter); // Check it's DHCP to our MAC address. gen.addLoadImmediate(Register.R0, DHCP_CLIENT_MAC_OFFSET); // NOTE: Relies on R1 containing IPv4 header offset. gen.addAddR1(); - gen.addJumpIfBytesNotEqual(Register.R0, mHardwareAddress, skipDhcpv4Filter); + gen.addJumpIfBytesAtR0NotEqual(mHardwareAddress, skipDhcpv4Filter); maybeSetupCounter(gen, Counter.PASSED_DHCP); gen.addJump(mCountAndPassLabel); @@ -1419,24 +1622,26 @@ int broadcastAddr = ipv4BroadcastAddress(mIPv4Address, mIPv4PrefixLength); gen.addJumpIfR0Equals(broadcastAddr, mCountAndDropLabel); } + } - // If any TCP keepalive filter matches, drop - generateV4KeepaliveFilters(gen); + // If any TCP keepalive filter matches, drop + generateV4KeepaliveFilters(gen); - // If any NAT-T keepalive filter matches, drop - generateV4NattKeepaliveFilters(gen); + // If any NAT-T keepalive filter matches, drop + generateV4NattKeepaliveFilters(gen); + // If TCP unicast on port 7, drop + generateV4TcpPort7FilterLocked(gen); + + if (mMulticastFilter) { // Otherwise, this is an IPv4 unicast, pass // If L2 broadcast packet, drop. // TODO: can we invert this condition to fall through to the common pass case below? maybeSetupCounter(gen, Counter.PASSED_IPV4_UNICAST); gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET); - gen.addJumpIfBytesNotEqual(Register.R0, ETHER_BROADCAST, mCountAndPassLabel); + gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel); maybeSetupCounter(gen, Counter.DROPPED_IPV4_L2_BROADCAST); gen.addJump(mCountAndDropLabel); - } else { - generateV4KeepaliveFilters(gen); - generateV4NattKeepaliveFilters(gen); } // Otherwise, pass @@ -1486,6 +1691,8 @@ private void generateIPv6FilterLocked(ApfGenerator gen) throws IllegalInstructionException { // Here's a basic summary of what the IPv6 filter program does: // + // if there is a hop-by-hop option present (e.g. MLD query) + // pass // if we're dropping multicast // if it's not IPCMv6 or it's ICMPv6 but we're in doze mode: // if it's multicast: @@ -1500,6 +1707,10 @@ gen.addLoad8(Register.R0, IPV6_NEXT_HEADER_OFFSET); + // MLD packets set the router-alert hop-by-hop option. + // TODO: be smarter about not blindly passing every packet with HBH options. + gen.addJumpIfR0Equals(IPPROTO_HOPOPTS, mCountAndPassLabel); + // Drop multicast if the multicast filter is enabled. if (mMulticastFilter) { final String skipIPv6MulticastFilterLabel = "skipIPv6MulticastFilter"; @@ -1552,8 +1763,7 @@ // TODO: Drop only if they don't contain the address of on-link neighbours. final byte[] unsolicitedNaDropPrefix = Arrays.copyOf(IPV6_ALL_NODES_ADDRESS, 15); gen.addLoadImmediate(Register.R0, IPV6_DEST_ADDR_OFFSET); - gen.addJumpIfBytesNotEqual(Register.R0, unsolicitedNaDropPrefix, - skipUnsolicitedMulticastNALabel); + gen.addJumpIfBytesAtR0NotEqual(unsolicitedNaDropPrefix, skipUnsolicitedMulticastNALabel); maybeSetupCounter(gen, Counter.DROPPED_IPV6_MULTICAST_NA); gen.addJump(mCountAndDropLabel); @@ -1594,7 +1804,9 @@ // Here's a basic summary of what the mDNS filter program does: // // if it is a multicast mDNS packet - // if QDCOUNT > 1 and the first QNAME is in the allowlist + // if QDCOUNT != 1 + // pass + // else if the QNAME is in the allowlist // pass // else: // drop @@ -1608,16 +1820,21 @@ // Check it's L2 mDNS multicast address. gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET); - gen.addJumpIfBytesNotEqual(Register.R0, ETH_MULTICAST_MDNS_V4_MAC_ADDRESS, - skipMdnsv4Filter); + gen.addJumpIfBytesAtR0NotEqual(ETH_MULTICAST_MDNS_V4_MAC_ADDRESS, skipMdnsv4Filter); // Checks it's IPv4. gen.addLoad16(Register.R0, ETH_ETHERTYPE_OFFSET); gen.addJumpIfR0NotEquals(ETH_P_IP, skipMdnsFilter); + // Check it's not a fragment. + gen.addLoad16(Register.R0, IPV4_FRAGMENT_OFFSET_OFFSET); + gen.addJumpIfR0AnyBitsSet(IPV4_FRAGMENT_MORE_FRAGS_MASK | IPV4_FRAGMENT_OFFSET_MASK, + skipMdnsFilter); + // Checks it's UDP. gen.addLoad8(Register.R0, IPV4_PROTOCOL_OFFSET); gen.addJumpIfR0NotEquals(IPPROTO_UDP, skipMdnsFilter); + // Set R1 to IPv4 header. gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT); gen.addJump(checkMdnsUdpPort); @@ -1626,8 +1843,7 @@ // Checks it's L2 mDNS multicast address. // Relies on R0 containing the ethernet destination mac address offset. - gen.addJumpIfBytesNotEqual(Register.R0, ETH_MULTICAST_MDNS_V6_MAC_ADDRESS, - skipMdnsFilter); + gen.addJumpIfBytesAtR0NotEqual(ETH_MULTICAST_MDNS_V6_MAC_ADDRESS, skipMdnsFilter); // Checks it's IPv6. gen.addLoad16(Register.R0, ETH_ETHERTYPE_OFFSET); @@ -1642,14 +1858,14 @@ // Checks it's mDNS UDP port gen.defineLabel(checkMdnsUdpPort); - gen.addLoad16Indexed(Register.R0, UDP_DESTINATION_PORT_OFFSET); + gen.addLoad16Indexed(Register.R0, TCP_UDP_DESTINATION_PORT_OFFSET); gen.addJumpIfR0NotEquals(MDNS_PORT, skipMdnsFilter); - // Only do the QNAME check if the QDCOUNT is more than 0. - // If there is more than one query (QDCOUNT > 1), we only matches the first QNAME. gen.addLoad16Indexed(Register.R0, MDNS_QDCOUNT_OFFSET); - gen.addJumpIfR0Equals(0, mDnsDropPacket); + // If QDCOUNT != 1, pass the packet + gen.addJumpIfR0NotEquals(1, mDnsAcceptPacket); + // If QDCOUNT == 1, matches the QNAME with allowlist. // Load offset for the first QNAME. gen.addLoadImmediate(Register.R0, MDNS_QNAME_OFFSET); gen.addAddR1(); @@ -1658,7 +1874,7 @@ for (int i = 0; i < mMdnsAllowList.size(); ++i) { final String mDnsNextAllowedQnameCheck = "mdns_next_allowed_qname_check" + i; final byte[] encodedQname = encodeQname(mMdnsAllowList.get(i)); - gen.addJumpIfBytesNotEqual(Register.R0, encodedQname, mDnsNextAllowedQnameCheck); + gen.addJumpIfBytesAtR0NotEqual(encodedQname, mDnsNextAllowedQnameCheck); // QNAME matched gen.addJump(mDnsAcceptPacket); // QNAME not matched @@ -1677,6 +1893,37 @@ gen.defineLabel(skipMdnsFilter); } + /** + * Generate filter code to drop IPv4 TCP packets on port 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(ApfGenerator gen) + throws IllegalInstructionException { + final String skipPort7V4Filter = "skip_port7_v4_filter"; + + // Check it's TCP. + gen.addLoad8(Register.R0, IPV4_PROTOCOL_OFFSET); + gen.addJumpIfR0NotEquals(IPPROTO_TCP, skipPort7V4Filter); + + // Check it's not a fragment or is the initial fragment. + gen.addLoad16(Register.R0, IPV4_FRAGMENT_OFFSET_OFFSET); + gen.addJumpIfR0AnyBitsSet(IPV4_FRAGMENT_OFFSET_MASK, skipPort7V4Filter); + + // Check it's destination port 7. + gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT); + gen.addLoad16Indexed(Register.R0, TCP_UDP_DESTINATION_PORT_OFFSET); + gen.addJumpIfR0NotEquals(ECHO_PORT, skipPort7V4Filter); + + // Drop it. + maybeSetupCounter(gen, Counter.DROPPED_IPV4_TCP_PORT7_UNICAST); + gen.addJump(mCountAndDropLabel); + + // Skip label. + gen.defineLabel(skipPort7V4Filter); + } private void generateV6KeepaliveFilters(ApfGenerator gen) throws IllegalInstructionException { generateKeepaliveFilters(gen, TcpKeepaliveAckV6.class, IPPROTO_TCP, IPV6_NEXT_HEADER_OFFSET, @@ -1704,7 +1951,8 @@ * </ul> */ @GuardedBy("this") - private ApfGenerator emitPrologueLocked() throws IllegalInstructionException { + @VisibleForTesting + protected ApfGenerator emitPrologueLocked() throws IllegalInstructionException { // This is guaranteed to succeed because of the check in maybeCreate. ApfGenerator gen = new ApfGenerator(mApfCapabilities.apfVersionSupported); @@ -1741,7 +1989,7 @@ } // Handle ether-type black list - maybeSetupCounter(gen, Counter.DROPPED_ETHERTYPE_BLACKLISTED); + maybeSetupCounter(gen, Counter.DROPPED_ETHERTYPE_DENYLISTED); for (int p : mEthTypeBlackList) { gen.addJumpIfR0Equals(p, mCountAndDropLabel); } @@ -1772,7 +2020,7 @@ // Drop non-IP non-ARP broadcasts, pass the rest gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET); maybeSetupCounter(gen, Counter.PASSED_NON_IP_UNICAST); - gen.addJumpIfBytesNotEqual(Register.R0, ETHER_BROADCAST, mCountAndPassLabel); + gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel); maybeSetupCounter(gen, Counter.DROPPED_ETH_BROADCAST); gen.addJump(mCountAndDropLabel); @@ -1821,17 +2069,17 @@ @GuardedBy("this") @VisibleForTesting public void installNewProgramLocked() { - purgeExpiredRasLocked(); ArrayList<Ra> rasToFilter = new ArrayList<>(); final byte[] program; - long programMinLifetime = Long.MAX_VALUE; - long maximumApfProgramSize = mApfCapabilities.maximumApfProgramSize; + int programMinLft = Integer.MAX_VALUE; + int maximumApfProgramSize = mApfCapabilities.maximumApfProgramSize; if (mApfCapabilities.hasDataAccess()) { // Reserve space for the counters. maximumApfProgramSize -= Counter.totalSize(); } - mProgramBaseTime = currentTimeSeconds(); + // Ensure the entire APF program uses the same time base. + int timeSeconds = secondsSinceBoot(); try { // Step 1: Determine how many RA filters we can fit in the program. ApfGenerator gen = emitPrologueLocked(); @@ -1843,15 +2091,18 @@ // Can't fit the program even without any RA filters? if (gen.programLengthOverEstimate() > maximumApfProgramSize) { Log.e(TAG, "Program exceeds maximum size " + maximumApfProgramSize); + sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_OVER_SIZE_FAILURE); return; } for (Ra ra : mRas) { - if (!ra.shouldFilter()) continue; - ra.generateFilterLocked(gen); + // skip filter if it has expired. + if (ra.getRemainingFilterLft(timeSeconds) <= 0) continue; + ra.generateFilterLocked(gen, timeSeconds); // Stop if we get too big. if (gen.programLengthOverEstimate() > maximumApfProgramSize) { if (VDBG) Log.d(TAG, "Past maximum program size, skipping RAs"); + sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_OVER_SIZE_FAILURE); break; } @@ -1861,88 +2112,89 @@ // Step 2: Actually generate the program gen = emitPrologueLocked(); for (Ra ra : rasToFilter) { - programMinLifetime = Math.min(programMinLifetime, ra.generateFilterLocked(gen)); + ra.generateFilterLocked(gen, timeSeconds); + programMinLft = Math.min(programMinLft, ra.getRemainingFilterLft(timeSeconds)); } emitEpilogue(gen); program = gen.generate(); } catch (IllegalInstructionException|IllegalStateException e) { Log.e(TAG, "Failed to generate APF program.", e); + sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_GENERATE_FILTER_EXCEPTION); return; } - mIpClientCallback.installPacketFilter(program); - mLastTimeInstalledProgram = mProgramBaseTime; - mLastInstalledProgramMinLifetime = programMinLifetime; + // Update data snapshot every time we install a new program + mIpClientCallback.startReadPacketFilter(); + if (!mIpClientCallback.installPacketFilter(program)) { + sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE); + } + mLastTimeInstalledProgram = timeSeconds; + mLastInstalledProgramMinLifetime = programMinLft; mLastInstalledProgram = program; mNumProgramUpdates++; + mMaxProgramSize = Math.max(mMaxProgramSize, program.length); if (VDBG) { hexDump("Installing filter: ", program, program.length); } - logApfProgramEventLocked(mProgramBaseTime); - mLastInstallEvent = new ApfProgramEvent.Builder() - .setLifetime(programMinLifetime) - .setFilteredRas(rasToFilter.size()) - .setCurrentRas(mRas.size()) - .setProgramLength(program.length) - .setFlags(mIPv4Address != null, mMulticastFilter); - } - - @GuardedBy("this") - private void logApfProgramEventLocked(long now) { - if (mLastInstallEvent == null) { - return; - } - ApfProgramEvent.Builder ev = mLastInstallEvent; - mLastInstallEvent = null; - final long actualLifetime = now - mLastTimeInstalledProgram; - ev.setActualLifetime(actualLifetime); - if (actualLifetime < APF_PROGRAM_EVENT_LIFETIME_THRESHOLD) { - return; - } - mMetricsLog.log(ev.build()); - } - - /** - * Returns {@code true} if a new program should be installed because the current one dies soon. - */ - private boolean shouldInstallnewProgram() { - long expiry = mLastTimeInstalledProgram + mLastInstalledProgramMinLifetime; - return expiry < currentTimeSeconds() + MAX_PROGRAM_LIFETIME_WORTH_REFRESHING; } private void hexDump(String msg, byte[] packet, int length) { log(msg + HexDump.toHexString(packet, 0, length, false /* lowercase */)); } - @GuardedBy("this") - private void purgeExpiredRasLocked() { - for (int i = 0; i < mRas.size();) { - if (mRas.get(i).isExpired()) { - log("Expiring " + mRas.get(i)); - mRas.remove(i); - } else { - i++; - } - } + // Get the minimum value excludes zero. This is used for calculating the lowest lifetime values + // in RA packets. Zero lifetimes are excluded because we want to detect whether there is any + // unusually small lifetimes but zero lifetime is actually valid (cease to be a default router + // or the option is no longer be used). Number of zero lifetime RAs is collected in a different + // Metrics. + private long getMinForPositiveValue(long oldMinValue, long value) { + if (value < 1) return oldMinValue; + return Math.min(oldMinValue, value); + } + + private int getMinForPositiveValue(int oldMinValue, int value) { + return (int) getMinForPositiveValue((long) oldMinValue, (long) value); } /** * Process an RA packet, updating the list of known RAs and installing a new APF program * if the current APF program should be updated. - * @return a ProcessRaResult enum describing what action was performed. */ @VisibleForTesting - public synchronized ProcessRaResult processRa(byte[] packet, int length) { + public synchronized void processRa(byte[] packet, int length) { if (VDBG) hexDump("Read packet = ", packet, length); + final Ra ra; + try { + ra = new Ra(packet, length); + } catch (Exception e) { + Log.e(TAG, "Error parsing RA", e); + mNumParseErrorRas++; + return; + } + + // Update info for Metrics + mLowestRouterLifetimeSeconds = getMinForPositiveValue( + mLowestRouterLifetimeSeconds, ra.routerLifetime()); + mLowestPioValidLifetimeSeconds = getMinForPositiveValue( + mLowestPioValidLifetimeSeconds, ra.minPioValidLifetime()); + mLowestRioRouteLifetimeSeconds = getMinForPositiveValue( + mLowestRioRouteLifetimeSeconds, ra.minRioRouteLifetime()); + mLowestRdnssLifetimeSeconds = getMinForPositiveValue( + mLowestRdnssLifetimeSeconds, ra.minRdnssLifetime()); + + // Remove all expired RA filters before trying to match the new RA. + // TODO: matches() still checks that the old RA filter has not expired. Consider removing + // that check. + final int now = secondsSinceBoot(); + mRas.removeIf(item -> item.getRemainingFilterLft(now) <= 0); + // Have we seen this RA before? for (int i = 0; i < mRas.size(); i++) { - Ra ra = mRas.get(i); - if (ra.matches(packet, length)) { - if (VDBG) log("matched RA " + ra); - // Update lifetimes. - ra.mLastSeen = currentTimeSeconds(); - ra.seenCount++; + final Ra oldRa = mRas.get(i); + final Ra.MatchType result = oldRa.matches(ra); + if (result == Ra.MatchType.MATCH_PASS) { + log("Updating RA from " + oldRa + " to " + ra); // Keep mRas in LRU order so as to prioritize generating filters for recently seen // RAs. LRU prioritizes this because RA filters are generated in order from mRas @@ -1951,36 +2203,34 @@ // filter program. // TODO: consider sorting the RAs in order of increasing expiry time as well. // Swap to front of array. - mRas.add(0, mRas.remove(i)); + mRas.remove(i); + mRas.add(0, ra); - // If the current program doesn't expire for a while, don't update. - if (shouldInstallnewProgram()) { + // Rate limit program installation + if (mTokenBucket.get()) { installNewProgramLocked(); - return ProcessRaResult.UPDATE_EXPIRY; + } else { + Log.e(TAG, "Failed to install prog for tracked RA, too many updates. " + ra); } - return ProcessRaResult.MATCH; + return; + } else if (result == Ra.MatchType.MATCH_DROP) { + log("Ignoring RA " + ra + " which matches " + oldRa); + return; } } - purgeExpiredRasLocked(); - // TODO: figure out how to proceed when we've received more then MAX_RAS RAs. + mMaxDistinctRas = Math.max(mMaxDistinctRas, mRas.size() + 1); if (mRas.size() >= MAX_RAS) { - return ProcessRaResult.DROPPED; - } - final Ra ra; - try { - ra = new Ra(packet, length); - } catch (Exception e) { - Log.e(TAG, "Error parsing RA", e); - return ProcessRaResult.PARSE_ERROR; - } - // Ignore 0 lifetime RAs. - if (ra.isExpired()) { - return ProcessRaResult.ZERO_LIFETIME; + // Remove the last (i.e. oldest) RA. + mRas.remove(mRas.size() - 1); } log("Adding " + ra); - mRas.add(ra); - installNewProgramLocked(); - return ProcessRaResult.UPDATE_NEW_RA; + mRas.add(0, ra); + // Rate limit program installation + if (mTokenBucket.get()) { + installNewProgramLocked(); + } else { + Log.e(TAG, "Failed to install prog for new RA, too many updates. " + ra); + } } /** @@ -1988,7 +2238,8 @@ * filtering using APF programs. */ public static ApfFilter maybeCreate(Context context, ApfConfiguration config, - InterfaceParams ifParams, IpClientCallbacksWrapper ipClientCallback) { + InterfaceParams ifParams, IpClientCallbacksWrapper ipClientCallback, + NetworkQuirkMetrics networkQuirkMetrics) { if (context == null || config == null || ifParams == null) return null; ApfCapabilities apfCapabilities = config.apfCapabilities; if (apfCapabilities == null) return null; @@ -1998,26 +2249,55 @@ return null; } // For now only support generating programs for Ethernet frames. If this restriction is - // lifted: - // 1. the program generator will need its offsets adjusted. - // 2. the packet filter attached to our packet socket will need its offset adjusted. + // lifted the program generator will need its offsets adjusted. if (apfCapabilities.apfPacketFormat != ARPHRD_ETHER) return null; if (!ApfGenerator.supportsVersion(apfCapabilities.apfVersionSupported)) { Log.e(TAG, "Unsupported APF version: " + apfCapabilities.apfVersionSupported); return null; } - return new ApfFilter(context, config, ifParams, ipClientCallback, new IpConnectivityLog()); + return new ApfFilter(context, config, ifParams, ipClientCallback, networkQuirkMetrics); + } + + private synchronized void collectAndSendMetrics() { + if (mIpClientRaInfoMetrics == null || mApfSessionInfoMetrics == null) return; + final long sessionDurationMs = mClock.elapsedRealtime() - mSessionStartMs; + if (sessionDurationMs < mMinMetricsSessionDurationMs) return; + + // Collect and send IpClientRaInfoMetrics. + mIpClientRaInfoMetrics.setMaxNumberOfDistinctRas(mMaxDistinctRas); + mIpClientRaInfoMetrics.setNumberOfZeroLifetimeRas(mNumZeroLifetimeRas); + mIpClientRaInfoMetrics.setNumberOfParsingErrorRas(mNumParseErrorRas); + mIpClientRaInfoMetrics.setLowestRouterLifetimeSeconds(mLowestRouterLifetimeSeconds); + mIpClientRaInfoMetrics.setLowestPioValidLifetimeSeconds(mLowestPioValidLifetimeSeconds); + mIpClientRaInfoMetrics.setLowestRioRouteLifetimeSeconds(mLowestRioRouteLifetimeSeconds); + mIpClientRaInfoMetrics.setLowestRdnssLifetimeSeconds(mLowestRdnssLifetimeSeconds); + mIpClientRaInfoMetrics.statsWrite(); + + // Collect and send ApfSessionInfoMetrics. + mApfSessionInfoMetrics.setVersion(mApfCapabilities.apfVersionSupported); + mApfSessionInfoMetrics.setMemorySize(mApfCapabilities.maximumApfProgramSize); + mApfSessionInfoMetrics.setApfSessionDurationSeconds( + (int) (sessionDurationMs / DateUtils.SECOND_IN_MILLIS)); + mApfSessionInfoMetrics.setNumOfTimesApfProgramUpdated(mNumProgramUpdates); + mApfSessionInfoMetrics.setMaxProgramSize(mMaxProgramSize); + for (Map.Entry<Counter, Long> entry : mApfCounterTracker.getCounters().entrySet()) { + if (entry.getValue() > 0) { + mApfSessionInfoMetrics.addApfCounter(entry.getKey(), entry.getValue()); + } + } + mApfSessionInfoMetrics.statsWrite(); } public synchronized void shutdown() { + collectAndSendMetrics(); if (mReceiveThread != null) { log("shutting down"); mReceiveThread.halt(); // Also closes socket. mReceiveThread = null; } mRas.clear(); - mContext.unregisterReceiver(mDeviceIdleReceiver); + mDependencies.removeBroadcastReceiver(mDeviceIdleReceiver); } public synchronized void setMulticastFilter(boolean isEnabled) { @@ -2052,6 +2332,11 @@ installNewProgramLocked(); } + @VisibleForTesting + public synchronized boolean isInDozeMode() { + return mInDozeMode; + } + /** Find the single IPv4 LinkAddress if there is one, otherwise return null. */ private static LinkAddress findIPv4LinkAddress(LinkProperties lp) { LinkAddress ipv4Address = null; @@ -2135,23 +2420,6 @@ installNewProgramLocked(); } - static public long counterValue(byte[] data, Counter counter) - throws ArrayIndexOutOfBoundsException { - // Follow the same wrap-around addressing scheme of the interpreter. - int offset = counter.offset(); - if (offset < 0) { - offset = data.length + offset; - } - - // Decode 32bit big-endian integer into a long so we can count up beyond 2^31. - long value = 0; - for (int i = 0; i < 4; i++) { - value = value << 8 | (data[offset] & 0xFF); - offset++; - } - return value; - } - public synchronized void dump(IndentingPrintWriter pw) { pw.println("Capabilities: " + mApfCapabilities); pw.println("Receive thread: " + (mReceiveThread != null ? "RUNNING" : "STOPPED")); @@ -2168,7 +2436,7 @@ pw.println("Program updates: " + mNumProgramUpdates); pw.println(String.format( "Last program length %d, installed %ds ago, lifetime %ds", - mLastInstalledProgram.length, currentTimeSeconds() - mLastTimeInstalledProgram, + mLastInstalledProgram.length, secondsSinceBoot() - mLastTimeInstalledProgram, mLastInstalledProgramMinLifetime)); pw.print("Denylisted Ethertypes:"); @@ -2182,7 +2450,7 @@ pw.println(ra); pw.increaseIndent(); pw.println(String.format( - "Seen: %d, last %ds ago", ra.seenCount, currentTimeSeconds() - ra.mLastSeen)); + "Last seen %ds ago", secondsSinceBoot() - ra.mLastSeen)); if (DBG) { pw.println("Last match:"); pw.increaseIndent(); @@ -2236,11 +2504,17 @@ try { Counter[] counters = Counter.class.getEnumConstants(); for (Counter c : Arrays.asList(counters).subList(1, counters.length)) { - long value = counterValue(mDataSnapshot, c); + long value = ApfCounterTracker.getCounterValue(mDataSnapshot, c); // Only print non-zero counters if (value != 0) { pw.println(c.toString() + ": " + value); } + + // If the counter's value decreases, it may have been cleaned up or there may be + // a bug. + if (value < mApfCounterTracker.getCounters().getOrDefault(c, 0L)) { + Log.e(TAG, "Error: Counter value unexpectedly decreased."); + } } } catch (ArrayIndexOutOfBoundsException e) { pw.println("Uh-oh: " + e); @@ -2295,4 +2569,10 @@ } return result; } + + private void sendNetworkQuirkMetrics(final NetworkQuirkEvent event) { + if (mNetworkQuirkMetrics == null) return; + mNetworkQuirkMetrics.setEvent(event); + mNetworkQuirkMetrics.statsWrite(); + } }
diff --git a/src/android/net/apf/ApfGenerator.java b/src/android/net/apf/ApfGenerator.java index ee713c5..6346a02 100644 --- a/src/android/net/apf/ApfGenerator.java +++ b/src/android/net/apf/ApfGenerator.java
@@ -16,16 +16,23 @@ package android.net.apf; +import static android.net.apf.ApfGenerator.Register.R0; +import static android.net.apf.ApfGenerator.Register.R1; + +import androidx.annotation.NonNull; + import com.android.internal.annotations.VisibleForTesting; +import com.android.net.module.util.HexDump; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; /** * APF assembler/generator. A tool for generating an APF program. * * Call add*() functions to add instructions to the program, then call - * {@link generate} to get the APF bytecode for the program. + * {@link ApfGenerator#generate} to get the APF bytecode for the program. * * @hide */ @@ -40,6 +47,15 @@ } private enum Opcodes { LABEL(-1), + // Unconditionally pass (if R=0) or drop (if R=1) packet. + // An optional unsigned immediate value can be provided to encode the counter number. + // If the value is non-zero, the instruction increments the counter. + // The counter is located (-4 * counter number) bytes from the end of the data region. + // It is a U32 big-endian value and is always incremented by 1. + // This is more or less equivalent to: lddw R0, -N4; add R0,1; stdw R0, -N4; {pass,drop} + // e.g. "pass", "pass 1", "drop", "drop 1" + PASS(0), + DROP(0), LDB(1), // Load 1 byte from immediate offset, e.g. "ldb R0, [5]" LDH(2), // Load 2 bytes from immediate offset, e.g. "ldh R0, [5]" LDW(3), // Load 4 bytes from immediate offset, e.g. "ldw R0, [5]" @@ -53,7 +69,12 @@ OR(11), // Or, e.g. "or R0,5" SH(12), // Left shift, e.g, "sh R0, 5" or "sh R0, -5" (shifts right) LI(13), // Load immediate, e.g. "li R0,5" (immediate encoded as signed value) - JMP(14), // Jump, e.g. "jmp label" + // Jump, e.g. "jmp label" + // In APFv6, we use JMP(R=1) to encode the DATA instruction. DATA is executed as a jump. + // It tells how many bytes of the program regions are used to store the data and followed + // by the actual data bytes. + // "e.g. data 5, abcde" + JMP(14), JEQ(15), // Compare equal and branch, e.g. "jeq R0,5,label" JNE(16), // Compare not equal and branch, e.g. "jne R0,5,label" JGT(17), // Compare greater than and branch, e.g. "jgt R0,5,label" @@ -62,7 +83,18 @@ JNEBS(20), // Compare not equal byte sequence, e.g. "jnebs R0,5,label,0x1122334455" EXT(21), // Followed by immediate indicating ExtendedOpcodes. LDDW(22), // Load 4 bytes from data memory address (register + immediate): "lddw R0, [5]R1" - STDW(23); // Store 4 bytes to data memory address (register + immediate): "stdw R0, [5]R1" + STDW(23), // Store 4 bytes to data memory address (register + immediate): "stdw R0, [5]R1" + // Write 1, 2 or 4 bytes immediate to the output buffer and auto-increment the pointer to + // write. e.g. "write 5" + WRITE(24), + // Copy bytes from input packet/APF program/data region to output buffer and + // auto-increment the output buffer pointer. + // Register bit is used to specify the source of data copy. + // R=0 means copy from packet. + // R=1 means copy from APF program/data region. + // The copy length is stored in (u8)imm2. + // e.g. "pktcopy 5, 5" "datacopy 5, 5" + PKTDATACOPY(25); final int value; @@ -78,7 +110,51 @@ NOT(32), // Not, e.g. "not R0" NEG(33), // Negate, e.g. "neg R0" SWAP(34), // Swap, e.g. "swap R0,R1" - MOVE(35); // Move, e.g. "move R0,R1" + MOVE(35), // Move, e.g. "move R0,R1" + // Allocate writable output buffer. + // R=0, use register R0 to store the length. R=1, encode the length in the u16 int imm2. + // "e.g. allocate R0" + // "e.g. allocate 123" + ALLOCATE(36), + // Transmit and deallocate the buffer (transmission can be delayed until the program + // terminates). R=0 means discard the buffer, R=1 means transmit the buffer. + // "e.g. trans" + // "e.g. discard" + TRANSMIT(37), + DISCARD(37), + // Write 1, 2 or 4 byte value from register to the output buffer and auto-increment the + // output buffer pointer. + // e.g. "ewrite1 r0" + EWRITE1(38), + EWRITE2(39), + EWRITE4(40), + // Copy bytes from input packet/APF program/data region to output buffer and + // auto-increment the output buffer pointer. + // The copy src offset is stored in R0. + // when R=0, the copy length is stored in (u8)imm2. + // when R=1, the copy length is stored in R1. + // e.g. "pktcopy r0, 5", "pktcopy r0, r1", "datacopy r0, 5", "datacopy r0, r1" + EPKTCOPY(41), + EDATACOPY(42), + // Jumps if the UDP payload content (starting at R0) does not contain ont + // of the specified QNAME, applying case insensitivity. + // R0: Offset to UDP payload content + // R=0/1 meanining 'does not match' vs 'matches' + // imm1: Opcode + // imm2: Label offset + // imm3(u8): Question type (PTR/SRV/TXT/A/AAAA) + // imm4(bytes): TLV-encoded QNAME list (null-terminated) + // e.g.: "jdnsqmatch R0,label,0x0c,\002aa\005local\0\0" + JDNSQMATCH(43), // Jumps if the UDP payload content (starting at R0) does not contain one + // of the specified NAME in answers/authority/additional records, applying + // case insensitivity. + // R=0/1 meanining 'does not match' vs 'matches' + // R0: Offset to UDP payload content + // imm1: Opcode + // imm2: Label offset + // imm3(bytes): TLV-encoded QNAME list (null-terminated) + // e.g.: "jdnsamatch R0,label,0x0c,\002aa\005local\0\0" + JDNSAMATCH(44); final int value; @@ -96,48 +172,224 @@ this.value = value; } } + + private enum IntImmediateType { + INDETERMINATE_SIZE_SIGNED, + INDETERMINATE_SIZE_UNSIGNED, + SIGNED_8, + UNSIGNED_8, + SIGNED_BE16, + UNSIGNED_BE16, + SIGNED_BE32, + UNSIGNED_BE32; + } + + private static class IntImmediate { + public final IntImmediateType mImmediateType; + public final int mValue; + + IntImmediate(int value, IntImmediateType type) { + mImmediateType = type; + mValue = value; + } + + private int calculateIndeterminateSize() { + switch (mImmediateType) { + case INDETERMINATE_SIZE_SIGNED: + return calculateImmSize(mValue, true /* signed */); + case INDETERMINATE_SIZE_UNSIGNED: + return calculateImmSize(mValue, false /* signed */); + default: + // For IMM with determinate size, return 0 to allow Math.max() calculation in + // caller function. + return 0; + } + } + + private int getEncodingSize(int immFieldSize) { + switch (mImmediateType) { + case SIGNED_8: + case UNSIGNED_8: + return 1; + case SIGNED_BE16: + case UNSIGNED_BE16: + return 2; + case SIGNED_BE32: + case UNSIGNED_BE32: + return 4; + case INDETERMINATE_SIZE_SIGNED: + case INDETERMINATE_SIZE_UNSIGNED: { + int minSizeRequired = calculateIndeterminateSize(); + if (minSizeRequired > immFieldSize) { + throw new IllegalStateException( + String.format("immFieldSize: %d is too small to encode value %d", + immFieldSize, mValue)); + } + return immFieldSize; + } + } + throw new IllegalStateException("UnhandledInvalid IntImmediateType: " + mImmediateType); + } + + private int writeValue(byte[] bytecode, Integer writingOffset, int immFieldSize) { + return Instruction.writeValue(mValue, bytecode, writingOffset, + getEncodingSize(immFieldSize)); + } + + public static IntImmediate newSigned(int imm) { + return new IntImmediate(imm, IntImmediateType.INDETERMINATE_SIZE_SIGNED); + } + + public static IntImmediate newUnsigned(long imm) { + // upperBound is 2^32 - 1 + checkRange("Unsigned IMM", imm, 0 /* lowerBound */, + 4294967295L /* upperBound */); + return new IntImmediate((int) imm, IntImmediateType.INDETERMINATE_SIZE_UNSIGNED); + } + + public static IntImmediate newTwosComplementUnsigned(long imm) { + checkRange("Unsigned TwosComplement IMM", imm, Integer.MIN_VALUE, + 4294967295L /* upperBound */); + return new IntImmediate((int) imm, IntImmediateType.INDETERMINATE_SIZE_UNSIGNED); + } + + public static IntImmediate newTwosComplementSigned(long imm) { + checkRange("Signed TwosComplement IMM", imm, Integer.MIN_VALUE, + 4294967295L /* upperBound */); + return new IntImmediate((int) imm, IntImmediateType.INDETERMINATE_SIZE_SIGNED); + } + + public static IntImmediate newS8(byte imm) { + checkRange("S8 IMM", imm, Byte.MIN_VALUE, Byte.MAX_VALUE); + return new IntImmediate(imm, IntImmediateType.SIGNED_8); + } + + public static IntImmediate newU8(int imm) { + checkRange("U8 IMM", imm, 0, 255); + return new IntImmediate(imm, IntImmediateType.UNSIGNED_8); + } + + public static IntImmediate newS16(short imm) { + return new IntImmediate(imm, IntImmediateType.SIGNED_BE16); + } + + public static IntImmediate newU16(int imm) { + checkRange("U16 IMM", imm, 0, 65535); + return new IntImmediate(imm, IntImmediateType.UNSIGNED_BE16); + } + + public static IntImmediate newS32(int imm) { + return new IntImmediate(imm, IntImmediateType.SIGNED_BE32); + } + + public static IntImmediate newU32(long imm) { + // upperBound is 2^32 - 1 + checkRange("U32 IMM", imm, 0 /* lowerBound */, + 4294967295L /* upperBound */); + return new IntImmediate((int) imm, IntImmediateType.UNSIGNED_BE32); + } + + @Override + public String toString() { + return "IntImmediate{" + "mImmediateType=" + mImmediateType + ", mValue=" + mValue + + '}'; + } + } + private class Instruction { private final byte mOpcode; // A "Opcode" value. private final byte mRegister; // A "Register" value. - private boolean mHasImm; - private byte mImmSize; - private boolean mImmSigned; - private int mImm; + public final List<IntImmediate> mIntImms = new ArrayList<>(); // When mOpcode is a jump: - private byte mTargetLabelSize; + private int mTargetLabelSize; + private int mLenFieldOverride = -1; private String mTargetLabel; // When mOpcode == Opcodes.LABEL: private String mLabel; - // When mOpcode == Opcodes.JNEBS: - private byte[] mCompareBytes; + private byte[] mBytesImm; // Offset in bytes from the beginning of this program. Set by {@link ApfGenerator#generate}. int offset; Instruction(Opcodes opcode, Register register) { - mOpcode = (byte)opcode.value; - mRegister = (byte)register.value; + mOpcode = (byte) opcode.value; + mRegister = (byte) register.value; + } + + Instruction(ExtendedOpcodes extendedOpcodes, Register register) { + this(Opcodes.EXT, register); + addUnsigned(extendedOpcodes.value); + } + + Instruction(ExtendedOpcodes extendedOpcodes, int slot, Register register) + throws IllegalInstructionException { + this(Opcodes.EXT, register); + if (slot < 0 || slot >= MEMORY_SLOTS) { + throw new IllegalInstructionException("illegal memory slot number: " + slot); + } + addUnsigned(extendedOpcodes.value + slot); } Instruction(Opcodes opcode) { - this(opcode, Register.R0); + this(opcode, R0); } - void setImm(int imm, boolean signed) { - mHasImm = true; - mImm = imm; - mImmSigned = signed; - mImmSize = calculateImmSize(imm, signed); + Instruction(ExtendedOpcodes extendedOpcodes) { + this(extendedOpcodes, R0); } - void setUnsignedImm(int imm) { - setImm(imm, false); + Instruction addSigned(int imm) { + mIntImms.add(IntImmediate.newSigned(imm)); + return this; } - void setSignedImm(int imm) { - setImm(imm, true); + Instruction addUnsigned(int imm) { + mIntImms.add(IntImmediate.newUnsigned(imm)); + return this; } - void setLabel(String label) throws IllegalInstructionException { + + Instruction addTwosCompSigned(int imm) { + mIntImms.add(IntImmediate.newTwosComplementSigned(imm)); + return this; + } + + + Instruction addTwosCompUnsigned(int imm) { + mIntImms.add(IntImmediate.newTwosComplementUnsigned(imm)); + return this; + } + + Instruction addS8(byte imm) { + mIntImms.add(IntImmediate.newS8(imm)); + return this; + } + + Instruction addU8(int imm) { + mIntImms.add(IntImmediate.newU8(imm)); + return this; + } + + Instruction addS16(short imm) { + mIntImms.add(IntImmediate.newS16(imm)); + return this; + } + + Instruction addU16(int imm) { + mIntImms.add(IntImmediate.newU16(imm)); + return this; + } + + Instruction addS32(int imm) { + mIntImms.add(IntImmediate.newS32(imm)); + return this; + } + + Instruction addU32(long imm) { + mIntImms.add(IntImmediate.newU32(imm)); + return this; + } + + Instruction setLabel(String label) throws IllegalInstructionException { if (mLabels.containsKey(label)) { throw new IllegalInstructionException("duplicate label " + label); } @@ -146,18 +398,23 @@ } mLabel = label; mLabels.put(label, this); + return this; } - void setTargetLabel(String label) { + Instruction setTargetLabel(String label) { mTargetLabel = label; mTargetLabelSize = 4; // May shrink later on in generate(). + return this; } - void setCompareBytes(byte[] bytes) { - if (mOpcode != Opcodes.JNEBS.value) { - throw new IllegalStateException("adding compare bytes to non-JNEBS instruction"); - } - mCompareBytes = bytes; + Instruction overrideLenField(int size) { + mLenFieldOverride = size; + return this; + } + + Instruction setBytesImm(byte[] bytes) { + mBytesImm = bytes; + return this; } /** @@ -168,14 +425,15 @@ return 0; } int size = 1; - if (mHasImm) { - size += generatedImmSize(); + int indeterminateSize = calculateRequiredIndeterminateSize(); + for (IntImmediate imm : mIntImms) { + size += imm.getEncodingSize(indeterminateSize); } if (mTargetLabel != null) { - size += generatedImmSize(); + size += indeterminateSize; } - if (mCompareBytes != null) { - size += mCompareBytes.length; + if (mBytesImm != null) { + size += mBytesImm.length; } return size; } @@ -189,20 +447,34 @@ if (mTargetLabel == null) { return false; } - int oldSize = size(); int oldTargetLabelSize = mTargetLabelSize; mTargetLabelSize = calculateImmSize(calculateTargetLabelOffset(), false); if (mTargetLabelSize > oldTargetLabelSize) { throw new IllegalStateException("instruction grew"); } - return size() < oldSize; + return mTargetLabelSize < oldTargetLabelSize; } /** * Assemble value for instruction size field. */ - private byte generateImmSizeField() { - byte immSize = generatedImmSize(); + private int generateImmSizeField() { + // If we already know the size the length field, just use it + switch (mLenFieldOverride) { + case -1: + break; + case 1: + return 1; + case 2: + return 2; + case 4: + return 3; + default: + throw new IllegalStateException( + "mLenFieldOverride has invalid value: " + mLenFieldOverride); + } + // Otherwise, calculate + int immSize = calculateRequiredIndeterminateSize(); // Encode size field to fit in 2 bits: 0->0, 1->1, 2->2, 3->4. return immSize == 4 ? 3 : immSize; } @@ -211,28 +483,28 @@ * Assemble first byte of generated instruction. */ private byte generateInstructionByte() { - byte sizeField = generateImmSizeField(); + int sizeField = generateImmSizeField(); return (byte)((mOpcode << 3) | (sizeField << 1) | mRegister); } /** * Write {@code value} at offset {@code writingOffset} into {@code bytecode}. - * {@link generatedImmSize} bytes are written. {@code value} is truncated to - * {@code generatedImmSize} bytes. {@code value} is treated simply as a + * {@code immSize} bytes are written. {@code value} is truncated to + * {@code immSize} bytes. {@code value} is treated simply as a * 32-bit value, so unsigned values should be zero extended and the truncation * should simply throw away their zero-ed upper bits, and signed values should * be sign extended and the truncation should simply throw away their signed * upper bits. */ - private int writeValue(int value, byte[] bytecode, int writingOffset) { - for (int i = generatedImmSize() - 1; i >= 0; i--) { + private static int writeValue(int value, byte[] bytecode, int writingOffset, int immSize) { + for (int i = immSize - 1; i >= 0; i--) { bytecode[writingOffset++] = (byte)((value >> (i * 8)) & 255); } return writingOffset; } /** - * Generate bytecode for this instruction at offset {@link offset}. + * Generate bytecode for this instruction at offset {@link Instruction#offset}. */ void generate(byte[] bytecode) throws IllegalInstructionException { if (mOpcode == Opcodes.LABEL.value) { @@ -240,15 +512,24 @@ } int writingOffset = offset; bytecode[writingOffset++] = generateInstructionByte(); + int indeterminateSize = calculateRequiredIndeterminateSize(); + int startOffset = 0; + if (mOpcode == Opcodes.EXT.value) { + // For extend opcode, always write the actual opcode first. + writingOffset = mIntImms.get(startOffset++).writeValue(bytecode, writingOffset, + indeterminateSize); + } if (mTargetLabel != null) { - writingOffset = writeValue(calculateTargetLabelOffset(), bytecode, writingOffset); + writingOffset = writeValue(calculateTargetLabelOffset(), bytecode, writingOffset, + indeterminateSize); } - if (mHasImm) { - writingOffset = writeValue(mImm, bytecode, writingOffset); + for (int i = startOffset; i < mIntImms.size(); ++i) { + writingOffset = mIntImms.get(i).writeValue(bytecode, writingOffset, + indeterminateSize); } - if (mCompareBytes != null) { - System.arraycopy(mCompareBytes, 0, bytecode, writingOffset, mCompareBytes.length); - writingOffset += mCompareBytes.length; + if (mBytesImm != null) { + System.arraycopy(mBytesImm, 0, bytecode, writingOffset, mBytesImm.length); + writingOffset += mBytesImm.length; } if ((writingOffset - offset) != size()) { throw new IllegalStateException("wrote " + (writingOffset - offset) + @@ -257,15 +538,17 @@ } /** - * Calculate the size of either the immediate field or the target label field, if either is - * present. Most instructions have either an immediate or a target label field, but for the - * instructions that have both, the size of the target label field must be the same as the - * size of the immediate field, because there is only one length field in the instruction - * byte, hence why this function simply takes the maximum of the two sizes, so neither is - * truncated. + * Calculates the maximum indeterminate size of all IMMs in this instruction. + * <p> + * This method finds the largest size needed to encode any indeterminate-sized IMMs in + * the instruction. This size will be stored in the immLen field. */ - private byte generatedImmSize() { - return mImmSize > mTargetLabelSize ? mImmSize : mTargetLabelSize; + private int calculateRequiredIndeterminateSize() { + int maxSize = mTargetLabelSize; + for (IntImmediate imm : mIntImms) { + maxSize = Math.max(maxSize, imm.calculateIndeterminateSize()); + } + return maxSize; } private int calculateTargetLabelOffset() throws IllegalInstructionException { @@ -284,21 +567,6 @@ final int targetLabelOffset = targetLabelInstruction.offset - (offset + size()); return targetLabelOffset; } - - private byte calculateImmSize(int imm, boolean signed) { - if (imm == 0) { - return 0; - } - if (signed && (imm >= -128 && imm <= 127) || - !signed && (imm >= 0 && imm <= 255)) { - return 1; - } - if (signed && (imm >= -32768 && imm <= 32767) || - !signed && (imm >= 0 && imm <= 65535)) { - return 2; - } - return 4; - } } /** @@ -356,7 +624,10 @@ public static final int LAST_PREFILLED_MEMORY_SLOT = FILTER_AGE_MEMORY_SLOT; // This version number syncs up with APF_VERSION in hardware/google/apf/apf_interpreter.h - private static final int MIN_APF_VERSION = 2; + public static final int MIN_APF_VERSION = 2; + public static final int MIN_APF_VERSION_IN_DEV = 5; + public static final int APF_VERSION_4 = 4; + private final ArrayList<Instruction> mInstructions = new ArrayList<Instruction>(); private final HashMap<String, Instruction> mLabels = new HashMap<String, Instruction>(); @@ -389,11 +660,12 @@ } } - private void addInstruction(Instruction instruction) { + private ApfGenerator append(Instruction instruction) { if (mGenerated) { throw new IllegalStateException("Program already generated"); } mInstructions.add(instruction); + return this; } /** @@ -412,53 +684,38 @@ * In this case "next_filter" may not have any generated code associated with it. */ public ApfGenerator defineLabel(String name) throws IllegalInstructionException { - Instruction instruction = new Instruction(Opcodes.LABEL); - instruction.setLabel(name); - addInstruction(instruction); - return this; + return append(new Instruction(Opcodes.LABEL).setLabel(name)); } /** * Add an unconditional jump instruction to the end of the program. */ public ApfGenerator addJump(String target) { - Instruction instruction = new Instruction(Opcodes.JMP); - instruction.setTargetLabel(target); - addInstruction(instruction); - return this; + return append(new Instruction(Opcodes.JMP).setTargetLabel(target)); } /** * Add an instruction to the end of the program to load the byte at offset {@code offset} * bytes from the beginning of the packet into {@code register}. */ - public ApfGenerator addLoad8(Register register, int offset) { - Instruction instruction = new Instruction(Opcodes.LDB, register); - instruction.setUnsignedImm(offset); - addInstruction(instruction); - return this; + public ApfGenerator addLoad8(Register r, int ofs) { + return append(new Instruction(Opcodes.LDB, r).addUnsigned(ofs)); } /** * Add an instruction to the end of the program to load 16-bits at offset {@code offset} * bytes from the beginning of the packet into {@code register}. */ - public ApfGenerator addLoad16(Register register, int offset) { - Instruction instruction = new Instruction(Opcodes.LDH, register); - instruction.setUnsignedImm(offset); - addInstruction(instruction); - return this; + public ApfGenerator addLoad16(Register r, int ofs) { + return append(new Instruction(Opcodes.LDH, r).addUnsigned(ofs)); } /** * Add an instruction to the end of the program to load 32-bits at offset {@code offset} * bytes from the beginning of the packet into {@code register}. */ - public ApfGenerator addLoad32(Register register, int offset) { - Instruction instruction = new Instruction(Opcodes.LDW, register); - instruction.setUnsignedImm(offset); - addInstruction(instruction); - return this; + public ApfGenerator addLoad32(Register r, int ofs) { + return append(new Instruction(Opcodes.LDW, r).addUnsigned(ofs)); } /** @@ -466,11 +723,8 @@ * {@code register}. The offset of the loaded byte from the beginning of the packet is * the sum of {@code offset} and the value in register R1. */ - public ApfGenerator addLoad8Indexed(Register register, int offset) { - Instruction instruction = new Instruction(Opcodes.LDBX, register); - instruction.setUnsignedImm(offset); - addInstruction(instruction); - return this; + public ApfGenerator addLoad8Indexed(Register r, int ofs) { + return append(new Instruction(Opcodes.LDBX, r).addUnsigned(ofs)); } /** @@ -478,11 +732,8 @@ * {@code register}. The offset of the loaded 16-bits from the beginning of the packet is * the sum of {@code offset} and the value in register R1. */ - public ApfGenerator addLoad16Indexed(Register register, int offset) { - Instruction instruction = new Instruction(Opcodes.LDHX, register); - instruction.setUnsignedImm(offset); - addInstruction(instruction); - return this; + public ApfGenerator addLoad16Indexed(Register r, int ofs) { + return append(new Instruction(Opcodes.LDHX, r).addUnsigned(ofs)); } /** @@ -490,109 +741,81 @@ * {@code register}. The offset of the loaded 32-bits from the beginning of the packet is * the sum of {@code offset} and the value in register R1. */ - public ApfGenerator addLoad32Indexed(Register register, int offset) { - Instruction instruction = new Instruction(Opcodes.LDWX, register); - instruction.setUnsignedImm(offset); - addInstruction(instruction); - return this; + public ApfGenerator addLoad32Indexed(Register r, int ofs) { + return append(new Instruction(Opcodes.LDWX, r).addUnsigned(ofs)); } /** * Add an instruction to the end of the program to add {@code value} to register R0. */ - public ApfGenerator addAdd(int value) { - Instruction instruction = new Instruction(Opcodes.ADD); - instruction.setSignedImm(value); - addInstruction(instruction); - return this; + public ApfGenerator addAdd(int val) { + return append(new Instruction(Opcodes.ADD).addTwosCompUnsigned(val)); } /** * Add an instruction to the end of the program to multiply register R0 by {@code value}. */ - public ApfGenerator addMul(int value) { - Instruction instruction = new Instruction(Opcodes.MUL); - instruction.setSignedImm(value); - addInstruction(instruction); - return this; + public ApfGenerator addMul(int val) { + return append(new Instruction(Opcodes.MUL).addUnsigned(val)); } /** * Add an instruction to the end of the program to divide register R0 by {@code value}. */ - public ApfGenerator addDiv(int value) { - Instruction instruction = new Instruction(Opcodes.DIV); - instruction.setSignedImm(value); - addInstruction(instruction); - return this; + public ApfGenerator addDiv(int val) { + return append(new Instruction(Opcodes.DIV).addUnsigned(val)); } /** * Add an instruction to the end of the program to logically and register R0 with {@code value}. */ - public ApfGenerator addAnd(int value) { - Instruction instruction = new Instruction(Opcodes.AND); - instruction.setUnsignedImm(value); - addInstruction(instruction); - return this; + public ApfGenerator addAnd(int val) { + return append(new Instruction(Opcodes.AND).addTwosCompUnsigned(val)); } /** * Add an instruction to the end of the program to logically or register R0 with {@code value}. */ - public ApfGenerator addOr(int value) { - Instruction instruction = new Instruction(Opcodes.OR); - instruction.setUnsignedImm(value); - addInstruction(instruction); - return this; + public ApfGenerator addOr(int val) { + return append(new Instruction(Opcodes.OR).addTwosCompUnsigned(val)); } /** * Add an instruction to the end of the program to shift left register R0 by {@code value} bits. */ - public ApfGenerator addLeftShift(int value) { - Instruction instruction = new Instruction(Opcodes.SH); - instruction.setSignedImm(value); - addInstruction(instruction); - return this; + // TODO: consider whether should change the argument type to byte + public ApfGenerator addLeftShift(int val) { + return append(new Instruction(Opcodes.SH).addSigned(val)); } /** * Add an instruction to the end of the program to shift right register R0 by {@code value} * bits. */ - public ApfGenerator addRightShift(int value) { - Instruction instruction = new Instruction(Opcodes.SH); - instruction.setSignedImm(-value); - addInstruction(instruction); - return this; + // TODO: consider whether should change the argument type to byte + public ApfGenerator addRightShift(int val) { + return append(new Instruction(Opcodes.SH).addSigned(-val)); } /** * Add an instruction to the end of the program to add register R1 to register R0. */ public ApfGenerator addAddR1() { - Instruction instruction = new Instruction(Opcodes.ADD, Register.R1); - addInstruction(instruction); - return this; + return append(new Instruction(Opcodes.ADD, R1)); } /** * Add an instruction to the end of the program to multiply register R0 by register R1. */ public ApfGenerator addMulR1() { - Instruction instruction = new Instruction(Opcodes.MUL, Register.R1); - addInstruction(instruction); - return this; + return append(new Instruction(Opcodes.MUL, R1)); } /** * Add an instruction to the end of the program to divide register R0 by register R1. */ public ApfGenerator addDivR1() { - Instruction instruction = new Instruction(Opcodes.DIV, Register.R1); - addInstruction(instruction); - return this; + return append(new Instruction(Opcodes.DIV, R1)); } /** @@ -600,9 +823,7 @@ * and store the result back into register R0. */ public ApfGenerator addAndR1() { - Instruction instruction = new Instruction(Opcodes.AND, Register.R1); - addInstruction(instruction); - return this; + return append(new Instruction(Opcodes.AND, R1)); } /** @@ -610,9 +831,7 @@ * and store the result back into register R0. */ public ApfGenerator addOrR1() { - Instruction instruction = new Instruction(Opcodes.OR, Register.R1); - addInstruction(instruction); - return this; + return append(new Instruction(Opcodes.OR, R1)); } /** @@ -620,111 +839,77 @@ * register R1. */ public ApfGenerator addLeftShiftR1() { - Instruction instruction = new Instruction(Opcodes.SH, Register.R1); - addInstruction(instruction); - return this; + return append(new Instruction(Opcodes.SH, R1)); } /** * Add an instruction to the end of the program to move {@code value} into {@code register}. */ public ApfGenerator addLoadImmediate(Register register, int value) { - Instruction instruction = new Instruction(Opcodes.LI, register); - instruction.setSignedImm(value); - addInstruction(instruction); - return this; + return append(new Instruction(Opcodes.LI, register).addSigned(value)); } /** * Add an instruction to the end of the program to jump to {@code target} if register R0's * value equals {@code value}. */ - public ApfGenerator addJumpIfR0Equals(int value, String target) { - Instruction instruction = new Instruction(Opcodes.JEQ); - instruction.setUnsignedImm(value); - instruction.setTargetLabel(target); - addInstruction(instruction); - return this; + public ApfGenerator addJumpIfR0Equals(int val, String tgt) { + return append(new Instruction(Opcodes.JEQ).addTwosCompUnsigned(val).setTargetLabel(tgt)); } /** * Add an instruction to the end of the program to jump to {@code target} if register R0's * value does not equal {@code value}. */ - public ApfGenerator addJumpIfR0NotEquals(int value, String target) { - Instruction instruction = new Instruction(Opcodes.JNE); - instruction.setUnsignedImm(value); - instruction.setTargetLabel(target); - addInstruction(instruction); - return this; + public ApfGenerator addJumpIfR0NotEquals(int val, String tgt) { + return append(new Instruction(Opcodes.JNE).addTwosCompUnsigned(val).setTargetLabel(tgt)); } /** * Add an instruction to the end of the program to jump to {@code target} if register R0's * value is greater than {@code value}. */ - public ApfGenerator addJumpIfR0GreaterThan(int value, String target) { - Instruction instruction = new Instruction(Opcodes.JGT); - instruction.setUnsignedImm(value); - instruction.setTargetLabel(target); - addInstruction(instruction); - return this; + public ApfGenerator addJumpIfR0GreaterThan(int val, String tgt) { + return append(new Instruction(Opcodes.JGT).addUnsigned(val).setTargetLabel(tgt)); } /** * Add an instruction to the end of the program to jump to {@code target} if register R0's * value is less than {@code value}. */ - public ApfGenerator addJumpIfR0LessThan(int value, String target) { - Instruction instruction = new Instruction(Opcodes.JLT); - instruction.setUnsignedImm(value); - instruction.setTargetLabel(target); - addInstruction(instruction); - return this; + public ApfGenerator addJumpIfR0LessThan(int val, String tgt) { + return append(new Instruction(Opcodes.JLT).addUnsigned(val).setTargetLabel(tgt)); } /** * Add an instruction to the end of the program to jump to {@code target} if register R0's * value has any bits set that are also set in {@code value}. */ - public ApfGenerator addJumpIfR0AnyBitsSet(int value, String target) { - Instruction instruction = new Instruction(Opcodes.JSET); - instruction.setUnsignedImm(value); - instruction.setTargetLabel(target); - addInstruction(instruction); - return this; + public ApfGenerator addJumpIfR0AnyBitsSet(int val, String tgt) { + return append(new Instruction(Opcodes.JSET).addTwosCompUnsigned(val).setTargetLabel(tgt)); } /** * Add an instruction to the end of the program to jump to {@code target} if register R0's * value equals register R1's value. */ - public ApfGenerator addJumpIfR0EqualsR1(String target) { - Instruction instruction = new Instruction(Opcodes.JEQ, Register.R1); - instruction.setTargetLabel(target); - addInstruction(instruction); - return this; + public ApfGenerator addJumpIfR0EqualsR1(String tgt) { + return append(new Instruction(Opcodes.JEQ, R1).setTargetLabel(tgt)); } /** * Add an instruction to the end of the program to jump to {@code target} if register R0's * value does not equal register R1's value. */ - public ApfGenerator addJumpIfR0NotEqualsR1(String target) { - Instruction instruction = new Instruction(Opcodes.JNE, Register.R1); - instruction.setTargetLabel(target); - addInstruction(instruction); - return this; + public ApfGenerator addJumpIfR0NotEqualsR1(String tgt) { + return append(new Instruction(Opcodes.JNE, R1).setTargetLabel(tgt)); } /** * Add an instruction to the end of the program to jump to {@code target} if register R0's * value is greater than register R1's value. */ - public ApfGenerator addJumpIfR0GreaterThanR1(String target) { - Instruction instruction = new Instruction(Opcodes.JGT, Register.R1); - instruction.setTargetLabel(target); - addInstruction(instruction); - return this; + public ApfGenerator addJumpIfR0GreaterThanR1(String tgt) { + return append(new Instruction(Opcodes.JGT, R1).setTargetLabel(tgt)); } /** @@ -732,140 +917,437 @@ * value is less than register R1's value. */ public ApfGenerator addJumpIfR0LessThanR1(String target) { - Instruction instruction = new Instruction(Opcodes.JLT, Register.R1); - instruction.setTargetLabel(target); - addInstruction(instruction); - return this; + return append(new Instruction(Opcodes.JLT, R1).setTargetLabel(target)); } /** * Add an instruction to the end of the program to jump to {@code target} if register R0's * value has any bits set that are also set in R1's value. */ - public ApfGenerator addJumpIfR0AnyBitsSetR1(String target) { - Instruction instruction = new Instruction(Opcodes.JSET, Register.R1); - instruction.setTargetLabel(target); - addInstruction(instruction); - return this; + public ApfGenerator addJumpIfR0AnyBitsSetR1(String tgt) { + return append(new Instruction(Opcodes.JSET, R1).setTargetLabel(tgt)); } /** - * Add an instruction to the end of the program to jump to {@code target} if the bytes of the - * packet at an offset specified by {@code register} don't match {@code bytes}, {@code register} - * must be R0. + * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the + * packet at an offset specified by {@code register} don't match {@code bytes} + * R=0 means check for not equal */ - public ApfGenerator addJumpIfBytesNotEqual(Register register, byte[] bytes, String target) + public ApfGenerator addJumpIfBytesAtR0NotEqual(byte[] bytes, String tgt) { + return append(new Instruction(Opcodes.JNEBS).addUnsigned( + bytes.length).setTargetLabel(tgt).setBytesImm(bytes)); + } + + /** + * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the + * packet at an offset specified by {@code register} match {@code bytes} + * R=1 means check for equal. + */ + public ApfGenerator addJumpIfBytesAtR0Equal(byte[] bytes, String tgt) throws IllegalInstructionException { - if (register == Register.R1) { - throw new IllegalInstructionException("JNEBS fails with R1"); - } - Instruction instruction = new Instruction(Opcodes.JNEBS, register); - instruction.setUnsignedImm(bytes.length); - instruction.setTargetLabel(target); - instruction.setCompareBytes(bytes); - addInstruction(instruction); - return this; + requireApfVersion(MIN_APF_VERSION_IN_DEV); + return append(new Instruction(Opcodes.JNEBS, R1).addUnsigned( + bytes.length).setTargetLabel(tgt).setBytesImm(bytes)); } /** * Add an instruction to the end of the program to load memory slot {@code slot} into * {@code register}. */ - public ApfGenerator addLoadFromMemory(Register register, int slot) + public ApfGenerator addLoadFromMemory(Register r, int slot) throws IllegalInstructionException { - if (slot < 0 || slot > (MEMORY_SLOTS - 1)) { - throw new IllegalInstructionException("illegal memory slot number: " + slot); - } - Instruction instruction = new Instruction(Opcodes.EXT, register); - instruction.setUnsignedImm(ExtendedOpcodes.LDM.value + slot); - addInstruction(instruction); - return this; + return append(new Instruction(ExtendedOpcodes.LDM, slot, r)); } /** * Add an instruction to the end of the program to store {@code register} into memory slot * {@code slot}. */ - public ApfGenerator addStoreToMemory(Register register, int slot) + public ApfGenerator addStoreToMemory(Register r, int slot) throws IllegalInstructionException { - if (slot < 0 || slot > (MEMORY_SLOTS - 1)) { - throw new IllegalInstructionException("illegal memory slot number: " + slot); - } - Instruction instruction = new Instruction(Opcodes.EXT, register); - instruction.setUnsignedImm(ExtendedOpcodes.STM.value + slot); - addInstruction(instruction); - return this; + return append(new Instruction(ExtendedOpcodes.STM, slot, r)); } /** * Add an instruction to the end of the program to logically not {@code register}. */ - public ApfGenerator addNot(Register register) { - Instruction instruction = new Instruction(Opcodes.EXT, register); - instruction.setUnsignedImm(ExtendedOpcodes.NOT.value); - addInstruction(instruction); - return this; + public ApfGenerator addNot(Register r) { + return append(new Instruction(ExtendedOpcodes.NOT, r)); } /** * Add an instruction to the end of the program to negate {@code register}. */ - public ApfGenerator addNeg(Register register) { - Instruction instruction = new Instruction(Opcodes.EXT, register); - instruction.setUnsignedImm(ExtendedOpcodes.NEG.value); - addInstruction(instruction); - return this; + public ApfGenerator addNeg(Register r) { + return append(new Instruction(ExtendedOpcodes.NEG, r)); } /** * Add an instruction to swap the values in register R0 and register R1. */ public ApfGenerator addSwap() { - Instruction instruction = new Instruction(Opcodes.EXT); - instruction.setUnsignedImm(ExtendedOpcodes.SWAP.value); - addInstruction(instruction); - return this; + return append(new Instruction(ExtendedOpcodes.SWAP)); } /** * Add an instruction to the end of the program to move the value into * {@code register} from the other register. */ - public ApfGenerator addMove(Register register) { - Instruction instruction = new Instruction(Opcodes.EXT, register); - instruction.setUnsignedImm(ExtendedOpcodes.MOVE.value); - addInstruction(instruction); - return this; + public ApfGenerator addMove(Register r) { + return append(new Instruction(ExtendedOpcodes.MOVE, r)); + } + + /** + * Add an instruction to the end of the program to let the program immediately return PASS. + */ + public ApfGenerator addPass() { + // PASS requires using R0 because it shares opcode with DROP + return append(new Instruction(Opcodes.PASS)); + } + + /** + * Add an instruction to the end of the program to increment the counter value and + * immediately return PASS. + */ + public ApfGenerator addCountAndPass(int cnt) throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + checkRange("CounterNumber", cnt /* value */, 1 /* lowerBound */, + 1000 /* upperBound */); + // PASS requires using R0 because it shares opcode with DROP + return append(new Instruction(Opcodes.PASS).addUnsigned(cnt)); + } + + /** + * Add an instruction to the end of the program to let the program immediately return DROP. + */ + public ApfGenerator addDrop() throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + // DROP requires using R1 because it shares opcode with PASS + return append(new Instruction(Opcodes.DROP, R1)); + } + + /** + * Add an instruction to the end of the program to increment the counter value and + * immediately return DROP. + */ + public ApfGenerator addCountAndDrop(int cnt) throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + checkRange("CounterNumber", cnt /* value */, 1 /* lowerBound */, + 1000 /* upperBound */); + // DROP requires using R1 because it shares opcode with PASS + return append(new Instruction(Opcodes.DROP, R1).addUnsigned(cnt)); + } + + /** + * Add an instruction to the end of the program to call the apf_allocate_buffer() function. + * Buffer length to be allocated is stored in register 0. + */ + public ApfGenerator addAllocateR0() throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + return append(new Instruction(ExtendedOpcodes.ALLOCATE)); + } + + /** + * Add an instruction to the end of the program to call the apf_allocate_buffer() function. + * + * @param size the buffer length to be allocated. + */ + public ApfGenerator addAllocate(int size) throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + // R1 means the extra be16 immediate is present + return append(new Instruction(ExtendedOpcodes.ALLOCATE, R1).addU16(size)); + } + + /** + * Add an instruction to the beginning of the program to reserve the data region. + * @param data the actual data byte + */ + public ApfGenerator addData(byte[] data) throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + if (!mInstructions.isEmpty()) { + throw new IllegalInstructionException("data instruction has to come first"); + } + return append(new Instruction(Opcodes.JMP, R1).addUnsigned(data.length).setBytesImm(data)); + } + + /** + * Add an instruction to the end of the program to transmit the allocated buffer. + */ + public ApfGenerator addTransmit() throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + // TRANSMIT requires using R0 because it shares opcode with DISCARD + return append(new Instruction(ExtendedOpcodes.TRANSMIT)); + } + + /** + * Add an instruction to the end of the program to discard the allocated buffer. + */ + public ApfGenerator addDiscard() throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + // DISCARD requires using R1 because it shares opcode with TRANSMIT + return append(new Instruction(ExtendedOpcodes.DISCARD, R1)); + } + + /** + * Add an instruction to the end of the program to write 1 byte value to output buffer. + */ + public ApfGenerator addWriteU8(int val) throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + return append(new Instruction(Opcodes.WRITE).overrideLenField(1).addU8(val)); + } + + /** + * Add an instruction to the end of the program to write 2 bytes value to output buffer. + */ + public ApfGenerator addWriteU16(int val) throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + return append(new Instruction(Opcodes.WRITE).overrideLenField(2).addU16(val)); + } + + /** + * Add an instruction to the end of the program to write 4 bytes value to output buffer. + */ + public ApfGenerator addWriteU32(long val) throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + return append(new Instruction(Opcodes.WRITE).overrideLenField(4).addU32(val)); + } + + /** + * Add an instruction to the end of the program to write 1 byte value from register to output + * buffer. + */ + public ApfGenerator addWriteU8(Register reg) throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + return append(new Instruction(ExtendedOpcodes.EWRITE1, reg)); + } + + /** + * Add an instruction to the end of the program to write 2 byte value from register to output + * buffer. + */ + public ApfGenerator addWriteU16(Register reg) throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + return append(new Instruction(ExtendedOpcodes.EWRITE2, reg)); + } + + /** + * Add an instruction to the end of the program to write 4 byte value from register to output + * buffer. + */ + public ApfGenerator addWriteU32(Register reg) throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + return append(new Instruction(ExtendedOpcodes.EWRITE4, reg)); + } + + /** + * Add an instruction to the end of the program to copy data from APF program/data region to + * output buffer and auto-increment the output buffer pointer. + * + * @param src the offset inside the APF program/data region for where to start copy. + * @param len the length of bytes needed to be copied, only <= 255 bytes can be copied at + * one time. + * @return the ApfGenerator object + */ + public ApfGenerator addDataCopy(int src, int len) + throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + return append(new Instruction(Opcodes.PKTDATACOPY, R1).addUnsigned(src).addU8(len)); + } + + /** + * Add an instruction to the end of the program to copy data from input packet to output + * buffer and auto-increment the output buffer pointer. + * + * @param src the offset inside the input packet for where to start copy. + * @param len the length of bytes needed to be copied, only <= 255 bytes can be copied at + * one time. + * @return the ApfGenerator object + */ + public ApfGenerator addPacketCopy(int src, int len) + throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + return append(new Instruction(Opcodes.PKTDATACOPY, R0).addUnsigned(src).addU8(len)); + } + + /** + * Add an instruction to the end of the program to copy data from APF program/data region to + * output buffer and auto-increment the output buffer pointer. + * Source offset is stored in R0. + * + * @param len the number of bytes to be copied, only <= 255 bytes can be copied at once. + * @return the ApfGenerator object + */ + public ApfGenerator addDataCopyFromR0(int len) throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + return append(new Instruction(ExtendedOpcodes.EDATACOPY).addU8(len)); + } + + /** + * Add an instruction to the end of the program to copy data from input packet to output + * buffer and auto-increment the output buffer pointer. + * Source offset is stored in R0. + * + * @param len the number of bytes to be copied, only <= 255 bytes can be copied at once. + * @return the ApfGenerator object + */ + public ApfGenerator addPacketCopyFromR0(int len) throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + return append(new Instruction(ExtendedOpcodes.EPKTCOPY).addU8(len)); + } + + /** + * Add an instruction to the end of the program to copy data from APF program/data region to + * output buffer and auto-increment the output buffer pointer. + * Source offset is stored in R0. + * Copy length is stored in R1. + * + * @return the ApfGenerator object + */ + public ApfGenerator addDataCopyFromR0LenR1() throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + return append(new Instruction(ExtendedOpcodes.EDATACOPY, R1)); + } + + /** + * Add an instruction to the end of the program to copy data from input packet to output + * buffer and auto-increment the output buffer pointer. + * Source offset is stored in R0. + * Copy length is stored in R1. + * + * @return the ApfGenerator object + */ + public ApfGenerator addPacketCopyFromR0LenR1() throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + return append(new Instruction(ExtendedOpcodes.EPKTCOPY, R1)); + } + + /** + * Check if the byte is valid dns character: A-Z,0-9,-,_ + */ + private static boolean isValidDnsCharacter(byte c) { + return (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_'; + } + + private static void validateNames(@NonNull byte[] names) { + final int len = names.length; + if (len < 4) { + throw new IllegalArgumentException("qnames must have at least length 4"); + } + final String errorMessage = "qname: " + HexDump.toHexString(names) + + "is not null-terminated list of TLV-encoded names"; + int i = 0; + while (i < len - 1) { + int label_len = names[i++]; + if (label_len < 1 || label_len > 63) { + throw new IllegalArgumentException( + "label len: " + label_len + " must be between 1 and 63"); + } + if (i + label_len >= len - 1) { + throw new IllegalArgumentException(errorMessage); + } + while (label_len-- > 0) { + if (!isValidDnsCharacter(names[i++])) { + throw new IllegalArgumentException("qname: " + HexDump.toHexString(names) + + " contains invalid character"); + } + } + if (names[i] == 0) { + i++; // skip null terminator. + } + } + if (names[len - 1] != 0) { + throw new IllegalArgumentException(errorMessage); + } + } + + /** + * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP + * payload's DNS questions do NOT contain the QNAME specified in {@code qnames} and qtype + * equals {@code qtype}. Examines the payload starting at the offset in R0. + * R = 0 means check for "does not contain". + */ + public ApfGenerator addJumpIfPktAtR0DoesNotContainDnsQ(@NonNull byte[] qnames, int qtype, + @NonNull String tgt) throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + validateNames(qnames); + return append(new Instruction(ExtendedOpcodes.JDNSQMATCH).setTargetLabel(tgt).addU8( + qtype).setBytesImm(qnames)); + } + + /** + * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP + * payload's DNS questions contain the QNAME specified in {@code qnames} and qtype + * equals {@code qtype}. Examines the payload starting at the offset in R0. + * R = 1 means check for "contain". + */ + public ApfGenerator addJumpIfPktAtR0ContainDnsQ(@NonNull byte[] qnames, int qtype, + @NonNull String tgt) throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + validateNames(qnames); + return append(new Instruction(ExtendedOpcodes.JDNSQMATCH, R1).setTargetLabel(tgt).addU8( + qtype).setBytesImm(qnames)); + } + + /** + * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP + * payload's DNS answers/authority/additional records do NOT contain the NAME + * specified in {@code Names}. Examines the payload starting at the offset in R0. + * R = 0 means check for "does not contain". + */ + public ApfGenerator addJumpIfPktAtR0DoesNotContainDnsA(@NonNull byte[] names, + @NonNull String tgt) throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + validateNames(names); + return append(new Instruction(ExtendedOpcodes.JDNSAMATCH).setTargetLabel(tgt).setBytesImm( + names)); + } + + /** + * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP + * payload's DNS answers/authority/additional records contain the NAME + * specified in {@code Names}. Examines the payload starting at the offset in R0. + * R = 1 means check for "contain". + */ + public ApfGenerator addJumpIfPktAtR0ContainDnsA(@NonNull byte[] names, + @NonNull String tgt) throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + validateNames(names); + return append(new Instruction(ExtendedOpcodes.JDNSAMATCH, R1).setTargetLabel( + tgt).setBytesImm(names)); + } + + private static void checkRange(@NonNull String variableName, long value, long lowerBound, + long upperBound) { + if (value >= lowerBound && value <= upperBound) { + return; + } + throw new IllegalArgumentException( + String.format("%s: %d, must be in range [%d, %d]", variableName, value, lowerBound, + upperBound)); } /** * Add an instruction to the end of the program to load 32 bits from the data memory into * {@code register}. The source address is computed by adding the signed immediate * @{code offset} to the other register. - * Requires APF v3 or greater. + * Requires APF v4 or greater. */ - public ApfGenerator addLoadData(Register destinationRegister, int offset) + public ApfGenerator addLoadData(Register dst, int ofs) throws IllegalInstructionException { - requireApfVersion(3); - Instruction instruction = new Instruction(Opcodes.LDDW, destinationRegister); - instruction.setSignedImm(offset); - addInstruction(instruction); - return this; + requireApfVersion(APF_VERSION_4); + return append(new Instruction(Opcodes.LDDW, dst).addSigned(ofs)); } /** * Add an instruction to the end of the program to store 32 bits from {@code register} into the * data memory. The destination address is computed by adding the signed immediate * @{code offset} to the other register. - * Requires APF v3 or greater. + * Requires APF v4 or greater. */ - public ApfGenerator addStoreData(Register sourceRegister, int offset) + public ApfGenerator addStoreData(Register src, int ofs) throws IllegalInstructionException { - requireApfVersion(3); - Instruction instruction = new Instruction(Opcodes.STDW, sourceRegister); - instruction.setSignedImm(offset); - addInstruction(instruction); - return this; + requireApfVersion(APF_VERSION_4); + return append(new Instruction(Opcodes.STDW, src).addSigned(ofs)); } /** @@ -882,6 +1364,22 @@ } /** + * Calculate the size of the imm. + */ + private static int calculateImmSize(int imm, boolean signed) { + if (imm == 0) { + return 0; + } + if (signed && (imm >= -128 && imm <= 127) || !signed && (imm >= 0 && imm <= 255)) { + return 1; + } + if (signed && (imm >= -32768 && imm <= 32767) || !signed && (imm >= 0 && imm <= 65535)) { + return 2; + } + return 4; + } + + /** * Returns an overestimate of the size of the generated program. {@link #generate} may return * a program that is smaller. */
diff --git a/src/android/net/apf/DnsUtils.java b/src/android/net/apf/DnsUtils.java index 6b0bdc4..5bd2515 100644 --- a/src/android/net/apf/DnsUtils.java +++ b/src/android/net/apf/DnsUtils.java
@@ -105,7 +105,7 @@ /** * // Now parse the label. - * LDBX R0, [R1+0] // R0 = label length, R1 = parsing offset + * LDBX R0, [R1] // R0 = label length, R1 = parsing offset * AND R0, 0xc0 // Is this a pointer? * * JEQ R0, 0, :parse_dns_label_real @@ -132,7 +132,7 @@ /** * :pointer_offset_stored - * LDHX R0, [R1+0] // R0 = 2-byte pointer value + * LDHX R0, [R1] // R0 = 2-byte pointer value * AND R0, 0x3ff // R0 = pointer destination offset (from DNS header) * LDM R1, 1 // R1 = offset in packet of DNS header * ADD R0, R1 // R0 = pointer destination offset @@ -162,7 +162,7 @@ * // This is where the real (non-pointer) label starts. * // Load label length into R1, and return to caller. * // m[SLOT_CURRENT_PARSE_OFFSET] already contains label offset. - * LDHX R1 [R1+0] // R1 = label length + * LDHX R1, [R1] // R1 = label length */ gen.defineLabel(labelParseDnsLabelReal); gen.addLoad8Indexed(R1, 0); @@ -301,7 +301,7 @@ gen.addJumpIfR0NotEquals(label.length(), noMatchLabel); gen.addLoadFromMemory(R0, SLOT_CURRENT_PARSE_OFFSET); gen.addAdd(1); - gen.addJumpIfBytesNotEqual(R0, label.getBytes(), noMatchLabel); + gen.addJumpIfBytesAtR0NotEqual(label.getBytes(), noMatchLabel); // Prep offset of next label. gen.addAdd(label.length());
diff --git a/src/android/net/apf/LegacyApfFilter.java b/src/android/net/apf/LegacyApfFilter.java new file mode 100644 index 0000000..6b93d89 --- /dev/null +++ b/src/android/net/apf/LegacyApfFilter.java
@@ -0,0 +1,2365 @@ +/* + * Copyright (C) 2016 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.apf; + +import static android.net.util.SocketUtils.makePacketSocketAddress; +import static android.system.OsConstants.AF_PACKET; +import static android.system.OsConstants.ARPHRD_ETHER; +import static android.system.OsConstants.ETH_P_ARP; +import static android.system.OsConstants.ETH_P_IP; +import static android.system.OsConstants.ETH_P_IPV6; +import static android.system.OsConstants.IPPROTO_ICMPV6; +import static android.system.OsConstants.IPPROTO_TCP; +import static android.system.OsConstants.IPPROTO_UDP; +import static android.system.OsConstants.SOCK_RAW; + +import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST; +import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE; +import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT; +import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT; +import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION; +import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_LEN; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.NattKeepalivePacketDataParcelable; +import android.net.TcpKeepalivePacketDataParcelable; +import android.net.apf.ApfCounterTracker.Counter; +import android.net.apf.ApfGenerator.IllegalInstructionException; +import android.net.apf.ApfGenerator.Register; +import android.net.ip.IpClient.IpClientCallbacksWrapper; +import android.net.metrics.ApfProgramEvent; +import android.net.metrics.ApfStats; +import android.net.metrics.IpConnectivityLog; +import android.net.metrics.RaEvent; +import android.os.PowerManager; +import android.stats.connectivity.NetworkQuirkEvent; +import android.system.ErrnoException; +import android.system.Os; +import android.text.format.DateUtils; +import android.util.Log; +import android.util.SparseArray; + +import androidx.annotation.Nullable; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.HexDump; +import com.android.internal.util.IndentingPrintWriter; +import com.android.net.module.util.CollectionUtils; +import com.android.net.module.util.ConnectivityUtils; +import com.android.net.module.util.InterfaceParams; +import com.android.net.module.util.SocketUtils; +import com.android.networkstack.metrics.ApfSessionInfoMetrics; +import com.android.networkstack.metrics.IpClientRaInfoMetrics; +import com.android.networkstack.metrics.NetworkQuirkMetrics; +import com.android.networkstack.util.NetworkStackUtils; + +import java.io.ByteArrayOutputStream; +import java.io.FileDescriptor; +import java.io.IOException; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.SocketAddress; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +/** + * For networks that support packet filtering via APF programs, {@code ApfFilter} + * listens for IPv6 ICMPv6 router advertisements (RAs) and generates APF programs to + * filter out redundant duplicate ones. + * + * Threading model: + * A collection of RAs we've received is kept in mRas. Generating APF programs uses mRas to + * know what RAs to filter for, thus generating APF programs is dependent on mRas. + * mRas can be accessed by multiple threads: + * - ReceiveThread, which listens for RAs and adds them to mRas, and generates APF programs. + * - callers of: + * - setMulticastFilter(), which can cause an APF program to be generated. + * - dump(), which dumps mRas among other things. + * - shutdown(), which clears mRas. + * So access to mRas is synchronized. + * + * @hide + */ +public class LegacyApfFilter implements AndroidPacketFilter { + + // Enums describing the outcome of receiving an RA packet. + private static enum ProcessRaResult { + MATCH, // Received RA matched a known RA + DROPPED, // Received RA ignored due to MAX_RAS + PARSE_ERROR, // Received RA could not be parsed + ZERO_LIFETIME, // Received RA had 0 lifetime + UPDATE_NEW_RA, // APF program updated for new RA + UPDATE_EXPIRY // APF program updated for expiry + } + + /** + * When APFv4 is supported, loads R1 with the offset of the specified counter. + */ + private void maybeSetupCounter(ApfGenerator gen, Counter c) { + if (mApfCapabilities.hasDataAccess()) { + gen.addLoadImmediate(Register.R1, c.offset()); + } + } + + // When APFv4 is supported, these point to the trampolines generated by emitEpilogue(). + // Otherwise, they're just aliases for PASS_LABEL and DROP_LABEL. + private final String mCountAndPassLabel; + private final String mCountAndDropLabel; + + // Thread to listen for RAs. + @VisibleForTesting + public class ReceiveThread extends Thread { + private final byte[] mPacket = new byte[1514]; + private final FileDescriptor mSocket; + private final long mStart = mClock.elapsedRealtime(); + + private int mReceivedRas = 0; + private int mMatchingRas = 0; + private int mDroppedRas = 0; + private int mParseErrors = 0; + private int mZeroLifetimeRas = 0; + private int mProgramUpdates = 0; + + private volatile boolean mStopped; + + public ReceiveThread(FileDescriptor socket) { + mSocket = socket; + } + + public void halt() { + mStopped = true; + // Interrupts the read() call the thread is blocked in. + SocketUtils.closeSocketQuietly(mSocket); + } + + @Override + public void run() { + log("begin monitoring"); + while (!mStopped) { + try { + int length = Os.read(mSocket, mPacket, 0, mPacket.length); + updateStats(processRa(mPacket, length)); + } catch (IOException|ErrnoException e) { + if (!mStopped) { + Log.e(TAG, "Read error", e); + } + } + } + logStats(); + } + + private void updateStats(ProcessRaResult result) { + mReceivedRas++; + switch(result) { + case MATCH: + mMatchingRas++; + return; + case DROPPED: + mDroppedRas++; + return; + case PARSE_ERROR: + mParseErrors++; + return; + case ZERO_LIFETIME: + mZeroLifetimeRas++; + return; + case UPDATE_EXPIRY: + mMatchingRas++; + mProgramUpdates++; + return; + case UPDATE_NEW_RA: + mProgramUpdates++; + return; + } + } + + private void logStats() { + final long nowMs = mClock.elapsedRealtime(); + synchronized (this) { + final ApfStats stats = new ApfStats.Builder() + .setReceivedRas(mReceivedRas) + .setMatchingRas(mMatchingRas) + .setDroppedRas(mDroppedRas) + .setParseErrors(mParseErrors) + .setZeroLifetimeRas(mZeroLifetimeRas) + .setProgramUpdates(mProgramUpdates) + .setDurationMs(nowMs - mStart) + .setMaxProgramSize(mApfCapabilities.maximumApfProgramSize) + .setProgramUpdatesAll(mNumProgramUpdates) + .setProgramUpdatesAllowingMulticast(mNumProgramUpdatesAllowingMulticast) + .build(); + mMetricsLog.log(stats); + logApfProgramEventLocked(nowMs / DateUtils.SECOND_IN_MILLIS); + } + } + } + + private static final String TAG = "ApfFilter"; + private static final boolean DBG = true; + private static final boolean VDBG = false; + + private static final int ETH_HEADER_LEN = 14; + private static final int ETH_DEST_ADDR_OFFSET = 0; + private static final int ETH_ETHERTYPE_OFFSET = 12; + private static final int ETH_TYPE_MIN = 0x0600; + private static final int ETH_TYPE_MAX = 0xFFFF; + // TODO: Make these offsets relative to end of link-layer header; don't include ETH_HEADER_LEN. + private static final int IPV4_TOTAL_LENGTH_OFFSET = ETH_HEADER_LEN + 2; + private static final int IPV4_FRAGMENT_OFFSET_OFFSET = ETH_HEADER_LEN + 6; + // Endianness is not an issue for this constant because the APF interpreter always operates in + // network byte order. + private static final int IPV4_FRAGMENT_OFFSET_MASK = 0x1fff; + private static final int IPV4_PROTOCOL_OFFSET = ETH_HEADER_LEN + 9; + private static final int IPV4_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 16; + private static final int IPV4_ANY_HOST_ADDRESS = 0; + private static final int IPV4_BROADCAST_ADDRESS = -1; // 255.255.255.255 + private static final int IPV4_HEADER_LEN = 20; // Without options + + // Traffic class and Flow label are not byte aligned. Luckily we + // don't care about either value so we'll consider bytes 1-3 of the + // IPv6 header as don't care. + private static final int IPV6_FLOW_LABEL_OFFSET = ETH_HEADER_LEN + 1; + private static final int IPV6_FLOW_LABEL_LEN = 3; + private static final int IPV6_NEXT_HEADER_OFFSET = ETH_HEADER_LEN + 6; + private static final int IPV6_SRC_ADDR_OFFSET = ETH_HEADER_LEN + 8; + private static final int IPV6_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 24; + private static final int IPV6_HEADER_LEN = 40; + // The IPv6 all nodes address ff02::1 + private static final byte[] IPV6_ALL_NODES_ADDRESS = + { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }; + + private static final int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN; + + private static final int IPPROTO_HOPOPTS = 0; + + // NOTE: this must be added to the IPv4 header length in IPV4_HEADER_SIZE_MEMORY_SLOT + private static final int UDP_DESTINATION_PORT_OFFSET = ETH_HEADER_LEN + 2; + private static final int UDP_HEADER_LEN = 8; + + private static final int TCP_HEADER_SIZE_OFFSET = 12; + + private static final int DHCP_CLIENT_PORT = 68; + // NOTE: this must be added to the IPv4 header length in IPV4_HEADER_SIZE_MEMORY_SLOT + private static final int DHCP_CLIENT_MAC_OFFSET = ETH_HEADER_LEN + UDP_HEADER_LEN + 28; + + private static final int ARP_HEADER_OFFSET = ETH_HEADER_LEN; + private static final byte[] ARP_IPV4_HEADER = { + 0, 1, // Hardware type: Ethernet (1) + 8, 0, // Protocol type: IP (0x0800) + 6, // Hardware size: 6 + 4, // Protocol size: 4 + }; + private static final int ARP_OPCODE_OFFSET = ARP_HEADER_OFFSET + 6; + // Opcode: ARP request (0x0001), ARP reply (0x0002) + private static final short ARP_OPCODE_REQUEST = 1; + private static final short ARP_OPCODE_REPLY = 2; + private static final int ARP_SOURCE_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 14; + private static final int ARP_TARGET_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 24; + // Do not log ApfProgramEvents whose actual lifetimes was less than this. + private static final int APF_PROGRAM_EVENT_LIFETIME_THRESHOLD = 2; + // Limit on the Black List size to cap on program usage for this + // TODO: Select a proper max length + private static final int APF_MAX_ETH_TYPE_BLACK_LIST_LEN = 20; + + private static final byte[] ETH_MULTICAST_MDNS_V4_MAC_ADDRESS = + {(byte) 0x01, (byte) 0x00, (byte) 0x5e, (byte) 0x00, (byte) 0x00, (byte) 0xfb}; + private static final byte[] ETH_MULTICAST_MDNS_V6_MAC_ADDRESS = + {(byte) 0x33, (byte) 0x33, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xfb}; + private static final int MDNS_PORT = 5353; + private static final int DNS_HEADER_LEN = 12; + private static final int DNS_QDCOUNT_OFFSET = 4; + // NOTE: this must be added to the IPv4 header length in IPV4_HEADER_SIZE_MEMORY_SLOT, or the + // IPv6 header length. + private static final int MDNS_QDCOUNT_OFFSET = + ETH_HEADER_LEN + UDP_HEADER_LEN + DNS_QDCOUNT_OFFSET; + private static final int MDNS_QNAME_OFFSET = + ETH_HEADER_LEN + UDP_HEADER_LEN + DNS_HEADER_LEN; + + + private final ApfCapabilities mApfCapabilities; + private final IpClientCallbacksWrapper mIpClientCallback; + private final InterfaceParams mInterfaceParams; + private final IpConnectivityLog mMetricsLog; + + @VisibleForTesting + public byte[] mHardwareAddress; + @VisibleForTesting + public ReceiveThread mReceiveThread; + @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 long mSessionStartMs = 0; + @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. + private final int mMinRdnssLifetimeSec; + + // Minimum session time for metrics, duration less than this time will not be logged. + private final long mMinMetricsSessionDurationMs; + + private final ApfFilter.Clock mClock; + private final NetworkQuirkMetrics mNetworkQuirkMetrics; + private final IpClientRaInfoMetrics mIpClientRaInfoMetrics; + private final ApfSessionInfoMetrics mApfSessionInfoMetrics; + + // Detects doze mode state transitions. + private final BroadcastReceiver mDeviceIdleReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)) { + PowerManager powerManager = + (PowerManager) context.getSystemService(Context.POWER_SERVICE); + final boolean deviceIdle = powerManager.isDeviceIdleMode(); + setDozeMode(deviceIdle); + } + } + }; + private final Context mContext; + + // 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; + + private final ApfFilter.Dependencies mDependencies; + + @VisibleForTesting + public LegacyApfFilter(Context context, ApfFilter.ApfConfiguration config, + InterfaceParams ifParams, IpClientCallbacksWrapper ipClientCallback, + IpConnectivityLog log, NetworkQuirkMetrics networkQuirkMetrics) { + this(context, config, ifParams, ipClientCallback, log, networkQuirkMetrics, + new ApfFilter.Dependencies(context), new ApfFilter.Clock()); + } + + @VisibleForTesting + public LegacyApfFilter(Context context, ApfFilter.ApfConfiguration config, + InterfaceParams ifParams, IpClientCallbacksWrapper ipClientCallback, + IpConnectivityLog log, NetworkQuirkMetrics networkQuirkMetrics, + ApfFilter.Dependencies dependencies, ApfFilter.Clock clock) { + mApfCapabilities = config.apfCapabilities; + mIpClientCallback = ipClientCallback; + mInterfaceParams = ifParams; + mMulticastFilter = config.multicastFilter; + mDrop802_3Frames = config.ieee802_3Filter; + mMinRdnssLifetimeSec = config.minRdnssLifetimeSec; + mContext = context; + mClock = clock; + mDependencies = dependencies; + mNetworkQuirkMetrics = networkQuirkMetrics; + mIpClientRaInfoMetrics = dependencies.getIpClientRaInfoMetrics(); + mApfSessionInfoMetrics = dependencies.getApfSessionInfoMetrics(); + mSessionStartMs = mClock.elapsedRealtime(); + mMinMetricsSessionDurationMs = config.minMetricsSessionDurationMs; + + if (mApfCapabilities.hasDataAccess()) { + mCountAndPassLabel = "countAndPass"; + mCountAndDropLabel = "countAndDrop"; + } else { + // APFv4 unsupported: turn jumps to the counter trampolines to immediately PASS or DROP, + // preserving the original pre-APFv4 behavior. + mCountAndPassLabel = ApfGenerator.PASS_LABEL; + mCountAndDropLabel = ApfGenerator.DROP_LABEL; + } + + // Now fill the black list from the passed array + mEthTypeBlackList = filterEthTypeBlackList(config.ethTypeBlackList); + + mMetricsLog = log; + + // TODO: ApfFilter should not generate programs until IpClient sends provisioning success. + maybeStartFilter(); + + // Listen for doze-mode transition changes to enable/disable the IPv6 multicast filter. + mContext.registerReceiver(mDeviceIdleReceiver, + new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)); + } + + public synchronized void setDataSnapshot(byte[] data) { + mDataSnapshot = data; + mApfCounterTracker.updateCountersFromData(data); + } + + private void log(String s) { + Log.d(TAG, "(" + mInterfaceParams.name + "): " + s); + } + + @GuardedBy("this") + private long getUniqueNumberLocked() { + return mUniqueCounter++; + } + + @GuardedBy("this") + private static int[] filterEthTypeBlackList(int[] ethTypeBlackList) { + ArrayList<Integer> bl = new ArrayList<Integer>(); + + for (int p : ethTypeBlackList) { + // Check if the protocol is a valid ether type + if ((p < ETH_TYPE_MIN) || (p > ETH_TYPE_MAX)) { + continue; + } + + // Check if the protocol is not repeated in the passed array + if (bl.contains(p)) { + continue; + } + + // Check if list reach its max size + if (bl.size() == APF_MAX_ETH_TYPE_BLACK_LIST_LEN) { + Log.w(TAG, "Passed EthType Black List size too large (" + bl.size() + + ") using top " + APF_MAX_ETH_TYPE_BLACK_LIST_LEN + " protocols"); + break; + } + + // Now add the protocol to the list + bl.add(p); + } + + return bl.stream().mapToInt(Integer::intValue).toArray(); + } + + /** + * Attempt to start listening for RAs and, if RAs are received, generating and installing + * filters to ignore useless RAs. + */ + @VisibleForTesting + public void maybeStartFilter() { + FileDescriptor socket; + try { + mHardwareAddress = mInterfaceParams.macAddr.toByteArray(); + synchronized(this) { + // Clear the APF memory to reset all counters upon connecting to the first AP + // in an SSID. This is limited to APFv4 devices because this large write triggers + // a crash on some older devices (b/78905546). + if (mApfCapabilities.hasDataAccess()) { + byte[] zeroes = new byte[mApfCapabilities.maximumApfProgramSize]; + if (!mIpClientCallback.installPacketFilter(zeroes)) { + sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE); + } + } + + // Install basic filters + installNewProgramLocked(); + } + socket = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IPV6); + SocketAddress addr = makePacketSocketAddress(ETH_P_IPV6, mInterfaceParams.index); + Os.bind(socket, addr); + NetworkStackUtils.attachRaFilter(socket); + } catch(SocketException|ErrnoException e) { + Log.e(TAG, "Error starting filter", e); + return; + } + mReceiveThread = new ReceiveThread(socket); + mReceiveThread.start(); + } + + // Returns seconds since device boot. + @VisibleForTesting + protected long currentTimeSeconds() { + return mClock.elapsedRealtime() / DateUtils.SECOND_IN_MILLIS; + } + + public static class InvalidRaException extends Exception { + public InvalidRaException(String m) { + super(m); + } + } + + /** + * Class to keep track of a section in a packet. + */ + private static class PacketSection { + public enum Type { + MATCH, // A field that should be matched (e.g., the router IP address). + IGNORE, // An ignored field such as the checksum of the flow label. Not matched. + LIFETIME, // A lifetime. Not matched, and generally counts toward minimum RA lifetime. + } + + /** The type of section. */ + public final Type type; + /** Offset into the packet at which this section begins. */ + public final int start; + /** Length of this section in bytes. */ + public final int length; + /** If this is a lifetime, the ICMP option that defined it. 0 for router lifetime. */ + public final int option; + /** If this is a lifetime, the lifetime value. */ + public final long lifetime; + + PacketSection(int start, int length, Type type, int option, long lifetime) { + this.start = start; + this.length = length; + this.type = type; + this.option = option; + this.lifetime = lifetime; + } + + public String toString() { + if (type == Type.LIFETIME) { + return String.format("%s: (%d, %d) %d %d", type, start, length, option, lifetime); + } else { + return String.format("%s: (%d, %d)", type, start, length); + } + } + } + + // A class to hold information about an RA. + @VisibleForTesting + public class Ra { + // From RFC4861: + private static final int ICMP6_RA_HEADER_LEN = 16; + private static final int ICMP6_RA_CHECKSUM_OFFSET = + ETH_HEADER_LEN + IPV6_HEADER_LEN + 2; + private static final int ICMP6_RA_CHECKSUM_LEN = 2; + private static final int ICMP6_RA_OPTION_OFFSET = + ETH_HEADER_LEN + IPV6_HEADER_LEN + ICMP6_RA_HEADER_LEN; + private static final int ICMP6_RA_ROUTER_LIFETIME_OFFSET = + ETH_HEADER_LEN + IPV6_HEADER_LEN + 6; + private static final int ICMP6_RA_ROUTER_LIFETIME_LEN = 2; + // Prefix information option. + private static final int ICMP6_PREFIX_OPTION_TYPE = 3; + private static final int ICMP6_PREFIX_OPTION_LEN = 32; + private static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET = 4; + private static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_LEN = 4; + private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET = 8; + private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_LEN = 4; + + // From RFC6106: Recursive DNS Server option + private static final int ICMP6_RDNSS_OPTION_TYPE = 25; + // From RFC6106: DNS Search List option + private static final int ICMP6_DNSSL_OPTION_TYPE = 31; + + // From RFC4191: Route Information option + private static final int ICMP6_ROUTE_INFO_OPTION_TYPE = 24; + // Above three options all have the same format: + private static final int ICMP6_4_BYTE_LIFETIME_OFFSET = 4; + private static final int ICMP6_4_BYTE_LIFETIME_LEN = 4; + + // Note: mPacket's position() cannot be assumed to be reset. + private final ByteBuffer mPacket; + + // List of sections in the packet. + private final ArrayList<PacketSection> mPacketSections = new ArrayList<>(); + + // Router lifetime in packet + private final int mRouterLifetime; + // Minimum valid lifetime of PIOs in packet, Long.MAX_VALUE means not seen. + private long mMinPioValidLifetime = Long.MAX_VALUE; + // Minimum route lifetime of RIOs in packet, Long.MAX_VALUE means not seen. + private long mMinRioRouteLifetime = Long.MAX_VALUE; + // Minimum lifetime of RDNSSs in packet, Long.MAX_VALUE means not seen. + private long mMinRdnssLifetime = Long.MAX_VALUE; + // Minimum lifetime in packet + long mMinLifetime; + // When the packet was last captured, in seconds since Unix Epoch + long mLastSeen; + + // For debugging only. Offsets into the packet where PIOs are. + private final ArrayList<Integer> mPrefixOptionOffsets = new ArrayList<>(); + + // For debugging only. Offsets into the packet where RDNSS options are. + private final ArrayList<Integer> mRdnssOptionOffsets = new ArrayList<>(); + + // For debugging only. Offsets into the packet where RIO options are. + private final ArrayList<Integer> mRioOptionOffsets = new ArrayList<>(); + + // For debugging only. How many times this RA was seen. + int seenCount = 0; + + // For debugging only. Returns the hex representation of the last matching packet. + String getLastMatchingPacket() { + return HexDump.toHexString(mPacket.array(), 0, mPacket.capacity(), + false /* lowercase */); + } + + // For debugging only. Returns the string representation of the IPv6 address starting at + // position pos in the packet. + private String IPv6AddresstoString(int pos) { + try { + byte[] array = mPacket.array(); + // Can't just call copyOfRange() and see if it throws, because if it reads past the + // end it pads with zeros instead of throwing. + if (pos < 0 || pos + 16 > array.length || pos + 16 < pos) { + return "???"; + } + byte[] addressBytes = Arrays.copyOfRange(array, pos, pos + 16); + InetAddress address = (Inet6Address) InetAddress.getByAddress(addressBytes); + return address.getHostAddress(); + } catch (UnsupportedOperationException e) { + // array() failed. Cannot happen, mPacket is array-backed and read-write. + return "???"; + } catch (ClassCastException|UnknownHostException e) { + // Cannot happen. + return "???"; + } + } + + // Can't be static because it's in a non-static inner class. + // TODO: Make this static once RA is its own class. + private void prefixOptionToString(StringBuffer sb, int offset) { + String prefix = IPv6AddresstoString(offset + 16); + int length = getUint8(mPacket, offset + 2); + long valid = getUint32(mPacket, offset + 4); + long preferred = getUint32(mPacket, offset + 8); + sb.append(String.format("%s/%d %ds/%ds ", prefix, length, valid, preferred)); + } + + private void rdnssOptionToString(StringBuffer sb, int offset) { + int optLen = getUint8(mPacket, offset + 1) * 8; + if (optLen < 24) return; // Malformed or empty. + long lifetime = getUint32(mPacket, offset + 4); + int numServers = (optLen - 8) / 16; + sb.append("DNS ").append(lifetime).append("s"); + for (int server = 0; server < numServers; server++) { + sb.append(" ").append(IPv6AddresstoString(offset + 8 + 16 * server)); + } + sb.append(" "); + } + + private void rioOptionToString(StringBuffer sb, int offset) { + int optLen = getUint8(mPacket, offset + 1) * 8; + if (optLen < 8 || optLen > 24) return; // Malformed or empty. + int prefixLen = getUint8(mPacket, offset + 2); + long lifetime = getUint32(mPacket, offset + 4); + + // This read is variable length because the prefix can be 0, 8 or 16 bytes long. + // We can't use any of the ByteBuffer#get methods here because they all start reading + // from the buffer's current position. + byte[] prefix = new byte[IPV6_ADDR_LEN]; + System.arraycopy(mPacket.array(), offset + 8, prefix, 0, optLen - 8); + sb.append("RIO ").append(lifetime).append("s "); + try { + InetAddress address = (Inet6Address) InetAddress.getByAddress(prefix); + sb.append(address.getHostAddress()); + } catch (UnknownHostException impossible) { + sb.append("???"); + } + sb.append("/").append(prefixLen).append(" "); + } + + public String toString() { + try { + StringBuffer sb = new StringBuffer(); + sb.append(String.format("RA %s -> %s %ds ", + IPv6AddresstoString(IPV6_SRC_ADDR_OFFSET), + IPv6AddresstoString(IPV6_DEST_ADDR_OFFSET), + getUint16(mPacket, ICMP6_RA_ROUTER_LIFETIME_OFFSET))); + for (int i: mPrefixOptionOffsets) { + prefixOptionToString(sb, i); + } + for (int i: mRdnssOptionOffsets) { + rdnssOptionToString(sb, i); + } + for (int i: mRioOptionOffsets) { + rioOptionToString(sb, i); + } + return sb.toString(); + } catch (BufferUnderflowException|IndexOutOfBoundsException e) { + return "<Malformed RA>"; + } + } + + /** + * Add a packet section that should be matched, starting from the current position. + * @param length the length of the section + */ + private void addMatchSection(int length) { + // Don't generate JNEBS instruction for 0 bytes as they will fail the + // ASSERT_FORWARD_IN_PROGRAM(pc + cmp_imm - 1) check (where cmp_imm is + // the number of bytes to compare) and immediately pass the packet. + // The code does not attempt to generate such matches, but add a safety + // check to prevent doing so in the presence of bugs or malformed or + // truncated packets. + if (length == 0) return; + mPacketSections.add( + new PacketSection(mPacket.position(), length, PacketSection.Type.MATCH, 0, 0)); + mPacket.position(mPacket.position() + length); + } + + /** + * Add a packet section that should be matched, starting from the current position. + * @param end the offset in the packet before which the section ends + */ + private void addMatchUntil(int end) { + addMatchSection(end - mPacket.position()); + } + + /** + * Add a packet section that should be ignored, starting from the current position. + * @param length the length of the section in bytes + */ + private void addIgnoreSection(int length) { + mPacketSections.add( + new PacketSection(mPacket.position(), length, PacketSection.Type.IGNORE, 0, 0)); + mPacket.position(mPacket.position() + length); + } + + /** + * Add a packet section that represents a lifetime, starting from the current position. + * @param length the length of the section in bytes + * @param optionType the RA option containing this lifetime, or 0 for router lifetime + * @param lifetime the lifetime + */ + private void addLifetimeSection(int length, int optionType, long lifetime) { + mPacketSections.add( + new PacketSection(mPacket.position(), length, PacketSection.Type.LIFETIME, + optionType, lifetime)); + mPacket.position(mPacket.position() + length); + } + + /** + * Adds packet sections for an RA option with a 4-byte lifetime 4 bytes into the option + * @param optionType the RA option that is being added + * @param optionLength the length of the option in bytes + */ + private long add4ByteLifetimeOption(int optionType, int optionLength) { + addMatchSection(ICMP6_4_BYTE_LIFETIME_OFFSET); + final long lifetime = getUint32(mPacket, mPacket.position()); + addLifetimeSection(ICMP6_4_BYTE_LIFETIME_LEN, optionType, lifetime); + addMatchSection(optionLength - ICMP6_4_BYTE_LIFETIME_OFFSET + - ICMP6_4_BYTE_LIFETIME_LEN); + return lifetime; + } + + /** + * Return the router lifetime of the RA + */ + public int routerLifetime() { + return mRouterLifetime; + } + + /** + * Return the minimum valid lifetime in PIOs + */ + public long minPioValidLifetime() { + return mMinPioValidLifetime; + } + + /** + * Return the minimum route lifetime in RIOs + */ + public long minRioRouteLifetime() { + return mMinRioRouteLifetime; + } + + /** + * Return the minimum lifetime in RDNSSs + */ + public long minRdnssLifetime() { + return mMinRdnssLifetime; + } + + // http://b/66928272 http://b/65056012 + // DnsServerRepository ignores RDNSS servers with lifetimes that are too low. Ignore these + // lifetimes for the purpose of filter lifetime calculations. + private boolean shouldIgnoreLifetime(int optionType, long lifetime) { + return optionType == ICMP6_RDNSS_OPTION_TYPE + && lifetime != 0 && lifetime < mMinRdnssLifetimeSec; + } + + private boolean isRelevantLifetime(PacketSection section) { + return section.type == PacketSection.Type.LIFETIME + && !shouldIgnoreLifetime(section.option, section.lifetime); + } + + // Note that this parses RA and may throw InvalidRaException (from + // Buffer.position(int) or due to an invalid-length option) or IndexOutOfBoundsException + // (from ByteBuffer.get(int) ) if parsing encounters something non-compliant with + // specifications. + @VisibleForTesting + public Ra(byte[] packet, int length) throws InvalidRaException { + if (length < ICMP6_RA_OPTION_OFFSET) { + throw new InvalidRaException("Not an ICMP6 router advertisement: too short"); + } + + mPacket = ByteBuffer.wrap(Arrays.copyOf(packet, length)); + mLastSeen = currentTimeSeconds(); + + // Check packet in case a packet arrives before we attach RA filter + // to our packet socket. b/29586253 + if (getUint16(mPacket, ETH_ETHERTYPE_OFFSET) != ETH_P_IPV6 || + getUint8(mPacket, IPV6_NEXT_HEADER_OFFSET) != IPPROTO_ICMPV6 || + getUint8(mPacket, ICMP6_TYPE_OFFSET) != ICMPV6_ROUTER_ADVERTISEMENT) { + throw new InvalidRaException("Not an ICMP6 router advertisement"); + } + + + RaEvent.Builder builder = new RaEvent.Builder(); + + // Ignore the flow label and low 4 bits of traffic class. + addMatchUntil(IPV6_FLOW_LABEL_OFFSET); + addIgnoreSection(IPV6_FLOW_LABEL_LEN); + + // Ignore checksum. + addMatchUntil(ICMP6_RA_CHECKSUM_OFFSET); + addIgnoreSection(ICMP6_RA_CHECKSUM_LEN); + + // Parse router lifetime + addMatchUntil(ICMP6_RA_ROUTER_LIFETIME_OFFSET); + mRouterLifetime = getUint16(mPacket, ICMP6_RA_ROUTER_LIFETIME_OFFSET); + addLifetimeSection(ICMP6_RA_ROUTER_LIFETIME_LEN, 0, mRouterLifetime); + builder.updateRouterLifetime(mRouterLifetime); + + // Add remaining fields (reachable time and retransmission timer) to match section. + addMatchUntil(ICMP6_RA_OPTION_OFFSET); + + while (mPacket.hasRemaining()) { + final int position = mPacket.position(); + final int optionType = getUint8(mPacket, position); + final int optionLength = getUint8(mPacket, position + 1) * 8; + long lifetime; + switch (optionType) { + case ICMP6_PREFIX_OPTION_TYPE: + mPrefixOptionOffsets.add(position); + + // Parse valid lifetime + addMatchSection(ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET); + lifetime = getUint32(mPacket, mPacket.position()); + addLifetimeSection(ICMP6_PREFIX_OPTION_VALID_LIFETIME_LEN, + ICMP6_PREFIX_OPTION_TYPE, lifetime); + builder.updatePrefixValidLifetime(lifetime); + mMinPioValidLifetime = getMinForPositiveValue( + mMinPioValidLifetime, lifetime); + + // Parse preferred lifetime + lifetime = getUint32(mPacket, mPacket.position()); + addLifetimeSection(ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_LEN, + ICMP6_PREFIX_OPTION_TYPE, lifetime); + builder.updatePrefixPreferredLifetime(lifetime); + + addMatchSection(4); // Reserved bytes + addMatchSection(IPV6_ADDR_LEN); // The prefix itself + break; + // These three options have the same lifetime offset and size, and + // are processed with the same specialized add4ByteLifetimeOption: + case ICMP6_RDNSS_OPTION_TYPE: + mRdnssOptionOffsets.add(position); + lifetime = add4ByteLifetimeOption(optionType, optionLength); + builder.updateRdnssLifetime(lifetime); + mMinRdnssLifetime = getMinForPositiveValue(mMinRdnssLifetime, lifetime); + break; + case ICMP6_ROUTE_INFO_OPTION_TYPE: + mRioOptionOffsets.add(position); + lifetime = add4ByteLifetimeOption(optionType, optionLength); + builder.updateRouteInfoLifetime(lifetime); + mMinRioRouteLifetime = getMinForPositiveValue( + mMinRioRouteLifetime, lifetime); + break; + case ICMP6_DNSSL_OPTION_TYPE: + lifetime = add4ByteLifetimeOption(optionType, optionLength); + builder.updateDnsslLifetime(lifetime); + break; + default: + // RFC4861 section 4.2 dictates we ignore unknown options for forwards + // compatibility. + mPacket.position(position + optionLength); + break; + } + if (optionLength <= 0) { + throw new InvalidRaException(String.format( + "Invalid option length opt=%d len=%d", optionType, optionLength)); + } + } + mMinLifetime = minLifetime(); + mMetricsLog.log(builder.build()); + } + + // Considering only the MATCH sections, does {@code packet} match this RA? + boolean matches(byte[] packet, int length) { + if (length != mPacket.capacity()) return false; + byte[] referencePacket = mPacket.array(); + for (PacketSection section : mPacketSections) { + if (section.type != PacketSection.Type.MATCH) continue; + for (int i = section.start; i < (section.start + section.length); i++) { + if (packet[i] != referencePacket[i]) return false; + } + } + return true; + } + + // What is the minimum of all lifetimes within {@code packet} in seconds? + // Precondition: matches(packet, length) already returned true. + long minLifetime() { + long minLifetime = Long.MAX_VALUE; + for (PacketSection section : mPacketSections) { + if (isRelevantLifetime(section)) { + minLifetime = Math.min(minLifetime, section.lifetime); + } + } + return minLifetime; + } + + // How many seconds does this RA's have to live, taking into account the fact + // that we might have seen it a while ago. + long currentLifetime() { + return mMinLifetime - (currentTimeSeconds() - mLastSeen); + } + + boolean isExpired() { + // TODO: We may want to handle 0 lifetime RAs differently, if they are common. We'll + // have to calculate the filter lifetime specially as a fraction of 0 is still 0. + return currentLifetime() <= 0; + } + + // Filter for a fraction of the lifetime and adjust for the age of the RA. + @GuardedBy("ApfFilter.this") + int filterLifetime() { + return (int) (mMinLifetime / FRACTION_OF_LIFETIME_TO_FILTER) + - (int) (mProgramBaseTime - mLastSeen); + } + + @GuardedBy("ApfFilter.this") + boolean shouldFilter() { + return filterLifetime() > 0; + } + + // 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. + // Return Long.MAX_VALUE if we don't install any filter program for this RA. As the return + // value of this function is used to calculate the program min lifetime (which corresponds + // to the smallest generated filter lifetime). Returning Long.MAX_VALUE in the case no + // filter gets generated makes sure the program lifetime stays unaffected. + @GuardedBy("ApfFilter.this") + long generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException { + String nextFilterLabel = "Ra" + getUniqueNumberLocked(); + // Skip if packet is not the right size + gen.addLoadFromMemory(Register.R0, gen.PACKET_SIZE_MEMORY_SLOT); + gen.addJumpIfR0NotEquals(mPacket.capacity(), nextFilterLabel); + // Skip filter if expired + gen.addLoadFromMemory(Register.R0, gen.FILTER_AGE_MEMORY_SLOT); + gen.addJumpIfR0GreaterThan(filterLifetime(), nextFilterLabel); + for (PacketSection section : mPacketSections) { + // Generate code to match the packet bytes. + if (section.type == PacketSection.Type.MATCH) { + gen.addLoadImmediate(Register.R0, section.start); + gen.addJumpIfBytesAtR0NotEqual( + Arrays.copyOfRange(mPacket.array(), section.start, + section.start + section.length), + nextFilterLabel); + } + + // Generate code to test the lifetimes haven't gone down too far. + // The packet is accepted if any non-ignored lifetime is lower than filterLifetime. + if (isRelevantLifetime(section)) { + switch (section.length) { + case 4: gen.addLoad32(Register.R0, section.start); break; + case 2: gen.addLoad16(Register.R0, section.start); break; + default: + throw new IllegalStateException( + "bogus lifetime size " + section.length); + } + gen.addJumpIfR0LessThan(filterLifetime(), nextFilterLabel); + } + } + maybeSetupCounter(gen, Counter.DROPPED_RA); + gen.addJump(mCountAndDropLabel); + gen.defineLabel(nextFilterLabel); + return filterLifetime(); + } + } + + // TODO: Refactor these subclasses to avoid so much repetition. + private abstract static class KeepalivePacket { + // Note that the offset starts from IP header. + // These must be added ether header length when generating program. + static final int IP_HEADER_OFFSET = 0; + static final int IPV4_SRC_ADDR_OFFSET = IP_HEADER_OFFSET + 12; + + // 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(ApfGenerator gen) throws IllegalInstructionException; + } + + // A class to hold NAT-T keepalive ack information. + private class NattKeepaliveResponse extends KeepalivePacket { + static final int UDP_LENGTH_OFFSET = 4; + static final int UDP_HEADER_LEN = 8; + + protected class NattKeepaliveResponseData { + public final byte[] srcAddress; + public final int srcPort; + public final byte[] dstAddress; + public final int dstPort; + + NattKeepaliveResponseData(final NattKeepalivePacketDataParcelable sentKeepalivePacket) { + srcAddress = sentKeepalivePacket.dstAddress; + srcPort = sentKeepalivePacket.dstPort; + dstAddress = sentKeepalivePacket.srcAddress; + dstPort = sentKeepalivePacket.srcPort; + } + } + + protected final NattKeepaliveResponseData mPacket; + protected final byte[] mSrcDstAddr; + protected final byte[] mPortFingerprint; + // NAT-T keepalive packet + protected final byte[] mPayload = {(byte) 0xff}; + + NattKeepaliveResponse(final NattKeepalivePacketDataParcelable sentKeepalivePacket) { + mPacket = new NattKeepaliveResponseData(sentKeepalivePacket); + mSrcDstAddr = concatArrays(mPacket.srcAddress, mPacket.dstAddress); + mPortFingerprint = generatePortFingerprint(mPacket.srcPort, mPacket.dstPort); + } + + byte[] generatePortFingerprint(int srcPort, int dstPort) { + final ByteBuffer fp = ByteBuffer.allocate(4); + fp.order(ByteOrder.BIG_ENDIAN); + fp.putShort((short) srcPort); + fp.putShort((short) dstPort); + return fp.array(); + } + + @Override + void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException { + final String nextFilterLabel = "natt_keepalive_filter" + getUniqueNumberLocked(); + + gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET); + gen.addJumpIfBytesAtR0NotEqual(mSrcDstAddr, nextFilterLabel); + + // A NAT-T keepalive packet contains 1 byte payload with the value 0xff + // Check payload length is 1 + gen.addLoadFromMemory(Register.R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT); + gen.addAdd(UDP_HEADER_LEN); + gen.addSwap(); + gen.addLoad16(Register.R0, IPV4_TOTAL_LENGTH_OFFSET); + gen.addNeg(Register.R1); + gen.addAddR1(); + gen.addJumpIfR0NotEquals(1, nextFilterLabel); + + // Check that the ports match + gen.addLoadFromMemory(Register.R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT); + gen.addAdd(ETH_HEADER_LEN); + gen.addJumpIfBytesAtR0NotEqual(mPortFingerprint, nextFilterLabel); + + // Payload offset = R0 + UDP header length + gen.addAdd(UDP_HEADER_LEN); + gen.addJumpIfBytesAtR0NotEqual(mPayload, nextFilterLabel); + + maybeSetupCounter(gen, Counter.DROPPED_IPV4_NATT_KEEPALIVE); + gen.addJump(mCountAndDropLabel); + gen.defineLabel(nextFilterLabel); + } + + public String toString() { + try { + return String.format("%s -> %s", + ConnectivityUtils.addressAndPortToString( + InetAddress.getByAddress(mPacket.srcAddress), mPacket.srcPort), + ConnectivityUtils.addressAndPortToString( + InetAddress.getByAddress(mPacket.dstAddress), mPacket.dstPort)); + } catch (UnknownHostException e) { + return "Unknown host"; + } + } + } + + // A class to hold TCP keepalive ack information. + private abstract static class TcpKeepaliveAck extends KeepalivePacket { + protected static class TcpKeepaliveAckData { + public final byte[] srcAddress; + public final int srcPort; + public final byte[] dstAddress; + public final int dstPort; + public final int seq; + public final int ack; + + // Create the characteristics of the ack packet from the sent keepalive packet. + TcpKeepaliveAckData(final TcpKeepalivePacketDataParcelable sentKeepalivePacket) { + srcAddress = sentKeepalivePacket.dstAddress; + srcPort = sentKeepalivePacket.dstPort; + dstAddress = sentKeepalivePacket.srcAddress; + dstPort = sentKeepalivePacket.srcPort; + seq = sentKeepalivePacket.ack; + ack = sentKeepalivePacket.seq + 1; + } + } + + protected final TcpKeepaliveAckData mPacket; + protected final byte[] mSrcDstAddr; + protected final byte[] mPortSeqAckFingerprint; + + TcpKeepaliveAck(final TcpKeepaliveAckData packet, final byte[] srcDstAddr) { + mPacket = packet; + mSrcDstAddr = srcDstAddr; + mPortSeqAckFingerprint = generatePortSeqAckFingerprint(mPacket.srcPort, + mPacket.dstPort, mPacket.seq, mPacket.ack); + } + + static byte[] generatePortSeqAckFingerprint(int srcPort, int dstPort, int seq, int ack) { + final ByteBuffer fp = ByteBuffer.allocate(12); + fp.order(ByteOrder.BIG_ENDIAN); + fp.putShort((short) srcPort); + fp.putShort((short) dstPort); + fp.putInt(seq); + fp.putInt(ack); + return fp.array(); + } + + public String toString() { + try { + return String.format("%s -> %s , seq=%d, ack=%d", + ConnectivityUtils.addressAndPortToString( + InetAddress.getByAddress(mPacket.srcAddress), mPacket.srcPort), + ConnectivityUtils.addressAndPortToString( + InetAddress.getByAddress(mPacket.dstAddress), mPacket.dstPort), + Integer.toUnsignedLong(mPacket.seq), + Integer.toUnsignedLong(mPacket.ack)); + } catch (UnknownHostException e) { + return "Unknown host"; + } + } + + // 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(ApfGenerator gen) throws IllegalInstructionException; + } + + private class TcpKeepaliveAckV4 extends TcpKeepaliveAck { + + TcpKeepaliveAckV4(final TcpKeepalivePacketDataParcelable sentKeepalivePacket) { + this(new TcpKeepaliveAckData(sentKeepalivePacket)); + } + TcpKeepaliveAckV4(final TcpKeepaliveAckData packet) { + super(packet, concatArrays(packet.srcAddress, packet.dstAddress) /* srcDstAddr */); + } + + @Override + void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException { + final String nextFilterLabel = "keepalive_ack" + getUniqueNumberLocked(); + + gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET); + gen.addJumpIfBytesAtR0NotEqual(mSrcDstAddr, nextFilterLabel); + + // Skip to the next filter if it's not zero-sized : + // TCP_HEADER_SIZE + IPV4_HEADER_SIZE - ipv4_total_length == 0 + // Load the IP header size into R1 + gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT); + // Load the TCP header size into R0 (it's indexed by R1) + gen.addLoad8Indexed(Register.R0, ETH_HEADER_LEN + TCP_HEADER_SIZE_OFFSET); + // Size offset is in the top nibble, but it must be multiplied by 4, and the two + // top bits of the low nibble are guaranteed to be zeroes. Right-shift R0 by 2. + gen.addRightShift(2); + // R0 += R1 -> R0 contains TCP + IP headers length + gen.addAddR1(); + // Load IPv4 total length + gen.addLoad16(Register.R1, IPV4_TOTAL_LENGTH_OFFSET); + gen.addNeg(Register.R0); + gen.addAddR1(); + gen.addJumpIfR0NotEquals(0, nextFilterLabel); + // Add IPv4 header length + gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT); + gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN); + gen.addAddR1(); + gen.addJumpIfBytesAtR0NotEqual(mPortSeqAckFingerprint, nextFilterLabel); + + maybeSetupCounter(gen, Counter.DROPPED_IPV4_KEEPALIVE_ACK); + gen.addJump(mCountAndDropLabel); + gen.defineLabel(nextFilterLabel); + } + } + + private class TcpKeepaliveAckV6 extends TcpKeepaliveAck { + TcpKeepaliveAckV6(final TcpKeepalivePacketDataParcelable sentKeepalivePacket) { + this(new TcpKeepaliveAckData(sentKeepalivePacket)); + } + TcpKeepaliveAckV6(final TcpKeepaliveAckData packet) { + super(packet, concatArrays(packet.srcAddress, packet.dstAddress) /* srcDstAddr */); + } + + @Override + void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException { + throw new UnsupportedOperationException("IPv6 TCP Keepalive is not supported yet"); + } + } + + // Maximum number of RAs to filter for. + private static final int MAX_RAS = 10; + + @GuardedBy("this") + private ArrayList<Ra> mRas = new ArrayList<>(); + @GuardedBy("this") + private SparseArray<KeepalivePacket> mKeepalivePackets = new SparseArray<>(); + @GuardedBy("this") + private final List<String[]> mMdnsAllowList = new ArrayList<>(); + + // There is always some marginal benefit to updating the installed APF program when an RA is + // seen because we can extend the program's lifetime slightly, but there is some cost to + // updating the program, so don't bother unless the program is going to expire soon. This + // constant defines "soon" in seconds. + private static final long MAX_PROGRAM_LIFETIME_WORTH_REFRESHING = 30; + // We don't want to filter an RA for it's whole lifetime as it'll be expired by the time we ever + // see a refresh. Using half the lifetime might be a good idea except for the fact that + // packets may be dropped, so let's use 6. + private static final int FRACTION_OF_LIFETIME_TO_FILTER = 6; + + // The base time for this filter program. In seconds since Unix Epoch. + // This is the time when the APF program was generated. All filters in the program should use + // this base time as their current time for consistency purposes. + @GuardedBy("this") + private long mProgramBaseTime; + // When did we last install a filter program? In seconds since Unix Epoch. + @GuardedBy("this") + private long mLastTimeInstalledProgram; + // How long should the last installed filter program live for? In seconds. + @GuardedBy("this") + private long mLastInstalledProgramMinLifetime; + @GuardedBy("this") + private ApfProgramEvent.Builder mLastInstallEvent; + + // For debugging only. The last program installed. + @GuardedBy("this") + private byte[] mLastInstalledProgram; + + /** + * For debugging only. Contains the latest APF buffer snapshot captured from the firmware. + * + * A typical size for this buffer is 4KB. It is present only if the WiFi HAL supports + * IWifiStaIface#readApfPacketFilterData(), and the APF interpreter advertised support for + * the opcodes to access the data buffer (LDDW and STDW). + */ + @GuardedBy("this") @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; + // How many times the program was updated since we started for allowing multicast traffic. + @GuardedBy("this") + private int mNumProgramUpdatesAllowingMulticast = 0; + + /** + * Generate filter code to process ARP packets. Execution of this code ends in either the + * DROP_LABEL or PASS_LABEL and does not fall off the end. + * Preconditions: + * - Packet being filtered is ARP + */ + @GuardedBy("this") + private void generateArpFilterLocked(ApfGenerator gen) throws IllegalInstructionException { + // Here's a basic summary of what the ARP filter program does: + // + // if not ARP IPv4 + // pass + // if not ARP IPv4 reply or request + // pass + // if ARP reply source ip is 0.0.0.0 + // drop + // if unicast ARP reply + // pass + // if interface has no IPv4 address + // if target ip is 0.0.0.0 + // drop + // else + // if target ip is not the interface ip + // drop + // pass + + final String checkTargetIPv4 = "checkTargetIPv4"; + + // Pass if not ARP IPv4. + gen.addLoadImmediate(Register.R0, ARP_HEADER_OFFSET); + maybeSetupCounter(gen, Counter.PASSED_ARP_NON_IPV4); + gen.addJumpIfBytesAtR0NotEqual(ARP_IPV4_HEADER, mCountAndPassLabel); + + // Pass if unknown ARP opcode. + gen.addLoad16(Register.R0, ARP_OPCODE_OFFSET); + gen.addJumpIfR0Equals(ARP_OPCODE_REQUEST, checkTargetIPv4); // Skip to unicast check + maybeSetupCounter(gen, Counter.PASSED_ARP_UNKNOWN); + gen.addJumpIfR0NotEquals(ARP_OPCODE_REPLY, mCountAndPassLabel); + + // Drop if ARP reply source IP is 0.0.0.0 + gen.addLoad32(Register.R0, ARP_SOURCE_IP_ADDRESS_OFFSET); + maybeSetupCounter(gen, Counter.DROPPED_ARP_REPLY_SPA_NO_HOST); + gen.addJumpIfR0Equals(IPV4_ANY_HOST_ADDRESS, mCountAndDropLabel); + + // Pass if unicast reply. + gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET); + maybeSetupCounter(gen, Counter.PASSED_ARP_UNICAST_REPLY); + gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel); + + // Either a unicast request, a unicast reply, or a broadcast reply. + gen.defineLabel(checkTargetIPv4); + if (mIPv4Address == null) { + // When there is no IPv4 address, drop GARP replies (b/29404209). + gen.addLoad32(Register.R0, ARP_TARGET_IP_ADDRESS_OFFSET); + maybeSetupCounter(gen, Counter.DROPPED_GARP_REPLY); + gen.addJumpIfR0Equals(IPV4_ANY_HOST_ADDRESS, mCountAndDropLabel); + } else { + // When there is an IPv4 address, drop unicast/broadcast requests + // and broadcast replies with a different target IPv4 address. + gen.addLoadImmediate(Register.R0, ARP_TARGET_IP_ADDRESS_OFFSET); + maybeSetupCounter(gen, Counter.DROPPED_ARP_OTHER_HOST); + gen.addJumpIfBytesAtR0NotEqual(mIPv4Address, mCountAndDropLabel); + } + + maybeSetupCounter(gen, Counter.PASSED_ARP); + gen.addJump(mCountAndPassLabel); + } + + /** + * Generate filter code to process IPv4 packets. Execution of this code ends in either the + * DROP_LABEL or PASS_LABEL and does not fall off the end. + * Preconditions: + * - Packet being filtered is IPv4 + */ + @GuardedBy("this") + private void generateIPv4FilterLocked(ApfGenerator gen) throws IllegalInstructionException { + // Here's a basic summary of what the IPv4 filter program does: + // + // if filtering multicast (i.e. multicast lock not held): + // if it's DHCP destined to our MAC: + // pass + // if it's L2 broadcast: + // drop + // if it's IPv4 multicast: + // drop + // if it's IPv4 broadcast: + // drop + // if keepalive ack + // drop + // pass + + if (mMulticastFilter) { + final String skipDhcpv4Filter = "skip_dhcp_v4_filter"; + + // Pass DHCP addressed to us. + // Check it's UDP. + gen.addLoad8(Register.R0, IPV4_PROTOCOL_OFFSET); + gen.addJumpIfR0NotEquals(IPPROTO_UDP, skipDhcpv4Filter); + // Check it's not a fragment. This matches the BPF filter installed by the DHCP client. + gen.addLoad16(Register.R0, IPV4_FRAGMENT_OFFSET_OFFSET); + gen.addJumpIfR0AnyBitsSet(IPV4_FRAGMENT_OFFSET_MASK, skipDhcpv4Filter); + // Check it's addressed to DHCP client port. + gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT); + gen.addLoad16Indexed(Register.R0, UDP_DESTINATION_PORT_OFFSET); + gen.addJumpIfR0NotEquals(DHCP_CLIENT_PORT, skipDhcpv4Filter); + // Check it's DHCP to our MAC address. + gen.addLoadImmediate(Register.R0, DHCP_CLIENT_MAC_OFFSET); + // NOTE: Relies on R1 containing IPv4 header offset. + gen.addAddR1(); + gen.addJumpIfBytesAtR0NotEqual(mHardwareAddress, skipDhcpv4Filter); + maybeSetupCounter(gen, Counter.PASSED_DHCP); + gen.addJump(mCountAndPassLabel); + + // Drop all multicasts/broadcasts. + gen.defineLabel(skipDhcpv4Filter); + + // If IPv4 destination address is in multicast range, drop. + gen.addLoad8(Register.R0, IPV4_DEST_ADDR_OFFSET); + gen.addAnd(0xf0); + maybeSetupCounter(gen, Counter.DROPPED_IPV4_MULTICAST); + gen.addJumpIfR0Equals(0xe0, mCountAndDropLabel); + + // If IPv4 broadcast packet, drop regardless of L2 (b/30231088). + maybeSetupCounter(gen, Counter.DROPPED_IPV4_BROADCAST_ADDR); + gen.addLoad32(Register.R0, IPV4_DEST_ADDR_OFFSET); + gen.addJumpIfR0Equals(IPV4_BROADCAST_ADDRESS, mCountAndDropLabel); + if (mIPv4Address != null && mIPv4PrefixLength < 31) { + maybeSetupCounter(gen, Counter.DROPPED_IPV4_BROADCAST_NET); + int broadcastAddr = ipv4BroadcastAddress(mIPv4Address, mIPv4PrefixLength); + gen.addJumpIfR0Equals(broadcastAddr, mCountAndDropLabel); + } + + // If any TCP keepalive filter matches, drop + generateV4KeepaliveFilters(gen); + + // If any NAT-T keepalive filter matches, drop + generateV4NattKeepaliveFilters(gen); + + // Otherwise, this is an IPv4 unicast, pass + // If L2 broadcast packet, drop. + // TODO: can we invert this condition to fall through to the common pass case below? + maybeSetupCounter(gen, Counter.PASSED_IPV4_UNICAST); + gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET); + gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel); + maybeSetupCounter(gen, Counter.DROPPED_IPV4_L2_BROADCAST); + gen.addJump(mCountAndDropLabel); + } else { + generateV4KeepaliveFilters(gen); + generateV4NattKeepaliveFilters(gen); + } + + // Otherwise, pass + maybeSetupCounter(gen, Counter.PASSED_IPV4); + gen.addJump(mCountAndPassLabel); + } + + private void generateKeepaliveFilters(ApfGenerator gen, Class<?> filterType, int proto, + int offset, String label) throws IllegalInstructionException { + final boolean haveKeepaliveResponses = CollectionUtils.any(mKeepalivePackets, + ack -> filterType.isInstance(ack)); + + // If no keepalive packets of this type + if (!haveKeepaliveResponses) return; + + // If not the right proto, skip keepalive filters + gen.addLoad8(Register.R0, offset); + gen.addJumpIfR0NotEquals(proto, label); + + // Drop Keepalive responses + for (int i = 0; i < mKeepalivePackets.size(); ++i) { + final KeepalivePacket response = mKeepalivePackets.valueAt(i); + if (filterType.isInstance(response)) response.generateFilterLocked(gen); + } + + gen.defineLabel(label); + } + + private void generateV4KeepaliveFilters(ApfGenerator gen) throws IllegalInstructionException { + generateKeepaliveFilters(gen, TcpKeepaliveAckV4.class, IPPROTO_TCP, IPV4_PROTOCOL_OFFSET, + "skip_v4_keepalive_filter"); + } + + private void generateV4NattKeepaliveFilters(ApfGenerator gen) + throws IllegalInstructionException { + generateKeepaliveFilters(gen, NattKeepaliveResponse.class, + IPPROTO_UDP, IPV4_PROTOCOL_OFFSET, "skip_v4_nattkeepalive_filter"); + } + + /** + * Generate filter code to process IPv6 packets. Execution of this code ends in either the + * DROP_LABEL or PASS_LABEL, or falls off the end for ICMPv6 packets. + * Preconditions: + * - Packet being filtered is IPv6 + */ + @GuardedBy("this") + private void generateIPv6FilterLocked(ApfGenerator gen) throws IllegalInstructionException { + // Here's a basic summary of what the IPv6 filter program does: + // + // if there is a hop-by-hop option present (e.g. MLD query) + // pass + // if we're dropping multicast + // if it's not IPCMv6 or it's ICMPv6 but we're in doze mode: + // if it's multicast: + // drop + // pass + // if it's ICMPv6 RS to any: + // drop + // if it's ICMPv6 NA to anything in ff02::/120 + // drop + // if keepalive ack + // drop + + gen.addLoad8(Register.R0, IPV6_NEXT_HEADER_OFFSET); + + // MLD packets set the router-alert hop-by-hop option. + // TODO: be smarter about not blindly passing every packet with HBH options. + gen.addJumpIfR0Equals(IPPROTO_HOPOPTS, mCountAndPassLabel); + + // Drop multicast if the multicast filter is enabled. + if (mMulticastFilter) { + final String skipIPv6MulticastFilterLabel = "skipIPv6MulticastFilter"; + final String dropAllIPv6MulticastsLabel = "dropAllIPv6Multicast"; + + // While in doze mode, drop ICMPv6 multicast pings, let the others pass. + // While awake, let all ICMPv6 multicasts through. + if (mInDozeMode) { + // Not ICMPv6? -> Proceed to multicast filtering + gen.addJumpIfR0NotEquals(IPPROTO_ICMPV6, dropAllIPv6MulticastsLabel); + + // ICMPv6 but not ECHO? -> Skip the multicast filter. + // (ICMPv6 ECHO requests will go through the multicast filter below). + gen.addLoad8(Register.R0, ICMP6_TYPE_OFFSET); + gen.addJumpIfR0NotEquals(ICMPV6_ECHO_REQUEST_TYPE, skipIPv6MulticastFilterLabel); + } else { + gen.addJumpIfR0Equals(IPPROTO_ICMPV6, skipIPv6MulticastFilterLabel); + } + + // Drop all other packets sent to ff00::/8 (multicast prefix). + gen.defineLabel(dropAllIPv6MulticastsLabel); + maybeSetupCounter(gen, Counter.DROPPED_IPV6_NON_ICMP_MULTICAST); + gen.addLoad8(Register.R0, IPV6_DEST_ADDR_OFFSET); + gen.addJumpIfR0Equals(0xff, mCountAndDropLabel); + // If any keepalive filter matches, drop + generateV6KeepaliveFilters(gen); + // Not multicast. Pass. + maybeSetupCounter(gen, Counter.PASSED_IPV6_UNICAST_NON_ICMP); + gen.addJump(mCountAndPassLabel); + gen.defineLabel(skipIPv6MulticastFilterLabel); + } else { + generateV6KeepaliveFilters(gen); + // If not ICMPv6, pass. + maybeSetupCounter(gen, Counter.PASSED_IPV6_NON_ICMP); + gen.addJumpIfR0NotEquals(IPPROTO_ICMPV6, mCountAndPassLabel); + } + + // If we got this far, the packet is ICMPv6. Drop some specific types. + + // Add unsolicited multicast neighbor announcements filter + String skipUnsolicitedMulticastNALabel = "skipUnsolicitedMulticastNA"; + gen.addLoad8(Register.R0, ICMP6_TYPE_OFFSET); + // Drop all router solicitations (b/32833400) + maybeSetupCounter(gen, Counter.DROPPED_IPV6_ROUTER_SOLICITATION); + gen.addJumpIfR0Equals(ICMPV6_ROUTER_SOLICITATION, mCountAndDropLabel); + // If not neighbor announcements, skip filter. + gen.addJumpIfR0NotEquals(ICMPV6_NEIGHBOR_ADVERTISEMENT, skipUnsolicitedMulticastNALabel); + // Drop all multicast NA to ff02::/120. + // This is a way to cover ff02::1 and ff02::2 with a single JNEBS. + // TODO: Drop only if they don't contain the address of on-link neighbours. + final byte[] unsolicitedNaDropPrefix = Arrays.copyOf(IPV6_ALL_NODES_ADDRESS, 15); + gen.addLoadImmediate(Register.R0, IPV6_DEST_ADDR_OFFSET); + gen.addJumpIfBytesAtR0NotEqual(unsolicitedNaDropPrefix, skipUnsolicitedMulticastNALabel); + + maybeSetupCounter(gen, Counter.DROPPED_IPV6_MULTICAST_NA); + gen.addJump(mCountAndDropLabel); + gen.defineLabel(skipUnsolicitedMulticastNALabel); + } + + /** Encodes qname in TLV pattern. */ + @VisibleForTesting + public static byte[] encodeQname(String[] labels) { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + for (String label : labels) { + byte[] labelBytes = label.getBytes(StandardCharsets.UTF_8); + out.write(labelBytes.length); + out.write(labelBytes, 0, labelBytes.length); + } + out.write(0); + return out.toByteArray(); + } + + /** + * 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(ApfGenerator gen) + throws IllegalInstructionException { + final String skipMdnsv4Filter = "skip_mdns_v4_filter"; + final String skipMdnsFilter = "skip_mdns_filter"; + final String checkMdnsUdpPort = "check_mdns_udp_port"; + final String mDnsAcceptPacket = "mdns_accept_packet"; + final String mDnsDropPacket = "mdns_drop_packet"; + + // Only turn on the filter if multicast filter is on and the qname allowlist is non-empty. + if (!mMulticastFilter || mMdnsAllowList.isEmpty()) { + return; + } + + // Here's a basic summary of what the mDNS filter program does: + // + // if it is a multicast mDNS packet + // if QDCOUNT != 1 + // pass + // else if the QNAME is in the allowlist + // pass + // else: + // drop + // + // A packet is considered as a multicast mDNS packet if it matches all the following + // conditions + // 1. its destination MAC address matches 01:00:5E:00:00:FB or 33:33:00:00:00:FB, for + // v4 and v6 respectively. + // 2. it is an IPv4/IPv6 packet + // 3. it is a UDP packet with port 5353 + + // Check it's L2 mDNS multicast address. + gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET); + gen.addJumpIfBytesAtR0NotEqual(ETH_MULTICAST_MDNS_V4_MAC_ADDRESS, + skipMdnsv4Filter); + + // Checks it's IPv4. + gen.addLoad16(Register.R0, ETH_ETHERTYPE_OFFSET); + gen.addJumpIfR0NotEquals(ETH_P_IP, skipMdnsFilter); + + // Checks it's UDP. + gen.addLoad8(Register.R0, IPV4_PROTOCOL_OFFSET); + gen.addJumpIfR0NotEquals(IPPROTO_UDP, skipMdnsFilter); + // Set R1 to IPv4 header. + gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT); + gen.addJump(checkMdnsUdpPort); + + gen.defineLabel(skipMdnsv4Filter); + + // Checks it's L2 mDNS multicast address. + // Relies on R0 containing the ethernet destination mac address offset. + gen.addJumpIfBytesAtR0NotEqual(ETH_MULTICAST_MDNS_V6_MAC_ADDRESS, skipMdnsFilter); + + // Checks it's IPv6. + gen.addLoad16(Register.R0, ETH_ETHERTYPE_OFFSET); + gen.addJumpIfR0NotEquals(ETH_P_IPV6, skipMdnsFilter); + + // Checks it's UDP. + gen.addLoad8(Register.R0, IPV6_NEXT_HEADER_OFFSET); + gen.addJumpIfR0NotEquals(IPPROTO_UDP, skipMdnsFilter); + + // Set R1 to IPv6 header. + gen.addLoadImmediate(Register.R1, IPV6_HEADER_LEN); + + // Checks it's mDNS UDP port + gen.defineLabel(checkMdnsUdpPort); + gen.addLoad16Indexed(Register.R0, UDP_DESTINATION_PORT_OFFSET); + gen.addJumpIfR0NotEquals(MDNS_PORT, skipMdnsFilter); + + gen.addLoad16Indexed(Register.R0, MDNS_QDCOUNT_OFFSET); + // If QDCOUNT != 1, pass the packet + gen.addJumpIfR0NotEquals(1, mDnsAcceptPacket); + + // If QDCOUNT == 1, matches the QNAME with allowlist. + // Load offset for the first QNAME. + gen.addLoadImmediate(Register.R0, MDNS_QNAME_OFFSET); + gen.addAddR1(); + + // Check first QNAME against allowlist + for (int i = 0; i < mMdnsAllowList.size(); ++i) { + final String mDnsNextAllowedQnameCheck = "mdns_next_allowed_qname_check" + i; + final byte[] encodedQname = encodeQname(mMdnsAllowList.get(i)); + gen.addJumpIfBytesAtR0NotEqual(encodedQname, mDnsNextAllowedQnameCheck); + // QNAME matched + gen.addJump(mDnsAcceptPacket); + // QNAME not matched + gen.defineLabel(mDnsNextAllowedQnameCheck); + } + // If QNAME doesn't match any entries in allowlist, drop the packet. + gen.defineLabel(mDnsDropPacket); + maybeSetupCounter(gen, Counter.DROPPED_MDNS); + gen.addJump(mCountAndDropLabel); + + gen.defineLabel(mDnsAcceptPacket); + maybeSetupCounter(gen, Counter.PASSED_MDNS); + gen.addJump(mCountAndPassLabel); + + + gen.defineLabel(skipMdnsFilter); + } + + + private void generateV6KeepaliveFilters(ApfGenerator gen) throws IllegalInstructionException { + generateKeepaliveFilters(gen, TcpKeepaliveAckV6.class, IPPROTO_TCP, IPV6_NEXT_HEADER_OFFSET, + "skip_v6_keepalive_filter"); + } + + /** + * Begin generating an APF program to: + * <ul> + * <li>Drop/Pass 802.3 frames (based on policy) + * <li>Drop packets with EtherType within the Black List + * <li>Drop ARP requests not for us, if mIPv4Address is set, + * <li>Drop IPv4 broadcast packets, except DHCP destined to our MAC, + * <li>Drop IPv4 multicast packets, if mMulticastFilter, + * <li>Pass all other IPv4 packets, + * <li>Drop all broadcast non-IP non-ARP packets. + * <li>Pass all non-ICMPv6 IPv6 packets, + * <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>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") + protected ApfGenerator emitPrologueLocked() throws IllegalInstructionException { + // This is guaranteed to succeed because of the check in maybeCreate. + ApfGenerator gen = new ApfGenerator(mApfCapabilities.apfVersionSupported); + + if (mApfCapabilities.hasDataAccess()) { + // Increment TOTAL_PACKETS + maybeSetupCounter(gen, Counter.TOTAL_PACKETS); + gen.addLoadData(Register.R0, 0); // load counter + gen.addAdd(1); + gen.addStoreData(Register.R0, 0); // write-back counter + } + + // Here's a basic summary of what the initial program does: + // + // if it's a 802.3 Frame (ethtype < 0x0600): + // drop or pass based on configurations + // if it has a ether-type that belongs to the black list + // drop + // if it's ARP: + // insert ARP filter to drop or pass these appropriately + // if it's IPv4: + // insert IPv4 filter to drop or pass these appropriately + // if it's not IPv6: + // if it's broadcast: + // drop + // pass + // insert IPv6 filter to drop, pass, or fall off the end for ICMPv6 packets + + gen.addLoad16(Register.R0, ETH_ETHERTYPE_OFFSET); + + if (mDrop802_3Frames) { + // drop 802.3 frames (ethtype < 0x0600) + maybeSetupCounter(gen, Counter.DROPPED_802_3_FRAME); + gen.addJumpIfR0LessThan(ETH_TYPE_MIN, mCountAndDropLabel); + } + + // Handle ether-type black list + maybeSetupCounter(gen, Counter.DROPPED_ETHERTYPE_DENYLISTED); + for (int p : mEthTypeBlackList) { + gen.addJumpIfR0Equals(p, mCountAndDropLabel); + } + + // Add ARP filters: + String skipArpFiltersLabel = "skipArpFilters"; + gen.addJumpIfR0NotEquals(ETH_P_ARP, skipArpFiltersLabel); + generateArpFilterLocked(gen); + gen.defineLabel(skipArpFiltersLabel); + + // Add mDNS filter: + generateMdnsFilterLocked(gen); + gen.addLoad16(Register.R0, ETH_ETHERTYPE_OFFSET); + + // Add IPv4 filters: + String skipIPv4FiltersLabel = "skipIPv4Filters"; + gen.addJumpIfR0NotEquals(ETH_P_IP, skipIPv4FiltersLabel); + generateIPv4FilterLocked(gen); + gen.defineLabel(skipIPv4FiltersLabel); + + // Check for IPv6: + // NOTE: Relies on R0 containing ethertype. This is safe because if we got here, we did + // not execute the IPv4 filter, since this filter do not fall through, but either drop or + // pass. + String ipv6FilterLabel = "IPv6Filters"; + gen.addJumpIfR0Equals(ETH_P_IPV6, ipv6FilterLabel); + + // Drop non-IP non-ARP broadcasts, pass the rest + gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET); + maybeSetupCounter(gen, Counter.PASSED_NON_IP_UNICAST); + gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel); + maybeSetupCounter(gen, Counter.DROPPED_ETH_BROADCAST); + gen.addJump(mCountAndDropLabel); + + // Add IPv6 filters: + gen.defineLabel(ipv6FilterLabel); + generateIPv6FilterLocked(gen); + return gen; + } + + /** + * Append packet counting epilogue to the APF program. + * + * 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(ApfGenerator gen) throws IllegalInstructionException { + // If APFv4 is unsupported, no epilogue is necessary: if execution reached this far, it + // will just fall-through to the PASS label. + if (!mApfCapabilities.hasDataAccess()) return; + + // Execution will reach the bottom of the program if none of the filters match, + // which will pass the packet to the application processor. + maybeSetupCounter(gen, Counter.PASSED_IPV6_ICMP); + + // Append the count & pass trampoline, which increments the counter at the data address + // pointed to by R1, then jumps to the pass label. This saves a few bytes over inserting + // the entire sequence inline for every counter. + gen.defineLabel(mCountAndPassLabel); + gen.addLoadData(Register.R0, 0); // R0 = *(R1 + 0) + gen.addAdd(1); // R0++ + gen.addStoreData(Register.R0, 0); // *(R1 + 0) = R0 + gen.addJump(gen.PASS_LABEL); + + // Same as above for the count & drop trampoline. + gen.defineLabel(mCountAndDropLabel); + gen.addLoadData(Register.R0, 0); // R0 = *(R1 + 0) + gen.addAdd(1); // R0++ + gen.addStoreData(Register.R0, 0); // *(R1 + 0) = R0 + gen.addJump(gen.DROP_LABEL); + } + + /** + * Generate and install a new filter program. + */ + @GuardedBy("this") + @VisibleForTesting + public void installNewProgramLocked() { + purgeExpiredRasLocked(); + ArrayList<Ra> rasToFilter = new ArrayList<>(); + final byte[] program; + long programMinLifetime = Long.MAX_VALUE; + long maximumApfProgramSize = mApfCapabilities.maximumApfProgramSize; + if (mApfCapabilities.hasDataAccess()) { + // Reserve space for the counters. + maximumApfProgramSize -= Counter.totalSize(); + } + + mProgramBaseTime = currentTimeSeconds(); + try { + // Step 1: Determine how many RA filters we can fit in the program. + ApfGenerator gen = emitPrologueLocked(); + + // The epilogue normally goes after the RA filters, but add it early to include its + // length when estimating the total. + emitEpilogue(gen); + + // Can't fit the program even without any RA filters? + if (gen.programLengthOverEstimate() > maximumApfProgramSize) { + Log.e(TAG, "Program exceeds maximum size " + maximumApfProgramSize); + sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_OVER_SIZE_FAILURE); + return; + } + + for (Ra ra : mRas) { + if (!ra.shouldFilter()) continue; + ra.generateFilterLocked(gen); + // Stop if we get too big. + if (gen.programLengthOverEstimate() > maximumApfProgramSize) { + if (VDBG) Log.d(TAG, "Past maximum program size, skipping RAs"); + sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_OVER_SIZE_FAILURE); + break; + } + + rasToFilter.add(ra); + } + + // Step 2: Actually generate the program + gen = emitPrologueLocked(); + for (Ra ra : rasToFilter) { + programMinLifetime = Math.min(programMinLifetime, ra.generateFilterLocked(gen)); + } + emitEpilogue(gen); + program = gen.generate(); + } catch (IllegalInstructionException|IllegalStateException e) { + Log.e(TAG, "Failed to generate APF program.", e); + sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_GENERATE_FILTER_EXCEPTION); + return; + } + if (!mIpClientCallback.installPacketFilter(program)) { + sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE); + } + mLastTimeInstalledProgram = mProgramBaseTime; + mLastInstalledProgramMinLifetime = programMinLifetime; + mLastInstalledProgram = program; + mNumProgramUpdates++; + mMaxProgramSize = Math.max(mMaxProgramSize, program.length); + + if (VDBG) { + hexDump("Installing filter: ", program, program.length); + } + logApfProgramEventLocked(mProgramBaseTime); + mLastInstallEvent = new ApfProgramEvent.Builder() + .setLifetime(programMinLifetime) + .setFilteredRas(rasToFilter.size()) + .setCurrentRas(mRas.size()) + .setProgramLength(program.length) + .setFlags(mIPv4Address != null, mMulticastFilter); + } + + @GuardedBy("this") + private void logApfProgramEventLocked(long now) { + if (mLastInstallEvent == null) { + return; + } + ApfProgramEvent.Builder ev = mLastInstallEvent; + mLastInstallEvent = null; + final long actualLifetime = now - mLastTimeInstalledProgram; + ev.setActualLifetime(actualLifetime); + if (actualLifetime < APF_PROGRAM_EVENT_LIFETIME_THRESHOLD) { + return; + } + mMetricsLog.log(ev.build()); + } + + /** + * Returns {@code true} if a new program should be installed because the current one dies soon. + */ + private boolean shouldInstallnewProgram() { + long expiry = mLastTimeInstalledProgram + mLastInstalledProgramMinLifetime; + return expiry < currentTimeSeconds() + MAX_PROGRAM_LIFETIME_WORTH_REFRESHING; + } + + private void hexDump(String msg, byte[] packet, int length) { + log(msg + HexDump.toHexString(packet, 0, length, false /* lowercase */)); + } + + @GuardedBy("this") + private void purgeExpiredRasLocked() { + for (int i = 0; i < mRas.size();) { + if (mRas.get(i).isExpired()) { + log("Expiring " + mRas.get(i)); + mRas.remove(i); + } else { + i++; + } + } + } + + // Get the minimum value excludes zero. This is used for calculating the lowest lifetime values + // in RA packets. Zero lifetimes are excluded because we want to detect whether there is any + // unusually small lifetimes but zero lifetime is actually valid (cease to be a default router + // or the option is no longer be used). Number of zero lifetime RAs is collected in a different + // Metrics. + private long getMinForPositiveValue(long oldMinValue, long value) { + if (value < 1) return oldMinValue; + return Math.min(oldMinValue, value); + } + + private int getMinForPositiveValue(int oldMinValue, int value) { + return (int) getMinForPositiveValue((long) oldMinValue, (long) value); + } + + /** + * Process an RA packet, updating the list of known RAs and installing a new APF program + * if the current APF program should be updated. + * @return a ProcessRaResult enum describing what action was performed. + */ + @VisibleForTesting + public synchronized ProcessRaResult processRa(byte[] packet, int length) { + if (VDBG) hexDump("Read packet = ", packet, length); + + // Have we seen this RA before? + for (int i = 0; i < mRas.size(); i++) { + Ra ra = mRas.get(i); + if (ra.matches(packet, length)) { + if (VDBG) log("matched RA " + ra); + // Update lifetimes. + ra.mLastSeen = currentTimeSeconds(); + ra.seenCount++; + + // Keep mRas in LRU order so as to prioritize generating filters for recently seen + // RAs. LRU prioritizes this because RA filters are generated in order from mRas + // until the filter program exceeds the maximum filter program size allowed by the + // chipset, so RAs appearing earlier in mRas are more likely to make it into the + // filter program. + // TODO: consider sorting the RAs in order of increasing expiry time as well. + // Swap to front of array. + mRas.add(0, mRas.remove(i)); + + // If the current program doesn't expire for a while, don't update. + if (shouldInstallnewProgram()) { + installNewProgramLocked(); + return ProcessRaResult.UPDATE_EXPIRY; + } + return ProcessRaResult.MATCH; + } + } + purgeExpiredRasLocked(); + + mMaxDistinctRas = Math.max(mMaxDistinctRas, mRas.size() + 1); + + // TODO: figure out how to proceed when we've received more than MAX_RAS RAs. + if (mRas.size() >= MAX_RAS) { + return ProcessRaResult.DROPPED; + } + final Ra ra; + try { + ra = new Ra(packet, length); + } catch (Exception e) { + Log.e(TAG, "Error parsing RA", e); + mNumParseErrorRas++; + return ProcessRaResult.PARSE_ERROR; + } + + // Update info for Metrics + mLowestRouterLifetimeSeconds = getMinForPositiveValue( + mLowestRouterLifetimeSeconds, ra.routerLifetime()); + mLowestPioValidLifetimeSeconds = getMinForPositiveValue( + mLowestPioValidLifetimeSeconds, ra.minPioValidLifetime()); + mLowestRioRouteLifetimeSeconds = getMinForPositiveValue( + mLowestRioRouteLifetimeSeconds, ra.minRioRouteLifetime()); + mLowestRdnssLifetimeSeconds = getMinForPositiveValue( + mLowestRdnssLifetimeSeconds, ra.minRdnssLifetime()); + + // Ignore 0 lifetime RAs. + if (ra.isExpired()) { + mNumZeroLifetimeRas++; + return ProcessRaResult.ZERO_LIFETIME; + } + log("Adding " + ra); + mRas.add(ra); + installNewProgramLocked(); + return ProcessRaResult.UPDATE_NEW_RA; + } + + /** + * Create an {@link LegacyApfFilter} if {@code apfCapabilities} indicates support for packet + * filtering using APF programs. + */ + public static LegacyApfFilter maybeCreate(Context context, ApfFilter.ApfConfiguration config, + InterfaceParams ifParams, IpClientCallbacksWrapper ipClientCallback, + NetworkQuirkMetrics networkQuirkMetrics) { + if (context == null || config == null || ifParams == null) return null; + ApfCapabilities apfCapabilities = config.apfCapabilities; + if (apfCapabilities == null) return null; + if (apfCapabilities.apfVersionSupported == 0) return null; + if (apfCapabilities.maximumApfProgramSize < 512) { + Log.e(TAG, "Unacceptably small APF limit: " + apfCapabilities.maximumApfProgramSize); + return null; + } + // For now only support generating programs for Ethernet frames. If this restriction is + // lifted: + // 1. the program generator will need its offsets adjusted. + // 2. the packet filter attached to our packet socket will need its offset adjusted. + if (apfCapabilities.apfPacketFormat != ARPHRD_ETHER) return null; + if (!ApfGenerator.supportsVersion(apfCapabilities.apfVersionSupported)) { + Log.e(TAG, "Unsupported APF version: " + apfCapabilities.apfVersionSupported); + return null; + } + + return new LegacyApfFilter(context, config, ifParams, ipClientCallback, + new IpConnectivityLog(), networkQuirkMetrics); + } + + private synchronized void collectAndSendMetrics() { + if (mIpClientRaInfoMetrics == null || mApfSessionInfoMetrics == null) return; + final long sessionDurationMs = mClock.elapsedRealtime() - mSessionStartMs; + if (sessionDurationMs < mMinMetricsSessionDurationMs) return; + + // Collect and send IpClientRaInfoMetrics. + mIpClientRaInfoMetrics.setMaxNumberOfDistinctRas(mMaxDistinctRas); + mIpClientRaInfoMetrics.setNumberOfZeroLifetimeRas(mNumZeroLifetimeRas); + mIpClientRaInfoMetrics.setNumberOfParsingErrorRas(mNumParseErrorRas); + mIpClientRaInfoMetrics.setLowestRouterLifetimeSeconds(mLowestRouterLifetimeSeconds); + mIpClientRaInfoMetrics.setLowestPioValidLifetimeSeconds(mLowestPioValidLifetimeSeconds); + mIpClientRaInfoMetrics.setLowestRioRouteLifetimeSeconds(mLowestRioRouteLifetimeSeconds); + mIpClientRaInfoMetrics.setLowestRdnssLifetimeSeconds(mLowestRdnssLifetimeSeconds); + mIpClientRaInfoMetrics.statsWrite(); + + // Collect and send ApfSessionInfoMetrics. + mApfSessionInfoMetrics.setVersion(mApfCapabilities.apfVersionSupported); + mApfSessionInfoMetrics.setMemorySize(mApfCapabilities.maximumApfProgramSize); + mApfSessionInfoMetrics.setApfSessionDurationSeconds( + (int) (sessionDurationMs / DateUtils.SECOND_IN_MILLIS)); + mApfSessionInfoMetrics.setNumOfTimesApfProgramUpdated(mNumProgramUpdates); + mApfSessionInfoMetrics.setMaxProgramSize(mMaxProgramSize); + for (Map.Entry<Counter, Long> entry : mApfCounterTracker.getCounters().entrySet()) { + if (entry.getValue() > 0) { + mApfSessionInfoMetrics.addApfCounter(entry.getKey(), entry.getValue()); + } + } + mApfSessionInfoMetrics.statsWrite(); + } + + public synchronized void shutdown() { + collectAndSendMetrics(); + if (mReceiveThread != null) { + log("shutting down"); + mReceiveThread.halt(); // Also closes socket. + mReceiveThread = null; + } + mRas.clear(); + mContext.unregisterReceiver(mDeviceIdleReceiver); + } + + public synchronized void setMulticastFilter(boolean isEnabled) { + if (mMulticastFilter == isEnabled) return; + mMulticastFilter = isEnabled; + if (!isEnabled) { + mNumProgramUpdatesAllowingMulticast++; + } + installNewProgramLocked(); + } + + /** Adds qname to the mDNS allowlist */ + public synchronized void addToMdnsAllowList(String[] labels) { + mMdnsAllowList.add(labels); + if (mMulticastFilter) { + installNewProgramLocked(); + } + } + + /** Removes qname from the mDNS allowlist */ + public synchronized void removeFromAllowList(String[] labels) { + mMdnsAllowList.removeIf(e -> Arrays.equals(labels, e)); + if (mMulticastFilter) { + installNewProgramLocked(); + } + } + + @VisibleForTesting + public synchronized void setDozeMode(boolean isEnabled) { + if (mInDozeMode == isEnabled) return; + mInDozeMode = isEnabled; + installNewProgramLocked(); + } + + /** Find the single IPv4 LinkAddress if there is one, otherwise return null. */ + private static LinkAddress findIPv4LinkAddress(LinkProperties lp) { + LinkAddress ipv4Address = null; + for (LinkAddress address : lp.getLinkAddresses()) { + if (!(address.getAddress() instanceof Inet4Address)) { + continue; + } + if (ipv4Address != null && !ipv4Address.isSameAddressAs(address)) { + // More than one IPv4 address, abort. + return null; + } + ipv4Address = address; + } + return ipv4Address; + } + + public synchronized void setLinkProperties(LinkProperties lp) { + // NOTE: Do not keep a copy of LinkProperties as it would further duplicate state. + final LinkAddress ipv4Address = findIPv4LinkAddress(lp); + final byte[] addr = (ipv4Address != null) ? ipv4Address.getAddress().getAddress() : null; + final int prefix = (ipv4Address != null) ? ipv4Address.getPrefixLength() : 0; + if ((prefix == mIPv4PrefixLength) && Arrays.equals(addr, mIPv4Address)) { + return; + } + mIPv4Address = addr; + mIPv4PrefixLength = prefix; + installNewProgramLocked(); + } + + /** + * Add TCP keepalive ack packet filter. + * This will add a filter to drop acks to the keepalive packet passed as an argument. + * + * @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, + final TcpKeepalivePacketDataParcelable sentKeepalivePacket) { + log("Adding keepalive ack(" + slot + ")"); + if (null != mKeepalivePackets.get(slot)) { + throw new IllegalArgumentException("Keepalive slot " + slot + " is occupied"); + } + final int ipVersion = sentKeepalivePacket.srcAddress.length == 4 ? 4 : 6; + mKeepalivePackets.put(slot, (ipVersion == 4) + ? new TcpKeepaliveAckV4(sentKeepalivePacket) + : new TcpKeepaliveAckV6(sentKeepalivePacket)); + installNewProgramLocked(); + } + + /** + * Add NAT-T keepalive packet filter. + * This will add a filter to drop NAT-T keepalive packet which is passed as an argument. + * + * @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, + final NattKeepalivePacketDataParcelable sentKeepalivePacket) { + log("Adding NAT-T keepalive packet(" + slot + ")"); + if (null != mKeepalivePackets.get(slot)) { + throw new IllegalArgumentException("NAT-T Keepalive slot " + slot + " is occupied"); + } + + // TODO : update ApfFilter to support dropping v6 keepalives + if (sentKeepalivePacket.srcAddress.length != 4) { + return; + } + + mKeepalivePackets.put(slot, new NattKeepaliveResponse(sentKeepalivePacket)); + installNewProgramLocked(); + } + + /** + * Remove keepalive packet filter. + * + * @param slot The index used to access the filter. + */ + public synchronized void removeKeepalivePacketFilter(int slot) { + log("Removing keepalive packet(" + slot + ")"); + mKeepalivePackets.remove(slot); + installNewProgramLocked(); + } + + public synchronized void dump(IndentingPrintWriter pw) { + pw.println("Capabilities: " + mApfCapabilities); + pw.println("Receive thread: " + (mReceiveThread != null ? "RUNNING" : "STOPPED")); + pw.println("Multicast: " + (mMulticastFilter ? "DROP" : "ALLOW")); + pw.println("Minimum RDNSS lifetime: " + mMinRdnssLifetimeSec); + try { + pw.println("IPv4 address: " + InetAddress.getByAddress(mIPv4Address).getHostAddress()); + } catch (UnknownHostException|NullPointerException e) {} + + if (mLastTimeInstalledProgram == 0) { + pw.println("No program installed."); + return; + } + pw.println("Program updates: " + mNumProgramUpdates); + pw.println(String.format( + "Last program length %d, installed %ds ago, lifetime %ds", + mLastInstalledProgram.length, currentTimeSeconds() - mLastTimeInstalledProgram, + mLastInstalledProgramMinLifetime)); + + pw.print("Denylisted Ethertypes:"); + for (int p : mEthTypeBlackList) { + pw.print(String.format(" %04x", p)); + } + pw.println(); + pw.println("RA filters:"); + pw.increaseIndent(); + for (Ra ra: mRas) { + pw.println(ra); + pw.increaseIndent(); + pw.println(String.format( + "Seen: %d, last %ds ago", ra.seenCount, currentTimeSeconds() - ra.mLastSeen)); + if (DBG) { + pw.println("Last match:"); + pw.increaseIndent(); + pw.println(ra.getLastMatchingPacket()); + pw.decreaseIndent(); + } + pw.decreaseIndent(); + } + pw.decreaseIndent(); + + pw.println("TCP Keepalive filters:"); + pw.increaseIndent(); + for (int i = 0; i < mKeepalivePackets.size(); ++i) { + final KeepalivePacket keepalivePacket = mKeepalivePackets.valueAt(i); + if (keepalivePacket instanceof TcpKeepaliveAck) { + pw.print("Slot "); + pw.print(mKeepalivePackets.keyAt(i)); + pw.print(": "); + pw.println(keepalivePacket); + } + } + pw.decreaseIndent(); + + pw.println("NAT-T Keepalive filters:"); + pw.increaseIndent(); + for (int i = 0; i < mKeepalivePackets.size(); ++i) { + final KeepalivePacket keepalivePacket = mKeepalivePackets.valueAt(i); + if (keepalivePacket instanceof NattKeepaliveResponse) { + pw.print("Slot "); + pw.print(mKeepalivePackets.keyAt(i)); + pw.print(": "); + pw.println(keepalivePacket); + } + } + pw.decreaseIndent(); + + if (DBG) { + pw.println("Last program:"); + pw.increaseIndent(); + pw.println(HexDump.toHexString(mLastInstalledProgram, false /* lowercase */)); + pw.decreaseIndent(); + } + + pw.println("APF packet counters: "); + pw.increaseIndent(); + if (!mApfCapabilities.hasDataAccess()) { + pw.println("APF counters not supported"); + } else if (mDataSnapshot == null) { + pw.println("No last snapshot."); + } else { + try { + Counter[] counters = Counter.class.getEnumConstants(); + 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); + } + + // If the counter's value decreases, it may have been cleaned up or there may be + // a bug. + if (value < mApfCounterTracker.getCounters().getOrDefault(c, 0L)) { + Log.e(TAG, "Error: Counter value unexpectedly decreased."); + } + } + } catch (ArrayIndexOutOfBoundsException e) { + pw.println("Uh-oh: " + e); + } + if (VDBG) { + pw.println("Raw data dump: "); + pw.println(HexDump.dumpHexString(mDataSnapshot)); + } + } + pw.decreaseIndent(); + } + + // TODO: move to android.net.NetworkUtils + @VisibleForTesting + public static int ipv4BroadcastAddress(byte[] addrBytes, int prefixLength) { + return bytesToBEInt(addrBytes) | (int) (Integer.toUnsignedLong(-1) >>> prefixLength); + } + + private static int uint8(byte b) { + return b & 0xff; + } + + private static int getUint16(ByteBuffer buffer, int position) { + return buffer.getShort(position) & 0xffff; + } + + private static long getUint32(ByteBuffer buffer, int position) { + return Integer.toUnsignedLong(buffer.getInt(position)); + } + + private static int getUint8(ByteBuffer buffer, int position) { + return uint8(buffer.get(position)); + } + + private static int bytesToBEInt(byte[] bytes) { + return (uint8(bytes[0]) << 24) + + (uint8(bytes[1]) << 16) + + (uint8(bytes[2]) << 8) + + (uint8(bytes[3])); + } + + private static byte[] concatArrays(final byte[]... arr) { + int size = 0; + for (byte[] a : arr) { + size += a.length; + } + final byte[] result = new byte[size]; + int offset = 0; + for (byte[] a : arr) { + System.arraycopy(a, 0, result, offset, a.length); + offset += a.length; + } + return result; + } + + private void sendNetworkQuirkMetrics(final NetworkQuirkEvent event) { + if (mNetworkQuirkMetrics == null) return; + mNetworkQuirkMetrics.setEvent(event); + mNetworkQuirkMetrics.statsWrite(); + } +}
diff --git a/src/android/net/dhcp/DhcpAckPacket.java b/src/android/net/dhcp/DhcpAckPacket.java index 225e447..3d20bda 100644 --- a/src/android/net/dhcp/DhcpAckPacket.java +++ b/src/android/net/dhcp/DhcpAckPacket.java
@@ -50,6 +50,7 @@ + ", netmask " + mSubnetMask + ", gateways " + mGateways + dnsServers + ", lease time " + mLeaseTime + + ", domain " + mDomainName + (mIpv6OnlyWaitTime != null ? ", V6ONLY_WAIT " + mIpv6OnlyWaitTime : ""); }
diff --git a/src/android/net/dhcp/DhcpClient.java b/src/android/net/dhcp/DhcpClient.java index 5378294..b0bac04 100644 --- a/src/android/net/dhcp/DhcpClient.java +++ b/src/android/net/dhcp/DhcpClient.java
@@ -20,6 +20,7 @@ import static android.net.dhcp.DhcpPacket.DHCP_CAPTIVE_PORTAL; import static android.net.dhcp.DhcpPacket.DHCP_DNS_SERVER; import static android.net.dhcp.DhcpPacket.DHCP_DOMAIN_NAME; +import static android.net.dhcp.DhcpPacket.DHCP_DOMAIN_SEARCHLIST; import static android.net.dhcp.DhcpPacket.DHCP_IPV6_ONLY_PREFERRED; import static android.net.dhcp.DhcpPacket.DHCP_LEASE_TIME; import static android.net.dhcp.DhcpPacket.DHCP_MTU; @@ -53,7 +54,6 @@ import static com.android.net.module.util.NetworkStackConstants.IPV4_CONFLICT_PROBE_NUM; import static com.android.net.module.util.SocketUtils.closeSocketQuietly; import static com.android.networkstack.util.NetworkStackUtils.DHCP_INIT_REBOOT_VERSION; -import static com.android.networkstack.util.NetworkStackUtils.DHCP_IPV6_ONLY_PREFERRED_VERSION; import static com.android.networkstack.util.NetworkStackUtils.DHCP_IP_CONFLICT_DETECT_VERSION; import static com.android.networkstack.util.NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION; import static com.android.networkstack.util.NetworkStackUtils.DHCP_SLOW_RETRANSMISSION_VERSION; @@ -75,7 +75,6 @@ import android.net.networkstack.aidl.dhcp.DhcpOption; import android.net.util.HostnameTransliterator; import android.net.util.SocketUtils; -import android.os.Build; import android.os.Handler; import android.os.Message; import android.os.PowerManager; @@ -101,11 +100,10 @@ import com.android.net.module.util.InterfaceParams; import com.android.net.module.util.NetworkStackConstants; 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.apishim.common.ShimUtils; -import com.android.networkstack.arp.ArpPacket; import com.android.networkstack.metrics.IpProvisioningMetrics; import com.android.networkstack.util.NetworkStackUtils; @@ -297,20 +295,29 @@ @NonNull private byte[] getRequestedParams() { // Set an initial size large enough for all optional parameters that we might request. - final int numOptionalParams = 2; + // mCreatorId + the size is changed + final int numOptionalParams; + if (mConfiguration.isWifiManagedProfile) { + numOptionalParams = 3 + mConfiguration.options.size(); + } else { + numOptionalParams = 2 + mConfiguration.options.size(); + } + 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); } - if (isIPv6OnlyPreferredModeEnabled()) { - params.write(DHCP_IPV6_ONLY_PREFERRED); - } + params.write(DHCP_IPV6_ONLY_PREFERRED); // Customized DHCP options to be put in PRL. for (DhcpOption option : mConfiguration.options) { if (option.value == null) params.write(option.type); } + // Check if the target network is managed by user. + if (mConfiguration.isWifiManagedProfile) { + params.write(DHCP_DOMAIN_SEARCHLIST); + } return params.toByteArray(); } @@ -446,12 +453,18 @@ /** * Return whether a feature guarded by a feature flag is enabled. - * @see DeviceConfigUtils#isFeatureEnabled(Context, String, String) + * @see DeviceConfigUtils#isNetworkStackFeatureEnabled(Context, String) */ - public boolean isFeatureEnabled(final Context context, final String name, - boolean defaultEnabled) { - return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name, - defaultEnabled); + public boolean isFeatureEnabled(final Context context, final String name) { + return DeviceConfigUtils.isNetworkStackFeatureEnabled(context, name); + } + + /** + * Check whether one specific feature is not disabled. + * @see DeviceConfigUtils#isNetworkStackFeatureNotChickenedOut(Context, String) + */ + public boolean isFeatureNotChickenedOut(final Context context, final String name) { + return DeviceConfigUtils.isNetworkStackFeatureNotChickenedOut(context, name); } /** @@ -543,43 +556,23 @@ /** * check whether or not to support caching the last lease info and INIT-REBOOT state. - * - * INIT-REBOOT state is supported on Android R by default if there is no experiment flag set to - * disable this feature explicitly, meanwhile turning this feature on/off by pushing experiment - * flag makes it possible to do A/B test and metrics collection on both of Android Q and R, but - * it's disabled on Android Q by default. */ public boolean isDhcpLeaseCacheEnabled() { - final boolean defaultEnabled = - ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q); - return mDependencies.isFeatureEnabled(mContext, DHCP_INIT_REBOOT_VERSION, defaultEnabled); + return mDependencies.isFeatureNotChickenedOut(mContext, DHCP_INIT_REBOOT_VERSION); } /** * check whether or not to support DHCP Rapid Commit option. */ public boolean isDhcpRapidCommitEnabled() { - return mDependencies.isFeatureEnabled(mContext, DHCP_RAPID_COMMIT_VERSION, - false /* defaultEnabled */); + return mDependencies.isFeatureEnabled(mContext, DHCP_RAPID_COMMIT_VERSION); } /** * check whether or not to support IP address conflict detection and DHCPDECLINE. */ public boolean isDhcpIpConflictDetectEnabled() { - return mDependencies.isFeatureEnabled(mContext, DHCP_IP_CONFLICT_DETECT_VERSION, - false /* defaultEnabled */); - } - - /** - * check whether or not to support IPv6-only preferred option. - * - * IPv6-only preferred option is enabled by default if there is no experiment flag set to - * disable this feature explicitly. - */ - public boolean isIPv6OnlyPreferredModeEnabled() { - return mDependencies.isFeatureEnabled(mContext, DHCP_IPV6_ONLY_PREFERRED_VERSION, - true /* defaultEnabled */); + return mDependencies.isFeatureEnabled(mContext, DHCP_IP_CONFLICT_DETECT_VERSION); } /** @@ -587,8 +580,7 @@ * suggested in RFC2131 section 4.4.5. */ public boolean isSlowRetransmissionEnabled() { - return mDependencies.isFeatureEnabled(mContext, DHCP_SLOW_RETRANSMISSION_VERSION, - false /* defaultEnabled */); + return mDependencies.isFeatureEnabled(mContext, DHCP_SLOW_RETRANSMISSION_VERSION); } private void recordMetricEnabledFeatures() { @@ -655,7 +647,9 @@ private byte[] getOptionsToSkip() { final ByteArrayOutputStream optionsToSkip = new ByteArrayOutputStream(2); if (!isCapportApiEnabled()) optionsToSkip.write(DHCP_CAPTIVE_PORTAL); - if (!isIPv6OnlyPreferredModeEnabled()) optionsToSkip.write(DHCP_IPV6_ONLY_PREFERRED); + if (!mConfiguration.isWifiManagedProfile) { + optionsToSkip.write(DHCP_DOMAIN_SEARCHLIST); + } return optionsToSkip.toByteArray(); } @@ -1013,12 +1007,15 @@ public final boolean isPreconnectionEnabled; @NonNull public final List<DhcpOption> options; + public final boolean isWifiManagedProfile; public Configuration(@Nullable final String l2Key, final boolean isPreconnectionEnabled, - @NonNull final List<DhcpOption> options) { + @NonNull final List<DhcpOption> options, + final boolean isWifiManagedProfile) { this.l2Key = l2Key; this.isPreconnectionEnabled = isPreconnectionEnabled; this.options = options; + this.isWifiManagedProfile = isWifiManagedProfile; } } @@ -1299,7 +1296,6 @@ } private boolean maybeTransitionToIpv6OnlyWaitState(@NonNull final DhcpPacket packet) { - if (!isIPv6OnlyPreferredModeEnabled()) return false; if (packet.getIpv6OnlyWaitTimeMillis() == DhcpPacket.V6ONLY_PREFERRED_ABSENCE) return false; mIpv6OnlyWaitTimeMs = packet.getIpv6OnlyWaitTimeMillis();
diff --git a/src/android/net/dhcp/DhcpOfferPacket.java b/src/android/net/dhcp/DhcpOfferPacket.java index e3e5d0f..daf520d 100644 --- a/src/android/net/dhcp/DhcpOfferPacket.java +++ b/src/android/net/dhcp/DhcpOfferPacket.java
@@ -48,7 +48,7 @@ } return s + " OFFER, ip " + mYourIp - + ", mask " + mSubnetMask + dnsServers + + ", netmask " + mSubnetMask + dnsServers + ", gateways " + mGateways + ", lease time " + mLeaseTime + ", domain " + mDomainName
diff --git a/src/android/net/dhcp/DhcpPacket.java b/src/android/net/dhcp/DhcpPacket.java index 65145e8..2649851 100644 --- a/src/android/net/dhcp/DhcpPacket.java +++ b/src/android/net/dhcp/DhcpPacket.java
@@ -33,6 +33,7 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import com.android.net.module.util.DomainUtils; import com.android.net.module.util.Inet4AddressUtils; import java.io.UnsupportedEncodingException; @@ -338,6 +339,12 @@ protected String mCaptivePortalUrl; /** + * DHCP Optional Type: Domain Search List, domain suffixes are space separated. + */ + public static final byte DHCP_DOMAIN_SEARCHLIST = (byte) 119; + protected List<String> mDmnSrchList; + + /** * DHCP zero-length option code: pad */ public static final byte DHCP_OPTION_PAD = 0x00; @@ -832,6 +839,7 @@ } } + // The common server TLVs are corresponding to the parameter request list from client. protected void addCommonServerTlvs(ByteBuffer buf) { addTlv(buf, DHCP_LEASE_TIME, mLeaseTime); if (mLeaseTime != null && mLeaseTime != INFINITE_LEASE) { @@ -854,6 +862,15 @@ if (mIpv6OnlyWaitTime != null) { addTlv(buf, DHCP_IPV6_ONLY_PREFERRED, (int) Integer.toUnsignedLong(mIpv6OnlyWaitTime)); } + if (mDmnSrchList != null && mDmnSrchList.size() > 0) { + // domain search list string is space separated. + String[] searchList = new String[mDmnSrchList.size()]; + for (int i = 0; i < mDmnSrchList.size(); i++) { + searchList[i] = mDmnSrchList.get(i); + } + final byte[] domains = DomainUtils.encode(searchList, true /* compression */); + addTlv(buf, DHCP_DOMAIN_SEARCHLIST, domains); + } addTlv(buf, DHCP_CAPTIVE_PORTAL, mCaptivePortalUrl); } @@ -987,6 +1004,7 @@ byte[] clientId = null; List<Inet4Address> dnsServers = new ArrayList<>(); List<Inet4Address> gateways = new ArrayList<>(); // aka router + ArrayList<String> dmnSrchList = new ArrayList<>(); Inet4Address serverIdentifier = null; Inet4Address netMask = null; String message = null; @@ -1283,8 +1301,22 @@ captivePortalUrl = readAsciiString(packet, optionLen, true); break; case DHCP_IPV6_ONLY_PREFERRED: - expectedLen = 4; - ipv6OnlyWaitTime = Integer.valueOf(packet.getInt()); + if (optionLen == 4) { + expectedLen = optionLen; + ipv6OnlyWaitTime = Integer.valueOf(packet.getInt()); + } else { + // rfc8925#section-3.1: The client MUST ignore the IPv6-Only + // Preferred option if the length field value is not 4. + expectedLen = skipOption(packet, optionLen); + } + break; + case DHCP_DOMAIN_SEARCHLIST: + // TODO: should support multiple options(i.e. length > 255)? + expectedLen = optionLen; + final byte[] bytes = new byte[expectedLen]; + packet.get(bytes); + final ByteBuffer buf = ByteBuffer.wrap(bytes); + dmnSrchList = DomainUtils.decode(buf, true /* compression */); break; default: expectedLen = skipOption(packet, optionLen); @@ -1353,7 +1385,6 @@ newPacket.mBroadcastAddress = bcAddr; newPacket.mClientId = clientId; newPacket.mDnsServers = dnsServers; - newPacket.mDomainName = domainName; newPacket.mGateways = gateways; newPacket.mHostName = hostName; newPacket.mLeaseTime = leaseTime; @@ -1376,6 +1407,10 @@ } else { newPacket.mServerHostName = ""; } + // Domain suffixes in the search list are concatenated to domain name with space separated, + // which will be set to DnsResolver via LinkProperties. + newPacket.mDmnSrchList = dmnSrchList; + newPacket.mDomainName = domainName; return newPacket; } @@ -1445,7 +1480,10 @@ results.mtu = (mMtu != null && MIN_MTU <= mMtu && mMtu <= MAX_MTU) ? mMtu : 0; results.serverHostName = mServerHostName; results.captivePortalApiUrl = mCaptivePortalUrl; - + // Add the check before setting it + if (mDmnSrchList != null && mDmnSrchList.size() > 0) { + results.dmnsrchList.addAll(mDmnSrchList); + } return results; } @@ -1506,7 +1544,8 @@ Inet4Address yourIp, byte[] mac, Integer timeout, Inet4Address netMask, Inet4Address bcAddr, List<Inet4Address> gateways, List<Inet4Address> dnsServers, Inet4Address dhcpServerIdentifier, String domainName, String hostname, boolean metered, - short mtu, String captivePortalUrl, Integer ipv6OnlyWaitTime) { + short mtu, String captivePortalUrl, Integer ipv6OnlyWaitTime, + List<String> domainSearchList) { DhcpPacket pkt = new DhcpOfferPacket( transactionId, (short) 0, broadcast, serverIpAddr, relayIp, INADDR_ANY /* clientIp */, yourIp, mac); @@ -1520,6 +1559,9 @@ pkt.mBroadcastAddress = bcAddr; pkt.mMtu = mtu; pkt.mCaptivePortalUrl = captivePortalUrl; + if (domainSearchList != null) { + pkt.mDmnSrchList = new ArrayList<>(domainSearchList); + } if (metered) { pkt.mVendorInfo = VENDOR_INFO_ANDROID_METERED; } @@ -1540,7 +1582,8 @@ short mtu, String captivePortalUrl) { return buildOfferPacket(encap, transactionId, broadcast, serverIpAddr, relayIp, yourIp, mac, timeout, netMask, bcAddr, gateways, dnsServers, dhcpServerIdentifier, - domainName, hostname, metered, mtu, captivePortalUrl, null /* V6ONLY_WAIT */); + domainName, hostname, metered, mtu, captivePortalUrl, null /* V6ONLY_WAIT */, + null /* domainSearchList */); } /** @@ -1551,7 +1594,8 @@ Inet4Address requestClientIp, byte[] mac, Integer timeout, Inet4Address netMask, Inet4Address bcAddr, List<Inet4Address> gateways, List<Inet4Address> dnsServers, Inet4Address dhcpServerIdentifier, String domainName, String hostname, boolean metered, - short mtu, boolean rapidCommit, String captivePortalUrl, Integer ipv6OnlyWaitTime) { + short mtu, boolean rapidCommit, String captivePortalUrl, Integer ipv6OnlyWaitTime, + List<String> domainSearchList) { DhcpPacket pkt = new DhcpAckPacket( transactionId, (short) 0, broadcast, serverIpAddr, relayIp, requestClientIp, yourIp, mac, rapidCommit); @@ -1565,6 +1609,9 @@ pkt.mBroadcastAddress = bcAddr; pkt.mMtu = mtu; pkt.mCaptivePortalUrl = captivePortalUrl; + if (domainSearchList != null) { + pkt.mDmnSrchList = new ArrayList<>(domainSearchList); + } if (metered) { pkt.mVendorInfo = VENDOR_INFO_ANDROID_METERED; } @@ -1586,7 +1633,7 @@ return buildAckPacket(encap, transactionId, broadcast, serverIpAddr, relayIp, yourIp, requestClientIp, mac, timeout, netMask, bcAddr, gateways, dnsServers, dhcpServerIdentifier, domainName, hostname, metered, mtu, rapidCommit, - captivePortalUrl, null /* V6ONLY_WAIT */); + captivePortalUrl, null /* V6ONLY_WAIT */, null /* domainSearchList */); } /**
diff --git a/src/android/net/dhcp/DhcpResultsParcelableUtil.java b/src/android/net/dhcp/DhcpResultsParcelableUtil.java index 7075925..3b97546 100644 --- a/src/android/net/dhcp/DhcpResultsParcelableUtil.java +++ b/src/android/net/dhcp/DhcpResultsParcelableUtil.java
@@ -17,15 +17,12 @@ package android.net.dhcp; import static android.net.shared.IpConfigurationParcelableUtil.parcelAddress; -import static android.net.shared.IpConfigurationParcelableUtil.unparcelAddress; import android.net.DhcpResults; import android.net.DhcpResultsParcelable; import androidx.annotation.Nullable; -import java.net.Inet4Address; - /** * A utility class to convert DhcpResults to DhcpResultsParcelable. */ @@ -45,19 +42,4 @@ p.captivePortalApiUrl = results.captivePortalApiUrl; return p; } - - /** - * Convert a DhcpResultsParcelable to DhcpResults. - */ - public static DhcpResults fromStableParcelable(@Nullable DhcpResultsParcelable p) { - if (p == null) return null; - final DhcpResults results = new DhcpResults(p.baseConfiguration); - results.leaseDuration = p.leaseDuration; - results.mtu = p.mtu; - results.serverAddress = (Inet4Address) unparcelAddress(p.serverAddress); - results.vendorInfo = p.vendorInfo; - results.serverHostName = p.serverHostName; - results.captivePortalApiUrl = p.captivePortalApiUrl; - return results; - } }
diff --git a/src/android/net/dhcp/DhcpServer.java b/src/android/net/dhcp/DhcpServer.java index dac9258..041417d 100644 --- a/src/android/net/dhcp/DhcpServer.java +++ b/src/android/net/dhcp/DhcpServer.java
@@ -23,7 +23,6 @@ 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.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; import static android.system.OsConstants.AF_INET; import static android.system.OsConstants.IPPROTO_UDP; import static android.system.OsConstants.SOCK_DGRAM; @@ -233,7 +232,7 @@ @Override public boolean isFeatureEnabled(@NonNull Context context, @NonNull String name) { - return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name); + return DeviceConfigUtils.isNetworkStackFeatureEnabled(context, name); } }
diff --git a/src/android/net/dhcp6/Dhcp6AdvertisePacket.java b/src/android/net/dhcp6/Dhcp6AdvertisePacket.java index a9fac83..263ab5f 100644 --- a/src/android/net/dhcp6/Dhcp6AdvertisePacket.java +++ b/src/android/net/dhcp6/Dhcp6AdvertisePacket.java
@@ -34,7 +34,7 @@ */ Dhcp6AdvertisePacket(int transId, @NonNull final byte[] clientDuid, @NonNull final byte[] serverDuid, final byte[] iapd) { - super(transId, (short) 0 /* secs */, clientDuid, serverDuid, iapd); + super(transId, 0 /* elapsedTime */, clientDuid, serverDuid, iapd); } /**
diff --git a/src/android/net/dhcp6/Dhcp6Client.java b/src/android/net/dhcp6/Dhcp6Client.java index 975c2c5..8d53048 100644 --- a/src/android/net/dhcp6/Dhcp6Client.java +++ b/src/android/net/dhcp6/Dhcp6Client.java
@@ -16,11 +16,11 @@ package android.net.dhcp6; +import static android.net.dhcp6.Dhcp6Packet.IAID; import static android.net.dhcp6.Dhcp6Packet.PrefixDelegation; -import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH; +import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; import static android.system.OsConstants.AF_INET6; import static android.system.OsConstants.IPPROTO_UDP; -import static android.system.OsConstants.RT_SCOPE_UNIVERSE; import static android.system.OsConstants.SOCK_DGRAM; import static android.system.OsConstants.SOCK_NONBLOCK; @@ -28,13 +28,9 @@ import static com.android.net.module.util.NetworkStackConstants.DHCP6_CLIENT_PORT; import static com.android.net.module.util.NetworkStackConstants.DHCP6_SERVER_PORT; import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ANY; -import static com.android.networkstack.apishim.ConstantsShim.IFA_F_MANAGETEMPADDR; -import static com.android.networkstack.apishim.ConstantsShim.IFA_F_NOPREFIXROUTE; -import static com.android.networkstack.util.NetworkStackUtils.createInet6AddressFromEui64; +import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH; import android.content.Context; -import android.net.IpPrefix; -import android.net.LinkAddress; import android.net.ip.IpClient; import android.net.util.SocketUtils; import android.os.Handler; @@ -51,18 +47,19 @@ import com.android.internal.util.State; import com.android.internal.util.StateMachine; import com.android.internal.util.WakeupMessage; +import com.android.net.module.util.DeviceConfigUtils; import com.android.net.module.util.InterfaceParams; import com.android.net.module.util.PacketReader; -import com.android.net.module.util.netlink.NetlinkUtils; +import com.android.net.module.util.structs.IaPrefixOption; import java.io.FileDescriptor; import java.io.IOException; -import java.net.Inet6Address; import java.net.SocketException; -import java.net.UnknownHostException; import java.nio.ByteBuffer; -import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Random; +import java.util.function.IntSupplier; /** * A DHCPv6 client. @@ -88,7 +85,6 @@ // Message.arg1 arguments to CMD_DHCP6_RESULT notification public static final int DHCP6_PD_SUCCESS = 1; public static final int DHCP6_PD_PREFIX_EXPIRED = 2; - public static final int DHCP6_PD_PREFIX_CHANGED = 3; // Notification from DHCPv6 state machine before quitting public static final int CMD_ON_QUIT = PUBLIC_BASE + 4; @@ -101,20 +97,26 @@ private static final int CMD_DHCP6_PD_REBIND = PRIVATE_BASE + 4; private static final int CMD_DHCP6_PD_EXPIRE = PRIVATE_BASE + 5; - // Timers and timeouts. - // TODO: comply with RFC8415 section 15(Reliability of Client-Initiated Message Exchanges) - private static final int SECONDS = 1000; - private static final int FIRST_TIMEOUT_MS = 1 * SECONDS; - private static final int MAX_TIMEOUT_MS = 512 * SECONDS; + // Transmission and Retransmission parameters in milliseconds. + private static final int SECONDS = 1000; + private static final int SOL_TIMEOUT = 1 * SECONDS; + private static final int SOL_MAX_RT = 3600 * SECONDS; + private static final int REQ_TIMEOUT = 1 * SECONDS; + private static final int REQ_MAX_RT = 30 * SECONDS; + private static final int REQ_MAX_RC = 10; + private static final int REN_TIMEOUT = 10 * SECONDS; + private static final int REN_MAX_RT = 600 * SECONDS; + private static final int REB_TIMEOUT = 10 * SECONDS; + private static final int REB_MAX_RT = 600 * SECONDS; - private int mTransId; - private int mIaId; - private long mTransStartMillis; + private int mSolMaxRtMs = SOL_MAX_RT; + @Nullable private PrefixDelegation mAdvertise; @Nullable private PrefixDelegation mReply; @Nullable private byte[] mServerDuid; // State variables. + @NonNull private final Dependencies mDependencies; @NonNull private final Context mContext; @NonNull private final Random mRandom; @NonNull private final StateMachine mController; @@ -136,15 +138,30 @@ private State mRenewState = new RenewState(); private State mRebindState = new RebindState(); + /** + * Encapsulates Dhcp6Client depencencies that's used for unit testing and + * integration testing. + */ + public static class Dependencies { + /** + * Read an integer DeviceConfig property. + */ + public int getDeviceConfigPropertyInt(String name, int defaultValue) { + return DeviceConfigUtils.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY, name, + defaultValue); + } + } + private WakeupMessage makeWakeupMessage(String cmdName, int cmd) { cmdName = Dhcp6Client.class.getSimpleName() + "." + mIface.name + "." + cmdName; return new WakeupMessage(mContext, getHandler(), cmdName, cmd); } private Dhcp6Client(@NonNull final Context context, @NonNull final StateMachine controller, - @NonNull final InterfaceParams iface) { + @NonNull final InterfaceParams iface, @NonNull final Dependencies deps) { super(TAG, controller.getHandler()); + mDependencies = deps; mContext = context; mController = controller; mIface = iface; @@ -178,8 +195,9 @@ * Make a Dhcp6Client instance. */ public static Dhcp6Client makeDhcp6Client(@NonNull final Context context, - @NonNull final StateMachine controller, @NonNull final InterfaceParams ifParams) { - final Dhcp6Client client = new Dhcp6Client(context, controller, ifParams); + @NonNull final StateMachine controller, @NonNull final InterfaceParams ifParams, + @NonNull final Dependencies deps) { + final Dhcp6Client client = new Dhcp6Client(context, controller, ifParams, deps); client.start(); return client; } @@ -201,24 +219,80 @@ } /** - * Retransmits packets using jittered exponential backoff with an optional timeout. Packet - * transmission is triggered by CMD_KICK, which is sent by an AlarmManager alarm. Kicks are - * cancelled when leaving the state. + * Retransmits packets per algorithm defined in RFC8415 section 15. Packet transmission is + * triggered by CMD_KICK, which is sent by an AlarmManager alarm. Kicks are cancelled when + * leaving the state. * - * Concrete subclasses must implement sendPacket, which is called when the alarm fires and a - * packet needs to be transmitted, and receivePacket, which is triggered by CMD_RECEIVED_PACKET - * sent by the receive thread. - * - * TODO: deduplicate with the similar code in DhcpClient.java + * Concrete subclasses must initialize retransmission parameters and implement sendPacket, + * which is called when the alarm fires and a packet needs to be transmitted, and receivePacket, + * which is triggered by CMD_RECEIVED_PACKET sent by the receive thread. */ - abstract class PacketRetransmittingState extends State { - private int mTimer; + abstract class MessageExchangeState extends State { + private int mTransId = 0; + private long mTransStartMs = 0; + private long mMaxRetransTimeMs = 0; + + private long mRetransTimeout = -1; + private int mRetransCount = 0; + private final long mInitialDelayMs; + private final long mInitialRetransTimeMs; + private final int mMaxRetransCount; + private final IntSupplier mMaxRetransTimeSupplier; + + MessageExchangeState(final int delay, final int irt, final int mrc, final IntSupplier mrt) { + mInitialDelayMs = delay; + mInitialRetransTimeMs = irt; + mMaxRetransCount = mrc; + mMaxRetransTimeSupplier = mrt; + } @Override public void enter() { super.enter(); - mTimer = FIRST_TIMEOUT_MS; - sendMessage(CMD_KICK); + mMaxRetransTimeMs = mMaxRetransTimeSupplier.getAsInt(); + // Every message exchange generates a new transaction id. + mTransId = mRandom.nextInt() & 0xffffff; + sendMessageDelayed(CMD_KICK, mInitialDelayMs); + } + + private void handleKick() { + // rfc8415#section-21.9: The elapsed time is measured from the time at which the + // client sent the first message in the message exchange, and the elapsed-time field + // is set to 0 in the first message in the message exchange. + final long elapsedTimeMs; + if (mRetransCount == 0) { + elapsedTimeMs = 0; + mTransStartMs = SystemClock.elapsedRealtime(); + } else { + elapsedTimeMs = SystemClock.elapsedRealtime() - mTransStartMs; + } + + sendPacket(mTransId, elapsedTimeMs); + // Compares retransmission parameters and reschedules alarm accordingly. + scheduleKick(); + } + + private void handleReceivedPacket(@NonNull final Dhcp6Packet packet) { + // Technically it is valid for the server to not include a prefix in an IA in certain + // scenarios (specifically in a reply to Renew / Rebind, which means: do not extend the + // prefix, e.g. the list of prefix is empty). However, if prefix(es) do exist and all + // prefixes are invalid, then we should just ignore this packet. + if (!packet.isValid(mTransId, mClientDuid)) return; + if (!packet.mPrefixDelegation.ipos.isEmpty()) { + boolean allInvalidPrefixes = true; + for (IaPrefixOption ipo : packet.mPrefixDelegation.ipos) { + if (ipo != null && ipo.isValid()) { + allInvalidPrefixes = false; + break; + } + } + if (allInvalidPrefixes) { + Log.w(TAG, "All IA_Prefix options included in the " + + packet.getClass().getSimpleName() + " are invalid, ignore it."); + return; + } + } + receivePacket(packet); } @Override @@ -229,11 +303,10 @@ switch (message.what) { case CMD_KICK: - sendPacket(); - scheduleKick(); + handleKick(); return HANDLED; case CMD_RECEIVED_PACKET: - receivePacket((Dhcp6Packet) message.obj); + handleReceivedPacket((Dhcp6Packet) message.obj); return HANDLED; default: return NOT_HANDLED; @@ -244,98 +317,152 @@ public void exit() { super.exit(); mKickAlarm.cancel(); + mRetransTimeout = -1; + mRetransCount = 0; + mMaxRetransTimeMs = 0; } - protected abstract boolean sendPacket(); + protected abstract boolean sendPacket(int transId, long elapsedTimeMs); protected abstract void receivePacket(Dhcp6Packet packet); + // If the message exchange is considered to have failed according to the retransmission + // mechanism(i.e. client has transmitted the message MRC times or MRD seconds has elapsed + // since the first message transmission), this method will be called to roll back to Solicit + // state and restart the configuration, and notify IpClient the DHCPv6 message exchange + // failure if needed. + protected void onMessageExchangeFailed() {} - protected int jitterTimer(int baseTimer) { - int maxJitter = baseTimer / 10; - int jitter = mRandom.nextInt(2 * maxJitter) - maxJitter; - return baseTimer + jitter; + /** + * Per RFC8415 section 15, each of the computations of a new RT includes a randomization + * factor (RAND), which is a random number chosen with a uniform distribution between -0.1 + * and +0.1. + */ + private double rand() { + return mRandom.nextDouble() / 5 - 0.1; } protected void scheduleKick() { - long now = SystemClock.elapsedRealtime(); - long timeout = jitterTimer(mTimer); - long alarmTime = now + timeout; - mKickAlarm.schedule(alarmTime); - mTimer *= 2; - if (mTimer > MAX_TIMEOUT_MS) { - mTimer = MAX_TIMEOUT_MS; + if (mRetransTimeout == -1) { + // RT for the first message transmission is based on IRT. + mRetransTimeout = mInitialRetransTimeMs + (long) (rand() * mInitialRetransTimeMs); + } else { + // RT for each subsequent message transmission is based on the previous value of RT. + mRetransTimeout = 2 * mRetransTimeout + (long) (rand() * mRetransTimeout); } + if (mMaxRetransTimeMs != 0 && mRetransTimeout > mMaxRetransTimeMs) { + mRetransTimeout = mMaxRetransTimeMs + (long) (rand() * mMaxRetransTimeMs); + } + // Per RFC8415 section 18.2.4 and 18.2.5, MRD equals to the remaining time until + // earliest T2(RenewState) or valid lifetimes of all leases in all IA have expired + // (RebindState), and message exchange is terminated when the earliest time T2 is + // reached, at which point client begins the Rebind message exchange, however, section + // 15 says the message exchange fails(terminated) once MRD seconds have elapsed since + // the client first transmitted the message. So far MRD is being used for Renew, Rebind + // and Confirm message retransmission. Given we don't support Confirm message yet, we + // can just use rebindTimeout and expirationTimeout on behalf of MRD which have been + // scheduled in BoundState to simplify the implementation, therefore, we don't need to + // explicitly assign the MRD in the subclasses. + if (mMaxRetransCount != 0 && mRetransCount > mMaxRetransCount) { + onMessageExchangeFailed(); + Log.i(TAG, "client has transmitted the message " + mMaxRetransCount + + " times, stopping retransmission"); + return; + } + mKickAlarm.schedule(SystemClock.elapsedRealtime() + mRetransTimeout); + mRetransCount++; } } private void scheduleLeaseTimers() { + // TODO: validate t1, t2, valid and preferred lifetimes before the timers are scheduled + // to prevent packet storms due to low timeouts. Preferred/valid lifetime of 0 should be + // excluded before scheduling the lease timer. + int renewTimeout = mReply.t1; + int rebindTimeout = mReply.t2; + final long preferredTimeout = mReply.getMinimalPreferredLifetime(); + final long expirationTimeout = mReply.getMinimalValidLifetime(); + + // rfc8415#section-14.2: if t1 and / or t2 are 0, the client chooses an appropriate value. + // rfc8415#section-21.21: Recommended values for T1 and T2 are 0.5 and 0.8 times the + // shortest preferred lifetime of the prefixes in the IA_PD that the server is willing to + // extend, respectively. + if (renewTimeout == 0) { + renewTimeout = (int) (preferredTimeout * 0.5); + } + if (rebindTimeout == 0) { + rebindTimeout = (int) (preferredTimeout * 0.8); + } + + // Note: message validation asserts that the received t1 <= t2 if both t1 > 0 and t2 > 0. + // However, if t1 or t2 are 0, it is possible for renewTimeout to become larger than + // rebindTimeout (and similarly, rebindTimeout to become larger than expirationTimeout). + // For example: t1 = 0, t2 = 40, valid lft = 100 results in renewTimeout = 50, and + // rebindTimeout = 40. Hence, their correct order must be asserted below. + + // If timeouts happen to coincide or are out of order, the former (in respect to the + // specified provisioning lifecycle) can be skipped. This also takes care of the case where + // the server sets t1 == t2 == valid lft, which indicates that the IA cannot be renewed, so + // there is no point in trying. + if (renewTimeout >= rebindTimeout) { + // skip RENEW + renewTimeout = 0; + } + if (rebindTimeout >= expirationTimeout) { + // skip REBIND + rebindTimeout = 0; + } + final long now = SystemClock.elapsedRealtime(); - mRenewAlarm.schedule(now + mReply.t1 * (long) SECONDS); - mRebindAlarm.schedule(now + mReply.t2 * (long) SECONDS); - mExpiryAlarm.schedule(now + mReply.ipo.valid * (long) SECONDS); - Log.d(TAG, "Scheduling IA_PD renewal in " + mReply.t1 + "s"); - Log.d(TAG, "Scheduling IA_PD rebind in " + mReply.t2 + "s"); - Log.d(TAG, "Scheduling IA_PD expiry in " + mReply.ipo.valid + "s"); + if (renewTimeout > 0) { + mRenewAlarm.schedule(now + renewTimeout * (long) SECONDS); + Log.d(TAG, "Scheduling IA_PD renewal in " + renewTimeout + "s"); + } + if (rebindTimeout > 0) { + mRebindAlarm.schedule(now + rebindTimeout * (long) SECONDS); + Log.d(TAG, "Scheduling IA_PD rebind in " + rebindTimeout + "s"); + } + mExpiryAlarm.schedule(now + expirationTimeout * (long) SECONDS); + Log.d(TAG, "Scheduling IA_PD expiry in " + expirationTimeout + "s"); } - private void notifyPrefixDelegation(int result, @Nullable final PrefixDelegation pd) { - mController.sendMessage(CMD_DHCP6_RESULT, result, 0, pd); + private void notifyPrefixDelegation(int result, @Nullable final List<IaPrefixOption> ipos) { + mController.sendMessage(CMD_DHCP6_RESULT, result, 0, ipos); } private void clearDhcp6State() { mAdvertise = null; mReply = null; mServerDuid = null; - } - - private void startNewTransaction() { - mTransId = mRandom.nextInt() & 0xffffff; - mTransStartMillis = SystemClock.elapsedRealtime(); - } - - private short getHundredthsOfSec() { - return (short) ((SystemClock.elapsedRealtime() - mTransStartMillis) / 10); + mSolMaxRtMs = SOL_MAX_RT; } @SuppressWarnings("ByteBufferBackingArray") - private boolean sendSolicitPacket(final ByteBuffer iapd) { - final ByteBuffer packet = Dhcp6Packet.buildSolicitPacket(mTransId, - getHundredthsOfSec() /* elapsed time */, iapd.array(), mClientDuid, - true /* rapidCommit */); + private boolean sendSolicitPacket(int transId, long elapsedTimeMs, final ByteBuffer iapd) { + final ByteBuffer packet = Dhcp6Packet.buildSolicitPacket(transId, elapsedTimeMs, + iapd.array(), mClientDuid, true /* rapidCommit */); return transmitPacket(packet, "solicit"); } @SuppressWarnings("ByteBufferBackingArray") - private boolean sendRequestPacket(final ByteBuffer iapd) { - final ByteBuffer packet = Dhcp6Packet.buildRequestPacket(mTransId, - getHundredthsOfSec() /* elapsed time */, iapd.array(), mClientDuid, - mServerDuid); + private boolean sendRequestPacket(int transId, long elapsedTimeMs, final ByteBuffer iapd) { + final ByteBuffer packet = Dhcp6Packet.buildRequestPacket(transId, elapsedTimeMs, + iapd.array(), mClientDuid, mServerDuid); return transmitPacket(packet, "request"); } @SuppressWarnings("ByteBufferBackingArray") - private boolean sendRenewPacket(final ByteBuffer iapd) { - final ByteBuffer packet = Dhcp6Packet.buildRenewPacket(mTransId, - getHundredthsOfSec() /* elapsed time*/, iapd.array(), mClientDuid, mServerDuid); + private boolean sendRenewPacket(int transId, long elapsedTimeMs, final ByteBuffer iapd) { + final ByteBuffer packet = Dhcp6Packet.buildRenewPacket(transId, elapsedTimeMs, + iapd.array(), mClientDuid, mServerDuid); return transmitPacket(packet, "renew"); } @SuppressWarnings("ByteBufferBackingArray") - private boolean sendRebindPacket(final ByteBuffer iapd) { - final ByteBuffer packet = Dhcp6Packet.buildRebindPacket(mTransId, - getHundredthsOfSec() /* elapsed time */, iapd.array(), mClientDuid); + private boolean sendRebindPacket(int transId, long elapsedTimeMs, final ByteBuffer iapd) { + final ByteBuffer packet = Dhcp6Packet.buildRebindPacket(transId, elapsedTimeMs, + iapd.array(), mClientDuid); return transmitPacket(packet, "rebind"); } - private ByteBuffer buildEmptyIaPdOption() { - return Dhcp6Packet.buildIaPdOption(mIaId, 0 /* t1 */, 0 /* t2 */, 0 /* preferred */, - 0 /* valid */, new byte[16] /* empty prefix */, (byte) RFC7421_PREFIX_LENGTH); - } - - private ByteBuffer buildIaPdOption(@NonNull final PrefixDelegation pd) { - return Dhcp6Packet.buildIaPdOption(pd.iaid, pd.t1, pd.t2, pd.ipo.preferred, pd.ipo.valid, - pd.ipo.prefix, pd.ipo.prefixLen); - } - /** * Parent state at which client does initialization of interface and packet handler, also * processes the CMD_STOP_DHCP6 command in this state which child states don't handle. @@ -395,41 +522,55 @@ * * Note: Not implement DHCPv6 server selection, always request the first Advertise we receive. */ - class SolicitState extends PacketRetransmittingState { + class SolicitState extends MessageExchangeState { + SolicitState() { + // First Solicit message should be delayed by a random amount of time between 0 + // and SOL_MAX_DELAY(1s). + super((int) (new Random().nextDouble() * SECONDS) /* delay */, SOL_TIMEOUT /* IRT */, + 0 /* MRC */, () -> mSolMaxRtMs /* MRT */); + } + @Override public void enter() { super.enter(); - startNewTransaction(); - mIaId = mRandom.nextInt(); } - protected boolean sendPacket() { - return sendSolicitPacket(buildEmptyIaPdOption()); + @Override + protected boolean sendPacket(int transId, long elapsedTimeMs) { + final IaPrefixOption hintOption = new IaPrefixOption((short) IaPrefixOption.LENGTH, + 0 /* preferred */, 0 /* valid */, (byte) RFC7421_PREFIX_LENGTH, + new byte[16] /* empty prefix */); + final PrefixDelegation pd = new PrefixDelegation(IAID, 0 /* t1 */, 0 /* t2 */, + Collections.singletonList(hintOption)); + return sendSolicitPacket(transId, elapsedTimeMs, pd.build()); } - // TODO: support multiple prefixes. + @Override protected void receivePacket(Dhcp6Packet packet) { - if (!packet.isValid(mTransId, mClientDuid)) return; + final PrefixDelegation pd = packet.mPrefixDelegation; + // Ignore any Advertise or Reply for Solicit(with Rapid Commit) with NoPrefixAvail + // status code, retransmit Solicit to see if any valid response from other Servers. + if (pd.statusCode == Dhcp6Packet.STATUS_NO_PREFIX_AVAIL) { + Log.w(TAG, "Server responded to Solicit without available prefix, ignoring"); + return; + } if (packet instanceof Dhcp6AdvertisePacket) { - mAdvertise = packet.mPrefixDelegation; - if (mAdvertise != null && mAdvertise.iaid == mIaId) { - Log.d(TAG, "Get prefix delegation option from Advertise: " + mAdvertise); - mServerDuid = packet.mServerDuid; - transitionTo(mRequestState); - } + Log.d(TAG, "Get prefix delegation option from Advertise: " + pd); + mAdvertise = pd; + mServerDuid = packet.mServerDuid; + mSolMaxRtMs = packet.getSolMaxRtMs().orElse(mSolMaxRtMs); + transitionTo(mRequestState); } else if (packet instanceof Dhcp6ReplyPacket) { if (!packet.mRapidCommit) { - Log.e(TAG, "Server responded to SOLICIT with REPLY without rapid commit option" + Log.e(TAG, "Server responded to Solicit with Reply without rapid commit option" + ", ignoring"); return; } - final PrefixDelegation pd = packet.mPrefixDelegation; - if (pd != null && pd.iaid == mIaId) { - Log.d(TAG, "Get prefix delegation option from RapidCommit Reply: " + pd); - mReply = pd; - mServerDuid = packet.mServerDuid; - transitionTo(mBoundState); - } + Log.d(TAG, "Get prefix delegation option from RapidCommit Reply: " + pd); + mReply = pd; + mServerDuid = packet.mServerDuid; + mSolMaxRtMs = packet.getSolMaxRtMs().orElse(mSolMaxRtMs); + transitionTo(mBoundState); } } } @@ -438,20 +579,35 @@ * Client (re)transmits a Request message to request configuration from a specific server and * process the Reply message in this state. */ - class RequestState extends PacketRetransmittingState { - protected boolean sendPacket() { - return sendRequestPacket(buildIaPdOption(mAdvertise)); + class RequestState extends MessageExchangeState { + RequestState() { + super(0 /* delay */, REQ_TIMEOUT /* IRT */, REQ_MAX_RC /* MRC */, + () -> REQ_MAX_RT /* MRT */); } + @Override + protected boolean sendPacket(int transId, long elapsedTimeMs) { + return sendRequestPacket(transId, elapsedTimeMs, mAdvertise.build()); + } + + @Override protected void receivePacket(Dhcp6Packet packet) { if (!(packet instanceof Dhcp6ReplyPacket)) return; - if (!packet.isValid(mTransId, mClientDuid)) return; final PrefixDelegation pd = packet.mPrefixDelegation; - if (pd != null && pd.iaid == mIaId) { - Log.d(TAG, "Get prefix delegation option from Reply: " + pd); - mReply = pd; - transitionTo(mBoundState); + if (pd.statusCode == Dhcp6Packet.STATUS_NO_PREFIX_AVAIL) { + Log.w(TAG, "Server responded to Request without available prefix, restart Solicit"); + transitionTo(mSolicitState); + return; } + Log.d(TAG, "Get prefix delegation option from Reply: " + pd); + mReply = pd; + mSolMaxRtMs = packet.getSolMaxRtMs().orElse(mSolMaxRtMs); + transitionTo(mBoundState); + } + + @Override + protected void onMessageExchangeFailed() { + transitionTo(mSolicitState); } } @@ -463,7 +619,7 @@ public boolean processMessage(Message message) { switch (message.what) { case CMD_DHCP6_PD_EXPIRE: - notifyPrefixDelegation(DHCP6_PD_PREFIX_EXPIRED, null); + notifyPrefixDelegation(DHCP6_PD_PREFIX_EXPIRED, mReply.getValidIaPrefixes()); transitionTo(mSolicitState); return HANDLED; default: @@ -490,52 +646,9 @@ public void enter() { super.enter(); scheduleLeaseTimers(); - - // TODO: roll back to SOLICIT state after a delay if something wrong happens - // instead of returning directly. - if (!Dhcp6Packet.hasValidPrefixDelegation(mReply)) { - Log.e(TAG, "Invalid prefix delegatioin " + mReply); - return; - } - // Configure the IPv6 addresses based on the delegated prefix on the interface. - // We've checked that delegated prefix is valid upon receiving the response - // from DHCPv6 server, and the server may assign a prefix with length less - // than 64. So for SLAAC use case we always set the prefix length to 64 even - // if the delegated prefix length is less than 64. - final IpPrefix prefix; - try { - prefix = new IpPrefix(Inet6Address.getByAddress(mReply.ipo.prefix), - RFC7421_PREFIX_LENGTH); - } catch (UnknownHostException e) { - Log.wtf(TAG, "Invalid delegated prefix " - + HexDump.toHexString(mReply.ipo.prefix)); - return; - } - // Create an IPv6 address from the interface mac address with IFA_F_MANAGETEMPADDR - // flag, kernel will create another privacy IPv6 address on behalf of user space. - // We don't need to remember IPv6 addresses that need to extend the lifetime every - // time it enters BoundState. - final Inet6Address address = createInet6AddressFromEui64(prefix, - mIface.macAddr.toByteArray()); - final int flags = IFA_F_NOPREFIXROUTE | IFA_F_MANAGETEMPADDR; - final long now = SystemClock.elapsedRealtime(); - final long deprecationTime = now + mReply.ipo.preferred; - final long expirationTime = now + mReply.ipo.valid; - final LinkAddress la = new LinkAddress(address, RFC7421_PREFIX_LENGTH, flags, - RT_SCOPE_UNIVERSE /* scope */, deprecationTime, expirationTime); - if (!la.isGlobalPreferred()) { - Log.e(TAG, la + " is not a global IPv6 address, ignoring"); - return; - } - if (!NetlinkUtils.sendRtmNewAddressRequest(mIface.index, address, - (short) RFC7421_PREFIX_LENGTH, - flags, (byte) RT_SCOPE_UNIVERSE /* scope */, - mReply.ipo.preferred, mReply.ipo.valid)) { - Log.e(TAG, "Failed to set IPv6 address " + address.getHostAddress() - + "%" + mIface.index); - return; - } - notifyPrefixDelegation(DHCP6_PD_SUCCESS, mReply); + // Pass valid delegated prefix(es) to IpClient for IPv6 address configuration and + // active prefix(es) maintenance. + notifyPrefixDelegation(DHCP6_PD_SUCCESS, mReply.getValidIaPrefixes()); } @Override @@ -551,38 +664,60 @@ } } - abstract class ReacquireState extends PacketRetransmittingState { + + /** + * Per RFC8415 section 18.2.10.1: Reply for renew or Rebind. + * - If all binding IA_PDs were renewed/rebound(so far we only support one IA_PD option per + * interface), then move to BoundState to update the existing global IPv6 addresses lifetime + * or install new global IPv6 address depending on the response from server. + * - Server may add new IA prefix option in Reply message(e.g. due to renumbering events), or + * may choose to deprecate some prefixes if it cannot extend the lifetime by: + * - either not including these requested IA prefixes in Reply message + * - or setting the valid lifetime equals to T1/T2 + * That forces previous delegated prefixes to expire in a natural way, and client should + * also stop trying to extend the lifetime for them. That being said, the global IPv6 address + * lifetime won't be updated in BoundState if corresponding prefix doesn't appear in Reply + * message, resulting in these global IPv6 addresses expire eventually and IpClient obtains + * these updates via netlink message and remove the delegated prefix(es) from LinkProperties. + * - If some binding IA_PDs were absent in Reply message, client should still stay at RenewState + * or RebindState and retransmit Renew/Rebind messages to see if it can get all later. So far + * we only support one IA_PD option per interface, if the received Reply message doesn't take + * any IA_Prefix option, then treat it as if IA_PD is absent, since there's no point in + * returning BoundState again. + */ + abstract class ReacquireState extends MessageExchangeState { + ReacquireState(final int irt, final int mrt) { + super(0 /* delay */, irt, 0 /* MRC */, () -> mrt /* MRT */); + } + @Override public void enter() { super.enter(); - startNewTransaction(); } + @Override protected void receivePacket(Dhcp6Packet packet) { if (!(packet instanceof Dhcp6ReplyPacket)) return; - if (!packet.isValid(mTransId, mClientDuid)) return; final PrefixDelegation pd = packet.mPrefixDelegation; - if (pd != null) { - if (pd.iaid != mIaId - || !(Arrays.equals(pd.ipo.prefix, mReply.ipo.prefix) - && pd.ipo.prefixLen == mReply.ipo.prefixLen)) { - Log.i(TAG, "Renewal prefix " + HexDump.toHexString(pd.ipo.prefix) - + " does not match current prefix " - + HexDump.toHexString(mReply.ipo.prefix)); - notifyPrefixDelegation(DHCP6_PD_PREFIX_CHANGED, null); - transitionTo(mSolicitState); - return; - } - mReply = pd; - mServerDuid = packet.mServerDuid; - // Once the delegated prefix gets refreshed successfully we have to extend the - // preferred lifetime and valid lifetime of global IPv6 addresses, otherwise - // these addresses will become depreacated finally and then provisioning failure - // happens. So we transit to mBoundState to update the address with refreshed - // preferred and valid lifetime via sending RTM_NEWADDR message, going back to - // Bound state after a success update. - transitionTo(mBoundState); + // Stay at Renew/Rebind state if the Reply message takes NoPrefixAvail status code, + // retransmit Renew/Rebind message to server, to retry obtaining the prefixes. + if (pd.statusCode == Dhcp6Packet.STATUS_NO_PREFIX_AVAIL) { + Log.w(TAG, "Server responded to Renew/Rebind without available prefix, ignoring"); + return; } + // TODO: send a Request message to the server that responded if any of the IA_PDs in + // Reply message contain NoBinding status code. + Log.d(TAG, "Get prefix delegation option from Reply as response to Renew/Rebind " + pd); + if (pd.ipos.isEmpty()) return; + mReply = pd; + mServerDuid = packet.mServerDuid; + // Once the delegated prefix gets refreshed successfully we have to extend the + // preferred lifetime and valid lifetime of global IPv6 addresses, otherwise + // these addresses will become depreacated finally and then provisioning failure + // happens. So we transit to mBoundState to update the address with refreshed + // preferred and valid lifetime via sending RTM_NEWADDR message, going back to + // Bound state after a success update. + transitionTo(mBoundState); } } @@ -592,6 +727,10 @@ * extend the lifetimes on the leases assigned to the client. */ class RenewState extends ReacquireState { + RenewState() { + super(REN_TIMEOUT, REN_MAX_RT); + } + @Override public boolean processMessage(Message message) { if (super.processMessage(message) == HANDLED) { @@ -606,8 +745,11 @@ } } - protected boolean sendPacket() { - return sendRenewPacket(buildIaPdOption(mReply)); + @Override + protected boolean sendPacket(int transId, long elapsedTimeMs) { + final List<IaPrefixOption> toBeRenewed = mReply.getRenewableIaPrefixes(); + if (toBeRenewed.isEmpty()) return false; + return sendRenewPacket(transId, elapsedTimeMs, mReply.build(toBeRenewed)); } } @@ -617,8 +759,15 @@ * update other configuration parameters. */ class RebindState extends ReacquireState { - protected boolean sendPacket() { - return sendRebindPacket(buildIaPdOption(mReply)); + RebindState() { + super(REB_TIMEOUT, REB_MAX_RT); + } + + @Override + protected boolean sendPacket(int transId, long elapsedTimeMs) { + final List<IaPrefixOption> toBeRebound = mReply.getRenewableIaPrefixes(); + if (toBeRebound.isEmpty()) return false; + return sendRebindPacket(transId, elapsedTimeMs, mReply.build(toBeRebound)); } } @@ -632,7 +781,7 @@ @Override protected void handlePacket(byte[] recvbuf, int length) { try { - final Dhcp6Packet packet = Dhcp6Packet.decodePacket(recvbuf, length); + final Dhcp6Packet packet = Dhcp6Packet.decode(recvbuf, length); if (DBG) Log.d(TAG, "Received packet: " + packet); sendMessage(CMD_RECEIVED_PACKET, packet); } catch (Dhcp6Packet.ParseException e) { @@ -655,16 +804,6 @@ return mUdpSock; } - @Override - protected int readPacket(FileDescriptor fd, byte[] packetBuffer) throws Exception { - try { - return Os.read(fd, packetBuffer, 0, packetBuffer.length); - } catch (IOException | ErrnoException e) { - Log.e(TAG, "Fail to read packet"); - throw e; - } - } - public int transmitPacket(final ByteBuffer buf) throws ErrnoException, SocketException { int ret = Os.sendto(mUdpSock, buf.array(), 0 /* byteOffset */, buf.limit() /* byteCount */, 0 /* flags */, ALL_DHCP_RELAY_AGENTS_AND_SERVERS,
diff --git a/src/android/net/dhcp6/Dhcp6Packet.java b/src/android/net/dhcp6/Dhcp6Packet.java index 9682556..53dd274 100644 --- a/src/android/net/dhcp6/Dhcp6Packet.java +++ b/src/android/net/dhcp6/Dhcp6Packet.java
@@ -22,8 +22,8 @@ import android.util.Log; import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.HexDump; import com.android.net.module.util.Struct; import com.android.net.module.util.structs.IaPdOption; @@ -32,8 +32,11 @@ import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.OptionalInt; /** * Defines basic data and operations needed to build and use packets for the @@ -76,26 +79,31 @@ protected final byte[] mServerDuid; /** + * DHCPv6 Optional Type: Option Request Option. + */ + public static final byte DHCP6_OPTION_REQUEST_OPTION = 6; + + /** * DHCPv6 Optional Type: Elapsed time. * This time is expressed in hundredths of a second. */ public static final byte DHCP6_ELAPSED_TIME = 8; - protected final short mSecs; + protected final int mElapsedTime; /** * DHCPv6 Optional Type: Status Code. */ public static final byte DHCP6_STATUS_CODE = 13; + private static final byte MIN_STATUS_CODE_OPT_LEN = 6; protected short mStatusCode; - protected String mStatusMsg; public static final short STATUS_SUCCESS = 0; public static final short STATUS_UNSPEC_FAIL = 1; - public static final short STATUS_NO_ADDR_AVAI = 2; + public static final short STATUS_NO_ADDRS_AVAIL = 2; public static final short STATUS_NO_BINDING = 3; - public static final short STATUS_PREFIX_NOT_ONLINK = 4; + public static final short STATUS_NOT_ONLINK = 4; public static final short STATUS_USE_MULTICAST = 5; - public static final short STATUS_NO_PREFIX_AVAI = 6; + public static final short STATUS_NO_PREFIX_AVAIL = 6; /** * DHCPv6 zero-length Optional Type: Rapid Commit. Per RFC4039, both DHCPDISCOVER and DHCPACK @@ -114,6 +122,17 @@ protected PrefixDelegation mPrefixDelegation; /** + * DHCPv6 Optional Type: IA Prefix Option. + */ + public static final byte DHCP6_IAPREFIX = 26; + + /** + * DHCPv6 Optional Type: SOL_MAX_RT. + */ + public static final byte DHCP6_SOL_MAX_RT = 82; + private OptionalInt mSolMaxRt; + + /** * The transaction identifier used in this particular DHCPv6 negotiation */ protected final int mTransId; @@ -122,11 +141,14 @@ * The unique identifier for IA_NA, IA_TA, IA_PD used in this particular DHCPv6 negotiation */ protected int mIaId; + // Per rfc8415#section-12, the IAID MUST be consistent across restarts. + // Since currently only one IAID is supported, a well-known value can be used (0). + public static final int IAID = 0; - Dhcp6Packet(int transId, short secs, @NonNull final byte[] clientDuid, final byte[] serverDuid, - @NonNull final byte[] iapd) { + Dhcp6Packet(int transId, int elapsedTime, @NonNull final byte[] clientDuid, + final byte[] serverDuid, @NonNull final byte[] iapd) { mTransId = transId; - mSecs = secs; + mElapsedTime = elapsedTime; mClientDuid = clientDuid; mServerDuid = serverDuid; mIaPd = iapd; @@ -140,6 +162,14 @@ } /** + * Returns decoded IA_PD options associated with IA_ID. + */ + @VisibleForTesting + public PrefixDelegation getPrefixDelegation() { + return mPrefixDelegation; + } + + /** * Returns IA_ID associated to IA_PD. */ public int getIaId() { @@ -162,26 +192,192 @@ } /** + * Returns the SOL_MAX_RT option value in milliseconds. + */ + public OptionalInt getSolMaxRtMs() { + return mSolMaxRt; + } + + /** * A class to take DHCPv6 IA_PD option allocated from server. * https://www.rfc-editor.org/rfc/rfc8415.html#section-21.21 */ public static class PrefixDelegation { - public int iaid; - public int t1; - public int t2; - public final IaPrefixOption ipo; + public final int iaid; + public final int t1; + public final int t2; + @NonNull + public final List<IaPrefixOption> ipos; + public final short statusCode; - PrefixDelegation(int iaid, int t1, int t2, final IaPrefixOption ipo) { + @VisibleForTesting + public PrefixDelegation(int iaid, int t1, int t2, + @NonNull final List<IaPrefixOption> ipos, short statusCode) { + Objects.requireNonNull(ipos); this.iaid = iaid; this.t1 = t1; this.t2 = t2; - this.ipo = ipo; + this.ipos = ipos; + this.statusCode = statusCode; + } + + public PrefixDelegation(int iaid, int t1, int t2, + @NonNull final List<IaPrefixOption> ipos) { + this(iaid, t1, t2, ipos, STATUS_SUCCESS /* statusCode */); + } + + /** + * Check whether or not the IA_PD option in DHCPv6 message is valid. + * + * TODO: ensure that the prefix has a reasonable lifetime, and the timers aren't too short. + */ + public boolean isValid() { + if (iaid != IAID) { + Log.w(TAG, "IA_ID doesn't match, expected: " + IAID + ", actual: " + iaid); + return false; + } + if (t1 < 0 || t2 < 0) { + Log.e(TAG, "IA_PD option with invalid T1 " + t1 + " or T2 " + t2); + return false; + } + // Generally, t1 must be smaller or equal to t2 (except when t2 is 0). + if (t2 != 0 && t1 > t2) { + Log.e(TAG, "IA_PD option with T1 " + t1 + " greater than T2 " + t2); + return false; + } + return true; + } + + /** + * Decode an IA_PD option from the byte buffer. + */ + public static PrefixDelegation decode(@NonNull final ByteBuffer buffer) + throws ParseException { + try { + final int iaid = buffer.getInt(); + final int t1 = buffer.getInt(); + final int t2 = buffer.getInt(); + final List<IaPrefixOption> ipos = new ArrayList<IaPrefixOption>(); + short statusCode = STATUS_SUCCESS; + while (buffer.remaining() > 0) { + final int original = buffer.position(); + final short optionType = buffer.getShort(); + final int optionLen = buffer.getShort() & 0xFFFF; + switch (optionType) { + case DHCP6_IAPREFIX: + buffer.position(original); + final IaPrefixOption ipo = Struct.parse(IaPrefixOption.class, buffer); + Log.d(TAG, "IA Prefix Option: " + ipo); + ipos.add(ipo); + break; + case DHCP6_STATUS_CODE: + statusCode = buffer.getShort(); + // Skip the status message if any. + if (optionLen > 2) { + skipOption(buffer, optionLen - 2); + } + break; + default: + skipOption(buffer, optionLen); + } + } + return new PrefixDelegation(iaid, t1, t2, ipos, statusCode); + } catch (BufferUnderflowException e) { + throw new ParseException(e.getMessage()); + } + } + + /** + * Build an IA_PD option from given specific parameters, including IA_PREFIX options. + */ + public ByteBuffer build() { + return build(ipos); + } + + /** + * Build an IA_PD option from given specific parameters, including IA_PREFIX options. + * + * Per RFC8415 section 21.13 if the Status Code option does not appear in a message in + * which the option could appear, the status of the message is assumed to be Success. So + * only put the Status Code option in IA_PD when the status code is not Success. + */ + public ByteBuffer build(@NonNull final List<IaPrefixOption> input) { + final ByteBuffer iapd = ByteBuffer.allocate(IaPdOption.LENGTH + + Struct.getSize(IaPrefixOption.class) * input.size() + + (statusCode != STATUS_SUCCESS ? MIN_STATUS_CODE_OPT_LEN : 0)); + iapd.putInt(iaid); + iapd.putInt(t1); + iapd.putInt(t2); + for (IaPrefixOption ipo : input) { + ipo.writeToByteBuffer(iapd); + } + if (statusCode != STATUS_SUCCESS) { + iapd.putShort(DHCP6_STATUS_CODE); + iapd.putShort((short) 2); + iapd.putShort(statusCode); + } + iapd.flip(); + return iapd; + } + + /** + * Return valid IA prefix options to be used and extended in the Reply message. It may + * return empty list if there isn't any valid IA prefix option in the Reply message. + * + * TODO: ensure that the prefix has a reasonable lifetime, and the timers aren't too short. + * and handle status code such as NoPrefixAvail. + */ + public List<IaPrefixOption> getValidIaPrefixes() { + final List<IaPrefixOption> validIpos = new ArrayList<IaPrefixOption>(); + for (IaPrefixOption ipo : ipos) { + if (!ipo.isValid()) continue; + validIpos.add(ipo); + } + return validIpos; } @Override public String toString() { - return "Prefix Delegation: iaid " + iaid + ", t1 " + t1 + ", t2 " + t2 - + ", prefix " + ipo; + return String.format("Prefix Delegation, iaid: %s, t1: %s, t2: %s, status code: %s," + + " IA prefix options: %s", iaid, t1, t2, statusCodeToString(statusCode), ipos); + } + + /** + * Compare the preferred lifetime in the IA prefix optin list and return the minimum one. + */ + public long getMinimalPreferredLifetime() { + long min = Long.MAX_VALUE; + for (IaPrefixOption ipo : ipos) { + min = (ipo.preferred != 0 && min > ipo.preferred) ? ipo.preferred : min; + } + return min; + } + + /** + * Compare the valid lifetime in the IA prefix optin list and return the minimum one. + */ + public long getMinimalValidLifetime() { + long min = Long.MAX_VALUE; + for (IaPrefixOption ipo : ipos) { + min = (ipo.valid != 0 && min > ipo.valid) ? ipo.valid : min; + } + return min; + } + + /** + * Return IA prefix option list to be renewed/rebound. + * + * Per RFC8415#section-18.2.4, client must not include any prefixes that it didn't obtain + * from server or that are no longer valid (that have a valid lifetime of 0). Section-18.3.4 + * also mentions that server can inform client that it will not extend the prefix by setting + * T1 and T2 to values equal to the valid lifetime, so in this case client has no point in + * renewing as well. + */ + public List<IaPrefixOption> getRenewableIaPrefixes() { + final List<IaPrefixOption> toBeRenewed = getValidIaPrefixes(); + toBeRenewed.removeIf(ipo -> ipo.preferred == 0 && ipo.valid == 0); + toBeRenewed.removeIf(ipo -> t1 == ipo.valid && t2 == ipo.valid); + return toBeRenewed; } } @@ -194,6 +390,27 @@ } } + private static String statusCodeToString(short statusCode) { + switch (statusCode) { + case STATUS_SUCCESS: + return "Success"; + case STATUS_UNSPEC_FAIL: + return "UnspecFail"; + case STATUS_NO_ADDRS_AVAIL: + return "NoAddrsAvail"; + case STATUS_NO_BINDING: + return "NoBinding"; + case STATUS_NOT_ONLINK: + return "NotOnLink"; + case STATUS_USE_MULTICAST: + return "UseMulticast"; + case STATUS_NO_PREFIX_AVAIL: + return "NoPrefixAvail"; + default: + return "Unknown"; + } + } + private static void skipOption(@NonNull final ByteBuffer packet, int optionLen) throws BufferUnderflowException { for (int i = 0; i < optionLen; i++) { @@ -202,35 +419,6 @@ } /** - * Reads a string of specified length from the buffer. - * - * TODO: move to a common place which can be shared with DhcpClient. - */ - private static String readAsciiString(@NonNull final ByteBuffer buf, int byteCount, - boolean isNullOk) { - final byte[] bytes = new byte[byteCount]; - buf.get(bytes); - return readAsciiString(bytes, isNullOk); - } - - private static String readAsciiString(@NonNull final byte[] payload, boolean isNullOk) { - final byte[] bytes = payload; - int length = bytes.length; - if (!isNullOk) { - // Stop at the first null byte. This is because some DHCP options (e.g., the domain - // name) are passed to netd via FrameworkListener, which refuses arguments containing - // null bytes. We don't do this by default because vendorInfo is an opaque string which - // could in theory contain null bytes. - for (length = 0; length < bytes.length; length++) { - if (bytes[length] == 0) { - break; - } - } - } - return new String(bytes, 0, length, StandardCharsets.US_ASCII); - } - - /** * Creates a concrete Dhcp6Packet from the supplied ByteBuffer. * * The buffer only starts with a UDP encapsulation (i.e. DHCPv6 message). A subset of the @@ -248,15 +436,15 @@ * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ - @VisibleForTesting - static Dhcp6Packet decodePacket(@NonNull final ByteBuffer packet) throws ParseException { - short secs = 0; + private static Dhcp6Packet decode(@NonNull final ByteBuffer packet) throws ParseException { + int elapsedTime = 0; byte[] iapd = null; byte[] serverDuid = null; byte[] clientDuid = null; short statusCode = STATUS_SUCCESS; - String statusMsg = null; boolean rapidCommit = false; + int solMaxRt = 0; + PrefixDelegation pd = null; packet.order(ByteOrder.BIG_ENDIAN); @@ -301,6 +489,7 @@ final byte[] bytes = new byte[expectedLen]; packet.get(bytes, 0 /* offset */, expectedLen); iapd = bytes; + pd = PrefixDelegation.decode(ByteBuffer.wrap(iapd)); break; case DHCP6_RAPID_COMMIT: expectedLen = 0; @@ -308,12 +497,21 @@ break; case DHCP6_ELAPSED_TIME: expectedLen = 2; - secs = packet.getShort(); + elapsedTime = (int) (packet.getShort() & 0xFFFF); break; case DHCP6_STATUS_CODE: expectedLen = optionLen; statusCode = packet.getShort(); - statusMsg = readAsciiString(packet, expectedLen - 2, false /* isNullOk */); + // Skip the status message (if any), which is a UTF-8 encoded text string + // suitable for display to the end user, but is not useful for Dhcp6Client + // to decide how to properly handle the status code. + if (optionLen - 2 > 0) { + skipOption(packet, optionLen - 2); + } + break; + case DHCP6_SOL_MAX_RT: + expectedLen = 4; + solMaxRt = packet.getInt(); break; default: expectedLen = optionLen; @@ -335,40 +533,43 @@ switch(messageType) { case DHCP6_MESSAGE_TYPE_SOLICIT: - newPacket = new Dhcp6SolicitPacket(transId, secs, clientDuid, iapd, rapidCommit); + newPacket = new Dhcp6SolicitPacket(transId, elapsedTime, clientDuid, iapd, + rapidCommit); break; case DHCP6_MESSAGE_TYPE_ADVERTISE: newPacket = new Dhcp6AdvertisePacket(transId, clientDuid, serverDuid, iapd); break; case DHCP6_MESSAGE_TYPE_REQUEST: - newPacket = new Dhcp6RequestPacket(transId, secs, clientDuid, serverDuid, iapd); + newPacket = new Dhcp6RequestPacket(transId, elapsedTime, clientDuid, serverDuid, + iapd); break; case DHCP6_MESSAGE_TYPE_REPLY: newPacket = new Dhcp6ReplyPacket(transId, clientDuid, serverDuid, iapd, rapidCommit); break; case DHCP6_MESSAGE_TYPE_RENEW: - newPacket = new Dhcp6RenewPacket(transId, secs, clientDuid, serverDuid, iapd); + newPacket = new Dhcp6RenewPacket(transId, elapsedTime, clientDuid, serverDuid, + iapd); break; case DHCP6_MESSAGE_TYPE_REBIND: - newPacket = new Dhcp6RebindPacket(transId, secs, clientDuid, iapd); + newPacket = new Dhcp6RebindPacket(transId, elapsedTime, clientDuid, iapd); break; default: throw new ParseException("Unimplemented DHCP6 message type %d" + messageType); } - if (iapd != null) { - final ByteBuffer buffer = ByteBuffer.wrap(iapd); - final int iaid = buffer.getInt(); - final int t1 = buffer.getInt(); - final int t2 = buffer.getInt(); - final IaPrefixOption ipo = Struct.parse(IaPrefixOption.class, buffer); - newPacket.mPrefixDelegation = new PrefixDelegation(iaid, t1, t2, ipo); - newPacket.mIaId = iaid; + if (pd != null) { + newPacket.mPrefixDelegation = pd; + newPacket.mIaId = pd.iaid; + } else { + throw new ParseException("Missing IA_PD option"); } newPacket.mStatusCode = statusCode; - newPacket.mStatusMsg = statusMsg; newPacket.mRapidCommit = rapidCommit; + newPacket.mSolMaxRt = + (solMaxRt >= 60 && solMaxRt <= 86400) + ? OptionalInt.of(solMaxRt * 1000) + : OptionalInt.empty(); return newPacket; } @@ -376,10 +577,10 @@ /** * Parse a packet from an array of bytes, stopping at the given length. */ - public static Dhcp6Packet decodePacket(@NonNull final byte[] packet, int length) + public static Dhcp6Packet decode(@NonNull final byte[] packet, int length) throws ParseException { final ByteBuffer buffer = ByteBuffer.wrap(packet, 0, length).order(ByteOrder.BIG_ENDIAN); - return decodePacket(buffer); + return decode(buffer); } /** @@ -399,49 +600,13 @@ Log.e(TAG, "Unexpected transaction ID " + mTransId + ", expected " + transId); return false; } - return true; - } - - /** - * Check whether or not the delegated prefix in DHCPv6 packet is valid. - * - * TODO: ensure that the prefix has a reasonable lifetime, and the timers aren't too short. - */ - public static boolean hasValidPrefixDelegation(@NonNull final PrefixDelegation pd) { - if (pd == null) { - Log.e(TAG, "DHCPv6 packet without IA_PD option, ignoring"); + // mPrefixDelegation is guaranteed to be non-null. In decode() function, we throw the + // exception if IA_PD option doesn't exist. + if (!mPrefixDelegation.isValid()) { + Log.e(TAG, "DHCPv6 message takes invalid IA_PD option, ignoring"); return false; } - if (pd.ipo.prefixLen > 64) { - Log.e(TAG, "IA_PD option with prefix length " + pd.ipo.prefixLen + " longer than 64"); - return false; - } - final long t1 = pd.t1; - final long t2 = pd.t2; - if (t1 < 0 || t2 < 0) { - Log.e(TAG, "IA_PD option with invalid T1 " + t1 + " or T2 " + t2); - return false; - } - if (t1 > t2) { - Log.e(TAG, "IA_PD option with T1 " + t1 + " greater than T2 " + t2); - return false; - } - final long preferred = pd.ipo.preferred; - final long valid = pd.ipo.valid; - if (preferred < 0 || valid < 0) { - Log.e(TAG, "IA_PD option with invalid lifetime, preferred lifetime " + preferred - + ", valid lifetime " + valid); - return false; - } - if (preferred > valid) { - Log.e(TAG, "IA_PD option with preferred lifetime " + preferred - + " greater than valid lifetime " + valid); - return false; - } - if (preferred < t2) { - Log.e(TAG, "preferred lifetime " + preferred + " is samller than T2 " + t2); - return false; - } + //TODO: check if the status code is success or not. return true; } @@ -505,29 +670,13 @@ } /** - * Build an IA_PD option from given specific parameters, including IA_PREFIX option. - */ - public static ByteBuffer buildIaPdOption(int iaid, int t1, int t2, long preferred, long valid, - final byte[] prefix, byte prefixLen) { - final ByteBuffer iapd = ByteBuffer.allocate(IaPdOption.LENGTH - + Struct.getSize(IaPrefixOption.class)); - iapd.putInt(iaid); - iapd.putInt(t1); - iapd.putInt(t2); - final ByteBuffer prefixOption = IaPrefixOption.build((short) IaPrefixOption.LENGTH, - preferred, valid, prefixLen, prefix); - iapd.put(prefixOption); - iapd.flip(); - return iapd; - } - - /** * Builds a DHCPv6 SOLICIT packet from the required specified parameters. */ - public static ByteBuffer buildSolicitPacket(int transId, short secs, @NonNull final byte[] iapd, - @NonNull final byte[] clientDuid, boolean rapidCommit) { + public static ByteBuffer buildSolicitPacket(int transId, long millisecs, + @NonNull final byte[] iapd, @NonNull final byte[] clientDuid, boolean rapidCommit) { final Dhcp6SolicitPacket pkt = - new Dhcp6SolicitPacket(transId, secs, clientDuid, iapd, rapidCommit); + new Dhcp6SolicitPacket(transId, (int) (millisecs / 10) /* elapsed time */, + clientDuid, iapd, rapidCommit); return pkt.buildPacket(); } @@ -555,29 +704,34 @@ /** * Builds a DHCPv6 REQUEST packet from the required specified parameters. */ - public static ByteBuffer buildRequestPacket(int transId, short secs, @NonNull final byte[] iapd, - @NonNull final byte[] clientDuid, @NonNull final byte[] serverDuid) { + public static ByteBuffer buildRequestPacket(int transId, long millisecs, + @NonNull final byte[] iapd, @NonNull final byte[] clientDuid, + @NonNull final byte[] serverDuid) { final Dhcp6RequestPacket pkt = - new Dhcp6RequestPacket(transId, secs, clientDuid, serverDuid, iapd); + new Dhcp6RequestPacket(transId, (int) (millisecs / 10) /* elapsed time */, + clientDuid, serverDuid, iapd); return pkt.buildPacket(); } /** * Builds a DHCPv6 RENEW packet from the required specified parameters. */ - public static ByteBuffer buildRenewPacket(int transId, short secs, @NonNull final byte[] iapd, - @NonNull final byte[] clientDuid, @NonNull final byte[] serverDuid) { + public static ByteBuffer buildRenewPacket(int transId, long millisecs, + @NonNull final byte[] iapd, @NonNull final byte[] clientDuid, + @NonNull final byte[] serverDuid) { final Dhcp6RenewPacket pkt = - new Dhcp6RenewPacket(transId, secs, clientDuid, serverDuid, iapd); + new Dhcp6RenewPacket(transId, (int) (millisecs / 10) /* elapsed time */, clientDuid, + serverDuid, iapd); return pkt.buildPacket(); } /** * Builds a DHCPv6 REBIND packet from the required specified parameters. */ - public static ByteBuffer buildRebindPacket(int transId, short secs, @NonNull final byte[] iapd, - @NonNull final byte[] clientDuid) { - final Dhcp6RebindPacket pkt = new Dhcp6RebindPacket(transId, secs, clientDuid, iapd); + public static ByteBuffer buildRebindPacket(int transId, long millisecs, + @NonNull final byte[] iapd, @NonNull final byte[] clientDuid) { + final Dhcp6RebindPacket pkt = new Dhcp6RebindPacket(transId, + (int) (millisecs / 10) /* elapsed time */, clientDuid, iapd); return pkt.buildPacket(); } }
diff --git a/src/android/net/dhcp6/Dhcp6RebindPacket.java b/src/android/net/dhcp6/Dhcp6RebindPacket.java index 33a9fc1..87f2f45 100644 --- a/src/android/net/dhcp6/Dhcp6RebindPacket.java +++ b/src/android/net/dhcp6/Dhcp6RebindPacket.java
@@ -33,9 +33,9 @@ /** * Generates a rebind packet with the specified parameters. */ - Dhcp6RebindPacket(int transId, short secs, @NonNull final byte[] clientDuid, + Dhcp6RebindPacket(int transId, int elapsedTime, @NonNull final byte[] clientDuid, @NonNull final byte[] iapd) { - super(transId, secs, clientDuid, null /* serverDuid */, iapd); + super(transId, elapsedTime, clientDuid, null /* serverDuid */, iapd); } /** @@ -47,7 +47,7 @@ packet.putInt(msgTypeAndTransId); addTlv(packet, DHCP6_CLIENT_IDENTIFIER, getClientDuid()); - addTlv(packet, DHCP6_ELAPSED_TIME, mSecs); + addTlv(packet, DHCP6_ELAPSED_TIME, (short) (mElapsedTime & 0xFFFF)); addTlv(packet, DHCP6_IA_PD, mIaPd); packet.flip();
diff --git a/src/android/net/dhcp6/Dhcp6RenewPacket.java b/src/android/net/dhcp6/Dhcp6RenewPacket.java index 3de73df..8c6686c 100644 --- a/src/android/net/dhcp6/Dhcp6RenewPacket.java +++ b/src/android/net/dhcp6/Dhcp6RenewPacket.java
@@ -33,9 +33,9 @@ /** * Generates a renew packet with the specified parameters. */ - Dhcp6RenewPacket(int transId, short secs, @NonNull final byte[] clientDuid, + Dhcp6RenewPacket(int transId, int elapsedTime, @NonNull final byte[] clientDuid, @NonNull final byte[] serverDuid, final byte[] iapd) { - super(transId, secs, clientDuid, serverDuid, iapd); + super(transId, elapsedTime, clientDuid, serverDuid, iapd); } /** @@ -48,7 +48,7 @@ addTlv(packet, DHCP6_SERVER_IDENTIFIER, mServerDuid); addTlv(packet, DHCP6_CLIENT_IDENTIFIER, mClientDuid); - addTlv(packet, DHCP6_ELAPSED_TIME, mSecs); + addTlv(packet, DHCP6_ELAPSED_TIME, (short) (mElapsedTime & 0xFFFF)); addTlv(packet, DHCP6_IA_PD, mIaPd); packet.flip();
diff --git a/src/android/net/dhcp6/Dhcp6ReplyPacket.java b/src/android/net/dhcp6/Dhcp6ReplyPacket.java index 15f748b..d68fbdb 100644 --- a/src/android/net/dhcp6/Dhcp6ReplyPacket.java +++ b/src/android/net/dhcp6/Dhcp6ReplyPacket.java
@@ -35,7 +35,7 @@ */ Dhcp6ReplyPacket(int transId, @NonNull final byte[] clientDuid, @NonNull final byte[] serverDuid, final byte[] iapd, boolean rapidCommit) { - super(transId, (short) 0 /* secs */, clientDuid, serverDuid, iapd); + super(transId, 0 /* elapsedTime */, clientDuid, serverDuid, iapd); mRapidCommit = rapidCommit; }
diff --git a/src/android/net/dhcp6/Dhcp6RequestPacket.java b/src/android/net/dhcp6/Dhcp6RequestPacket.java index 5671502..6d4dfdf 100644 --- a/src/android/net/dhcp6/Dhcp6RequestPacket.java +++ b/src/android/net/dhcp6/Dhcp6RequestPacket.java
@@ -32,9 +32,9 @@ /** * Generates a request packet with the specified parameters. */ - Dhcp6RequestPacket(int transId, short secs, @NonNull final byte[] clientDuid, + Dhcp6RequestPacket(int transId, int elapsedTime, @NonNull final byte[] clientDuid, @NonNull final byte[] serverDuid, final byte[] iapd) { - super(transId, secs, clientDuid, serverDuid, iapd); + super(transId, elapsedTime, clientDuid, serverDuid, iapd); } /** @@ -47,8 +47,9 @@ addTlv(packet, DHCP6_SERVER_IDENTIFIER, mServerDuid); addTlv(packet, DHCP6_CLIENT_IDENTIFIER, mClientDuid); - addTlv(packet, DHCP6_ELAPSED_TIME, mSecs); + addTlv(packet, DHCP6_ELAPSED_TIME, (short) (mElapsedTime & 0xFFFF)); addTlv(packet, DHCP6_IA_PD, mIaPd); + addTlv(packet, DHCP6_OPTION_REQUEST_OPTION, DHCP6_SOL_MAX_RT); packet.flip(); return packet;
diff --git a/src/android/net/dhcp6/Dhcp6SolicitPacket.java b/src/android/net/dhcp6/Dhcp6SolicitPacket.java index 69dc81e..5cf5d01 100644 --- a/src/android/net/dhcp6/Dhcp6SolicitPacket.java +++ b/src/android/net/dhcp6/Dhcp6SolicitPacket.java
@@ -31,9 +31,9 @@ /** * Generates a solicit packet with the specified parameters. */ - Dhcp6SolicitPacket(int transId, short secs, @NonNull final byte[] clientDuid, + Dhcp6SolicitPacket(int transId, int elapsedTime, @NonNull final byte[] clientDuid, final byte[] iapd, boolean rapidCommit) { - super(transId, secs, clientDuid, null /* serverDuid */, iapd); + super(transId, elapsedTime, clientDuid, null /* serverDuid */, iapd); mRapidCommit = rapidCommit; } @@ -45,9 +45,10 @@ final int msgTypeAndTransId = (DHCP6_MESSAGE_TYPE_SOLICIT << 24) | mTransId; packet.putInt(msgTypeAndTransId); - addTlv(packet, DHCP6_ELAPSED_TIME, mSecs); + addTlv(packet, DHCP6_ELAPSED_TIME, (short) (mElapsedTime & 0xFFFF)); addTlv(packet, DHCP6_CLIENT_IDENTIFIER, mClientDuid); addTlv(packet, DHCP6_IA_PD, mIaPd); + addTlv(packet, DHCP6_OPTION_REQUEST_OPTION, DHCP6_SOL_MAX_RT); if (mRapidCommit) { addTlv(packet, DHCP6_RAPID_COMMIT); }
diff --git a/src/android/net/ip/ConnectivityPacketTracker.java b/src/android/net/ip/ConnectivityPacketTracker.java index 4b92179..51fb428 100644 --- a/src/android/net/ip/ConnectivityPacketTracker.java +++ b/src/android/net/ip/ConnectivityPacketTracker.java
@@ -18,7 +18,6 @@ import static android.net.util.SocketUtils.makePacketSocketAddress; import static android.system.OsConstants.AF_PACKET; -import static android.system.OsConstants.ARPHRD_ETHER; import static android.system.OsConstants.ETH_P_ALL; import static android.system.OsConstants.SOCK_NONBLOCK; import static android.system.OsConstants.SOCK_RAW; @@ -110,7 +109,7 @@ FileDescriptor s = null; try { s = Os.socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, 0); - NetworkStackUtils.attachControlPacketFilter(s, ARPHRD_ETHER); + NetworkStackUtils.attachControlPacketFilter(s); Os.bind(s, makePacketSocketAddress(ETH_P_ALL, mInterface.index)); } catch (ErrnoException | IOException e) { logError("Failed to create packet tracking socket: ", e);
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java index b3a0652..d7ef4df 100644 --- a/src/android/net/ip/IpClient.java +++ b/src/android/net/ip/IpClient.java
@@ -19,7 +19,6 @@ import static android.net.RouteInfo.RTN_UNICAST; import static android.net.RouteInfo.RTN_UNREACHABLE; import static android.net.dhcp.DhcpResultsParcelableUtil.toStableParcelable; -import static android.net.dhcp6.Dhcp6Packet.PrefixDelegation; import static android.net.ip.IIpClient.PROV_IPV4_DISABLED; import static android.net.ip.IIpClient.PROV_IPV6_DISABLED; import static android.net.ip.IIpClient.PROV_IPV6_LINKLOCAL; @@ -27,28 +26,40 @@ import static android.net.ip.IIpClientCallbacks.DTIM_MULTIPLIER_RESET; import static android.net.ip.IpReachabilityMonitor.INVALID_REACHABILITY_LOSS_TYPE; import static android.net.ip.IpReachabilityMonitor.nudEventTypeToInt; -import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH; import static android.net.util.SocketUtils.makePacketSocketAddress; import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; import static android.system.OsConstants.AF_PACKET; import static android.system.OsConstants.ETH_P_ARP; import static android.system.OsConstants.ETH_P_IPV6; +import static android.system.OsConstants.IFA_F_NODAD; +import static android.system.OsConstants.RT_SCOPE_UNIVERSE; import static android.system.OsConstants.SOCK_NONBLOCK; import static android.system.OsConstants.SOCK_RAW; +import static com.android.net.module.util.LinkPropertiesUtils.CompareResult; import static com.android.net.module.util.NetworkStackConstants.ARP_REPLY; import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST; import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_ROUTERS_MULTICAST; +import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH; import static com.android.net.module.util.NetworkStackConstants.VENDOR_SPECIFIC_IE_ID; +import static com.android.networkstack.apishim.ConstantsShim.IFA_F_MANAGETEMPADDR; +import static com.android.networkstack.apishim.ConstantsShim.IFA_F_NOPREFIXROUTE; +import static com.android.networkstack.util.NetworkStackUtils.APF_HANDLE_LIGHT_DOZE_FORCE_DISABLE; +import static com.android.networkstack.util.NetworkStackUtils.APF_NEW_RA_FILTER_VERSION; +import static com.android.networkstack.util.NetworkStackUtils.APF_POLLING_COUNTERS_VERSION; import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_DHCPV6_PREFIX_DELEGATION_VERSION; -import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_DISABLE_ACCEPT_RA_VERSION; import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_GARP_NA_ROAMING_VERSION; import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION; -import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_MULTICAST_NS_VERSION; +import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_IGNORE_LOW_RA_LIFETIME_VERSION; +import static com.android.networkstack.util.NetworkStackUtils.createInet6AddressFromEui64; +import static com.android.networkstack.util.NetworkStackUtils.macAddressToEui64; import static com.android.server.util.PermissionUtil.enforceNetworkStackCallingPermission; import android.annotation.SuppressLint; +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; import android.content.Context; +import android.content.pm.PackageManager; import android.content.res.Resources; import android.net.ConnectivityManager; import android.net.DhcpResults; @@ -66,8 +77,10 @@ import android.net.RouteInfo; import android.net.TcpKeepalivePacketDataParcelable; import android.net.Uri; +import android.net.apf.AndroidPacketFilter; import android.net.apf.ApfCapabilities; import android.net.apf.ApfFilter; +import android.net.apf.LegacyApfFilter; import android.net.dhcp.DhcpClient; import android.net.dhcp.DhcpPacket; import android.net.dhcp6.Dhcp6Client; @@ -89,12 +102,14 @@ import android.os.RemoteException; import android.os.ServiceSpecificException; import android.os.SystemClock; +import android.os.UserHandle; import android.stats.connectivity.DisconnectCode; import android.stats.connectivity.NetworkQuirkEvent; import android.stats.connectivity.NudEventType; import android.system.ErrnoException; import android.system.Os; import android.text.TextUtils; +import android.text.format.DateUtils; import android.util.LocalLog; import android.util.Log; import android.util.Pair; @@ -111,19 +126,23 @@ import com.android.internal.util.State; import com.android.internal.util.StateMachine; import com.android.internal.util.WakeupMessage; +import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.CollectionUtils; +import com.android.net.module.util.ConnectivityUtils; import com.android.net.module.util.DeviceConfigUtils; import com.android.net.module.util.InterfaceParams; +import com.android.net.module.util.LinkPropertiesUtils; import com.android.net.module.util.SharedLog; import com.android.net.module.util.SocketUtils; +import com.android.net.module.util.arp.ArpPacket; import com.android.net.module.util.ip.InterfaceController; import com.android.net.module.util.netlink.NetlinkUtils; +import com.android.net.module.util.structs.IaPrefixOption; import com.android.networkstack.R; import com.android.networkstack.apishim.NetworkInformationShimImpl; import com.android.networkstack.apishim.SocketUtilsShimImpl; import com.android.networkstack.apishim.common.NetworkInformationShim; import com.android.networkstack.apishim.common.ShimUtils; -import com.android.networkstack.arp.ArpPacket; import com.android.networkstack.metrics.IpProvisioningMetrics; import com.android.networkstack.metrics.NetworkQuirkMetrics; import com.android.networkstack.packets.NeighborAdvertisement; @@ -132,6 +151,7 @@ import com.android.server.NetworkObserverRegistry; import com.android.server.NetworkStackService.NetworkStackServiceManager; +import java.io.File; import java.io.FileDescriptor; import java.io.PrintWriter; import java.net.Inet4Address; @@ -141,7 +161,6 @@ import java.net.SocketAddress; import java.net.SocketException; import java.net.URL; -import java.net.UnknownHostException; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -367,13 +386,15 @@ /** * Called to indicate that a new APF program must be installed to filter incoming packets. */ - public void installPacketFilter(byte[] filter) { + public boolean installPacketFilter(byte[] filter) { log("installPacketFilter(byte[" + filter.length + "])"); try { mCallback.installPacketFilter(filter); } catch (RemoteException e) { log("Failed to call installPacketFilter", e); + return false; } + return true; } /** @@ -445,8 +466,14 @@ * Set maximum acceptable DTIM multiplier to hardware driver. */ public void setMaxDtimMultiplier(int multiplier) { - log("setMaxDtimMultiplier(" + multiplier + ")"); try { + // {@link IWifiStaIface#setDtimMultiplier} has been implemented since U, calling + // this method on U- platforms does nothing actually. + if (!SdkLevel.isAtLeastU()) { + log("SDK level is lower than U, do not call setMaxDtimMultiplier method"); + return; + } + log("setMaxDtimMultiplier(" + multiplier + ")"); mCallback.setMaxDtimMultiplier(multiplier); } catch (RemoteException e) { log("Failed to call setMaxDtimMultiplier", e); @@ -474,6 +501,13 @@ public static final String DUMP_ARG_CONFIRM = "confirm"; + // Sysctl parameter strings. + private static final String ACCEPT_RA = "accept_ra"; + private static final String ACCEPT_RA_DEFRTR = "accept_ra_defrtr"; + @VisibleForTesting + static final String ACCEPT_RA_MIN_LFT = "accept_ra_min_lft"; + private static final String DAD_TRANSMITS = "dad_transmits"; + // Below constants are picked up by MessageUtils and exempt from ProGuard optimization. private static final int CMD_TERMINATE_AFTER_STOP = 1; private static final int CMD_STOP = 2; @@ -496,6 +530,7 @@ private static final int CMD_SET_DTIM_MULTIPLIER_AFTER_DELAY = 18; private static final int CMD_UPDATE_APF_CAPABILITIES = 19; private static final int EVENT_IPV6_AUTOCONF_TIMEOUT = 20; + private static final int CMD_UPDATE_APF_DATA_SNAPSHOT = 21; private static final int ARG_LINKPROP_CHANGED_LINKSTATE_DOWN = 0; private static final int ARG_LINKPROP_CHANGED_LINKSTATE_UP = 1; @@ -512,6 +547,8 @@ // IpClient shares a handler with Dhcp6Client: commands must not overlap public static final int DHCP6CLIENT_CMD_BASE = 2000; + private static final int DHCPV6_PREFIX_DELEGATION_ADDRESS_FLAGS = + IFA_F_MANAGETEMPADDR | IFA_F_NOPREFIXROUTE | IFA_F_NODAD; // Settings and default values. private static final int MAX_LOG_RECORDS = 500; @@ -522,6 +559,17 @@ 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 = 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 // provisioning connection latency metrics, the latency of 95% can go up to 16s, so pick @@ -593,11 +641,11 @@ // Maps each DHCP option code to a list of IEs, any of which will allow that option. private static final Map<Byte, List<byte[]>> DHCP_OPTIONS_ALLOWED = Map.of( (byte) 60, Collections.singletonList( - // KT OUI: 00:17:C3, type: 17. See b/170928882. - new byte[]{ (byte) 0x00, (byte) 0x17, (byte) 0xc3, (byte) 0x11 }), + // KT OUI: 00:17:C3, type: 33(0x21). See b/236745261. + new byte[]{ (byte) 0x00, (byte) 0x17, (byte) 0xc3, (byte) 0x21 }), (byte) 77, Collections.singletonList( - // KT OUI: 00:17:C3, type: 17. See b/170928882. - new byte[]{ (byte) 0x00, (byte) 0x17, (byte) 0xc3, (byte) 0x11 }) + // KT OUI: 00:17:C3, type: 33(0x21). See b/236745261. + new byte[]{ (byte) 0x00, (byte) 0x17, (byte) 0xc3, (byte) 0x21 }) ); // Initialize configurable particular SSID set supporting DHCP Roaming feature. See @@ -637,12 +685,26 @@ private final Set<Inet6Address> mGratuitousNaTargetAddresses = new HashSet<>(); // Set of IPv6 addresses from which multicast NS packets have been sent. private final Set<Inet6Address> mMulticastNsSourceAddresses = new HashSet<>(); + // Set of delegated prefixes. + private final Set<IpPrefix> mDelegatedPrefixes = new HashSet<>(); + @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; + + // Polling interval to update APF data snapshot + private final long mApfCounterPollingIntervalMs; + // Experiment flag read from device config. private final boolean mDhcp6PrefixDelegationEnabled; + private final boolean mUseNewApfFilter; + private final boolean mEnableIpClientIgnoreLowRaLifetime; + private final boolean mApfShouldHandleLightDoze; + private final boolean mEnableApfPollingCounters; private InterfaceParams mInterfaceParams; @@ -657,18 +719,18 @@ private DhcpResults mDhcpResults; private String mTcpBufferSizes; private ProxyInfo mHttpProxy; - private ApfFilter mApfFilter; + private AndroidPacketFilter mApfFilter; private String mL2Key; // The L2 key for this network, for writing into the memory store private String mCluster; // The cluster for this network, for writing into the memory store + private int mCreatorUid; // Uid of app creating the wifi configuration private boolean mMulticastFiltering; private long mStartTimeMillis; private long mIPv6ProvisioningDtimGracePeriodMillis; private MacAddress mCurrentBssid; - private boolean mHasDisabledIpv6OrAcceptRaOnProvLoss; + private boolean mHasDisabledAcceptRaDefrtrOnProvLoss; private Integer mDadTransmits = null; private int mMaxDtimMultiplier = DTIM_MULTIPLIER_RESET; private ApfCapabilities mCurrentApfCapabilities; - private PrefixDelegation mPrefixDelegation; private WakeupMessage mIpv6AutoconfTimeoutAlarm = null; /** @@ -714,8 +776,8 @@ * Get a Dhcp6Client instance. */ public Dhcp6Client makeDhcp6Client(Context context, StateMachine controller, - InterfaceParams ifParams) { - return Dhcp6Client.makeDhcp6Client(context, controller, ifParams); + InterfaceParams ifParams, Dhcp6Client.Dependencies deps) { + return Dhcp6Client.makeDhcp6Client(context, controller, ifParams, deps); } /** @@ -727,6 +789,13 @@ } /** + * Get a Dhcp6Client Dependencies instance. + */ + public Dhcp6Client.Dependencies getDhcp6ClientDependencies() { + return new Dhcp6Client.Dependencies(); + } + + /** * Read an integer DeviceConfig property. */ public int getDeviceConfigPropertyInt(String name, int defaultValue) { @@ -769,12 +838,18 @@ /** * Return whether a feature guarded by a feature flag is enabled. - * @see NetworkStackUtils#isFeatureEnabled(Context, String, String) + * @see DeviceConfigUtils#isNetworkStackFeatureEnabled(Context, String) */ - public boolean isFeatureEnabled(final Context context, final String name, - boolean defaultEnabled) { - return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name, - defaultEnabled); + public boolean isFeatureEnabled(final Context context, final String name) { + return DeviceConfigUtils.isNetworkStackFeatureEnabled(context, name); + } + + /** + * Check whether one specific feature is not disabled. + * @see DeviceConfigUtils#isNetworkStackFeatureNotChickenedOut(Context, String) + */ + public boolean isFeatureNotChickenedOut(final Context context, final String name) { + return DeviceConfigUtils.isNetworkStackFeatureNotChickenedOut(context, name); } /** @@ -782,10 +857,34 @@ * APF programs. * @see ApfFilter#maybeCreate */ - public ApfFilter maybeCreateApfFilter(Context context, ApfFilter.ApfConfiguration config, - InterfaceParams ifParams, IpClientCallbacksWrapper cb) { - return ApfFilter.maybeCreate(context, config, ifParams, cb); + public AndroidPacketFilter maybeCreateApfFilter(Context context, + ApfFilter.ApfConfiguration config, InterfaceParams ifParams, + IpClientCallbacksWrapper cb, NetworkQuirkMetrics networkQuirkMetrics, + boolean useNewApfFilter) { + if (useNewApfFilter) { + return ApfFilter.maybeCreate(context, config, ifParams, cb, networkQuirkMetrics); + } else { + return LegacyApfFilter.maybeCreate(context, config, ifParams, cb, + networkQuirkMetrics); + } } + + /** + * Check if a specific IPv6 sysctl file exists or not. + */ + public boolean hasIpv6Sysctl(final String ifname, final String name) { + final String path = "/proc/sys/net/ipv6/conf/" + ifname + "/" + name; + final File sysctl = new File(path); + return sysctl.exists(); + } + /** + * Get the configuration from RRO to check whether or not to send domain search list + * option in DHCPDISCOVER/DHCPREQUEST message. + */ + public boolean getSendDomainSearchListOption(final Context context) { + return context.getResources().getBoolean(R.bool.config_dhcp_client_domain_search_list); + } + } public IpClient(Context context, String ifName, IIpClientCallbacks callback, @@ -803,6 +902,8 @@ mTag = getName(); + mDevicePolicyManager = (DevicePolicyManager) + context.getSystemService(Context.DEVICE_POLICY_SERVICE); mContext = context; mInterfaceName = ifName; mDependencies = deps; @@ -826,10 +927,23 @@ mInterfaceCtrl = new InterfaceController(mInterfaceName, mNetd, mLog); mDhcp6PrefixDelegationEnabled = mDependencies.isFeatureEnabled(mContext, - IPCLIENT_DHCPV6_PREFIX_DELEGATION_VERSION, false /* defaultEnabled */); + IPCLIENT_DHCPV6_PREFIX_DELEGATION_VERSION); mMinRdnssLifetimeSec = mDependencies.getDeviceConfigPropertyInt( CONFIG_MIN_RDNSS_LIFETIME, DEFAULT_MIN_RDNSS_LIFETIME); + mAcceptRaMinLft = mDependencies.getDeviceConfigPropertyInt(CONFIG_ACCEPT_RA_MIN_LFT, + DEFAULT_ACCEPT_RA_MIN_LFT); + mApfCounterPollingIntervalMs = mDependencies.getDeviceConfigPropertyInt( + CONFIG_APF_COUNTER_POLLING_INTERVAL_SECS, + DEFAULT_APF_COUNTER_POLLING_INTERVAL_SECS) * DateUtils.SECOND_IN_MILLIS; + mUseNewApfFilter = mDependencies.isFeatureEnabled(context, APF_NEW_RA_FILTER_VERSION); + mEnableApfPollingCounters = mDependencies.isFeatureEnabled(context, + APF_POLLING_COUNTERS_VERSION); + mEnableIpClientIgnoreLowRaLifetime = mDependencies.isFeatureEnabled(context, + IPCLIENT_IGNORE_LOW_RA_LIFETIME_VERSION); + // Light doze mode status checking API is only available at T or later releases. + mApfShouldHandleLightDoze = SdkLevel.isAtLeastT() && mDependencies.isFeatureNotChickenedOut( + mContext, APF_HANDLE_LIGHT_DOZE_FORCE_DISABLE); IpClientLinkObserver.Configuration config = new IpClientLinkObserver.Configuration( mMinRdnssLifetimeSec); @@ -1023,18 +1137,11 @@ } private boolean isGratuitousNaEnabled() { - return mDependencies.isFeatureEnabled(mContext, IPCLIENT_GRATUITOUS_NA_VERSION, - false /* defaultEnabled */); + return mDependencies.isFeatureNotChickenedOut(mContext, IPCLIENT_GRATUITOUS_NA_VERSION); } private boolean isGratuitousArpNaRoamingEnabled() { - return mDependencies.isFeatureEnabled(mContext, IPCLIENT_GARP_NA_ROAMING_VERSION, - false /* defaultEnabled */); - } - - private boolean isMulticastNsEnabled() { - return mDependencies.isFeatureEnabled(mContext, IPCLIENT_MULTICAST_NS_VERSION, - true /* defaultEnabled */); + return mDependencies.isFeatureEnabled(mContext, IPCLIENT_GARP_NA_ROAMING_VERSION); } @VisibleForTesting @@ -1061,11 +1168,6 @@ return bssid; } - private boolean shouldDisableAcceptRaOnProvisioningLoss() { - return mDependencies.isFeatureEnabled(mContext, IPCLIENT_DISABLE_ACCEPT_RA_VERSION, - true /* defaultEnabled */); - } - @Override protected void onQuitting() { mCallback.onQuit(); @@ -1092,6 +1194,7 @@ mCurrentBssid = getInitialBssid(req.mLayer2Info, req.mScanResultInfo, ShimUtils.isAtLeastS()); mCurrentApfCapabilities = req.mApfCapabilities; + mCreatorUid = req.mCreatorUid; if (req.mLayer2Info != null) { mL2Key = req.mLayer2Info.mL2Key; mCluster = req.mLayer2Info.mCluster; @@ -1239,7 +1342,7 @@ } // Thread-unsafe access to mApfFilter but just used for debugging. - final ApfFilter apfFilter = mApfFilter; + final AndroidPacketFilter apfFilter = mApfFilter; final android.net.shared.ProvisioningConfiguration provisioningConfig = mConfiguration; final ApfCapabilities apfCapabilities = (provisioningConfig != null) ? provisioningConfig.mApfCapabilities : null; @@ -1247,7 +1350,8 @@ IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); pw.println(mTag + " APF dump:"); pw.increaseIndent(); - if (apfFilter != null) { + if (apfFilter != null && apfCapabilities != null + && apfCapabilities.apfVersionSupported > 0) { if (apfCapabilities.hasDataAccess()) { // Request a new snapshot, then wait for it. mApfDataSnapshotComplete.close(); @@ -1361,7 +1465,6 @@ mDhcpResults = null; mTcpBufferSizes = ""; mHttpProxy = null; - mPrefixDelegation = null; mLinkProperties = new LinkProperties(); mLinkProperties.setInterfaceName(mInterfaceName); @@ -1442,39 +1545,27 @@ return config.isProvisionedBy(lp.getLinkAddresses(), lp.getRoutes()); } - private void setIpv6AcceptRa(int acceptRa) { + // Set "/proc/sys/net/ipv6/conf/${iface}/${name}" with the given specific value. + private void setIpv6Sysctl(@NonNull final String name, int value) { try { - mNetd.setProcSysNet(INetd.IPV6, INetd.CONF, mInterfaceParams.name, "accept_ra", - Integer.toString(acceptRa)); + mNetd.setProcSysNet(INetd.IPV6, INetd.CONF, mInterfaceName, + name, Integer.toString(value)); } catch (Exception e) { - Log.e(mTag, "Failed to set accept_ra to " + acceptRa + ": " + e); + Log.e(mTag, "Failed to set " + name + " to " + value + ": " + e); } } - private Integer getIpv6DadTransmits() { + // Read "/proc/sys/net/ipv6/conf/${iface}/${name}". + private Integer getIpv6Sysctl(@NonNull final String name) { try { - return Integer.parseUnsignedInt(mNetd.getProcSysNet(INetd.IPV6, INetd.CONF, - mInterfaceName, "dad_transmits")); + return Integer.parseInt(mNetd.getProcSysNet(INetd.IPV6, INetd.CONF, + mInterfaceName, name)); } catch (RemoteException | ServiceSpecificException e) { - logError("Couldn't read dad_transmits on " + mInterfaceName, e); + logError("Couldn't read " + name + " on " + mInterfaceName, e); return null; } } - private void setIpv6DadTransmits(int dadTransmits) { - try { - mNetd.setProcSysNet(INetd.IPV6, INetd.CONF, mInterfaceParams.name, - "dad_transmits", Integer.toString(dadTransmits)); - } catch (Exception e) { - Log.e(mTag, "Failed to set dad_transmits to " + dadTransmits + ": " + e); - } - } - - private void restartIpv6WithAcceptRaDisabled() { - mInterfaceCtrl.disableIPv6(); - startIPv6(0 /* acceptRa */); - } - // TODO: Investigate folding all this into the existing static function // LinkProperties.compareProvisioning() or some other single function that // takes two LinkProperties objects and returns a ProvisioningChange @@ -1524,7 +1615,7 @@ // Note that we can still be disconnected by IpReachabilityMonitor // if the IPv6 default gateway (but not the IPv6 DNS servers; see // accompanying code in IpReachabilityMonitor) is unreachable. - final boolean ignoreIPv6ProvisioningLoss = mHasDisabledIpv6OrAcceptRaOnProvLoss + final boolean ignoreIPv6ProvisioningLoss = mHasDisabledAcceptRaDefrtrOnProvLoss || (mConfiguration != null && mConfiguration.mUsingMultinetworkPolicyTracker && !mCm.shouldAvoidBadWifi()); @@ -1552,31 +1643,27 @@ if (oldLp.hasGlobalIpv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) { // Although link properties have lost IPv6 default route in this case, if IPv4 is still // working with appropriate routes and DNS servers, we can keep the current connection - // without disconnecting from the network, just disable IPv6 or accept_ra parameter on - // that given network until to the next provisioning. + // without disconnecting from the network, just disable accept_ra_defrtr sysctl on that + // given network until to the next provisioning. // // Disabling IPv6 stack will result in all IPv6 connectivity torn down and all IPv6 // sockets being closed, the non-routable IPv6 DNS servers will be stripped out, so // applications will be able to reconnect immediately over IPv4. See b/131781810. // - // Sometimes disabling IPv6 stack might introduce other issues(see b/179222860), - // instead disabling accept_ra will result in only IPv4 provisioning and IPv6 link - // local address left on the interface, so applications will be able to reconnect - // immediately over IPv4 and keep IPv6 link-local capable. + // Sometimes disabling IPv6 stack can cause other problems(see b/179222860), conversely, + // disabling accept_ra_defrtr can still keep the interface IPv6 capable, but no longer + // learns the default router from incoming RA, partial IPv6 connectivity will remain on + // the interface, through which applications can still communicate locally. if (newLp.isIpv4Provisioned()) { - if (shouldDisableAcceptRaOnProvisioningLoss()) { - restartIpv6WithAcceptRaDisabled(); - } else { - mInterfaceCtrl.disableIPv6(); - } + // Restart ipv6 with accept_ra_defrtr set to 0. + mInterfaceCtrl.disableIPv6(); + startIPv6(0 /* accept_ra_defrtr */); + mNetworkQuirkMetrics.setEvent(NetworkQuirkEvent.QE_IPV6_PROVISIONING_ROUTER_LOST); mNetworkQuirkMetrics.statsWrite(); - mHasDisabledIpv6OrAcceptRaOnProvLoss = true; + mHasDisabledAcceptRaDefrtrOnProvLoss = true; delta = PROV_CHANGE_STILL_PROVISIONED; - mLog.log(shouldDisableAcceptRaOnProvisioningLoss() - ? "Disabled accept_ra parameter " - : "Disabled IPv6 stack completely " - + "when the IPv6 default router has gone"); + mLog.log("Disabled accept_ra_defrtr sysctl on loss of IPv6 default router"); } else { delta = PROV_CHANGE_LOST_PROVISIONING; } @@ -1658,6 +1745,31 @@ addAllReachableDnsServers(newLp, netlinkLinkProperties.getDnsServers()); mShim.setNat64Prefix(newLp, mShim.getNat64Prefix(netlinkLinkProperties)); + // Check if any link address update from netlink. + final CompareResult<LinkAddress> results = + LinkPropertiesUtils.compareAddresses(mLinkProperties, newLp); + // In the case that there are multiple netlink update events about a global IPv6 address + // derived from the delegated prefix, a flag-only change event(e.g. due to the duplicate + // address detection) will cause an identical IP address to be put into both Added and + // Removed list based on the CompareResult implementation. To prevent a prefix from being + // mistakenly removed from the delegate prefix list, it is better to always check the + // removed list before checking the added list(e.g. anyway we can add the removed prefix + // back again). + for (LinkAddress la : results.removed) { + if (mDhcp6PrefixDelegationEnabled && isIpv6StableDelegatedAddress(la)) { + final IpPrefix prefix = new IpPrefix(la.getAddress(), RFC7421_PREFIX_LENGTH); + mDelegatedPrefixes.remove(prefix); + } + // TODO: remove onIpv6AddressRemoved callback. + } + + for (LinkAddress la : results.added) { + if (mDhcp6PrefixDelegationEnabled && isIpv6StableDelegatedAddress(la)) { + final IpPrefix prefix = new IpPrefix(la.getAddress(), RFC7421_PREFIX_LENGTH); + mDelegatedPrefixes.add(prefix); + } + } + // [3] Add in data from DHCPv4, if available. // // mDhcpResults is never shared with any other owner so we don't have @@ -1669,7 +1781,12 @@ newLp.addRoute(route); } addAllReachableDnsServers(newLp, mDhcpResults.dnsServers); - newLp.setDomains(mDhcpResults.domains); + if (mDhcpResults.dmnsrchList.size() == 0) { + newLp.setDomains(mDhcpResults.domains); + } else { + final String domainsString = mDhcpResults.appendDomainsSearchList(); + newLp.setDomains(TextUtils.isEmpty(domainsString) ? null : domainsString); + } if (mDhcpResults.mtu != 0) { newLp.setMtu(mDhcpResults.mtu); @@ -1689,26 +1806,22 @@ // TODO: also look at the IPv6 RA (netlink) for captive portal URL } - // [4] Add in data from DHCPv6 Prefix Delegation, if available. - if (mPrefixDelegation != null) { - try { - final IpPrefix destination = - new IpPrefix(Inet6Address.getByAddress(mPrefixDelegation.ipo.prefix), - mPrefixDelegation.ipo.prefixLen); - // Direct-connected route to delegated prefix. Add RTN_UNREACHABLE to this route - // based on the delegated prefix. To prevent the traffic loop between host and - // upstream delegated router. Because we specify the IFA_F_NOPREFIXROUTE when adding - // the IPv6 address, the kernel does not create a delegated prefix route, as a - // result, the user space won't receive any RTM_NEWROUTE message about the delegated - // prefix, we still need to install an unreachable route for the delegated prefix - // manually in LinkProperties to notify the caller this update. - // TODO: support RTN_BLACKHOLE in netd and use that on newer Android versions. - final RouteInfo route = new RouteInfo(destination, null /* gateway */, - mInterfaceName, RTN_UNREACHABLE); + // [4] Add route with delegated prefix according to the global address update. + if (mDhcp6PrefixDelegationEnabled) { + for (IpPrefix destination : mDelegatedPrefixes) { + // Direct-connected route to delegated prefix. Add RTN_UNREACHABLE to + // this route based on the delegated prefix. To prevent the traffic loop + // between host and upstream delegated router. Because we specify the + // IFA_F_NOPREFIXROUTE when adding the IPv6 address, the kernel does not + // create a delegated prefix route, as a result, the user space won't + // receive any RTM_NEWROUTE message about the delegated prefix, we still + // need to install an unreachable route for the delegated prefix manually + // in LinkProperties to notify the caller this update. + // TODO: support RTN_BLACKHOLE in netd and use that on newer Android + // versions. + final RouteInfo route = new RouteInfo(destination, + null /* gateway */, mInterfaceName, RTN_UNREACHABLE); newLp.addRoute(route); - } catch (UnknownHostException e) { - Log.wtf(mTag, "Invalid delegated prefix " - + HexDump.toHexString(mPrefixDelegation.ipo.prefix)); } } @@ -1906,6 +2019,24 @@ } } + private static boolean hasFlag(@NonNull final LinkAddress la, final int flags) { + return (la.getFlags() & flags) == flags; + + } + + // Check whether a global IPv6 stable address is derived from DHCPv6 prefix delegation. + // Address derived from delegated prefix should be: + // - unicast global routable address + // - with prefix length of 64 + // - has IFA_F_MANAGETEMPADDR, IFA_F_NOPREFIXROUTE and IFA_F_NODAD flags + private static boolean isIpv6StableDelegatedAddress(@NonNull final LinkAddress la) { + return la.isIpv6() + && !ConnectivityUtils.isIPv6ULA(la.getAddress()) + && (la.getPrefixLength() == RFC7421_PREFIX_LENGTH) + && (la.getScope() == (byte) RT_SCOPE_UNIVERSE) + && hasFlag(la, DHCPV6_PREFIX_DELEGATION_ADDRESS_FLAGS); + } + // Returns false if we have lost provisioning, true otherwise. private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) { final LinkProperties newLp = assembleLinkProperties(); @@ -1947,9 +2078,7 @@ // // TODO: stop sending this multicast NS after deployment of RFC9131 in the field, leverage // the gratuitous NA to update the first-hop router's neighbor cache entry. - if (isMulticastNsEnabled()) { - maybeSendMulticastNSes(newLp); - } + maybeSendMulticastNSes(newLp); // Either success IPv4 or IPv6 provisioning triggers new LinkProperties update, // wait for the provisioning completion and record the latency. @@ -2119,15 +2248,24 @@ == ProvisioningConfiguration.IPV6_ADDR_GEN_MODE_EUI64; } - private boolean startIPv6(int acceptRa) { - setIpv6AcceptRa(acceptRa); + private boolean startIPv6(int acceptRaDefrtr) { + setIpv6Sysctl(ACCEPT_RA, + mConfiguration.mIPv6ProvisioningMode == PROV_IPV6_LINKLOCAL ? 0 : 2); + setIpv6Sysctl(ACCEPT_RA_DEFRTR, acceptRaDefrtr); if (shouldDisableDad()) { - final Integer dadTransmits = getIpv6DadTransmits(); + final Integer dadTransmits = getIpv6Sysctl(DAD_TRANSMITS); if (dadTransmits != null) { mDadTransmits = dadTransmits; - setIpv6DadTransmits(0 /* dad_transmits */); + setIpv6Sysctl(DAD_TRANSMITS, 0 /* dad_transmits */); } } + // 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. + if (mEnableIpClientIgnoreLowRaLifetime && mUseNewApfFilter + && mDependencies.hasIpv6Sysctl(mInterfaceName, ACCEPT_RA_MIN_LFT)) { + setIpv6Sysctl(ACCEPT_RA_MIN_LFT, mAcceptRaMinLft); + } return mInterfaceCtrl.setIPv6PrivacyExtensions(true) && mInterfaceCtrl.setIPv6AddrGenModeIfSupported(mConfiguration.mIPv6AddrGenMode) && mInterfaceCtrl.enableIPv6(); @@ -2139,7 +2277,8 @@ Log.wtf(mTag, "Dhcp6Client should never be non-null in startDhcp6PrefixDelegation"); return; } - mDhcp6Client = mDependencies.makeDhcp6Client(mContext, IpClient.this, mInterfaceParams); + mDhcp6Client = mDependencies.makeDhcp6Client(mContext, IpClient.this, mInterfaceParams, + mDependencies.getDhcp6ClientDependencies()); mDhcp6Client.sendMessage(Dhcp6Client.CMD_START_DHCP6); } @@ -2194,9 +2333,20 @@ // - disableIpv6() will clear autoconf IPv6 routes as well, and // - we don't get IPv4 routes from netlink // so we neither react to nor need to wait for changes in either. - mInterfaceCtrl.disableIPv6(); mInterfaceCtrl.clearAllAddresses(); + + // Reset IPv6 sysctls to their initial state. It's better to restore + // sysctls after IPv6 stack is disabled, which prevents a potential + // race where receiving an RA between restoring accept_ra and disabling + // IPv6 stack, although it's unlikely. + setIpv6Sysctl(ACCEPT_RA, 2); + setIpv6Sysctl(ACCEPT_RA_DEFRTR, 1); + maybeRestoreDadTransmits(); + if (mUseNewApfFilter && mEnableIpClientIgnoreLowRaLifetime + && mDependencies.hasIpv6Sysctl(mInterfaceName, ACCEPT_RA_MIN_LFT)) { + setIpv6Sysctl(ACCEPT_RA_MIN_LFT, 0 /* sysctl default */); + } } private void maybeSaveNetworkToIpMemoryStore() { @@ -2232,7 +2382,7 @@ private void maybeRestoreDadTransmits() { if (mDadTransmits == null) return; - setIpv6DadTransmits(mDadTransmits); + setIpv6Sysctl(DAD_TRANSMITS, mDadTransmits); mDadTransmits = null; } @@ -2284,7 +2434,7 @@ } @Nullable - private ApfFilter maybeCreateApfFilter(final ApfCapabilities apfCapabilities) { + private AndroidPacketFilter maybeCreateApfFilter(final ApfCapabilities apfCapabilities) { ApfFilter.ApfConfiguration apfConfig = new ApfFilter.ApfConfiguration(); apfConfig.apfCapabilities = apfCapabilities; apfConfig.multicastFilter = mMulticastFiltering; @@ -2300,8 +2450,11 @@ } apfConfig.minRdnssLifetimeSec = mMinRdnssLifetimeSec; + apfConfig.acceptRaMinLft = mAcceptRaMinLft; + apfConfig.shouldHandleLightDoze = mApfShouldHandleLightDoze; + apfConfig.minMetricsSessionDurationMs = mApfCounterPollingIntervalMs; return mDependencies.maybeCreateApfFilter(mContext, apfConfig, mInterfaceParams, - mCallback); + mCallback, mNetworkQuirkMetrics, mUseNewApfFilter); } private boolean handleUpdateApfCapabilities(@NonNull final ApfCapabilities apfCapabilities) { @@ -2326,9 +2479,10 @@ @Override public void enter() { stopAllIP(); - mHasDisabledIpv6OrAcceptRaOnProvLoss = false; + mHasDisabledAcceptRaDefrtrOnProvLoss = false; mGratuitousNaTargetAddresses.clear(); mMulticastNsSourceAddresses.clear(); + mDelegatedPrefixes.clear(); resetLinkProperties(); if (mStartTimeMillis > 0) { @@ -2415,8 +2569,6 @@ // Restore the interface MTU to initial value if it has changed. maybeRestoreInterfaceMtu(); - // Reset number of dad_transmits to default value if changed. - maybeRestoreDadTransmits(); // Reset DTIM multiplier to default value if changed. if (mMaxDtimMultiplier != DTIM_MULTIPLIER_RESET) { mCallback.setMaxDtimMultiplier(DTIM_MULTIPLIER_RESET); @@ -2506,8 +2658,92 @@ // registerForPreDhcpNotification is called later when processing the CMD_*_PRECONNECTION // messages. if (!isUsingPreconnection()) mDhcpClient.registerForPreDhcpNotification(); + boolean isManagedWifiProfile = false; + if (mDependencies.getSendDomainSearchListOption(mContext) + && (mCreatorUid > 0) && (isDeviceOwnerNetwork(mCreatorUid) + || isProfileOwner(mCreatorUid))) { + isManagedWifiProfile = true; + } mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP, new DhcpClient.Configuration(mL2Key, - isUsingPreconnection(), options)); + isUsingPreconnection(), options, isManagedWifiProfile)); + } + + private boolean hasPermission(String permissionName) { + return (mContext.checkCallingOrSelfPermission(permissionName) + == PackageManager.PERMISSION_GRANTED); + } + + private boolean isDeviceOwnerNetwork(int creatorUid) { + if (mDevicePolicyManager == null) return false; + if (!hasPermission(android.Manifest.permission.MANAGE_USERS)) return false; + final ComponentName devicecmpName = mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser(); + if (devicecmpName == null) return false; + final String deviceOwnerPackageName = devicecmpName.getPackageName(); + if (deviceOwnerPackageName == null) return false; + + final String[] packages = mContext.getPackageManager().getPackagesForUid(creatorUid); + + for (String pkg : packages) { + if (pkg.equals(deviceOwnerPackageName)) { + return true; + } + } + return false; + } + + @Nullable + private Context createPackageContextAsUser(int uid) { + Context userContext = null; + try { + userContext = mContext.createPackageContextAsUser(mContext.getPackageName(), 0, + UserHandle.getUserHandleForUid(uid)); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Unknown package name"); + return null; + } + return userContext; + } + + /** + * Returns the DevicePolicyManager from context + */ + private DevicePolicyManager retrieveDevicePolicyManagerFromContext(Context context) { + DevicePolicyManager devicePolicyManager = + context.getSystemService(DevicePolicyManager.class); + if (devicePolicyManager == null + && context.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_DEVICE_ADMIN)) { + Log.wtf(TAG, "Error retrieving DPM service"); + } + return devicePolicyManager; + } + + private DevicePolicyManager retrieveDevicePolicyManagerFromUserContext(int uid) { + Context userContext = createPackageContextAsUser(uid); + if (userContext == null) return null; + return retrieveDevicePolicyManagerFromContext(userContext); + } + + /** + * Returns {@code true} if the calling {@code uid} is the profile owner + * + */ + + private boolean isProfileOwner(int uid) { + DevicePolicyManager devicePolicyManager = retrieveDevicePolicyManagerFromUserContext(uid); + if (devicePolicyManager == null) return false; + String[] packages = mContext.getPackageManager().getPackagesForUid(uid); + if (packages == null) { + Log.w(TAG, "isProfileOwner: could not find packages for uid=" + + uid); + return false; + } + for (String packageName : packages) { + if (devicePolicyManager.isProfileOwnerApp(packageName)) { + return true; + } + } + return false; } class ClearingIpAddressesState extends State { @@ -2709,13 +2945,14 @@ if (mApfFilter == null) { mCallback.setFallbackMulticastFilter(mMulticastFiltering); } + if (mEnableApfPollingCounters) { + sendMessageDelayed(CMD_UPDATE_APF_DATA_SNAPSHOT, mApfCounterPollingIntervalMs); + } mPacketTracker = createPacketTracker(); if (mPacketTracker != null) mPacketTracker.start(mConfiguration.mDisplayName); - final int acceptRa = - mConfiguration.mIPv6ProvisioningMode == PROV_IPV6_LINKLOCAL ? 0 : 2; - if (isIpv6Enabled() && !startIPv6(acceptRa)) { + if (isIpv6Enabled() && !startIPv6(1 /* acceptRaDefrtr */)) { doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6); enqueueJumpToStoppingState(DisconnectCode.DC_ERROR_STARTING_IPV6); return; @@ -2768,6 +3005,8 @@ } resetLinkProperties(); + + removeMessages(CMD_UPDATE_APF_DATA_SNAPSHOT); } private void enqueueJumpToStoppingState(final DisconnectCode code) { @@ -2801,25 +3040,68 @@ } } - private void clearIpv6PrefixDelegationAddresses() { - final IpPrefix prefix; - try { - prefix = new IpPrefix(Inet6Address.getByAddress(mPrefixDelegation.ipo.prefix), - RFC7421_PREFIX_LENGTH); - } catch (UnknownHostException e) { - Log.wtf(TAG, "Invalid delegated prefix " - + HexDump.toHexString(mPrefixDelegation.ipo.prefix)); + private void deleteIpv6PrefixDelegationAddresses(final IpPrefix prefix) { + for (LinkAddress la : mLinkProperties.getLinkAddresses()) { + final InetAddress address = la.getAddress(); + if (prefix.contains(address)) { + if (!NetlinkUtils.sendRtmDelAddressRequest(mInterfaceParams.index, + (Inet6Address) address, (short) la.getPrefixLength())) { + Log.e(TAG, "Failed to delete IPv6 address " + address.getHostAddress()); + } + } + } + } + + private void addInterfaceAddress(@NonNull final Inet6Address address, + @NonNull final IaPrefixOption ipo) { + final int flags = IFA_F_NOPREFIXROUTE | IFA_F_MANAGETEMPADDR | IFA_F_NODAD; + final long now = SystemClock.elapsedRealtime(); + final long deprecationTime = now + ipo.preferred; + final long expirationTime = now + ipo.valid; + final LinkAddress la = new LinkAddress(address, RFC7421_PREFIX_LENGTH, flags, + RT_SCOPE_UNIVERSE /* scope */, deprecationTime, expirationTime); + if (!la.isGlobalPreferred()) { + Log.w(TAG, la + " is not a global IPv6 address"); return; } + if (!NetlinkUtils.sendRtmNewAddressRequest(mInterfaceParams.index, address, + (short) RFC7421_PREFIX_LENGTH, + flags, (byte) RT_SCOPE_UNIVERSE /* scope */, + ipo.preferred, ipo.valid)) { + Log.e(TAG, "Failed to set IPv6 address on " + address.getHostAddress() + + "%" + mInterfaceParams.index); + } + } - // Delete the global IPv6 address based on delegated prefix from interface. - for (LinkAddress la : mLinkProperties.getLinkAddresses()) { - if (!la.isIpv6()) continue; - final Inet6Address address = (Inet6Address) la.getAddress(); - if (la.isIpv6() && prefix.contains(address)) { - NetlinkUtils.sendRtmDelAddressRequest(mInterfaceParams.index, address, - (short) la.getPrefixLength()); + private void updateDelegatedAddresses(@NonNull final List<IaPrefixOption> valid) { + if (valid.isEmpty()) return; + for (IaPrefixOption ipo : valid) { + final IpPrefix prefix = ipo.getIpPrefix(); + // The prefix with preferred/valid lifetime of 0 is considered as a valid prefix, + // it can be passed to IpClient from Dhcp6Client, however, client should stop using + // the global addresses derived from this prefix immediately. + if (ipo.withZeroLifetimes()) { + Log.d(TAG, "Delete IPv6 address derived from prefix " + prefix + + " with 0 preferred/valid lifetime"); + deleteIpv6PrefixDelegationAddresses(prefix); } + // Otherwise, configure IPv6 addresses derived from the delegated prefix(es) on + // the interface. We've checked that delegated prefix is valid upon receiving the + // response from DHCPv6 server, and the server may assign a prefix with length less + // than 64. So for SLAAC use case we always set the prefix length to 64 even if the + // delegated prefix length is less than 64. + final Inet6Address address = createInet6AddressFromEui64(prefix, + macAddressToEui64(mInterfaceParams.macAddr)); + addInterfaceAddress(address, ipo); + } + } + + private void removeExpiredDelegatedAddresses(@NonNull final List<IaPrefixOption> expired) { + if (expired.isEmpty()) return; + for (IaPrefixOption ipo : expired) { + final IpPrefix prefix = ipo.getIpPrefix(); + Log.d(TAG, "Delete IPv6 address derived from expired prefix " + prefix); + deleteIpv6PrefixDelegationAddresses(prefix); } } @@ -2927,10 +3209,17 @@ break; case EVENT_IPV6_AUTOCONF_TIMEOUT: - if (mLinkProperties.isIpv6Provisioned()) break; - Log.d(mTag, "Fail to get IPv6 address via autoconf, " - + "start DHCPv6 Prefix Delegation"); - startDhcp6PrefixDelegation(); + // Only enable DHCPv6 PD on networks that support IPv6 but not autoconf. The + // right way to do it is to use the P flag, once it's defined. For now, assume + // that the network doesn't support autoconf if it provides an IPv6 default + // route but no addresses via an RA. + // TODO: leverage the P flag in RA to determine if starting DHCPv6 PD or not, + // which is more clear and straightforward. + if (!hasIpv6Address(mLinkProperties) + && mLinkProperties.hasIpv6DefaultRoute()) { + Log.d(TAG, "Network supports IPv6 but not autoconf, starting DHCPv6 PD"); + startDhcp6PrefixDelegation(); + } break; case DhcpClient.CMD_PRE_DHCP_ACTION: @@ -3010,14 +3299,14 @@ case Dhcp6Client.CMD_DHCP6_RESULT: switch(msg.arg1) { case Dhcp6Client.DHCP6_PD_SUCCESS: - mPrefixDelegation = (PrefixDelegation) msg.obj; + final List<IaPrefixOption> toBeUpdated = (List<IaPrefixOption>) msg.obj; + updateDelegatedAddresses(toBeUpdated); handleLinkPropertiesUpdate(SEND_CALLBACKS); break; case Dhcp6Client.DHCP6_PD_PREFIX_EXPIRED: - case Dhcp6Client.DHCP6_PD_PREFIX_CHANGED: - clearIpv6PrefixDelegationAddresses(); - mPrefixDelegation = null; + final List<IaPrefixOption> toBeRemoved = (List<IaPrefixOption>) msg.obj; + removeExpiredDelegatedAddresses(toBeRemoved); handleLinkPropertiesUpdate(SEND_CALLBACKS); break; @@ -3049,6 +3338,11 @@ } break; + case CMD_UPDATE_APF_DATA_SNAPSHOT: + mCallback.startReadPacketFilter(); + sendMessageDelayed(CMD_UPDATE_APF_DATA_SNAPSHOT, mApfCounterPollingIntervalMs); + break; + default: return NOT_HANDLED; } @@ -3079,16 +3373,28 @@ mCallback.setMaxDtimMultiplier(multiplier); } + /** + * Check if current LinkProperties has either global IPv6 address or ULA (i.e. non IPv6 + * link-local addres). + * + * This function can be used to derive the DTIM multiplier per current network situation or + * decide if we should start DHCPv6 Prefix Delegation when no IPv6 addresses are available + * after autoconf timeout(5s). + */ + private static boolean hasIpv6Address(@NonNull final LinkProperties lp) { + return CollectionUtils.any(lp.getLinkAddresses(), + la -> { + final InetAddress address = la.getAddress(); + return (address instanceof Inet6Address) && !address.isLinkLocalAddress(); + }); + } + private int deriveDtimMultiplier() { final boolean hasIpv4Addr = mLinkProperties.hasIpv4Address(); // For a host in the network that has only ULA and link-local but no GUA, consider // that it also has IPv6 connectivity. LinkProperties#isIpv6Provisioned only returns // true when it has a GUA, so we cannot use it for IPv6-only network case. - final boolean hasIpv6Addr = CollectionUtils.any(mLinkProperties.getLinkAddresses(), - la -> { - final InetAddress address = la.getAddress(); - return (address instanceof Inet6Address) && !address.isLinkLocalAddress(); - }); + final boolean hasIpv6Addr = hasIpv6Address(mLinkProperties); final int multiplier; if (!mMulticastFiltering) {
diff --git a/src/android/net/ip/IpClientLinkObserver.java b/src/android/net/ip/IpClientLinkObserver.java index bd4095b..2068caa 100644 --- a/src/android/net/ip/IpClientLinkObserver.java +++ b/src/android/net/ip/IpClientLinkObserver.java
@@ -20,7 +20,6 @@ import static android.system.OsConstants.AF_UNSPEC; import static android.system.OsConstants.IFF_LOOPBACK; -import static com.android.modules.utils.build.SdkLevel.isAtLeastT; import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT; import static com.android.net.module.util.netlink.NetlinkConstants.IFF_LOWER_UP; import static com.android.net.module.util.netlink.NetlinkConstants.RTM_F_CLONED; @@ -29,7 +28,7 @@ import static com.android.net.module.util.netlink.NetlinkConstants.RTPROT_RA; import static com.android.net.module.util.netlink.NetlinkConstants.RT_SCOPE_UNIVERSE; import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_ACCEPT_IPV6_LINK_LOCAL_DNS_VERSION; -import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_PARSE_NETLINK_EVENTS_VERSION; +import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_PARSE_NETLINK_EVENTS_FORCE_DISABLE; import android.app.AlarmManager; import android.content.Context; @@ -190,8 +189,8 @@ mDnsServerRepository = new DnsServerRepository(config.minRdnssLifetime); mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); mDependencies = deps; - mNetlinkEventParsingEnabled = deps.isFeatureEnabled(context, - IPCLIENT_PARSE_NETLINK_EVENTS_VERSION, isAtLeastT() /* default value */); + mNetlinkEventParsingEnabled = deps.isFeatureNotChickenedOut(context, + IPCLIENT_PARSE_NETLINK_EVENTS_FORCE_DISABLE); mNetlinkMonitor = new MyNetlinkMonitor(h, log, mTag); mHandler.post(() -> { if (!mNetlinkMonitor.start()) { @@ -205,8 +204,8 @@ } private boolean isIpv6LinkLocalDnsAccepted() { - return mDependencies.isFeatureEnabled(mContext, - IPCLIENT_ACCEPT_IPV6_LINK_LOCAL_DNS_VERSION, true /* default value */); + return mDependencies.isFeatureNotChickenedOut(mContext, + IPCLIENT_ACCEPT_IPV6_LINK_LOCAL_DNS_VERSION); } private void maybeLog(String operation, String iface, LinkAddress address) {
diff --git a/src/android/net/ip/IpReachabilityMonitor.java b/src/android/net/ip/IpReachabilityMonitor.java index ff6d65e..e252a68 100644 --- a/src/android/net/ip/IpReachabilityMonitor.java +++ b/src/android/net/ip/IpReachabilityMonitor.java
@@ -20,11 +20,9 @@ import static android.net.metrics.IpReachabilityEvent.NUD_FAILED_ORGANIC; import static android.net.metrics.IpReachabilityEvent.PROVISIONING_LOST; import static android.net.metrics.IpReachabilityEvent.PROVISIONING_LOST_ORGANIC; -import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; import static com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION; import static com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION; -import static com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_MCAST_RESOLICIT_VERSION; import android.content.Context; import android.net.ConnectivityManager; @@ -182,7 +180,8 @@ public interface Dependencies { void acquireWakeLock(long durationMs); IpNeighborMonitor makeIpNeighborMonitor(Handler h, SharedLog log, NeighborEventConsumer cb); - boolean isFeatureEnabled(Context context, String name, boolean defaultEnabled); + boolean isFeatureEnabled(Context context, String name); + boolean isFeatureNotChickenedOut(Context context, String name); IpReachabilityMonitorMetrics getIpReachabilityMonitorMetrics(); static Dependencies makeDefault(Context context, String iface) { @@ -200,10 +199,12 @@ return new IpNeighborMonitor(h, log, cb); } - public boolean isFeatureEnabled(final Context context, final String name, - boolean defaultEnabled) { - return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name, - defaultEnabled); + public boolean isFeatureEnabled(final Context context, final String name) { + return DeviceConfigUtils.isNetworkStackFeatureEnabled(context, name); + } + + public boolean isFeatureNotChickenedOut(final Context context, final String name) { + return DeviceConfigUtils.isNetworkStackFeatureNotChickenedOut(context, name); } public IpReachabilityMonitorMetrics getIpReachabilityMonitorMetrics() { @@ -234,7 +235,6 @@ private int mInterSolicitIntervalMs; @NonNull private final Callback mCallback; - private final boolean mMulticastResolicitEnabled; private final boolean mIgnoreIncompleteIpv6DnsServerEnabled; private final boolean mIgnoreIncompleteIpv6DefaultRouterEnabled; @@ -258,14 +258,10 @@ mUsingMultinetworkPolicyTracker = usingMultinetworkPolicyTracker; mCm = context.getSystemService(ConnectivityManager.class); mDependencies = dependencies; - mMulticastResolicitEnabled = dependencies.isFeatureEnabled(context, - IP_REACHABILITY_MCAST_RESOLICIT_VERSION, true /* defaultEnabled */); - mIgnoreIncompleteIpv6DnsServerEnabled = dependencies.isFeatureEnabled(context, - IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION, - false /* defaultEnabled */); + mIgnoreIncompleteIpv6DnsServerEnabled = dependencies.isFeatureNotChickenedOut(context, + IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION); mIgnoreIncompleteIpv6DefaultRouterEnabled = dependencies.isFeatureEnabled(context, - IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION, - false /* defaultEnabled */); + IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION); mMetricsLog = metricsLog; mNetd = netd; Preconditions.checkNotNull(mNetd); @@ -274,10 +270,8 @@ // In case the overylaid parameters specify an invalid configuration, set the parameters // to the hardcoded defaults first, then set them to the values used in the steady state. try { - int numResolicits = mMulticastResolicitEnabled - ? NUD_MCAST_RESOLICIT_NUM - : INVALID_NUD_MCAST_RESOLICIT_NUM; - setNeighborParameters(MIN_NUD_SOLICIT_NUM, MIN_NUD_SOLICIT_INTERVAL_MS, numResolicits); + setNeighborParameters(MIN_NUD_SOLICIT_NUM, MIN_NUD_SOLICIT_INTERVAL_MS, + NUD_MCAST_RESOLICIT_NUM); } catch (Exception e) { Log.e(TAG, "Failed to adjust neighbor parameters with hardcoded defaults"); } @@ -414,8 +408,7 @@ private void handleNeighborReachable(@Nullable final NeighborEvent prev, @NonNull final NeighborEvent event) { - if (mMulticastResolicitEnabled - && hasDefaultRouterNeighborMacAddressChanged(prev, event)) { + if (hasDefaultRouterNeighborMacAddressChanged(prev, event)) { // This implies device has confirmed the neighbor's reachability from // other states(e.g., NUD_PROBE or NUD_STALE), checking if the mac // address hasn't changed is required. If Mac address does change, then @@ -581,8 +574,7 @@ private long getProbeWakeLockDuration() { final long gracePeriodMs = 500; - final int numSolicits = - mNumSolicits + (mMulticastResolicitEnabled ? NUD_MCAST_RESOLICIT_NUM : 0); + final int numSolicits = mNumSolicits + NUD_MCAST_RESOLICIT_NUM; return (long) (numSolicits * mInterSolicitIntervalMs) + gracePeriodMs; }
diff --git a/src/com/android/networkstack/arp/ArpPacket.java b/src/com/android/networkstack/arp/ArpPacket.java deleted file mode 100644 index a25d7bf..0000000 --- a/src/com/android/networkstack/arp/ArpPacket.java +++ /dev/null
@@ -1,171 +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.arp; - -import static android.system.OsConstants.ETH_P_ARP; -import static android.system.OsConstants.ETH_P_IP; - -import static com.android.net.module.util.NetworkStackConstants.ARP_ETHER_IPV4_LEN; -import static com.android.net.module.util.NetworkStackConstants.ARP_HWTYPE_ETHER; -import static com.android.net.module.util.NetworkStackConstants.ARP_REPLY; -import static com.android.net.module.util.NetworkStackConstants.ARP_REQUEST; -import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN; -import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_LEN; - -import android.net.MacAddress; - -import com.android.internal.annotations.VisibleForTesting; - -import java.net.Inet4Address; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.nio.BufferUnderflowException; -import java.nio.ByteBuffer; - -/** - * Defines basic data and operations needed to build and parse packets for the - * ARP protocol. - * - * @hide - */ -public class ArpPacket { - private static final String TAG = "ArpPacket"; - - public final short opCode; - public final Inet4Address senderIp; - public final Inet4Address targetIp; - public final MacAddress senderHwAddress; - public final MacAddress targetHwAddress; - - ArpPacket(short opCode, MacAddress senderHwAddress, Inet4Address senderIp, - MacAddress targetHwAddress, Inet4Address targetIp) { - this.opCode = opCode; - this.senderHwAddress = senderHwAddress; - this.senderIp = senderIp; - this.targetHwAddress = targetHwAddress; - this.targetIp = targetIp; - } - - /** - * Build an ARP packet from the required specified parameters. - */ - @VisibleForTesting - public static ByteBuffer buildArpPacket(final byte[] dstMac, final byte[] srcMac, - final byte[] targetIp, final byte[] targetHwAddress, byte[] senderIp, - final short opCode) { - final ByteBuffer buf = ByteBuffer.allocate(ARP_ETHER_IPV4_LEN); - - // Ether header - buf.put(dstMac); - buf.put(srcMac); - buf.putShort((short) ETH_P_ARP); - - // ARP header - buf.putShort((short) ARP_HWTYPE_ETHER); // hrd - buf.putShort((short) ETH_P_IP); // pro - buf.put((byte) ETHER_ADDR_LEN); // hln - buf.put((byte) IPV4_ADDR_LEN); // pln - buf.putShort(opCode); // op - buf.put(srcMac); // sha - buf.put(senderIp); // spa - buf.put(targetHwAddress); // tha - buf.put(targetIp); // tpa - buf.flip(); - return buf; - } - - /** - * Parse an ARP packet from an ByteBuffer object. - */ - @VisibleForTesting - public static ArpPacket parseArpPacket(final byte[] recvbuf, final int length) - throws ParseException { - try { - if (length < ARP_ETHER_IPV4_LEN || recvbuf.length < length) { - throw new ParseException("Invalid packet length: " + length); - } - - final ByteBuffer buffer = ByteBuffer.wrap(recvbuf, 0, length); - byte[] l2dst = new byte[ETHER_ADDR_LEN]; - byte[] l2src = new byte[ETHER_ADDR_LEN]; - buffer.get(l2dst); - buffer.get(l2src); - - final short etherType = buffer.getShort(); - if (etherType != ETH_P_ARP) { - throw new ParseException("Incorrect Ether Type: " + etherType); - } - - final short hwType = buffer.getShort(); - if (hwType != ARP_HWTYPE_ETHER) { - throw new ParseException("Incorrect HW Type: " + hwType); - } - - final short protoType = buffer.getShort(); - if (protoType != ETH_P_IP) { - throw new ParseException("Incorrect Protocol Type: " + protoType); - } - - final byte hwAddrLength = buffer.get(); - if (hwAddrLength != ETHER_ADDR_LEN) { - throw new ParseException("Incorrect HW address length: " + hwAddrLength); - } - - final byte ipAddrLength = buffer.get(); - if (ipAddrLength != IPV4_ADDR_LEN) { - throw new ParseException("Incorrect Protocol address length: " + ipAddrLength); - } - - final short opCode = buffer.getShort(); - if (opCode != ARP_REQUEST && opCode != ARP_REPLY) { - throw new ParseException("Incorrect opCode: " + opCode); - } - - byte[] senderHwAddress = new byte[ETHER_ADDR_LEN]; - byte[] senderIp = new byte[IPV4_ADDR_LEN]; - buffer.get(senderHwAddress); - buffer.get(senderIp); - - byte[] targetHwAddress = new byte[ETHER_ADDR_LEN]; - byte[] targetIp = new byte[IPV4_ADDR_LEN]; - buffer.get(targetHwAddress); - buffer.get(targetIp); - - return new ArpPacket(opCode, MacAddress.fromBytes(senderHwAddress), - (Inet4Address) InetAddress.getByAddress(senderIp), - MacAddress.fromBytes(targetHwAddress), - (Inet4Address) InetAddress.getByAddress(targetIp)); - } catch (IndexOutOfBoundsException e) { - throw new ParseException("Invalid index when wrapping a byte array into a buffer"); - } catch (BufferUnderflowException e) { - throw new ParseException("Invalid buffer position"); - } catch (IllegalArgumentException e) { - throw new ParseException("Invalid MAC address representation"); - } catch (UnknownHostException e) { - throw new ParseException("Invalid IP address of Host"); - } - } - - /** - * Thrown when parsing ARP packet failed. - */ - public static class ParseException extends Exception { - ParseException(String message) { - super(message); - } - } -}
diff --git a/src/com/android/networkstack/metrics/ApfSessionInfoMetrics.java b/src/com/android/networkstack/metrics/ApfSessionInfoMetrics.java new file mode 100644 index 0000000..32f7c10 --- /dev/null +++ b/src/com/android/networkstack/metrics/ApfSessionInfoMetrics.java
@@ -0,0 +1,188 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.networkstack.metrics; + +import android.net.apf.ApfCounterTracker.Counter; +import android.stats.connectivity.CounterName; + +import androidx.annotation.VisibleForTesting; + +/** + * Class to record the network stack ApfSessionInfo metrics into statsd. + * + * This class is not thread-safe, and should always be accessed from the same thread. + * + * @hide + */ +public class ApfSessionInfoMetrics { + // Define the maximum size of the counter list + public static final int MAX_NUM_OF_COUNTERS = Counter.class.getEnumConstants().length - 1; + private final ApfSessionInfoReported.Builder mStatsBuilder = + ApfSessionInfoReported.newBuilder(); + private final ApfCounterList.Builder mApfCounterListBuilder = ApfCounterList.newBuilder(); + + /** + * Write the version to mStatsBuilder. + */ + public void setVersion(final int version) { + mStatsBuilder.setVersion(version); + } + + /** + * Write the memory size to mStatsBuilder. + */ + public void setMemorySize(final int memorySize) { + mStatsBuilder.setMemorySize(memorySize); + } + + /** + * Add an APF counter to the metrics builder. + */ + public void addApfCounter(final Counter counter, final long value) { + if (mApfCounterListBuilder.getApfCounterCount() >= MAX_NUM_OF_COUNTERS) return; + final ApfCounter.Builder apfCounterBuilder = ApfCounter.newBuilder() + .setCounterName(apfFilterCounterToEnum(counter)) + .setCounterValue(value); + + mApfCounterListBuilder.addApfCounter(apfCounterBuilder); + } + + /** + * Write the session duration to mStatsBuilder. + */ + public void setApfSessionDurationSeconds(final int durationSeconds) { + mStatsBuilder.setApfSessionDurationSeconds(durationSeconds); + } + + /** + * Write the number of times APF program updated to mStatsBuilder. + */ + public void setNumOfTimesApfProgramUpdated(final int updatedTimes) { + mStatsBuilder.setNumOfTimesApfProgramUpdated(updatedTimes); + } + + /** + * Write the maximum program size to mStatsBuilder. + */ + public void setMaxProgramSize(final int programSize) { + mStatsBuilder.setMaxProgramSize(programSize); + } + + /** + * Write the ApfSessionInfoReported proto into statsd. + */ + public ApfSessionInfoReported statsWrite() { + mStatsBuilder.setApfCounterList(mApfCounterListBuilder); + final ApfSessionInfoReported stats = mStatsBuilder.build(); + final byte[] apfCounterList = stats.getApfCounterList().toByteArray(); + NetworkStackStatsLog.write(NetworkStackStatsLog.APF_SESSION_INFO_REPORTED, + stats.getVersion(), + stats.getMemorySize(), + apfCounterList, + stats.getApfSessionDurationSeconds(), + stats.getNumOfTimesApfProgramUpdated(), + stats.getMaxProgramSize()); + return stats; + } + + /** + * Map ApfCounterTracker.Counter to {@link CounterName}. + */ + @VisibleForTesting + public static CounterName apfFilterCounterToEnum(final Counter counter) { + switch(counter) { + case TOTAL_PACKETS: + return CounterName.CN_TOTAL_PACKETS; + case PASSED_ARP: + return CounterName.CN_PASSED_ARP; + case PASSED_DHCP: + return CounterName.CN_PASSED_DHCP; + case PASSED_IPV4: + return CounterName.CN_PASSED_IPV4; + case PASSED_IPV6_NON_ICMP: + return CounterName.CN_PASSED_IPV6_NON_ICMP; + case PASSED_IPV4_UNICAST: + return CounterName.CN_PASSED_IPV4_UNICAST; + case PASSED_IPV6_ICMP: + return CounterName.CN_PASSED_IPV6_ICMP; + case PASSED_IPV6_UNICAST_NON_ICMP: + return CounterName.CN_PASSED_IPV6_UNICAST_NON_ICMP; + // PASSED_ARP_NON_IPV4 and PASSED_ARP_UNKNOWN were deprecated in ApfFilter: + // PASSED_ARP_NON_IPV4 ==> DROPPED_ARP_NON_IPV4 + // PASSED_ARP_UNKNOWN ==> DROPPED_ARP_UNKNOWN + // They are not supported in the metrics. + case PASSED_ARP_NON_IPV4: + case PASSED_ARP_UNKNOWN: + return CounterName.CN_UNKNOWN; + case PASSED_ARP_UNICAST_REPLY: + return CounterName.CN_PASSED_ARP_UNICAST_REPLY; + case PASSED_NON_IP_UNICAST: + return CounterName.CN_PASSED_NON_IP_UNICAST; + case PASSED_MDNS: + return CounterName.CN_PASSED_MDNS; + case DROPPED_ETH_BROADCAST: + return CounterName.CN_DROPPED_ETH_BROADCAST; + case DROPPED_RA: + return CounterName.CN_DROPPED_RA; + case DROPPED_GARP_REPLY: + return CounterName.CN_DROPPED_GARP_REPLY; + case DROPPED_ARP_OTHER_HOST: + return CounterName.CN_DROPPED_ARP_OTHER_HOST; + case DROPPED_IPV4_L2_BROADCAST: + return CounterName.CN_DROPPED_IPV4_L2_BROADCAST; + case DROPPED_IPV4_BROADCAST_ADDR: + return CounterName.CN_DROPPED_IPV4_BROADCAST_ADDR; + case DROPPED_IPV4_BROADCAST_NET: + return CounterName.CN_DROPPED_IPV4_BROADCAST_NET; + case DROPPED_IPV4_MULTICAST: + return CounterName.CN_DROPPED_IPV4_MULTICAST; + case DROPPED_IPV6_ROUTER_SOLICITATION: + return CounterName.CN_DROPPED_IPV6_ROUTER_SOLICITATION; + case DROPPED_IPV6_MULTICAST_NA: + return CounterName.CN_DROPPED_IPV6_MULTICAST_NA; + case DROPPED_IPV6_MULTICAST: + return CounterName.CN_DROPPED_IPV6_MULTICAST; + case DROPPED_IPV6_MULTICAST_PING: + return CounterName.CN_DROPPED_IPV6_MULTICAST_PING; + case DROPPED_IPV6_NON_ICMP_MULTICAST: + return CounterName.CN_DROPPED_IPV6_NON_ICMP_MULTICAST; + case DROPPED_802_3_FRAME: + return CounterName.CN_DROPPED_802_3_FRAME; + case DROPPED_ETHERTYPE_DENYLISTED: + return CounterName.CN_DROPPED_ETHERTYPE_DENYLISTED; + case DROPPED_ARP_REPLY_SPA_NO_HOST: + return CounterName.CN_DROPPED_ARP_REPLY_SPA_NO_HOST; + case DROPPED_IPV4_KEEPALIVE_ACK: + return CounterName.CN_DROPPED_IPV4_KEEPALIVE_ACK; + case DROPPED_IPV6_KEEPALIVE_ACK: + return CounterName.CN_DROPPED_IPV6_KEEPALIVE_ACK; + case DROPPED_IPV4_NATT_KEEPALIVE: + return CounterName.CN_DROPPED_IPV4_NATT_KEEPALIVE; + case DROPPED_MDNS: + return CounterName.CN_DROPPED_MDNS; + case DROPPED_IPV4_TCP_PORT7_UNICAST: + // TODO: Not supported yet in the metrics backend. + return CounterName.CN_UNKNOWN; + case DROPPED_ARP_NON_IPV4: + return CounterName.CN_DROPPED_ARP_NON_IPV4; + case DROPPED_ARP_UNKNOWN: + return CounterName.CN_DROPPED_ARP_UNKNOWN; + default: + return CounterName.CN_UNKNOWN; + } + } +}
diff --git a/src/com/android/networkstack/metrics/IpClientRaInfoMetrics.java b/src/com/android/networkstack/metrics/IpClientRaInfoMetrics.java new file mode 100644 index 0000000..ba310ed --- /dev/null +++ b/src/com/android/networkstack/metrics/IpClientRaInfoMetrics.java
@@ -0,0 +1,94 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.networkstack.metrics; + +/** + * Class to record the network stack IpClientRaInfo metrics into statsd. + * + * This class is not thread-safe, and should always be accessed from the same thread. + * + * @hide + */ +public class IpClientRaInfoMetrics { + private final IpClientRaInfoReported.Builder mStatsBuilder = + IpClientRaInfoReported.newBuilder(); + + /** + * Write the maximum number of distinct RAs into mStatsBuilder. + */ + public void setMaxNumberOfDistinctRas(final int maxNum) { + mStatsBuilder.setMaxNumberOfDistinctRas(maxNum); + } + + /** + * Write the number of zero lifetime RAs into mStatsBuilder. + */ + public void setNumberOfZeroLifetimeRas(final int number) { + mStatsBuilder.setNumberOfZeroLifetimeRas(number); + } + + /** + * Write the number of parsing error RAs into mStatsBuilder. + */ + public void setNumberOfParsingErrorRas(final int number) { + mStatsBuilder.setNumberOfParsingErrorRas(number); + } + + /** + * Write the lowest router lifetime into mStatsBuilder. + */ + public void setLowestRouterLifetimeSeconds(final int lifetime) { + mStatsBuilder.setLowestRouterLifetimeSeconds(lifetime); + } + + /** + * Write the lowest valid lifetime of PIOs into mStatsBuilder. + */ + public void setLowestPioValidLifetimeSeconds(final long lifetime) { + mStatsBuilder.setLowestPioValidLifetimeSeconds(lifetime); + } + + /** + * Write the lowest route lifetime of RIOs into mStatsBuilder. + */ + public void setLowestRioRouteLifetimeSeconds(final long lifetime) { + mStatsBuilder.setLowestRioRouteLifetimeSeconds(lifetime); + } + + /** + * Write the lowest lifetime of RDNSSs into mStatsBuilder. + */ + public void setLowestRdnssLifetimeSeconds(final long lifetime) { + mStatsBuilder.setLowestRdnssLifetimeSeconds(lifetime); + } + + /** + * Write the IpClientRaInfoReported proto into statsd. + */ + public IpClientRaInfoReported statsWrite() { + final IpClientRaInfoReported stats = mStatsBuilder.build(); + NetworkStackStatsLog.write(NetworkStackStatsLog.IP_CLIENT_RA_INFO_REPORTED, + stats.getMaxNumberOfDistinctRas(), + stats.getNumberOfZeroLifetimeRas(), + stats.getNumberOfParsingErrorRas(), + stats.getLowestRouterLifetimeSeconds(), + stats.getLowestPioValidLifetimeSeconds(), + stats.getLowestRioRouteLifetimeSeconds(), + stats.getLowestRdnssLifetimeSeconds()); + return stats; + } +}
diff --git a/src/com/android/networkstack/metrics/stats.proto b/src/com/android/networkstack/metrics/stats.proto index c09f082..06419f9 100644 --- a/src/com/android/networkstack/metrics/stats.proto +++ b/src/com/android/networkstack/metrics/stats.proto
@@ -188,3 +188,73 @@ // NUD neighbor type, default gateway, DNS server or both. optional .android.stats.connectivity.NudNeighborType neighbor_type = 3; } + +/** + * Logs Ip client RA(Router Advertisement) info + * Logged from: + * packages/modules/NetworkStack/src/android/net/ip/IpClient.java + */ +message IpClientRaInfoReported { + // The maximum number of distinct RAs (Router Advertisements). + optional int32 max_number_of_distinct_ras = 1; + + // The number of zero lifetime RAs (Router Advertisements). + optional int32 number_of_zero_lifetime_ras = 2; + + // The number of parsing error for RAs (Router Advertisements). + optional int32 number_of_parsing_error_ras = 3; + + // The lowest router lifetime in seconds, excluding 0. + optional int32 lowest_router_lifetime_seconds = 4; + + // The lowest valid lifetime of PIO (Prefix Information Option) in seconds, excluding 0. + optional int64 lowest_pio_valid_lifetime_seconds = 5; + + // The lowest route lifetime of RIO (Route Information Option) in seconds, excluding 0. + optional int64 lowest_rio_route_lifetime_seconds = 6; + + // The lowest lifetime of RDNSS (Recursive DNS Server Option) in seconds, excluding 0. + optional int64 lowest_rdnss_lifetime_seconds = 7; +} + +/** + * Logs value of the APF counter. + */ +message ApfCounter { + // The name of APF counter. + optional .android.stats.connectivity.CounterName counter_name = 1; + + // The value of APF counter. + optional int64 counter_value = 2; +} + + +message ApfCounterList { + repeated ApfCounter apf_counter = 1; +} + +/** + * Logs APF session information event. + * Logged from: + * packages/modules/NetworkStack/src/android/net/apf/ApfFilter.java or + * packages/modules/NetworkStack/src/android/net/apf/LegacyApfFilter.java + */ +message ApfSessionInfoReported { + // The version of APF, where version = -1 equals APF disable. + optional int32 version = 1; + + // The memory size of APF module. + optional int32 memory_size = 2; + + // The values of all APF counters. + optional ApfCounterList apf_counter_list = 3; + + // The duration of APF session in seconds. + optional int32 apf_session_duration_seconds = 4; + + // Number of times APF program updated. + optional int32 num_of_times_apf_program_updated = 5; + + // Record the maximum of program size. + optional int32 max_program_size = 6; +}
diff --git a/src/com/android/networkstack/netlink/TcpInfo.java b/src/com/android/networkstack/netlink/TcpInfo.java index 31a408f..de450e9 100644 --- a/src/com/android/networkstack/netlink/TcpInfo.java +++ b/src/com/android/networkstack/netlink/TcpInfo.java
@@ -19,8 +19,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; - -import com.android.internal.annotations.VisibleForTesting; +import androidx.annotation.VisibleForTesting; import java.nio.BufferOverflowException; import java.nio.BufferUnderflowException; @@ -97,10 +96,35 @@ static final int SEGS_IN_OFFSET = getFieldOffset(Field.SEGS_IN); @VisibleForTesting static final int SEGS_OUT_OFFSET = getFieldOffset(Field.SEGS_OUT); + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + static final int TOTAL_RETRANS_OFFSET = getFieldOffset(Field.TOTAL_RETRANS); + /** + * This counts individual incoming packets that appeared on the wire, including: + * SYN, SYN-ACK, pure ACKs, data segments (after segmentation offload into small <=mtu + * packets), FIN, FIN-ACK, and any retransmits. + * + * This field is read from the tcpi_segs_in field from {@code struct tcp_info} + * in bionic/libc/kernel/uapi/linux/tcp.h. Also see [tcpEStatsPerfSegsIn] in the RFC4898. + */ final int mSegsIn; + /** + * This counts individual outgoing packets that have been sent to the network, including: + * SYN, SYN-ACK, pure ACKs, data segments (after segmentation offload into small <=mtu + * packets), FIN, FIN-ACK, and any retransmits. + * + * This field is read from the tcpi_segs_out field from {@code struct tcp_info} + * in bionic/libc/kernel/uapi/linux/tcp.h. Also see [tcpEStatsPerfSegsOut] in the RFC4898. + */ final int mSegsOut; - final int mLost; - final int mRetransmits; + /** + * This counts individual accumulated retransmitted packets that have been sent to the network, + * including any retransmits for SYN, SYN-ACK, pure ACKs, data segments (after segmentation + * offload into small <=mtu packets), FIN and FIN-ACK. + * + * This field is read from the tcpi_total_retrans field from {@code struct tcp_info} + * in bionic/libc/kernel/uapi/linux/tcp.h. + */ + final int mTotalRetrans; private static int getFieldOffset(@NonNull final Field needle) { int offset = 0; @@ -120,20 +144,18 @@ final int start = bytes.position(); mSegsIn = bytes.getInt(start + SEGS_IN_OFFSET); mSegsOut = bytes.getInt(start + SEGS_OUT_OFFSET); - mLost = bytes.getInt(start + LOST_OFFSET); - mRetransmits = bytes.get(start + RETRANSMITS_OFFSET); + mTotalRetrans = bytes.get(start + TOTAL_RETRANS_OFFSET); // tcp_info structure grows over time as new fields are added. Jump to the end of the // structure, as unknown fields might remain at the end of the structure if the tcp_info // struct was expanded. bytes.position(Math.min(infolen + start, bytes.limit())); } - @VisibleForTesting - TcpInfo(int retransmits, int lost, int segsOut, int segsIn) { - mRetransmits = retransmits; - mLost = lost; + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + TcpInfo(int segsOut, int segsIn, int totalRetrans) { mSegsOut = segsOut; mSegsIn = segsIn; + mTotalRetrans = totalRetrans; } /** Parse a TcpInfo from a giving ByteBuffer with a specific length. */ @@ -148,7 +170,8 @@ } } - private static String decodeWscale(byte num) { + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + static String decodeWscale(byte num) { return String.valueOf((num >> 4) & 0x0f) + ":" + String.valueOf(num & 0x0f); } @@ -180,17 +203,17 @@ TcpInfo other = (TcpInfo) obj; return mSegsIn == other.mSegsIn && mSegsOut == other.mSegsOut - && mRetransmits == other.mRetransmits && mLost == other.mLost; + && mTotalRetrans == other.mTotalRetrans; } @Override public int hashCode() { - return Objects.hash(mLost, mRetransmits, mSegsIn, mSegsOut); + return Objects.hash(mSegsIn, mSegsOut, mTotalRetrans); } @Override public String toString() { - return "TcpInfo{lost=" + mLost + ", retransmit=" + mRetransmits + ", received=" + mSegsIn - + ", sent=" + mSegsOut + "}"; + return "TcpInfo{received=" + mSegsIn + ", sent=" + mSegsOut + + ", totalRetrans=" + mTotalRetrans + "}"; } }
diff --git a/src/com/android/networkstack/netlink/TcpSocketTracker.java b/src/com/android/networkstack/netlink/TcpSocketTracker.java index c0a5e64..658fe8a 100644 --- a/src/com/android/networkstack/netlink/TcpSocketTracker.java +++ b/src/com/android/networkstack/netlink/TcpSocketTracker.java
@@ -19,27 +19,35 @@ import static android.net.util.DataStallUtils.CONFIG_TCP_PACKETS_FAIL_PERCENTAGE; import static android.net.util.DataStallUtils.DEFAULT_DATA_STALL_MIN_PACKETS_THRESHOLD; import static android.net.util.DataStallUtils.DEFAULT_TCP_PACKETS_FAIL_PERCENTAGE; +import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED; +import static android.os.PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED; import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; import static android.system.OsConstants.AF_INET; import static android.system.OsConstants.AF_INET6; import static android.system.OsConstants.SOL_SOCKET; import static android.system.OsConstants.SO_SNDTIMEO; +import static com.android.net.module.util.FeatureVersions.FEATURE_IS_UID_NETWORKING_BLOCKED; import static com.android.net.module.util.NetworkStackConstants.DNS_OVER_TLS_PORT; import static com.android.net.module.util.netlink.NetlinkConstants.NLMSG_DONE; import static com.android.net.module.util.netlink.NetlinkConstants.SOCKDIAG_MSG_HEADER_SIZE; import static com.android.net.module.util.netlink.NetlinkConstants.SOCK_DIAG_BY_FAMILY; import static com.android.net.module.util.netlink.NetlinkUtils.DEFAULT_RECV_BUFSIZE; import static com.android.net.module.util.netlink.NetlinkUtils.IO_TIMEOUT_MS; +import static com.android.networkstack.util.NetworkStackUtils.IGNORE_TCP_INFO_FOR_BLOCKED_UIDS; +import static com.android.networkstack.util.NetworkStackUtils.SKIP_TCP_POLL_IN_LIGHT_DOZE; +import android.annotation.TargetApi; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.net.ConnectivityManager; import android.net.INetd; import android.net.LinkProperties; import android.net.MarkMaskParcel; import android.net.Network; +import android.net.NetworkCapabilities; import android.os.AsyncTask; import android.os.Build; import android.os.IBinder; @@ -60,15 +68,15 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.DeviceConfigUtils; import com.android.net.module.util.SocketUtils; import com.android.net.module.util.netlink.InetDiagMessage; -import com.android.net.module.util.netlink.NetlinkConstants; import com.android.net.module.util.netlink.NetlinkUtils; import com.android.net.module.util.netlink.StructInetDiagMsg; +import com.android.net.module.util.netlink.StructNlAttr; import com.android.net.module.util.netlink.StructNlMsgHdr; import com.android.networkstack.apishim.NetworkShimImpl; -import com.android.networkstack.apishim.common.ShimUtils; import com.android.networkstack.apishim.common.UnsupportedApiLevelException; import java.io.FileDescriptor; @@ -125,6 +133,8 @@ private int mMinPacketsThreshold = DEFAULT_DATA_STALL_MIN_PACKETS_THRESHOLD; private int mTcpPacketsFailRateThreshold = DEFAULT_TCP_PACKETS_FAIL_PERCENTAGE; + // TODO: Remove doze mode solution since uid networking blocked traffic is filtered out by + // the info provided by bpf maps. private final Object mDozeModeLock = new Object(); @GuardedBy("mDozeModeLock") private boolean mInDozeMode = false; @@ -135,6 +145,13 @@ private boolean mInOpportunisticMode; @NonNull private LinkProperties mLinkProperties; + @NonNull + private NetworkCapabilities mNetworkCapabilities; + + private final boolean mShouldDisableInDeepDoze; + private final boolean mShouldDisableInLightDoze; + private final boolean mShouldIgnoreTcpInfoForBlockedUids; + private final ConnectivityManager mCm; @VisibleForTesting protected final DeviceConfig.OnPropertiesChangedListener mConfigListener = @@ -152,14 +169,32 @@ } }; + private boolean isDeviceIdleModeChangedAction(Intent intent) { + return mShouldDisableInDeepDoze + && ACTION_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction()); + } + + @TargetApi(Build.VERSION_CODES.TIRAMISU) + private boolean isDeviceLightIdleModeChangedAction(Intent intent) { + return mShouldDisableInLightDoze + && ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED.equals(intent.getAction()); + } + final BroadcastReceiver mDeviceIdleReceiver = new BroadcastReceiver() { @Override + @TargetApi(Build.VERSION_CODES.TIRAMISU) public void onReceive(Context context, Intent intent) { if (intent == null) return; - if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction())) { + if (isDeviceIdleModeChangedAction(intent) + || isDeviceLightIdleModeChangedAction(intent)) { final PowerManager powerManager = context.getSystemService(PowerManager.class); - final boolean deviceIdle = powerManager.isDeviceIdleMode(); + // For tcp polling mechanism, there is no difference between deep doze mode and + // light doze mode. The deep doze mode and light doze mode block networking + // for uids in the same way, use single variable to control. + final boolean deviceIdle = (mShouldDisableInDeepDoze + && powerManager.isDeviceIdleMode()) + || (mShouldDisableInLightDoze && powerManager.isDeviceLightIdleMode()); setDozeMode(deviceIdle); } } @@ -169,6 +204,15 @@ mDependencies = dps; mNetwork = network; mNetd = mDependencies.getNetd(); + mShouldIgnoreTcpInfoForBlockedUids = mDependencies.shouldIgnoreTcpInfoForBlockedUids(); + + // Previous workarounds can be disabled if the device supports ignore blocked uids feature. + // To prevent inconsistencies and issues like broadcast receiver leaks, the feature flags + // are fixed after being read. + // TODO: Remove these workarounds when pre-T devices are no longer supported. + mShouldDisableInLightDoze = mDependencies.shouldDisableInLightDoze( + mShouldIgnoreTcpInfoForBlockedUids); + mShouldDisableInDeepDoze = !mShouldIgnoreTcpInfoForBlockedUids; // If the parcel is null, nothing should be matched which is achieved by the combination of // {@code NetlinkUtils#NULL_MASK} and {@code NetlinkUtils#UNKNOWN_MARK}. @@ -176,16 +220,15 @@ mNetworkMark = (parcel != null) ? parcel.mark : NetlinkUtils.UNKNOWN_MARK; mNetworkMask = (parcel != null) ? parcel.mask : NetlinkUtils.NULL_MASK; - // Request tcp info from NetworkStack directly needs extra SELinux permission added after Q - // release. - if (!mDependencies.isTcpInfoParsingSupported()) return; // Build SocketDiag messages. for (final int family : ADDRESS_FAMILIES) { mSockDiagMsg.put( family, InetDiagMessage.buildInetDiagReqForAliveTcpSockets(family)); } mDependencies.addDeviceConfigChangedListener(mConfigListener); - mDependencies.addDeviceIdleReceiver(mDeviceIdleReceiver); + mDependencies.addDeviceIdleReceiver(mDeviceIdleReceiver, mShouldDisableInDeepDoze, + mShouldDisableInLightDoze); + mCm = mDependencies.getContext().getSystemService(ConnectivityManager.class); } @Nullable @@ -194,9 +237,9 @@ final int netId = NetworkShimImpl.newInstance(mNetwork).getNetId(); return mNetd.getFwmarkForNetwork(netId); } catch (UnsupportedApiLevelException e) { - log("Get netId is not available in this API level."); + logd("Get netId is not available in this API level."); } catch (RemoteException e) { - Log.e(TAG, "Error getting fwmark for network, ", e); + loge("Error getting fwmark for network, ", e); } return null; } @@ -208,7 +251,6 @@ * @Return if this polling request is sent to kernel and executes successfully or not. */ public boolean pollSocketsInfo() { - if (!mDependencies.isTcpInfoParsingSupported()) return false; // Traffic will be restricted in doze mode. TCP info may not reflect the correct network // behavior. // TODO: Traffic may be restricted by other reason. Get the restriction info from bpf in T+. @@ -227,12 +269,13 @@ mDependencies.sendPollingRequest(fd, mSockDiagMsg.get(family)); while (parseMessage(mDependencies.recvMessage(fd), family, newSocketInfoList, time)) { - log("Pending info exist. Attempt to read more"); + logd("Pending info exist. Attempt to read more"); } } // Append TcpStats based on previous and current socket info. final TcpStat stat = new TcpStat(); + final ArrayList<Integer> skippedBlockedUids = new ArrayList<>(); mLatestReportedUids.clear(); for (final SocketInfo newInfo : newSocketInfoList) { final TcpStat diff = calculateLatestPacketsStat(newInfo, @@ -252,24 +295,41 @@ continue; } + if (mShouldIgnoreTcpInfoForBlockedUids) { + // For backward-compatibility, NET_CAPABILITY_TEMPORARILY_NOT_METERED + // is not referenced when deciding meteredness in NetworkPolicyManagerService. + // Thus, whether to block metered networking should only be judged with + // NET_CAPABILITY_NOT_METERED. + final boolean metered = !mNetworkCapabilities.hasCapability( + NetworkCapabilities.NET_CAPABILITY_NOT_METERED); + final boolean uidBlocked = mCm.isUidNetworkingBlocked(newInfo.uid, metered); + if (uidBlocked) { + skippedBlockedUids.add(newInfo.uid); + continue; + } + } + if (diff != null) { mLatestReportedUids.add(newInfo.uid); stat.accumulate(diff); } } + if (!skippedBlockedUids.isEmpty()) { + logd("Skip blocked uids: " + skippedBlockedUids); + } // Calculate mLatestReceiveCount, mSentSinceLastRecv and mLatestPacketFailPercentage. mSentSinceLastRecv = (stat.receivedCount == 0) ? (mSentSinceLastRecv + stat.sentCount) : 0; mLatestReceivedCount = stat.receivedCount; mLatestPacketFailPercentage = ((stat.sentCount != 0) - ? ((stat.retransmitCount + stat.lostCount) * 100 / stat.sentCount) : 0); + ? (stat.retransCount * 100 / stat.sentCount) : 0); // Remove out-of-date socket info. cleanupSocketInfo(time); return true; } catch (ErrnoException | SocketException | InterruptedIOException e) { - Log.e(TAG, "Fail to get TCP info via netlink.", e); + loge("Fail to get TCP info via netlink.", e); } finally { SocketUtils.closeSocketQuietly(fd); } @@ -283,11 +343,11 @@ // Return true if there are more pending messages to read @VisibleForTesting - static boolean parseMessage(ByteBuffer bytes, int family, + boolean parseMessage(ByteBuffer bytes, int family, ArrayList<SocketInfo> outputSocketInfoList, long time) { if (!NetlinkUtils.enoughBytesRemainForValidNlMsg(bytes)) { // This is unlikely to happen in real cases. Check this first for testing. - Log.e(TAG, "Size is less than header size. Ignored."); + loge("Size is less than header size. Ignored."); return false; } @@ -321,12 +381,12 @@ outputSocketInfoList.add(info); } while (NetlinkUtils.enoughBytesRemainForValidNlMsg(bytes)); } catch (IllegalArgumentException | BufferUnderflowException e) { - Log.wtf(TAG, "Unexpected socket info parsing, family " + family + logwtf("Unexpected socket info parsing, family " + family + " buffer:" + bytes + " " + Base64.getEncoder().encodeToString(bytes.array()), e); return false; } catch (IllegalStateException e) { - Log.e(TAG, "Unexpected socket info parsing, family " + family + loge("Unexpected socket info parsing, family " + family + " buffer:" + bytes + " " + Base64.getEncoder().encodeToString(bytes.array()), e); return false; @@ -335,21 +395,21 @@ return true; } - private static int getLengthAndVerifyMsgHeader(@NonNull ByteBuffer bytes, int family) { + private int getLengthAndVerifyMsgHeader(@NonNull ByteBuffer bytes, int family) { final StructNlMsgHdr nlmsghdr = StructNlMsgHdr.parse(bytes); if (nlmsghdr == null) { - Log.e(TAG, "Badly formatted data."); + loge("Badly formatted data."); return END_OF_PARSING; } - log("pollSocketsInfo: nlmsghdr=" + nlmsghdr + ", limit=" + bytes.limit()); + logd("pollSocketsInfo: nlmsghdr=" + nlmsghdr + ", limit=" + bytes.limit()); // End of the message. Stop parsing. if (nlmsghdr.nlmsg_type == NLMSG_DONE) { return END_OF_PARSING; } if (nlmsghdr.nlmsg_type != SOCK_DIAG_BY_FAMILY) { - Log.e(TAG, "Expect to get family " + family + loge("Expect to get family " + family + " SOCK_DIAG_BY_FAMILY message but get " + nlmsghdr.nlmsg_type); return END_OF_PARSING; @@ -374,7 +434,7 @@ /** Parse a {@code SocketInfo} from the given position of the given byte buffer. */ @NonNull - private static SocketInfo parseSockInfo(@NonNull final ByteBuffer bytes, final int family, + private SocketInfo parseSockInfo(@NonNull final ByteBuffer bytes, final int family, final int nlmsgLen, final long time, final int uid, final long cookie, final int dstPort) { final int remainingDataSize = bytes.position() + nlmsgLen - SOCKDIAG_MSG_HEADER_SIZE; @@ -382,22 +442,17 @@ int mark = NetlinkUtils.INIT_MARK_VALUE; // Get a tcp_info. while (bytes.position() < remainingDataSize) { - final RoutingAttribute rtattr = - new RoutingAttribute(bytes.getShort(), bytes.getShort()); - final short dataLen = rtattr.getDataLength(); - if (rtattr.rtaType == NetlinkUtils.INET_DIAG_INFO) { - tcpInfo = TcpInfo.parse(bytes, dataLen); - } else if (rtattr.rtaType == NetlinkUtils.INET_DIAG_MARK) { - mark = bytes.getInt(); - } else { - // Data provided by kernel will include both valid data and padding data. The data - // len provided from kernel indicates the valid data size. Readers must deduce the - // alignment by themselves. - skipRemainingAttributesBytesAligned(bytes, dataLen); + final StructNlAttr nlattr = StructNlAttr.parse(bytes); + if (nlattr == null) break; + + if (nlattr.nla_type == NetlinkUtils.INET_DIAG_MARK) { + mark = nlattr.getValueAsInteger(); + } else if (nlattr.nla_type == NetlinkUtils.INET_DIAG_INFO) { + tcpInfo = TcpInfo.parse(nlattr.getValueAsByteBuffer(), nlattr.getAlignedLength()); } } final SocketInfo info = new SocketInfo(tcpInfo, family, mark, time, uid, cookie, dstPort); - log("parseSockInfo, " + info); + logd("parseSockInfo, " + info); return info; } @@ -407,8 +462,6 @@ * statemachine thread of NetworkMonitor. */ public boolean isDataStallSuspected() { - if (!mDependencies.isTcpInfoParsingSupported()) return false; - // Skip checking data stall since the traffic will be restricted and it will not be real // network stall. // TODO: Traffic may be restricted by other reason. Get the restriction info from bpf in T+. @@ -417,7 +470,7 @@ } final boolean ret = (getLatestPacketFailPercentage() >= getTcpPacketsFailRateThreshold()); if (ret) { - Log.d(TAG, "data stall suspected, uids: " + mLatestReportedUids.toString()); + log("data stall suspected, uids: " + mLatestReportedUids.toString()); } return ret; } @@ -433,22 +486,20 @@ } if (current.tcpInfo == null) { - log("Current tcpInfo is null."); + logd("Current tcpInfo is null."); return null; } stat.sentCount = current.tcpInfo.mSegsOut; stat.receivedCount = current.tcpInfo.mSegsIn; - stat.lostCount = current.tcpInfo.mLost; - stat.retransmitCount = current.tcpInfo.mRetransmits; + stat.retransCount = current.tcpInfo.mTotalRetrans; if (previous != null && previous.tcpInfo != null) { stat.sentCount -= previous.tcpInfo.mSegsOut; stat.receivedCount -= previous.tcpInfo.mSegsIn; - stat.lostCount -= previous.tcpInfo.mLost; - stat.retransmitCount -= previous.tcpInfo.mRetransmits; + stat.retransCount -= previous.tcpInfo.mTotalRetrans; } - log("calculateLatestPacketsStat, stat:" + stat); + logd("calculateLatestPacketsStat, stat:" + stat); return stat; } @@ -458,7 +509,6 @@ * @return the latest packet fail percentage. -1 denotes that there is no available data. */ public int getLatestPacketFailPercentage() { - if (!mDependencies.isTcpInfoParsingSupported()) return -1; // Only return fail rate if device sent enough packets. if (getSentSinceLastRecv() < getMinPacketsThreshold()) return -1; return mLatestPacketFailPercentage; @@ -469,13 +519,11 @@ * between each polling period, not an accurate number. */ public int getSentSinceLastRecv() { - if (!mDependencies.isTcpInfoParsingSupported()) return -1; return mSentSinceLastRecv; } /** Return the number of the packets received in the latest polling cycle. */ public int getLatestReceivedCount() { - if (!mDependencies.isTcpInfoParsingSupported()) return -1; return mLatestReceivedCount; } @@ -491,67 +539,31 @@ return mTcpPacketsFailRateThreshold; } - /** - * Method to skip the remaining attributes bytes. - * Corresponds to NLMSG_NEXT in bionic/libc/kernel/uapi/linux/netlink.h. - * - * @param buffer the target ByteBuffer - * @param len the remaining length to skip. - */ - private static void skipRemainingAttributesBytesAligned(@NonNull final ByteBuffer buffer, - final short len) { - // Data in {@Code RoutingAttribute} is followed after header with size {@Code NLA_ALIGNTO} - // bytes long for each block. Next attribute will start after the padding bytes if any. - // If all remaining bytes after header are valid in a data block, next attr will just start - // after valid bytes. - // - // E.g. With NLA_ALIGNTO(4), an attr struct with length 5 means 1 byte valid data remains - // after header and 3(4-1) padding bytes. Next attr with length 8 will start after the - // padding bytes and contain 4(8-4) valid bytes of data. The next attr start after the - // valid bytes, like: - // - // [HEADER(L=5)][ 4-Bytes DATA ][ HEADER(L=8) ][4 bytes DATA][Next attr] - // [ 5 valid bytes ][3 padding bytes ][ 8 valid bytes ] ... - final int cur = buffer.position(); - buffer.position(cur + NetlinkConstants.alignedLengthOf(len)); + private void logd(final String str) { + if (DBG) log(str); } - private static void log(final String str) { - if (DBG) Log.d(TAG, str); + private void log(final String s) { + Log.d(TAG + "/" + mNetwork.toString(), s); + } + + private void loge(final String str) { + loge(str, null /* tr */); + } + + private void loge(final String str, @Nullable Throwable tr) { + Log.e(TAG + "/" + mNetwork.toString(), str, tr); + } + + private void logwtf(final String str, @Nullable Throwable tr) { + Log.wtf(TAG + "/" + mNetwork.toString(), str, tr); } /** Stops monitoring and releases resources. */ public void quit() { - // Do not need to unregister receiver and listener since registration is skipped - // in the constructor. - if (!mDependencies.isTcpInfoParsingSupported()) return; - mDependencies.removeDeviceConfigChangedListener(mConfigListener); - mDependencies.removeBroadcastReceiver(mDeviceIdleReceiver); - } - - /** - * Corresponds to {@code struct rtattr} from bionic/libc/kernel/uapi/linux/rtnetlink.h - * - * struct rtattr { - * unsigned short rta_len; // Length of option - * unsigned short rta_type; // Type of option - * // Data follows - * }; - */ - static class RoutingAttribute { - public static final int HEADER_LENGTH = 4; - - public final short rtaLen; // The whole valid size of the struct. - public final short rtaType; - - RoutingAttribute(final short len, final short type) { - rtaLen = len; - rtaType = type; - } - public short getDataLength() { - return (short) (rtaLen - HEADER_LENGTH); - } + mDependencies.removeBroadcastReceiver(mDeviceIdleReceiver, + mShouldDisableInDeepDoze, mShouldDisableInLightDoze); } /** @@ -609,23 +621,21 @@ * */ private class TcpStat { public int sentCount; - public int lostCount; - public int retransmitCount; public int receivedCount; + public int retransCount; void accumulate(@Nullable final TcpStat stat) { if (stat == null) return; sentCount += stat.sentCount; - lostCount += stat.lostCount; receivedCount += stat.receivedCount; - retransmitCount += stat.retransmitCount; + retransCount += stat.retransCount; } @Override public String toString() { - return "TcpStat {sent=" + sentCount + ", lost=" + lostCount - + ", retransmit=" + retransmitCount + ", received=" + receivedCount + "}"; + return "TcpStat {sent=" + sentCount + ", retransCount=" + retransCount + + ", received=" + receivedCount + "}"; } } @@ -633,7 +643,7 @@ synchronized (mDozeModeLock) { if (mInDozeMode == isEnabled) return; mInDozeMode = isEnabled; - log("Doze mode enabled=" + mInDozeMode); + logd("Doze mode enabled=" + mInDozeMode); } } @@ -641,13 +651,17 @@ if (mInOpportunisticMode == isEnabled) return; mInOpportunisticMode = isEnabled; - log("Private DNS Opportunistic mode enabled=" + mInOpportunisticMode); + logd("Private DNS Opportunistic mode enabled=" + mInOpportunisticMode); } public void setLinkProperties(@NonNull LinkProperties lp) { mLinkProperties = lp; } + public void setNetworkCapabilities(@NonNull NetworkCapabilities caps) { + mNetworkCapabilities = caps; + } + /** * Dependencies class for testing. */ @@ -667,7 +681,7 @@ */ public FileDescriptor connectToKernel() throws ErrnoException, SocketException { final FileDescriptor fd = NetlinkUtils.createNetLinkInetDiagSocket(); - NetlinkUtils.connectSocketToNetlink(fd); + NetlinkUtils.connectToKernel(fd); Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(IO_TIMEOUT_MS)); return fd; @@ -699,15 +713,6 @@ } /** - * Return if request tcp info via netlink socket is supported or not. - */ - public boolean isTcpInfoParsingSupported() { - // Request tcp info from NetworkStack directly needs extra SELinux permission added - // after Q release. - return ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q); - } - - /** * Receive the request message from kernel via given fd. */ public ByteBuffer recvMessage(@NonNull final FileDescriptor fd) @@ -741,14 +746,56 @@ } /** Add receiver for detecting doze mode change to control TCP detection. */ - public void addDeviceIdleReceiver(@NonNull final BroadcastReceiver receiver) { - mContext.registerReceiver(receiver, - new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)); + @TargetApi(Build.VERSION_CODES.TIRAMISU) + public void addDeviceIdleReceiver(@NonNull final BroadcastReceiver receiver, + boolean shouldDisableInDeepDoze, boolean shouldDisableInLightDoze) { + // No need to register receiver if no related feature is enabled. + if (!shouldDisableInDeepDoze && !shouldDisableInLightDoze) return; + + final IntentFilter intentFilter = new IntentFilter(); + if (shouldDisableInDeepDoze) { + intentFilter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED); + } + if (shouldDisableInLightDoze) { + intentFilter.addAction(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED); + } + mContext.registerReceiver(receiver, intentFilter); } /** Remove broadcast receiver. */ - public void removeBroadcastReceiver(@NonNull final BroadcastReceiver receiver) { + public void removeBroadcastReceiver(@NonNull final BroadcastReceiver receiver, + boolean shouldDisableInDeepDoze, boolean shouldDisableInLightDoze) { + if (!shouldDisableInDeepDoze && !shouldDisableInLightDoze) return; mContext.unregisterReceiver(receiver); } + + /** + * Get whether polling should be disabled in light doze mode. This method should + * only be called once in the constructor, to ensure that the code does not need + * to deal with flag values changing at runtime. + */ + @TargetApi(Build.VERSION_CODES.TIRAMISU) + public boolean shouldDisableInLightDoze(boolean ignoreBlockedUidsSupported) { + // Light doze mode status checking API is only available at T or later releases. + if (!SdkLevel.isAtLeastT()) return false; + + // Disable light doze mode design is replaced by ignoring blocked uids design. + if (ignoreBlockedUidsSupported) return false; + + return DeviceConfigUtils.isNetworkStackFeatureNotChickenedOut( + mContext, SKIP_TCP_POLL_IN_LIGHT_DOZE); + } + + /** + * Get whether the ignore Tcp info for blocked uids is supported. This method should + * only be called once in the constructor, to ensure that the code does not need + * to deal with flag values changing at runtime. + */ + public boolean shouldIgnoreTcpInfoForBlockedUids() { + return SdkLevel.isAtLeastT() && DeviceConfigUtils.isFeatureSupported( + mContext, FEATURE_IS_UID_NETWORKING_BLOCKED) + && DeviceConfigUtils.isNetworkStackFeatureNotChickenedOut(mContext, + IGNORE_TCP_INFO_FOR_BLOCKED_UIDS); + } } }
diff --git a/src/com/android/networkstack/util/NetworkStackUtils.java b/src/com/android/networkstack/util/NetworkStackUtils.java index 8559a26..829d0c6 100755 --- a/src/com/android/networkstack/util/NetworkStackUtils.java +++ b/src/com/android/networkstack/util/NetworkStackUtils.java
@@ -34,7 +34,6 @@ import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; -import java.net.SocketException; import java.net.UnknownHostException; /** @@ -152,21 +151,6 @@ new String [] {"https://www.google.com/generate_204"}; /** - * @deprecated Considering boolean experiment flag is likely to cause misconfiguration - * particularly when NetworkStack module rolls back to previous version. It's - * much safer to determine whether or not to enable one specific experimental - * feature by comparing flag version with module version. - */ - @Deprecated - public static final String DHCP_INIT_REBOOT_ENABLED = "dhcp_init_reboot_enabled"; - - /** - * @deprecated See above explanation. - */ - @Deprecated - public static final String DHCP_RAPID_COMMIT_ENABLED = "dhcp_rapid_commit_enabled"; - - /** * Minimum module version at which to enable the DHCP INIT-REBOOT state. */ public static final String DHCP_INIT_REBOOT_VERSION = "dhcp_init_reboot_version"; @@ -182,12 +166,6 @@ public static final String DHCP_IP_CONFLICT_DETECT_VERSION = "dhcp_ip_conflict_detect_version"; /** - * Minimum module version at which to enable the IPv6-Only preferred option. - */ - public static final String DHCP_IPV6_ONLY_PREFERRED_VERSION = - "dhcp_ipv6_only_preferred_version"; - - /** * Minimum module version at which to enable slow DHCP retransmission approach in renew/rebind * state suggested in RFC2131 section 4.4.5. */ @@ -219,13 +197,6 @@ public static final String IPCLIENT_GRATUITOUS_NA_VERSION = "ipclient_gratuitous_na_version"; /** - * Experiment flag to send multicast NS from the global IPv6 GUA to the solicited-node - * multicast address based on the default router's IPv6 link-local address, which helps - * flush the first-hop routers' neighbor cache entry for the global IPv6 GUA. - */ - public static final String IPCLIENT_MULTICAST_NS_VERSION = "ipclient_multicast_ns_version"; - - /** * Experiment flag to enable sending Gratuitous APR and Gratuitous Neighbor Advertisement for * all assigned IPv4 and IPv6 GUAs after completing L2 roaming. */ @@ -233,13 +204,6 @@ "ipclient_garp_na_roaming_version"; /** - * Experiment flag to enable parsing netlink events from kernel directly instead from netd aidl - * interface. - */ - public static final String IPCLIENT_PARSE_NETLINK_EVENTS_VERSION = - "ipclient_parse_netlink_events_version"; - - /** * Experiment flag to check if an on-link IPv6 link local DNS is acceptable. The default flag * value is true, just add this flag for A/B testing to see if this fix works as expected via * experiment rollout. @@ -248,19 +212,6 @@ "ipclient_accept_ipv6_link_local_dns_version"; /** - * Experiment flag to disable accept_ra parameter when IPv6 provisioning loss happens due to - * the default route has gone. - */ - public static final String IPCLIENT_DISABLE_ACCEPT_RA_VERSION = "ipclient_disable_accept_ra"; - - /** - * Experiment flag to enable "mcast_resolicit" neighbor parameter in IpReachabilityMonitor, - * set it to 3 by default. - */ - public static final String IP_REACHABILITY_MCAST_RESOLICIT_VERSION = - "ip_reachability_mcast_resolicit_version"; - - /** * Experiment flag to attempt to ignore the on-link IPv6 DNS server which fails to respond to * address resolution. */ @@ -275,18 +226,59 @@ "ip_reachability_ignore_incompleted_ipv6_default_router_version"; /** - * Experiment flag to use the RA lifetime calculation fix in aosp/2276160. It can be disabled - * if OEM finds additional battery usage and want to use the old buggy behavior again. - */ - public static final String APF_USE_RA_LIFETIME_CALCULATION_FIX_VERSION = - "apf_use_ra_lifetime_calculation_fix_version"; - - /** * Experiment flag to enable DHCPv6 Prefix Delegation(RFC8415) in IpClient. */ public static final String IPCLIENT_DHCPV6_PREFIX_DELEGATION_VERSION = "ipclient_dhcpv6_prefix_delegation_version"; + /** + * Experiment flag to enable new ra filter. + */ + public static final String APF_NEW_RA_FILTER_VERSION = "apf_new_ra_filter_version"; + /** + * Experiment flag to enable the feature of polling counters in Apf. + */ + public static final String APF_POLLING_COUNTERS_VERSION = "apf_polling_counters_version"; + /** + * Experiment flag to enable the feature of ignoring any individual RA section with lifetime + * below accept_ra_min_lft sysctl. + */ + public static final String IPCLIENT_IGNORE_LOW_RA_LIFETIME_VERSION = + "ipclient_ignore_low_ra_lifetime_version"; + + /**** BEGIN Feature Kill Switch Flags ****/ + + /** + * Kill switch flag to disable the feature of parsing netlink events from kernel directly + * instead from netd aidl interface by flag push. + */ + public static final String IPCLIENT_PARSE_NETLINK_EVENTS_FORCE_DISABLE = + "ipclient_parse_netlink_events_force_disable"; + + /** + * Kill switch flag to disable the feature of handle light doze mode in Apf. + */ + public static final String APF_HANDLE_LIGHT_DOZE_FORCE_DISABLE = + "apf_handle_light_doze_force_disable"; + + /** + * Kill switch flag to disable the feature of skipping Tcp socket info polling when light + * doze mode is enabled. + */ + public static final String SKIP_TCP_POLL_IN_LIGHT_DOZE = "skip_tcp_poll_in_light_doze_mode"; + + /** + * Experiment flag to enable the feature of re-evaluate when network resumes. + */ + public static final String REEVALUATE_WHEN_RESUME = "reevaluate_when_resume"; + + /** + * Kill switch flag to disable the feature of ignoring Tcp socket info for uids which + * networking are blocked. + */ + public static final String IGNORE_TCP_INFO_FOR_BLOCKED_UIDS = + "ignore_tcp_info_for_blocked_uids"; + static { System.loadLibrary("networkstackutilsjni"); } @@ -385,10 +377,8 @@ /** * Attaches a socket filter that accepts ICMPv6 router advertisements to the given socket. * @param fd the socket's {@link FileDescriptor}. - * @param packetType the hardware address type, one of ARPHRD_*. */ - public static native void attachRaFilter(FileDescriptor fd, int packetType) - throws SocketException; + public static native void attachRaFilter(FileDescriptor fd) throws ErrnoException; /** * Attaches a socket filter that accepts L2-L4 signaling traffic required for IP connectivity. @@ -396,10 +386,8 @@ * This includes: all ARP, ICMPv6 RS/RA/NS/NA messages, and DHCPv4 exchanges. * * @param fd the socket's {@link FileDescriptor}. - * @param packetType the hardware address type, one of ARPHRD_*. */ - public static native void attachControlPacketFilter(FileDescriptor fd, int packetType) - throws SocketException; + public static native void attachControlPacketFilter(FileDescriptor fd) throws ErrnoException; /** * Add an entry into the ARP cache.
diff --git a/src/com/android/server/NetworkStackService.java b/src/com/android/server/NetworkStackService.java index 368a6d4..40aee28 100644 --- a/src/com/android/server/NetworkStackService.java +++ b/src/com/android/server/NetworkStackService.java
@@ -21,11 +21,15 @@ import static android.net.dhcp.IDhcpServer.STATUS_UNKNOWN_ERROR; import static com.android.net.module.util.DeviceConfigUtils.getResBooleanConfig; +import static com.android.net.module.util.FeatureVersions.FEATURE_IS_UID_NETWORKING_BLOCKED; +import static com.android.networkstack.util.NetworkStackUtils.IGNORE_TCP_INFO_FOR_BLOCKED_UIDS; +import static com.android.networkstack.util.NetworkStackUtils.SKIP_TCP_POLL_IN_LIGHT_DOZE; import static com.android.server.util.PermissionUtil.checkDumpPermission; import android.app.Service; import android.content.Context; import android.content.Intent; +import android.net.ConnectivityManager; import android.net.IIpMemoryStore; import android.net.IIpMemoryStoreCallbacks; import android.net.INetd; @@ -49,6 +53,7 @@ import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; +import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.text.TextUtils; import android.util.ArraySet; @@ -59,6 +64,8 @@ import com.android.internal.annotations.GuardedBy; 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.SharedLog; import com.android.networkstack.NetworkStackNotifier; import com.android.networkstack.R; @@ -435,6 +442,20 @@ return; } + pw.println("Device Configs:"); + pw.increaseIndent(); + pw.println("SKIP_TCP_POLL_IN_LIGHT_DOZE=" + + DeviceConfigUtils.isNetworkStackFeatureNotChickenedOut( + mContext, SKIP_TCP_POLL_IN_LIGHT_DOZE)); + pw.println("FEATURE_IS_UID_NETWORKING_BLOCKED=" + DeviceConfigUtils.isFeatureSupported( + mContext, FEATURE_IS_UID_NETWORKING_BLOCKED)); + pw.println("IGNORE_TCP_INFO_FOR_BLOCKED_UIDS=" + + DeviceConfigUtils.isNetworkStackFeatureNotChickenedOut(mContext, + IGNORE_TCP_INFO_FOR_BLOCKED_UIDS)); + pw.decreaseIndent(); + pw.println(); + + pw.println("NetworkStack logs:"); mLog.dump(fd, pw, args); @@ -482,6 +503,69 @@ R.bool.config_no_sim_card_uses_neighbor_mcc, false)); } + @Override + public int handleShellCommand(@NonNull ParcelFileDescriptor in, + @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err, + @NonNull String[] args) { + return new ShellCmd().exec(this, in.getFileDescriptor(), out.getFileDescriptor(), + err.getFileDescriptor(), args); + } + + private class ShellCmd extends BasicShellCommandHandler { + @Override + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(cmd); + } + final PrintWriter pw = getOutPrintWriter(); + try { + switch (cmd) { + case "is-uid-networking-blocked": + if (!DeviceConfigUtils.isFeatureSupported(mContext, + FEATURE_IS_UID_NETWORKING_BLOCKED)) { + pw.println("API is unsupported"); + return -1; + } + + // Usage : cmd network_stack is-uid-networking-blocked <uid> <metered> + // If no argument, get and display the usage help. + if (getRemainingArgsCount() != 2) { + onHelp(); + return -1; + } + final int uid; + final boolean metered; + // If any fail, throws and output to the stdout. + // Let the caller handle it. + uid = Integer.parseInt(getNextArg()); + metered = Boolean.parseBoolean(getNextArg()); + final ConnectivityManager cm = + mContext.getSystemService(ConnectivityManager.class); + pw.println(cm.isUidNetworkingBlocked( + uid, metered /* isNetworkMetered */)); + return 0; + default: + return handleDefaultCommands(cmd); + } + } catch (Exception e) { + pw.println(e); + } + return -1; + } + + @Override + public void onHelp() { + PrintWriter pw = getOutPrintWriter(); + pw.println("NetworkStack service commands:"); + pw.println(" help"); + pw.println(" Print this help text."); + pw.println(" is-uid-networking-blocked <uid> <metered>"); + pw.println(" Get whether the networking is blocked for given uid and metered."); + pw.println(" <uid>: The target uid."); + pw.println(" <metered>: [true|false], Whether the target network is metered."); + } + } + /** * Dump version information of the module and detected system version. */
diff --git a/src/com/android/server/connectivity/NetworkMonitor.java b/src/com/android/server/connectivity/NetworkMonitor.java index 3be8979..9303f95 100755 --- a/src/com/android/server/connectivity/NetworkMonitor.java +++ b/src/com/android/server/connectivity/NetworkMonitor.java
@@ -36,6 +36,7 @@ import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; import static android.net.captiveportal.CaptivePortalProbeSpec.parseCaptivePortalProbeSpecs; import static android.net.metrics.ValidationProbeEvent.DNS_FAILURE; import static android.net.metrics.ValidationProbeEvent.DNS_SUCCESS; @@ -125,7 +126,6 @@ import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; -import android.provider.DeviceConfig; import android.provider.Settings; import android.stats.connectivity.ProbeResult; import android.stats.connectivity.ProbeType; @@ -543,6 +543,7 @@ private final boolean mPrivateIpNoInternetEnabled; private final boolean mMetricsEnabled; + private final boolean mReevaluateWhenResumeEnabled; @NonNull private final NetworkInformationShim mInfoShim = NetworkInformationShimImpl.newInstance(); @@ -627,8 +628,10 @@ mTestCaptivePortalHttpUrl = getTestUrl(TEST_CAPTIVE_PORTAL_HTTP_URL, validationLogs, deps); mIsCaptivePortalCheckEnabled = getIsCaptivePortalCheckEnabled(context, deps); mPrivateIpNoInternetEnabled = getIsPrivateIpNoInternetEnabled(); - mMetricsEnabled = deps.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, - NetworkStackUtils.VALIDATION_METRICS_VERSION, true /* defaultEnabled */); + mMetricsEnabled = deps.isFeatureNotChickenedOut(context, + NetworkStackUtils.VALIDATION_METRICS_VERSION); + mReevaluateWhenResumeEnabled = deps.isFeatureEnabled( + context, NetworkStackUtils.REEVALUATE_WHEN_RESUME); mUseHttps = getUseHttpsValidation(); mCaptivePortalUserAgent = getCaptivePortalUserAgent(); mCaptivePortalFallbackSpecs = @@ -939,6 +942,7 @@ // Initialization. tst.setOpportunisticMode(false); tst.setLinkProperties(mLinkProperties); + tst.setNetworkCapabilities(mNetworkCapabilities); } Log.d(TAG, "Starting on network " + mNetwork + " with capport HTTPS URL " + Arrays.toString(mCaptivePortalHttpsUrls) @@ -1102,16 +1106,8 @@ } break; case EVENT_NETWORK_CAPABILITIES_CHANGED: - final NetworkCapabilities newCap = (NetworkCapabilities) message.obj; - // Reevaluate network if underlying network changes on the validation required - // VPN. - if (isVpnUnderlyingNetworkChangeReevaluationRequired( - newCap, mNetworkCapabilities)) { - sendMessage(CMD_FORCE_REEVALUATION, NO_UID, 0); - } - - mNetworkCapabilities = newCap; - suppressNotificationIfNetworkRestricted(); + handleCapabilitiesChanged((NetworkCapabilities) message.obj, + true /* reevaluateOnResume */); break; case EVENT_RESOURCE_CONFIG_CHANGED: // RRO generation does not happen during package installation and instead after @@ -1130,20 +1126,53 @@ return HANDLED; } - private boolean isVpnUnderlyingNetworkChangeReevaluationRequired( - final NetworkCapabilities newCap, final NetworkCapabilities oldCap) { - return !newCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) - && isValidationRequired() - && !Objects.equals(mInfoShim.getUnderlyingNetworks(newCap), - mInfoShim.getUnderlyingNetworks(oldCap)); - } - @Override public void exit() { mContext.unregisterReceiver(mConfigurationReceiver); } } + private void handleCapabilitiesChanged(@NonNull final NetworkCapabilities newCap, + boolean reevaluateOnResume) { + // Go to EvaluatingState to reset the network re-evaluation timer when + // the network resumes from suspended. + // This is because the network is expected to be down + // when the device is suspended, and if the delay timer falls back to + // the maximum interval, re-evaluation will be triggered slowly after + // the network resumes. + // Suppress re-evaluation in validated state, if the network has been validated, + // then it's in the expected state. + // TODO(b/287183389): Evaluate once but do not re-evaluate when suspended, to make + // exclamation mark visible by user but doesn't cause too much network traffic. + if (mReevaluateWhenResumeEnabled && reevaluateOnResume + && !mNetworkCapabilities.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) + && newCap.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)) { + // Interrupt if waiting for next probe. + sendMessage(CMD_FORCE_REEVALUATION, NO_UID, 1 /* forceAccept */); + } else if (isVpnUnderlyingNetworkChangeReevaluationRequired(newCap, mNetworkCapabilities)) { + // If no re-evaluation is needed from the previous check, fall-through for lower + // priority checks. + // Reevaluate network if underlying network changes on the validation required + // VPN. + sendMessage(CMD_FORCE_REEVALUATION, NO_UID, 0 /* forceAccept */); + } + final TcpSocketTracker tst = getTcpSocketTracker(); + if (tst != null) { + tst.setNetworkCapabilities(newCap); + } + + mNetworkCapabilities = newCap; + suppressNotificationIfNetworkRestricted(); + } + + private boolean isVpnUnderlyingNetworkChangeReevaluationRequired( + final NetworkCapabilities newCap, final NetworkCapabilities oldCap) { + return !newCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) + && isValidationRequired() + && !Objects.equals(mInfoShim.getUnderlyingNetworks(newCap), + mInfoShim.getUnderlyingNetworks(oldCap)); + } + // Being in the ValidatedState State indicates a Network is: // - Successfully validated, or // - Wanted "as is" by the user, or @@ -1209,6 +1238,12 @@ sendTcpPollingEvent(); } break; + case EVENT_NETWORK_CAPABILITIES_CHANGED: + // The timer does not need to reset, and it won't need to re-evaluate if + // the network is already validated when resumes. + handleCapabilitiesChanged((NetworkCapabilities) message.obj, + false /* reevaluateOnResume */); + break; default: return NOT_HANDLED; } @@ -3325,27 +3360,19 @@ * Check whether or not one experimental feature in the connectivity namespace is * enabled. * @param name Flag name of the experiment in the connectivity namespace. - * @see DeviceConfigUtils#isFeatureEnabled(Context, String, String) + * @see DeviceConfigUtils#isNetworkStackFeatureEnabled(Context, String) */ public boolean isFeatureEnabled(@NonNull Context context, @NonNull String name) { - return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name); + return DeviceConfigUtils.isNetworkStackFeatureEnabled(context, name); } /** - * Check whether or not one specific experimental feature for a particular namespace from - * {@link DeviceConfig} is enabled by comparing NetworkStack module version - * {@link NetworkStack} with current version of property. If this property version is valid, - * the corresponding experimental feature would be enabled, otherwise disabled. - * @param context The global context information about an app environment. - * @param namespace The namespace containing the property to look up. - * @param name The name of the property to look up. - * @param defaultEnabled The value to return if the property does not exist or its value is - * null. - * @return true if this feature is enabled, or false if disabled. + * Check whether one specific feature is not disabled. + * @param name Flag name of the experiment in the connectivity namespace. + * @see DeviceConfigUtils#isNetworkStackFeatureNotChickenedOut(Context, String) */ - public boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace, - @NonNull String name, boolean defaultEnabled) { - return DeviceConfigUtils.isFeatureEnabled(context, namespace, name, defaultEnabled); + public boolean isFeatureNotChickenedOut(@NonNull Context context, @NonNull String name) { + return DeviceConfigUtils.isNetworkStackFeatureNotChickenedOut(context, name); } /** @@ -3465,6 +3492,11 @@ } @VisibleForTesting + public int getReevaluationDelayMs() { + return mReevaluateDelayMs; + } + + @VisibleForTesting protected boolean isDataStall() { if (!isDataStallDetectionRequired()) { return false;
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp index 8300c73..ec058d7 100644 --- a/tests/integration/Android.bp +++ b/tests/integration/Android.bp
@@ -68,6 +68,9 @@ static_libs: [ "NetworkStackApiStableLib", ], + lint: { + baseline_filename: "lint-baseline.xml", + }, } // Network stack integration tests. @@ -84,6 +87,9 @@ jarjar_rules: ":NetworkStackJarJarRules", host_required: ["net-tests-utils-host-common"], test_config_template: "AndroidTestTemplate_Integration.xml", + lint: { + baseline_filename: "lint-baseline.xml", + }, } // Network stack next integration tests. @@ -107,6 +113,9 @@ jarjar_rules: ":NetworkStackJarJarRules", host_required: ["net-tests-utils-host-common"], test_config_template: "AndroidTestTemplate_Integration.xml", + lint: { + baseline_filename: "lint-baseline.xml", + }, } // Network stack integration root tests. @@ -124,12 +133,18 @@ "NetworkStackApiStableLib", ], platform_apis: true, - test_suites: ["general-tests", "mts-networking"], + test_suites: [ + "general-tests", + "mts-networking", + ], compile_multilib: "both", manifest: "AndroidManifest_root.xml", jarjar_rules: ":NetworkStackJarJarRules", host_required: ["net-tests-utils-host-common"], test_config_template: "AndroidTestTemplate_Integration.xml", + lint: { + baseline_filename: "lint-baseline.xml", + }, } // Special version of the network stack tests that includes all tests necessary for code coverage @@ -138,7 +153,10 @@ name: "NetworkStackCoverageTests", certificate: "networkstack", platform_apis: true, - test_suites: ["device-tests", "mts-networking"], + test_suites: [ + "device-tests", + "mts-networking", + ], test_config: "AndroidTest_Coverage.xml", defaults: [ "NetworkStackReleaseTargetSdk", @@ -154,4 +172,7 @@ compile_multilib: "both", manifest: "AndroidManifest_coverage.xml", jarjar_rules: ":NetworkStackJarJarRules", + lint: { + baseline_filename: "lint-baseline.xml", + }, }
diff --git a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java index d7d225e..2f1f7d1 100644 --- a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java +++ b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
@@ -17,6 +17,8 @@ package android.net.ip; import static android.Manifest.permission.MANAGE_TEST_NETWORKS; +import static android.Manifest.permission.READ_DEVICE_CONFIG; +import static android.Manifest.permission.WRITE_DEVICE_CONFIG; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; @@ -25,7 +27,6 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED; import static android.net.NetworkCapabilities.TRANSPORT_TEST; import static android.net.RouteInfo.RTN_UNICAST; -import static android.net.RouteInfo.RTN_UNREACHABLE; import static android.net.dhcp.DhcpClient.EXPIRED_LEASE; import static android.net.dhcp.DhcpPacket.DHCP_BOOTREQUEST; import static android.net.dhcp.DhcpPacket.DHCP_CLIENT; @@ -36,9 +37,13 @@ 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.dhcp.DhcpResultsParcelableUtil.fromStableParcelable; +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.DEFAULT_ACCEPT_RA_MIN_LFT; +import static android.net.ip.IpClient.DEFAULT_APF_COUNTER_POLLING_INTERVAL_SECS; import static android.net.ip.IpClientLinkObserver.CLAT_PREFIX; import static android.net.ip.IpClientLinkObserver.CONFIG_SOCKET_RECV_BUFSIZE; import static android.net.ip.IpReachabilityMonitor.NUD_MCAST_RESOLICIT_NUM; @@ -69,13 +74,13 @@ import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_NODES_MULTICAST; import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_ROUTERS_MULTICAST; import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ANY; -import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN; import static com.android.net.module.util.NetworkStackConstants.IPV6_PROTOCOL_OFFSET; import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE; import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER; import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED; import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_AUTONOMOUS; import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_ON_LINK; +import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH; import static com.android.testutils.MiscAsserts.assertThrows; import static com.android.testutils.ParcelUtils.parcelingRoundTrip; import static com.android.testutils.TestPermissionUtil.runAsShell; @@ -116,11 +121,14 @@ import android.app.AlarmManager; import android.app.AlarmManager.OnAlarmListener; import android.app.Instrumentation; +import android.app.UiAutomation; +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; 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.DhcpResults; import android.net.DhcpResultsParcelable; import android.net.INetd; import android.net.InetAddresses; @@ -149,6 +157,7 @@ import android.net.dhcp.DhcpRequestPacket; import android.net.dhcp6.Dhcp6Client; import android.net.dhcp6.Dhcp6Packet; +import android.net.dhcp6.Dhcp6Packet.PrefixDelegation; import android.net.dhcp6.Dhcp6RebindPacket; import android.net.dhcp6.Dhcp6RenewPacket; import android.net.dhcp6.Dhcp6RequestPacket; @@ -172,10 +181,12 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; -import android.stats.connectivity.NetworkQuirkEvent; +import android.provider.DeviceConfig; import android.stats.connectivity.NudEventType; import android.system.ErrnoException; import android.system.Os; +import android.util.ArrayMap; +import android.util.Log; import androidx.annotation.NonNull; import androidx.test.InstrumentationRegistry; @@ -183,16 +194,20 @@ import com.android.internal.util.HexDump; import com.android.internal.util.StateMachine; +import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.ArrayTrackRecord; import com.android.net.module.util.InterfaceParams; import com.android.net.module.util.Ipv6Utils; import com.android.net.module.util.PacketBuilder; import com.android.net.module.util.SharedLog; import com.android.net.module.util.Struct; +import com.android.net.module.util.arp.ArpPacket; import com.android.net.module.util.ip.IpNeighborMonitor; import com.android.net.module.util.ip.IpNeighborMonitor.NeighborEventConsumer; +import com.android.net.module.util.netlink.NetlinkUtils; import com.android.net.module.util.netlink.StructNdOptPref64; import com.android.net.module.util.structs.EthernetHeader; +import com.android.net.module.util.structs.IaPrefixOption; import com.android.net.module.util.structs.Ipv6Header; import com.android.net.module.util.structs.LlaOption; import com.android.net.module.util.structs.PrefixInformationOption; @@ -201,7 +216,6 @@ import com.android.networkstack.apishim.CaptivePortalDataShimImpl; import com.android.networkstack.apishim.ConstantsShim; import com.android.networkstack.apishim.common.ShimUtils; -import com.android.networkstack.arp.ArpPacket; import com.android.networkstack.ipmemorystore.IpMemoryStoreService; import com.android.networkstack.metrics.IpProvisioningMetrics; import com.android.networkstack.metrics.IpReachabilityMonitorMetrics; @@ -221,6 +235,9 @@ import com.android.testutils.TestableNetworkAgent; import com.android.testutils.TestableNetworkCallback; +import kotlin.Lazy; +import kotlin.LazyKt; + import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -265,9 +282,6 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; -import kotlin.Lazy; -import kotlin.LazyKt; - /** * Base class for IpClient tests. * @@ -276,6 +290,7 @@ @RunWith(Parameterized.class) @SmallTest public abstract class IpClientIntegrationTestCommon { + private static final String TAG = IpClientIntegrationTestCommon.class.getSimpleName(); private static final int DATA_BUFFER_LEN = 4096; private static final int PACKET_TIMEOUT_MS = 5_000; private static final String TEST_CLUSTER = "some cluster"; @@ -284,13 +299,20 @@ private static final int TEST_LOWER_IPV6_ONLY_WAIT_S = (int) (MIN_V6ONLY_WAIT_MS / 1000 - 1); private static final int TEST_ZERO_IPV6_ONLY_WAIT_S = 0; private static final long TEST_MAX_IPV6_ONLY_WAIT_S = 0xffffffffL; + private static final int TEST_DEVICE_OWNER_APP_UID = 14242; + private static final String TEST_DEVICE_OWNER_APP_PACKAGE = "com.example.deviceowner"; protected static final String TEST_L2KEY = "some l2key"; // TODO: move to NetlinkConstants, NetworkStackConstants, or OsConstants. private static final int IFA_F_STABLE_PRIVACY = 0x800; + // To fix below AndroidLint warning: + // [InlinedApi] Field requires version 3 of the U Extensions SDK (current min is 0). + private static final int RTN_UNREACHABLE = + SdkLevel.isAtLeastT() ? RouteInfo.RTN_UNREACHABLE : 7; protected static final long TEST_TIMEOUT_MS = 2_000L; private static final long TEST_WAIT_ENOBUFS_TIMEOUT_MS = 30_000L; + private static final long TEST_WAIT_RENEW_REBIND_RETRANSMIT_MS = 15_000L; @Rule public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule(); @@ -340,7 +362,8 @@ @Mock protected NetworkStackIpMemoryStore mIpMemoryStore; @Mock private NetworkQuirkMetrics.Dependencies mNetworkQuirkMetricsDeps; @Mock private IpReachabilityMonitorMetrics mIpReachabilityMonitorMetrics; - + @Mock private DevicePolicyManager mDevicePolicyManager; + @Mock private PackageManager mPackageManager; @Spy private INetd mNetd; private NetworkObserverRegistry mNetworkObserverRegistry; @@ -438,7 +461,9 @@ private static final byte[] TEST_HOTSPOT_OUI = new byte[] { (byte) 0x00, (byte) 0x17, (byte) 0xF2 }; - private static final byte TEST_VENDOR_SPECIFIC_TYPE = 0x06; + private static final byte LEGACY_TEST_VENDOR_SPECIFIC_IE_TYPE = 0x11; + private static final byte TEST_VENDOR_SPECIFIC_IE_TYPE = 0x21; + private static final int TEST_VENDOR_SPECIFIC_IE_ID = 0xdd; private static final String TEST_DEFAULT_SSID = "test_ssid"; private static final String TEST_DEFAULT_BSSID = "00:11:22:33:44:55"; @@ -507,8 +532,8 @@ @Override public Dhcp6Client makeDhcp6Client(Context context, StateMachine controller, - InterfaceParams ifParams) { - mDhcp6Client = Dhcp6Client.makeDhcp6Client(context, controller, ifParams); + InterfaceParams ifParams, Dhcp6Client.Dependencies deps) { + mDhcp6Client = Dhcp6Client.makeDhcp6Client(context, controller, ifParams, deps); return mDhcp6Client; } @@ -522,9 +547,24 @@ } @Override - public boolean isFeatureEnabled(final Context context, final String name, - final boolean defaultEnabled) { - return IpClientIntegrationTestCommon.this.isFeatureEnabled(name, defaultEnabled); + public boolean isFeatureEnabled(final Context context, final String name) { + return IpClientIntegrationTestCommon.this.isFeatureEnabled(name); + } + + @Override + public boolean isFeatureNotChickenedOut(final Context context, final String name) { + return IpClientIntegrationTestCommon.this.isFeatureNotChickenedOut(name); + } + + @Override + public Dhcp6Client.Dependencies getDhcp6ClientDependencies() { + return new Dhcp6Client.Dependencies() { + @Override + public int getDeviceConfigPropertyInt(String name, int defaultValue) { + return Dependencies.this.getDeviceConfigPropertyInt(name, + 0 /* default value */); + } + }; } @Override @@ -532,9 +572,13 @@ NetworkStackIpMemoryStore ipMemoryStore, IpProvisioningMetrics metrics) { return new DhcpClient.Dependencies(ipMemoryStore, metrics) { @Override - public boolean isFeatureEnabled(final Context context, final String name, - final boolean defaultEnabled) { - return Dependencies.this.isFeatureEnabled(context, name, defaultEnabled); + public boolean isFeatureEnabled(final Context context, final String name) { + return Dependencies.this.isFeatureEnabled(context, name); + } + + @Override + public boolean isFeatureNotChickenedOut(final Context context, final String name) { + return Dependencies.this.isFeatureNotChickenedOut(context, name); } @Override @@ -575,9 +619,12 @@ return new IpNeighborMonitor(h, log, cb); } - public boolean isFeatureEnabled(final Context context, final String name, - boolean defaultEnabled) { - return Dependencies.this.isFeatureEnabled(context, name, defaultEnabled); + public boolean isFeatureEnabled(final Context context, final String name) { + return Dependencies.this.isFeatureEnabled(context, name); + } + + public boolean isFeatureNotChickenedOut(final Context context, final String name) { + return Dependencies.this.isFeatureNotChickenedOut(context, name); } public IpReachabilityMonitorMetrics getIpReachabilityMonitorMetrics() { @@ -611,9 +658,9 @@ protected abstract void setFeatureEnabled(String name, boolean enabled); - protected abstract void setDeviceConfigProperty(String name, int value); + protected abstract boolean isFeatureEnabled(String name); - protected abstract boolean isFeatureEnabled(String name, boolean defaultEnabled); + protected abstract boolean isFeatureNotChickenedOut(String name); protected abstract boolean useNetworkStackSignature(); @@ -634,15 +681,39 @@ return !useNetworkStackSignature() && mIsSignatureRequiredTest; } + private ArrayMap<String, String> mOriginalPropertyValues = new ArrayMap<>(); + + protected void setDeviceConfigProperty(String name, String value) { + final UiAutomation am = InstrumentationRegistry.getInstrumentation().getUiAutomation(); + am.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG); + try { + // Do not use computeIfAbsent as it would overwrite null values, + // property originally unset. + if (!mOriginalPropertyValues.containsKey(name)) { + mOriginalPropertyValues.put(name, + DeviceConfig.getProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, name)); + } + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, name, value, + false /* makeDefault */); + } finally { + am.dropShellPermissionIdentity(); + } + } + + protected void setDeviceConfigProperty(String name, int value) { + setDeviceConfigProperty(name, Integer.toString(value)); + } + + private void setFeatureChickenedOut(String name, boolean chickenedOut) { + setDeviceConfigProperty(name, chickenedOut ? "-1" : "0"); + } + protected void setDhcpFeatures(final boolean isDhcpLeaseCacheEnabled, - final boolean isRapidCommitEnabled, final boolean isDhcpIpConflictDetectEnabled, - final boolean isIPv6OnlyPreferredEnabled) { + final boolean isRapidCommitEnabled, final boolean isDhcpIpConflictDetectEnabled) { setFeatureEnabled(NetworkStackUtils.DHCP_INIT_REBOOT_VERSION, isDhcpLeaseCacheEnabled); setFeatureEnabled(NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION, isRapidCommitEnabled); setFeatureEnabled(NetworkStackUtils.DHCP_IP_CONFLICT_DETECT_VERSION, isDhcpIpConflictDetectEnabled); - setFeatureEnabled(NetworkStackUtils.DHCP_IPV6_ONLY_PREFERRED_VERSION, - isIPv6OnlyPreferredEnabled); } private void setDeviceConfigForMaxDtimMultiplier() { @@ -681,8 +752,12 @@ // in IpClientLinkObserver will use mIsNetlinkEventParseEnabled to decide the proper // bindGroups, otherwise, the parameterized value got from ArrayMap(integration test) is // always false. - setFeatureEnabled(NetworkStackUtils.IPCLIENT_PARSE_NETLINK_EVENTS_VERSION, - mIsNetlinkEventParseEnabled /* default value */); + // + // Set feature kill switch flag with the parameterized value to keep running test cases on + // both code paths. Once we clean up the old code path (i.e.when the parameterized variable + // is false), then we can also delete this code. + setFeatureChickenedOut(NetworkStackUtils.IPCLIENT_PARSE_NETLINK_EVENTS_FORCE_DISABLE, + !mIsNetlinkEventParseEnabled); // Enable DHCPv6 Prefix Delegation. setFeatureEnabled(NetworkStackUtils.IPCLIENT_DHCPV6_PREFIX_DELEGATION_VERSION, @@ -705,6 +780,8 @@ enableRealAlarm("DhcpClient." + mIfaceName + ".KICK"); // Enable alarm for IPv6 autoconf via SLAAC in IpClient. enableRealAlarm("IpClient." + mIfaceName + ".EVENT_IPV6_AUTOCONF_TIMEOUT"); + // Enable packet retransmit alarm in Dhcp6Client. + enableRealAlarm("Dhcp6Client." + mIfaceName + ".KICK"); } mIIpClient = makeIIpClient(mIfaceName, mCb); @@ -713,16 +790,24 @@ // more realistic. mIIpClient.setMulticastFilter(true); setDeviceConfigForMaxDtimMultiplier(); - // Set IPv6 autoconfi timeout. - setDeviceConfigProperty(IpClient.CONFIG_IPV6_AUTOCONF_TIMEOUT, 500 /* default value */); + // Set IPv6 autoconf timeout. For signature tests, it has disabled the provisioning delay, + // use a small timeout value to speed up the test execution; For root tests, we have to + // wait a bit longer to make sure that we do see the success IPv6 provisioning, otherwise, + // the global IPv6 address may show up later due to DAD, so we consider that autoconf fails + // in this case and start DHCPv6 Prefix Delegation then. + final int timeout = useNetworkStackSignature() ? 500 : (int) TEST_TIMEOUT_MS; + setDeviceConfigProperty(IpClient.CONFIG_IPV6_AUTOCONF_TIMEOUT, timeout /* default value */); } protected void setUpMocks() throws Exception { MockitoAnnotations.initMocks(this); mDependencies = new Dependencies(); - when(mContext.getSystemService(eq(Context.ALARM_SERVICE))).thenReturn(mAlarm); - when(mContext.getSystemService(eq(ConnectivityManager.class))).thenReturn(mCm); + when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarm); + when(mContext.getSystemService(ConnectivityManager.class)).thenReturn(mCm); + when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)) + .thenReturn(mDevicePolicyManager); + when(mContext.getPackageManager()).thenReturn(mPackageManager); when(mContext.getResources()).thenReturn(mResources); when(mResources.getInteger(eq(R.integer.config_nud_postroaming_solicit_num))).thenReturn(5); when(mResources.getInteger(eq(R.integer.config_nud_postroaming_solicit_interval))) @@ -742,6 +827,11 @@ // liftime. when(mCm.shouldAvoidBadWifi()).thenReturn(true); + when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser()).thenReturn( + new ComponentName(TEST_DEVICE_OWNER_APP_PACKAGE, "com.example.SomeClass")); + when(mPackageManager.getPackagesForUid(TEST_DEVICE_OWNER_APP_UID)).thenReturn( + new String[] { TEST_DEVICE_OWNER_APP_PACKAGE }); + mDependencies.setDeviceConfigProperty(IpClient.CONFIG_MIN_RDNSS_LIFETIME, 67); mDependencies.setDeviceConfigProperty(DhcpClient.DHCP_RESTART_CONFIG_DELAY, 10); mDependencies.setDeviceConfigProperty(DhcpClient.ARP_FIRST_PROBE_DELAY_MS, 10); @@ -757,6 +847,14 @@ // Set the timeout to wait IPv6 autoconf to complete. mDependencies.setDeviceConfigProperty(CONFIG_IPV6_AUTOCONF_TIMEOUT, 500); + + // Set the minimal RA lifetime value, any RA section with liftime below this value will be + // ignored. + mDependencies.setDeviceConfigProperty(CONFIG_ACCEPT_RA_MIN_LFT, DEFAULT_ACCEPT_RA_MIN_LFT); + + // Set the polling interval to update APF data snapshot. + mDependencies.setDeviceConfigProperty(CONFIG_APF_COUNTER_POLLING_INTERVAL_SECS, + DEFAULT_APF_COUNTER_POLLING_INTERVAL_SECS); } private void awaitIpClientShutdown() throws Exception { @@ -778,6 +876,22 @@ awaitIpClientShutdown(); } + @After + public void tearDownDeviceConfigProperties() { + if (testSkipped()) return; + final UiAutomation am = InstrumentationRegistry.getInstrumentation().getUiAutomation(); + am.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG); + try { + for (String key : mOriginalPropertyValues.keySet()) { + if (key == null) continue; + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, key, + mOriginalPropertyValues.get(key), false /* makeDefault */); + } + } finally { + am.dropShellPermissionIdentity(); + } + } + private void setUpTapInterface() throws Exception { final Instrumentation inst = InstrumentationRegistry.getInstrumentation(); final TestNetworkInterface iface = runAsShell(MANAGE_TEST_NETWORKS, () -> { @@ -1017,43 +1131,47 @@ private static ByteBuffer buildDhcpOfferPacket(final DhcpPacket packet, final Inet4Address clientAddress, final Integer leaseTimeSec, final short mtu, - final String captivePortalUrl, final Integer ipv6OnlyWaitTime) { + final String captivePortalUrl, final Integer ipv6OnlyWaitTime, + final String domainName, final List<String> domainSearchList) { return DhcpPacket.buildOfferPacket(DhcpPacket.ENCAP_L2, packet.getTransactionId(), false /* broadcast */, SERVER_ADDR, INADDR_ANY /* relayIp */, clientAddress /* yourIp */, packet.getClientMac(), leaseTimeSec, NETMASK /* netMask */, BROADCAST_ADDR /* bcAddr */, Collections.singletonList(SERVER_ADDR) /* gateways */, Collections.singletonList(SERVER_ADDR) /* dnsServers */, - SERVER_ADDR /* dhcpServerIdentifier */, null /* domainName */, HOSTNAME, - false /* metered */, mtu, captivePortalUrl, ipv6OnlyWaitTime); + SERVER_ADDR /* dhcpServerIdentifier */, domainName, HOSTNAME, + false /* metered */, mtu, captivePortalUrl, ipv6OnlyWaitTime, domainSearchList); } private static ByteBuffer buildDhcpOfferPacket(final DhcpPacket packet, final Inet4Address clientAddress, final Integer leaseTimeSec, final short mtu, final String captivePortalUrl) { return buildDhcpOfferPacket(packet, clientAddress, leaseTimeSec, mtu, captivePortalUrl, - null /* ipv6OnlyWaitTime */); + null /* ipv6OnlyWaitTime */, null /* domainName */, null /* domainSearchList */); } private static ByteBuffer buildDhcpAckPacket(final DhcpPacket packet, final Inet4Address clientAddress, final Integer leaseTimeSec, final short mtu, final boolean rapidCommit, final String captivePortalApiUrl, - final Integer ipv6OnlyWaitTime) { + final Integer ipv6OnlyWaitTime, final String domainName, + final List<String> domainSearchList) { return DhcpPacket.buildAckPacket(DhcpPacket.ENCAP_L2, packet.getTransactionId(), false /* broadcast */, SERVER_ADDR, INADDR_ANY /* relayIp */, clientAddress /* yourIp */, CLIENT_ADDR /* requestIp */, packet.getClientMac(), leaseTimeSec, NETMASK /* netMask */, BROADCAST_ADDR /* bcAddr */, Collections.singletonList(SERVER_ADDR) /* gateways */, Collections.singletonList(SERVER_ADDR) /* dnsServers */, - SERVER_ADDR /* dhcpServerIdentifier */, null /* domainName */, HOSTNAME, - false /* metered */, mtu, rapidCommit, captivePortalApiUrl, ipv6OnlyWaitTime); + SERVER_ADDR /* dhcpServerIdentifier */, domainName, HOSTNAME, + false /* metered */, mtu, rapidCommit, captivePortalApiUrl, ipv6OnlyWaitTime, + domainSearchList); } private static ByteBuffer buildDhcpAckPacket(final DhcpPacket packet, final Inet4Address clientAddress, final Integer leaseTimeSec, final short mtu, final boolean rapidCommit, final String captivePortalApiUrl) { return buildDhcpAckPacket(packet, clientAddress, leaseTimeSec, mtu, rapidCommit, - captivePortalApiUrl, null /* ipv6OnlyWaitTime */); + captivePortalApiUrl, null /* ipv6OnlyWaitTime */, null /* domainName */, + null /* domainSearchList */); } private static ByteBuffer buildDhcpNakPacket(final DhcpPacket packet, final String message) { @@ -1114,9 +1232,9 @@ private void startIpClientProvisioning(final boolean isDhcpLeaseCacheEnabled, final boolean shouldReplyRapidCommitAck, final boolean isPreconnectionEnabled, - final boolean isDhcpIpConflictDetectEnabled, final boolean isIPv6OnlyPreferredEnabled, - final String displayName, final ScanResultInfo scanResultInfo, - final Layer2Information layer2Info) throws Exception { + final boolean isDhcpIpConflictDetectEnabled, final String displayName, + final ScanResultInfo scanResultInfo, final Layer2Information layer2Info) + throws Exception { ProvisioningConfiguration.Builder prov = new ProvisioningConfiguration.Builder() .withoutIpReachabilityMonitor() .withLayer2Information(layer2Info == null @@ -1129,7 +1247,7 @@ if (scanResultInfo != null) prov.withScanResultInfo(scanResultInfo); setDhcpFeatures(isDhcpLeaseCacheEnabled, shouldReplyRapidCommitAck, - isDhcpIpConflictDetectEnabled, isIPv6OnlyPreferredEnabled); + isDhcpIpConflictDetectEnabled); startIpClientProvisioning(prov.build()); if (!isPreconnectionEnabled) { @@ -1140,10 +1258,9 @@ private void startIpClientProvisioning(final boolean isDhcpLeaseCacheEnabled, final boolean isDhcpRapidCommitEnabled, final boolean isPreconnectionEnabled, - final boolean isDhcpIpConflictDetectEnabled, final boolean isIPv6OnlyPreferredEnabled) - throws Exception { + final boolean isDhcpIpConflictDetectEnabled) throws Exception { startIpClientProvisioning(isDhcpLeaseCacheEnabled, isDhcpRapidCommitEnabled, - isPreconnectionEnabled, isDhcpIpConflictDetectEnabled, isIPv6OnlyPreferredEnabled, + isPreconnectionEnabled, isDhcpIpConflictDetectEnabled, null /* displayName */, null /* ScanResultInfo */, null /* layer2Info */); } @@ -1197,13 +1314,12 @@ final Integer leaseTimeSec, final boolean isDhcpLeaseCacheEnabled, final boolean shouldReplyRapidCommitAck, final int mtu, final boolean isDhcpIpConflictDetectEnabled, - final boolean isIPv6OnlyPreferredEnabled, final String captivePortalApiUrl, final String displayName, final ScanResultInfo scanResultInfo, final Layer2Information layer2Info) throws Exception { startIpClientProvisioning(isDhcpLeaseCacheEnabled, shouldReplyRapidCommitAck, false /* isPreconnectionEnabled */, isDhcpIpConflictDetectEnabled, - isIPv6OnlyPreferredEnabled, displayName, scanResultInfo, layer2Info); + displayName, scanResultInfo, layer2Info); return handleDhcpPackets(isSuccessLease, leaseTimeSec, shouldReplyRapidCommitAck, mtu, captivePortalApiUrl); } @@ -1211,6 +1327,15 @@ private List<DhcpPacket> handleDhcpPackets(final boolean isSuccessLease, final Integer leaseTimeSec, final boolean shouldReplyRapidCommitAck, final int mtu, final String captivePortalApiUrl) throws Exception { + return handleDhcpPackets(isSuccessLease, leaseTimeSec, shouldReplyRapidCommitAck, + mtu, captivePortalApiUrl, null /* ipv6OnlyWaitTime */, + null /* domainName */, null /* domainSearchList */); + } + + private List<DhcpPacket> handleDhcpPackets(final boolean isSuccessLease, + final Integer leaseTimeSec, final boolean shouldReplyRapidCommitAck, final int mtu, + final String captivePortalApiUrl, final Integer ipv6OnlyWaitTime, + final String domainName, final List<String> domainSearchList) throws Exception { final List<DhcpPacket> packetList = new ArrayList<>(); DhcpPacket packet; while ((packet = getNextDhcpPacket()) != null) { @@ -1218,15 +1343,18 @@ if (packet instanceof DhcpDiscoverPacket) { if (shouldReplyRapidCommitAck) { mPacketReader.sendResponse(buildDhcpAckPacket(packet, CLIENT_ADDR, leaseTimeSec, - (short) mtu, true /* rapidCommit */, captivePortalApiUrl)); + (short) mtu, true /* rapidCommit */, captivePortalApiUrl, + ipv6OnlyWaitTime, domainName, domainSearchList)); } else { mPacketReader.sendResponse(buildDhcpOfferPacket(packet, CLIENT_ADDR, - leaseTimeSec, (short) mtu, captivePortalApiUrl)); + leaseTimeSec, (short) mtu, captivePortalApiUrl, ipv6OnlyWaitTime, + domainName, domainSearchList)); } } else if (packet instanceof DhcpRequestPacket) { final ByteBuffer byteBuffer = isSuccessLease ? buildDhcpAckPacket(packet, CLIENT_ADDR, leaseTimeSec, (short) mtu, - false /* rapidCommit */, captivePortalApiUrl) + false /* rapidCommit */, captivePortalApiUrl, ipv6OnlyWaitTime, + domainName, domainSearchList) : buildDhcpNakPacket(packet, "duplicated request IP address"); mPacketReader.sendResponse(byteBuffer); } else { @@ -1248,7 +1376,6 @@ final boolean isDhcpIpConflictDetectEnabled) throws Exception { return performDhcpHandshake(isSuccessLease, leaseTimeSec, isDhcpLeaseCacheEnabled, isDhcpRapidCommitEnabled, mtu, isDhcpIpConflictDetectEnabled, - false /* isIPv6OnlyPreferredEnabled */, null /* captivePortalApiUrl */, null /* displayName */, null /* scanResultInfo */, null /* layer2Info */); } @@ -1283,7 +1410,7 @@ // Strip the Ethernet/IPv6/UDP headers, only keep DHCPv6 message payload for decode. final byte[] payload = Arrays.copyOfRange(packet, DHCP6_HEADER_OFFSET, packet.length); - final Dhcp6Packet dhcp6Packet = Dhcp6Packet.decodePacket(payload, payload.length); + final Dhcp6Packet dhcp6Packet = Dhcp6Packet.decode(payload, payload.length); if (dhcp6Packet != null) return dhcp6Packet; } return null; @@ -1305,7 +1432,7 @@ }).when(mIpMemoryStore).retrieveNetworkAttributes(eq(TEST_L2KEY), any()); startIpClientProvisioning(true /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */, false /* isPreconnectionEnabled */, - false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */); + false /* isDhcpIpConflictDetectEnabled */); return getNextDhcpPacket(); } @@ -1407,7 +1534,7 @@ startIpClientProvisioning(true /* isDhcpLeaseCacheEnabled */, shouldReplyRapidCommitAck, true /* isDhcpPreConnectionEnabled */, - false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */); + false /* isDhcpIpConflictDetectEnabled */); DhcpPacket packet = assertDiscoverPacketOnPreconnectionStart(); final int preconnDiscoverTransId = packet.getTransactionId(); @@ -1587,7 +1714,7 @@ public void testDhcpInit() throws Exception { startIpClientProvisioning(false /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */, false /* isPreconnectionEnabled */, - false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */); + false /* isDhcpIpConflictDetectEnabled */); final DhcpPacket packet = getNextDhcpPacket(); assertTrue(packet instanceof DhcpDiscoverPacket); } @@ -1655,7 +1782,7 @@ public void testRollbackFromRapidCommitOption() throws Exception { startIpClientProvisioning(false /* isDhcpLeaseCacheEnabled */, true /* isDhcpRapidCommitEnabled */, false /* isPreConnectionEnabled */, - false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */); + false /* isDhcpIpConflictDetectEnabled */); final List<DhcpPacket> discoverList = new ArrayList<DhcpPacket>(); DhcpPacket packet; @@ -1732,7 +1859,7 @@ public void testDhcpClientRapidCommitEnabled() throws Exception { startIpClientProvisioning(false /* isDhcpLeaseCacheEnabled */, true /* shouldReplyRapidCommitAck */, false /* isPreconnectionEnabled */, - false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */); + false /* isDhcpIpConflictDetectEnabled */); final DhcpPacket packet = getNextDhcpPacket(); assertTrue(packet instanceof DhcpDiscoverPacket); } @@ -2458,7 +2585,7 @@ // PreconnectionState instead of RunningState. startIpClientProvisioning(false /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */, true /* isDhcpPreConnectionEnabled */, - false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */); + false /* isDhcpIpConflictDetectEnabled */); assertDiscoverPacketOnPreconnectionStart(); // Force to enter RunningState. @@ -2584,7 +2711,7 @@ .withPreconnection() .build(); setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */, - false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */); + false /* isDhcpIpConflictDetectEnabled */); startIpClientProvisioning(config); assertDiscoverPacketOnPreconnectionStart(); @@ -2616,7 +2743,7 @@ // StoppedState. startIpClientProvisioning(false /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */, false /* isPreConnectionEnabled */, - false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */); + false /* isDhcpIpConflictDetectEnabled */); final DhcpPacket discover = getNextDhcpPacket(); assertTrue(discover instanceof DhcpDiscoverPacket); } @@ -2733,7 +2860,7 @@ boolean serverSendsOption) throws Exception { startIpClientProvisioning(false /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */, false /* isPreConnectionEnabled */, - false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */); + false /* isDhcpIpConflictDetectEnabled */); final DhcpPacket discover = getNextDhcpPacket(); assertTrue(discover instanceof DhcpDiscoverPacket); assertEquals(featureEnabled, discover.hasRequestedParam(DhcpPacket.DHCP_CAPTIVE_PORTAL)); @@ -2830,7 +2957,8 @@ private ScanResultInfo makeScanResultInfo(final String ssid, final String bssid) { byte[] data = new byte[10]; new Random().nextBytes(data); - return makeScanResultInfo(0xdd, ssid, bssid, TEST_AP_OUI, (byte) 0x06, data); + return makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, ssid, bssid, TEST_AP_OUI, + (byte) 0x06, data); } private void doUpstreamHotspotDetectionTest(final int id, final String displayName, @@ -2843,7 +2971,6 @@ TEST_LEASE_DURATION_S, true /* isDhcpLeaseCacheEnabled */, false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */, - false /* isIPv6OnlyPreferredEnabled */, null /* captivePortalApiUrl */, displayName, info /* scanResultInfo */, null /* layer2Info */); assertEquals(2, sentPackets.size()); @@ -2852,14 +2979,14 @@ ArgumentCaptor<DhcpResultsParcelable> captor = ArgumentCaptor.forClass(DhcpResultsParcelable.class); verify(mCb, timeout(TEST_TIMEOUT_MS)).onNewDhcpResults(captor.capture()); - DhcpResults lease = fromStableParcelable(captor.getValue()); + final DhcpResultsParcelable lease = captor.getValue(); assertNotNull(lease); - assertEquals(lease.getIpAddress().getAddress(), CLIENT_ADDR); - assertEquals(lease.getGateway(), SERVER_ADDR); - assertEquals(1, lease.getDnsServers().size()); - assertTrue(lease.getDnsServers().contains(SERVER_ADDR)); - assertEquals(lease.getServerAddress(), SERVER_ADDR); - assertEquals(lease.getMtu(), TEST_DEFAULT_MTU); + assertEquals(CLIENT_ADDR, lease.baseConfiguration.getIpAddress().getAddress()); + assertEquals(SERVER_ADDR, lease.baseConfiguration.getGateway()); + assertEquals(1, lease.baseConfiguration.getDnsServers().size()); + assertTrue(lease.baseConfiguration.getDnsServers().contains(SERVER_ADDR)); + assertEquals(SERVER_ADDR, InetAddresses.parseNumericAddress(lease.serverAddress)); + assertEquals(TEST_DEFAULT_MTU, lease.mtu); if (expectMetered) { assertEquals(lease.vendorInfo, DhcpPacket.VENDOR_INFO_ANDROID_METERED); @@ -2874,7 +3001,7 @@ public void testUpstreamHotspotDetection() throws Exception { byte[] data = new byte[10]; new Random().nextBytes(data); - doUpstreamHotspotDetectionTest(0xdd, "\"ssid\"", "ssid", + doUpstreamHotspotDetectionTest(TEST_VENDOR_SPECIFIC_IE_ID, "\"ssid\"", "ssid", new byte[] { (byte) 0x00, (byte) 0x17, (byte) 0xF2 }, (byte) 0x06, data, true /* expectMetered */); } @@ -2892,7 +3019,7 @@ public void testUpstreamHotspotDetection_incorrectOUI() throws Exception { byte[] data = new byte[10]; new Random().nextBytes(data); - doUpstreamHotspotDetectionTest(0xdd, "\"ssid\"", "ssid", + doUpstreamHotspotDetectionTest(TEST_VENDOR_SPECIFIC_IE_ID, "\"ssid\"", "ssid", new byte[] { (byte) 0x00, (byte) 0x1A, (byte) 0x11 }, (byte) 0x06, data, false /* expectMetered */); } @@ -2901,7 +3028,7 @@ public void testUpstreamHotspotDetection_incorrectSsid() throws Exception { byte[] data = new byte[10]; new Random().nextBytes(data); - doUpstreamHotspotDetectionTest(0xdd, "\"another ssid\"", "ssid", + doUpstreamHotspotDetectionTest(TEST_VENDOR_SPECIFIC_IE_ID, "\"another ssid\"", "ssid", new byte[] { (byte) 0x00, (byte) 0x17, (byte) 0xF2 }, (byte) 0x06, data, false /* expectMetered */); } @@ -2910,7 +3037,7 @@ public void testUpstreamHotspotDetection_incorrectType() throws Exception { byte[] data = new byte[10]; new Random().nextBytes(data); - doUpstreamHotspotDetectionTest(0xdd, "\"ssid\"", "ssid", + doUpstreamHotspotDetectionTest(TEST_VENDOR_SPECIFIC_IE_ID, "\"ssid\"", "ssid", new byte[] { (byte) 0x00, (byte) 0x17, (byte) 0xF2 }, (byte) 0x0a, data, false /* expectMetered */); } @@ -2918,7 +3045,7 @@ @Test public void testUpstreamHotspotDetection_zeroLengthData() throws Exception { byte[] data = new byte[0]; - doUpstreamHotspotDetectionTest(0xdd, "\"ssid\"", "ssid", + doUpstreamHotspotDetectionTest(TEST_VENDOR_SPECIFIC_IE_ID, "\"ssid\"", "ssid", new byte[] { (byte) 0x00, (byte) 0x17, (byte) 0xF2 }, (byte) 0x06, data, true /* expectMetered */); } @@ -2958,7 +3085,6 @@ performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S, true /* isDhcpLeaseCacheEnabled */, false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */, - false /* isIPv6OnlyPreferredEnabled */, null /* captivePortalApiUrl */, displayName, null /* scanResultInfo */, layer2Info); verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR)); @@ -3003,7 +3129,7 @@ ArgumentCaptor<DhcpResultsParcelable> resultsCaptor = ArgumentCaptor.forClass(DhcpResultsParcelable.class); verify(mCb, timeout(TEST_TIMEOUT_MS)).onNewDhcpResults(resultsCaptor.capture()); - DhcpResults lease = fromStableParcelable(resultsCaptor.getValue()); + final DhcpResultsParcelable lease = resultsCaptor.getValue(); assertNull(lease); // DhcpClient rolls back to StoppedState instead of INIT state after calling @@ -3103,17 +3229,15 @@ return lp; } - private void doDualStackProvisioning(boolean shouldDisableAcceptRa) throws Exception { + private LinkProperties doDualStackProvisioning() throws Exception { final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() .withoutIpReachabilityMonitor() .build(); - setFeatureEnabled(NetworkStackUtils.IPCLIENT_DISABLE_ACCEPT_RA_VERSION, - shouldDisableAcceptRa); // Enable rapid commit to accelerate DHCP handshake to shorten test duration, // not strictly necessary. setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */, - false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */); + false /* isDhcpIpConflictDetectEnabled */); // Both signature and root tests can use this function to do dual-stack provisioning. if (useNetworkStackSignature()) { mIpc.startProvisioning(config); @@ -3121,38 +3245,7 @@ mIIpClient.startProvisioning(config.toStableParcelable()); } - performDualStackProvisioning(); - } - - @Test @SignatureRequiredTest(reason = "signature perms are required due to mocked callabck") - public void testIgnoreIpv6ProvisioningLoss_disableIPv6Stack() throws Exception { - doDualStackProvisioning(false /* shouldDisableAcceptRa */); - - final CompletableFuture<LinkProperties> lpFuture = new CompletableFuture<>(); - - // Send RA with 0-lifetime and wait until all IPv6-related default route and DNS servers - // have been removed, then verify if there is IPv4-only info left in the LinkProperties. - sendRouterAdvertisementWithZeroRouterLifetime(); - verify(mCb, timeout(TEST_TIMEOUT_MS).atLeastOnce()).onLinkPropertiesChange( - argThat(x -> { - final boolean isOnlyIPv4Provisioned = (x.getLinkAddresses().size() == 1 - && x.getDnsServers().size() == 1 - && x.getAddresses().get(0) instanceof Inet4Address - && x.getDnsServers().get(0) instanceof Inet4Address); - - if (!isOnlyIPv4Provisioned) return false; - lpFuture.complete(x); - return true; - })); - final LinkProperties lp = lpFuture.get(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS); - assertNotNull(lp); - assertEquals(lp.getAddresses().get(0), CLIENT_ADDR); - assertEquals(lp.getDnsServers().get(0), SERVER_ADDR); - - final ArgumentCaptor<Integer> quirkEvent = ArgumentCaptor.forClass(Integer.class); - verify(mNetworkQuirkMetricsDeps, timeout(TEST_TIMEOUT_MS)).writeStats(quirkEvent.capture()); - assertEquals((long) quirkEvent.getValue(), - (long) NetworkQuirkEvent.QE_IPV6_PROVISIONING_ROUTER_LOST.ordinal()); + return performDualStackProvisioning(); } private boolean hasRouteTo(@NonNull final LinkProperties lp, @NonNull final String prefix) { @@ -3172,15 +3265,17 @@ for (LinkAddress la : lp.getLinkAddresses()) { final InetAddress addr = la.getAddress(); if ((addr instanceof Inet6Address) && !addr.isLinkLocalAddress()) { - return prefix.contains(addr); + if (prefix.contains(addr)) return true; } } return false; } - @Test @SignatureRequiredTest(reason = "signature perms are required due to mocked callabck") - public void testIgnoreIpv6ProvisioningLoss_disableAcceptRa() throws Exception { - doDualStackProvisioning(true /* shouldDisableAcceptRa */); + @Test + @SignatureRequiredTest(reason = "Out of SLO flakiness") + public void testIgnoreIpv6ProvisioningLoss_disableAcceptRaDefrtr() throws Exception { + LinkProperties lp = doDualStackProvisioning(); + Log.d(TAG, "current LinkProperties: " + lp); final CompletableFuture<LinkProperties> lpFuture = new CompletableFuture<>(); @@ -3193,8 +3288,6 @@ // Only IPv4 provisioned and IPv6 link-local address final boolean isIPv6LinkLocalAndIPv4OnlyProvisioned = (x.getLinkAddresses().size() == 2 - // fe80::/64, IPv4 default route, IPv4 subnet route - && x.getRoutes().size() == 3 && x.getDnsServers().size() == 1 && x.getAddresses().get(0) instanceof Inet4Address && x.getDnsServers().get(0) instanceof Inet4Address); @@ -3203,26 +3296,33 @@ lpFuture.complete(x); return true; })); - final LinkProperties lp = lpFuture.get(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS); + lp = lpFuture.get(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS); + Log.d(TAG, "After receiving RA with 0 router lifetime, LinkProperties: " + lp); assertNotNull(lp); assertEquals(lp.getAddresses().get(0), CLIENT_ADDR); assertEquals(lp.getDnsServers().get(0), SERVER_ADDR); - assertEquals(3, lp.getRoutes().size()); assertTrue(hasRouteTo(lp, IPV6_LINK_LOCAL_PREFIX)); // fe80::/64 assertTrue(hasRouteTo(lp, IPV4_TEST_SUBNET_PREFIX)); // IPv4 directly-connected route assertTrue(hasRouteTo(lp, IPV4_ANY_ADDRESS_PREFIX)); // IPv4 default route assertTrue(lp.getAddresses().get(1).isLinkLocalAddress()); - reset(mCb); + clearInvocations(mCb); - // Send an RA to verify that global IPv6 addresses won't be configured on the interface. - sendBasicRouterAdvertisement(false /* waitForRs */); - verify(mCb, timeout(TEST_TIMEOUT_MS).times(0)).onLinkPropertiesChange(any()); + // Wait for RS after IPv6 stack has been restarted and reply with a normal RA to verify + // that device gains the IPv6 provisioning without default route and off-link DNS server. + sendBasicRouterAdvertisement(true /* waitForRs */); + verify(mCb, timeout(TEST_TIMEOUT_MS).atLeastOnce()).onLinkPropertiesChange(argThat( + x -> x.hasGlobalIpv6Address() + // IPv4, IPv6 link local, privacy and stable privacy + && x.getLinkAddresses().size() == 4 + && !x.hasIpv6DefaultRoute() + && x.getDnsServers().size() == 1 + && x.getDnsServers().get(0).equals(SERVER_ADDR))); } @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required") public void testDualStackProvisioning() throws Exception { - doDualStackProvisioning(false /* shouldDisableAcceptRa */); + doDualStackProvisioning(); verify(mCb, never()).onProvisioningFailure(any()); } @@ -3241,7 +3341,7 @@ .withoutIpReachabilityMonitor() .build(); setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, false /* isRapidCommitEnabled */, - false /* isDhcpIpConflictDetectEnabled */, true /* isIPv6OnlyPreferredEnabled */); + false /* isDhcpIpConflictDetectEnabled */); startIpClientProvisioning(config); final DhcpPacket packet = @@ -3250,7 +3350,7 @@ // Respond DHCPOFFER with IPv6-Only preferred option and offered address. mPacketReader.sendResponse(buildDhcpOfferPacket(packet, clientAddress, TEST_LEASE_DURATION_S, (short) TEST_DEFAULT_MTU, null /* captivePortalUrl */, - ipv6OnlyWaitTime)); + ipv6OnlyWaitTime, null /* domainName */, null /* domainSearchList */)); } private void doDiscoverIPv6OnlyPreferredOptionTest(final int optionSecs, @@ -3305,7 +3405,7 @@ .build(); setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */, - false /* isDhcpIpConflictDetectEnabled */, true /* isIPv6OnlyPreferredEnabled */); + false /* isDhcpIpConflictDetectEnabled */); startIpClientProvisioning(config); final DhcpPacket packet = assertDiscoverPacketOnPreconnectionStart(); @@ -3320,7 +3420,8 @@ // contain the IPv6-only Preferred option to the client, instead respond with // a DHCPOFFER. mPacketReader.sendResponse(buildDhcpOfferPacket(packet, CLIENT_ADDR, TEST_LEASE_DURATION_S, - (short) TEST_DEFAULT_MTU, null /* captivePortalUrl */, TEST_IPV6_ONLY_WAIT_S)); + (short) TEST_DEFAULT_MTU, null /* captivePortalUrl */, TEST_IPV6_ONLY_WAIT_S, + null /* domainName */, null /* domainSearchList */)); final OnAlarmListener alarm = expectAlarmSet(null /* inOrder */, "TIMEOUT", 1800, mDependencies.mDhcpClient.getHandler()); @@ -3360,7 +3461,7 @@ .build(); setDhcpFeatures(true /* isDhcpLeaseCacheEnabled */, false /* isRapidCommitEnabled */, - false /* isDhcpIpConflictDetectEnabled */, true /* isIPv6OnlyPreferredEnabled */); + false /* isDhcpIpConflictDetectEnabled */); startIpClientProvisioning(config); final DhcpPacket packet = @@ -3369,7 +3470,8 @@ // Respond DHCPACK with IPv6-Only preferred option. mPacketReader.sendResponse(buildDhcpAckPacket(packet, CLIENT_ADDR, TEST_LEASE_DURATION_S, (short) TEST_DEFAULT_MTU, false /* rapidcommit */, - null /* captivePortalUrl */, ipv6OnlyWaitTime)); + null /* captivePortalUrl */, ipv6OnlyWaitTime, null /* domainName */, + null /* domainSearchList */)); if (ipv6OnlyWaitTime != null) { expectAlarmSet(null /* inOrder */, "TIMEOUT", expectedWaitSecs, @@ -3443,7 +3545,7 @@ public void testNoFdLeaks() throws Exception { // Shut down and restart IpClient once to ensure that any fds that are opened the first // time it runs do not cause the test to fail. - doDualStackProvisioning(false /* shouldDisableAcceptRa */); + doDualStackProvisioning(); shutdownAndRecreateIpClient(); // Unfortunately we cannot use a large number of iterations as it would make the test run @@ -3451,7 +3553,7 @@ final int iterations = 10; final int before = getNumOpenFds(); for (int i = 0; i < iterations; i++) { - doDualStackProvisioning(false /* shouldDisableAcceptRa */); + doDualStackProvisioning(); shutdownAndRecreateIpClient(); // The last time this loop runs, mIpc will be shut down in tearDown. } @@ -3494,7 +3596,7 @@ .withoutIPv6(); setDhcpFeatures(isDhcpLeaseCacheEnabled, false /* isRapidCommitEnabled */, - false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */); + false /* isDhcpIpConflictDetectEnabled */); startIpClientProvisioning(prov.build()); verify(mCb, timeout(TEST_TIMEOUT_MS)).setFallbackMulticastFilter(true); @@ -3505,8 +3607,8 @@ @Test public void testDiscoverCustomizedDhcpOptions() throws Exception { - final ScanResultInfo info = makeScanResultInfo(0xdd /* vendor-specific IE */, TEST_OEM_OUI, - (byte) 0x11 /* vendor-specific IE type */); + final ScanResultInfo info = makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, TEST_OEM_OUI, + TEST_VENDOR_SPECIFIC_IE_TYPE); final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info, false /* isDhcpLeaseCacheEnabled */); @@ -3517,8 +3619,8 @@ @Test public void testDiscoverCustomizedDhcpOptions_nullDhcpOptions() throws Exception { - final ScanResultInfo info = makeScanResultInfo(0xdd /* vendor-specific IE */, TEST_OEM_OUI, - (byte) 0x11 /* vendor-specific IE type */); + final ScanResultInfo info = makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, TEST_OEM_OUI, + TEST_VENDOR_SPECIFIC_IE_TYPE); final DhcpPacket packet = doCustomizedDhcpOptionsTest(null /* options */, info, false /* isDhcpLeaseCacheEnabled */); @@ -3539,8 +3641,8 @@ @Test public void testDiscoverCustomizedDhcpOptions_disallowedOui() throws Exception { - final ScanResultInfo info = makeScanResultInfo(0xdd /* vendor-specific IE */, - new byte[]{ 0x00, 0x11, 0x22} /* oui */, (byte) 0x11 /* vendor-specific IE type */); + final ScanResultInfo info = makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, + new byte[]{ 0x00, 0x11, 0x22} /* oui */, TEST_VENDOR_SPECIFIC_IE_TYPE); final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info, false /* isDhcpLeaseCacheEnabled */); @@ -3552,7 +3654,7 @@ @Test public void testDiscoverCustomizedDhcpOptions_invalidIeId() throws Exception { final ScanResultInfo info = makeScanResultInfo(0xde /* vendor-specific IE */, TEST_OEM_OUI, - (byte) 0x11 /* vendor-specific IE type */); + TEST_VENDOR_SPECIFIC_IE_TYPE); final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info, false /* isDhcpLeaseCacheEnabled */); @@ -3563,7 +3665,7 @@ @Test public void testDiscoverCustomizedDhcpOptions_invalidVendorSpecificType() throws Exception { - final ScanResultInfo info = makeScanResultInfo(0xdd /* vendor-specific IE */, TEST_OEM_OUI, + final ScanResultInfo info = makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, TEST_OEM_OUI, (byte) 0x10 /* vendor-specific IE type */); final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info, false /* isDhcpLeaseCacheEnabled */); @@ -3574,14 +3676,26 @@ } @Test + public void testDiscoverCustomizedDhcpOptions_legacyVendorSpecificType() throws Exception { + final ScanResultInfo info = makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, TEST_OEM_OUI, + LEGACY_TEST_VENDOR_SPECIFIC_IE_TYPE); + final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info, + false /* isDhcpLeaseCacheEnabled */); + + assertTrue(packet instanceof DhcpDiscoverPacket); + assertEquals(packet.mVendorId, new String("android-dhcp-" + Build.VERSION.RELEASE)); + assertNull(packet.mUserClass); + } + + @Test public void testDisoverCustomizedDhcpOptions_disallowedOption() throws Exception { final List<DhcpOption> options = Arrays.asList( makeDhcpOption((byte) 60, TEST_OEM_VENDOR_ID.getBytes()), makeDhcpOption((byte) 77, TEST_OEM_USER_CLASS_INFO), // Option 26: MTU makeDhcpOption((byte) 26, HexDump.toByteArray(TEST_DEFAULT_MTU))); - final ScanResultInfo info = makeScanResultInfo(0xdd /* vendor-specific IE */, TEST_OEM_OUI, - (byte) 0x11 /* vendor-specific IE type */); + final ScanResultInfo info = makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, TEST_OEM_OUI, + TEST_VENDOR_SPECIFIC_IE_TYPE); final DhcpPacket packet = doCustomizedDhcpOptionsTest(options, info, false /* isDhcpLeaseCacheEnabled */); @@ -3598,8 +3712,8 @@ makeDhcpOption((byte) 77, TEST_OEM_USER_CLASS_INFO), // NTP_SERVER makeDhcpOption((byte) 42, null)); - final ScanResultInfo info = makeScanResultInfo(0xdd /* vendor-specific IE */, TEST_OEM_OUI, - (byte) 0x11 /* vendor-specific IE type */); + final ScanResultInfo info = makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, TEST_OEM_OUI, + TEST_VENDOR_SPECIFIC_IE_TYPE); final DhcpPacket packet = doCustomizedDhcpOptionsTest(options, info, false /* isDhcpLeaseCacheEnabled */); @@ -3614,8 +3728,8 @@ final List<DhcpOption> options = Arrays.asList( // DHCP_USER_CLASS makeDhcpOption((byte) 77, null)); - final ScanResultInfo info = makeScanResultInfo(0xdd /* vendor-specific IE */, TEST_OEM_OUI, - (byte) 0x11 /* vendor-specific IE type */); + final ScanResultInfo info = makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, TEST_OEM_OUI, + TEST_VENDOR_SPECIFIC_IE_TYPE); final DhcpPacket packet = doCustomizedDhcpOptionsTest(options, info, false /* isDhcpLeaseCacheEnabled */); @@ -3628,8 +3742,8 @@ public void testRequestCustomizedDhcpOptions() throws Exception { setUpRetrievedNetworkAttributesForInitRebootState(); - final ScanResultInfo info = makeScanResultInfo(0xdd /* vendor-specific IE */, TEST_OEM_OUI, - (byte) 0x11 /* vendor-specific IE type */); + final ScanResultInfo info = makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, TEST_OEM_OUI, + TEST_VENDOR_SPECIFIC_IE_TYPE); final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info, true /* isDhcpLeaseCacheEnabled */); @@ -3642,8 +3756,8 @@ public void testRequestCustomizedDhcpOptions_nullDhcpOptions() throws Exception { setUpRetrievedNetworkAttributesForInitRebootState(); - final ScanResultInfo info = makeScanResultInfo(0xdd /* vendor-specific IE */, TEST_OEM_OUI, - (byte) 0x11 /* vendor-specific IE type */); + final ScanResultInfo info = makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, TEST_OEM_OUI, + TEST_VENDOR_SPECIFIC_IE_TYPE); final DhcpPacket packet = doCustomizedDhcpOptionsTest(null /* options */, info, true /* isDhcpLeaseCacheEnabled */); @@ -3668,8 +3782,8 @@ public void testRequestCustomizedDhcpOptions_disallowedOui() throws Exception { setUpRetrievedNetworkAttributesForInitRebootState(); - final ScanResultInfo info = makeScanResultInfo(0xdd /* vendor-specific IE */, - new byte[]{ 0x00, 0x11, 0x22} /* oui */, (byte) 0x11 /* vendor-specific IE type */); + final ScanResultInfo info = makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, + new byte[]{ 0x00, 0x11, 0x22} /* oui */, TEST_VENDOR_SPECIFIC_IE_TYPE); final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info, true /* isDhcpLeaseCacheEnabled */); @@ -3683,7 +3797,7 @@ setUpRetrievedNetworkAttributesForInitRebootState(); final ScanResultInfo info = makeScanResultInfo(0xde /* vendor-specific IE */, TEST_OEM_OUI, - (byte) 0x11 /* vendor-specific IE type */); + TEST_VENDOR_SPECIFIC_IE_TYPE); final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info, true /* isDhcpLeaseCacheEnabled */); @@ -3696,8 +3810,22 @@ public void testRequestCustomizedDhcpOptions_invalidVendorSpecificType() throws Exception { setUpRetrievedNetworkAttributesForInitRebootState(); - final ScanResultInfo info = makeScanResultInfo(0xdd /* vendor-specific IE */, TEST_OEM_OUI, - (byte) 0x10 /* vendor-specific IE type */); + final ScanResultInfo info = makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, TEST_OEM_OUI, + (byte) 0x20 /* vendor-specific IE type */); + final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info, + true /* isDhcpLeaseCacheEnabled */); + + assertTrue(packet instanceof DhcpRequestPacket); + assertEquals(packet.mVendorId, new String("android-dhcp-" + Build.VERSION.RELEASE)); + assertNull(packet.mUserClass); + } + + @Test + public void testRequestCustomizedDhcpOptions_legacyVendorSpecificType() throws Exception { + setUpRetrievedNetworkAttributesForInitRebootState(); + + final ScanResultInfo info = makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, TEST_OEM_OUI, + LEGACY_TEST_VENDOR_SPECIFIC_IE_TYPE); final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info, true /* isDhcpLeaseCacheEnabled */); @@ -3715,8 +3843,8 @@ makeDhcpOption((byte) 77, TEST_OEM_USER_CLASS_INFO), // Option 26: MTU makeDhcpOption((byte) 26, HexDump.toByteArray(TEST_DEFAULT_MTU))); - final ScanResultInfo info = makeScanResultInfo(0xdd /* vendor-specific IE */, TEST_OEM_OUI, - (byte) 0x11 /* vendor-specific IE type */); + final ScanResultInfo info = makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, TEST_OEM_OUI, + TEST_VENDOR_SPECIFIC_IE_TYPE); final DhcpPacket packet = doCustomizedDhcpOptionsTest(options, info, true /* isDhcpLeaseCacheEnabled */); @@ -3735,8 +3863,8 @@ makeDhcpOption((byte) 77, TEST_OEM_USER_CLASS_INFO), // NTP_SERVER makeDhcpOption((byte) 42, null)); - final ScanResultInfo info = makeScanResultInfo(0xdd /* vendor-specific IE */, TEST_OEM_OUI, - (byte) 0x11 /* vendor-specific IE type */); + final ScanResultInfo info = makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, TEST_OEM_OUI, + TEST_VENDOR_SPECIFIC_IE_TYPE); final DhcpPacket packet = doCustomizedDhcpOptionsTest(options, info, true /* isDhcpLeaseCacheEnabled */); @@ -3753,8 +3881,8 @@ final List<DhcpOption> options = Arrays.asList( // DHCP_USER_CLASS makeDhcpOption((byte) 77, null)); - final ScanResultInfo info = makeScanResultInfo(0xdd /* vendor-specific IE */, TEST_OEM_OUI, - (byte) 0x11 /* vendor-specific IE type */); + final ScanResultInfo info = makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, TEST_OEM_OUI, + TEST_VENDOR_SPECIFIC_IE_TYPE); final DhcpPacket packet = doCustomizedDhcpOptionsTest(options, info, true /* isDhcpLeaseCacheEnabled */); @@ -3808,7 +3936,7 @@ setFeatureEnabled(NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION, true /* isGratuitousNaEnabled */); - assertTrue(isFeatureEnabled(NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION, false)); + assertTrue(isFeatureEnabled(NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION)); startIpClientProvisioning(config); doIpv6OnlyProvisioning(); @@ -3839,10 +3967,20 @@ // Enable rapid commit to accelerate DHCP handshake to shorten test duration, // not strictly necessary. setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */, - false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */); + false /* isDhcpIpConflictDetectEnabled */); + + // Disable gratuitious neighbor discovery feature manually, if the feature is enabled on + // the DUT during experiment launch, that will send another two duplicate NA packets and + // mess up the assert of received NA packets. + setFeatureEnabled(NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION, + false /* isGratuitousNaEnabled */); + assumeFalse(isFeatureEnabled(NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION)); if (isGratuitousArpNaRoamingEnabled) { setFeatureEnabled(NetworkStackUtils.IPCLIENT_GARP_NA_ROAMING_VERSION, true); - assertTrue(isFeatureEnabled(NetworkStackUtils.IPCLIENT_GARP_NA_ROAMING_VERSION, false)); + assumeTrue(isFeatureEnabled(NetworkStackUtils.IPCLIENT_GARP_NA_ROAMING_VERSION)); + } else { + setFeatureEnabled(NetworkStackUtils.IPCLIENT_GARP_NA_ROAMING_VERSION, false); + assumeFalse(isFeatureEnabled(NetworkStackUtils.IPCLIENT_GARP_NA_ROAMING_VERSION)); } startIpClientProvisioning(prov.build()); } @@ -4002,12 +4140,6 @@ return ns; } - // Override this function with disabled experiment flag by default, in order not to - // affect those tests which are just related to basic IpReachabilityMonitor infra. - private void prepareIpReachabilityMonitorTest() throws Exception { - prepareIpReachabilityMonitorTest(false /* isMulticastResolicitEnabled */); - } - private void assertNotifyNeighborLost(Inet6Address targetIp, NudEventType eventType) throws Exception { // For root test suite, rely on the IIpClient aidl interface version constant defined in @@ -4040,8 +4172,7 @@ verify(mCb, never()).onReachabilityLost(any()); } - private void prepareIpReachabilityMonitorTest(boolean isMulticastResolicitEnabled) - throws Exception { + private void prepareIpReachabilityMonitorTest() throws Exception { final ScanResultInfo info = makeScanResultInfo(TEST_DEFAULT_SSID, TEST_DEFAULT_BSSID); ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() .withLayer2Information(new Layer2Information(TEST_L2KEY, TEST_CLUSTER, @@ -4050,8 +4181,6 @@ .withDisplayName(TEST_DEFAULT_SSID) .withoutIPv4() .build(); - setFeatureEnabled(NetworkStackUtils.IP_REACHABILITY_MCAST_RESOLICIT_VERSION, - isMulticastResolicitEnabled); startIpClientProvisioning(config); verify(mCb, timeout(TEST_TIMEOUT_MS)).setFallbackMulticastFilter(true); doIpv6OnlyProvisioning(); @@ -4065,11 +4194,15 @@ final List<NeighborSolicitation> nsList = waitForMultipleNeighborSolicitations(); final int expectedNudSolicitNum = readNudSolicitNumPostRoamingFromResource(); - assertEquals(expectedNudSolicitNum, nsList.size()); - for (NeighborSolicitation ns : nsList) { + int expectedSize = expectedNudSolicitNum + NUD_MCAST_RESOLICIT_NUM; + assertEquals(expectedSize, nsList.size()); + for (NeighborSolicitation ns : nsList.subList(0, expectedNudSolicitNum)) { assertUnicastNeighborSolicitation(ns, ROUTER_MAC /* dstMac */, ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */); } + for (NeighborSolicitation ns : nsList.subList(expectedNudSolicitNum, nsList.size())) { + assertMulticastNeighborSolicitation(ns, ROUTER_LINK_LOCAL /* targetIp */); + } } @Test @@ -4104,43 +4237,10 @@ assertNeverNotifyNeighborLost(); } - private void runIpReachabilityMonitorMcastResolicitProbeFailedTest() throws Exception { - prepareIpReachabilityMonitorTest(true /* isMulticastResolicitEnabled */); - - final List<NeighborSolicitation> nsList = waitForMultipleNeighborSolicitations(); - final int expectedNudSolicitNum = readNudSolicitNumPostRoamingFromResource(); - int expectedSize = expectedNudSolicitNum + NUD_MCAST_RESOLICIT_NUM; - assertEquals(expectedSize, nsList.size()); - for (NeighborSolicitation ns : nsList.subList(0, expectedNudSolicitNum)) { - assertUnicastNeighborSolicitation(ns, ROUTER_MAC /* dstMac */, - ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */); - } - for (NeighborSolicitation ns : nsList.subList(expectedNudSolicitNum, nsList.size())) { - assertMulticastNeighborSolicitation(ns, ROUTER_LINK_LOCAL /* targetIp */); - } - } - - @Test - public void testIpReachabilityMonitor_mcastResolicitProbeFailed() throws Exception { - runIpReachabilityMonitorMcastResolicitProbeFailedTest(); - assertNotifyNeighborLost(ROUTER_LINK_LOCAL /* targetIp */, - NudEventType.NUD_POST_ROAMING_FAILED_CRITICAL); - } - - @Test @SignatureRequiredTest(reason = "requires mock callback object") - public void testIpReachabilityMonitor_mcastResolicitProbeFailed_legacyCallback() - throws Exception { - when(mCb.getInterfaceVersion()).thenReturn(12 /* assign an older interface aidl version */); - - runIpReachabilityMonitorMcastResolicitProbeFailedTest(); - verify(mCb, timeout(TEST_TIMEOUT_MS)).onReachabilityLost(any()); - verify(mCb, never()).onReachabilityFailure(any()); - } - @Test public void testIpReachabilityMonitor_mcastResolicitProbeReachableWithSameLinkLayerAddress() throws Exception { - prepareIpReachabilityMonitorTest(true /* isMulticastResolicitEnabled */); + prepareIpReachabilityMonitorTest(); final NeighborSolicitation ns = waitForUnicastNeighborSolicitation(ROUTER_MAC /* dstMac */, ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */); @@ -4157,7 +4257,7 @@ @Test public void testIpReachabilityMonitor_mcastResolicitProbeReachableWithDiffLinkLayerAddress() throws Exception { - prepareIpReachabilityMonitorTest(true /* isMulticastResolicitEnabled */); + prepareIpReachabilityMonitorTest(); final NeighborSolicitation ns = waitForUnicastNeighborSolicitation(ROUTER_MAC /* dstMac */, ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */); @@ -4196,8 +4296,7 @@ mNetworkAgentThread.start(); setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */, - false /* isDhcpIpConflictDetectEnabled */, - false /* isIPv6OnlyPreferredEnabled */); + false /* isDhcpIpConflictDetectEnabled */); setFeatureEnabled( NetworkStackUtils.IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION, isIgnoreIncompleteIpv6DnsServerEnabled); @@ -4340,6 +4439,25 @@ } @Test + public void testIPv6LinkLocalOnly_verifyAcceptRaDefrtr() throws Exception { + ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() + .withoutIPv4() + .withIpv6LinkLocalOnly() + .withRandomMacAddress() + .build(); + startIpClientProvisioning(config); + verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningSuccess(any()); + + clearInvocations(mCb); + + // accept_ra is set to 0 and accept_ra_defrtr is set to 1 in IPv6 link-local only mode, + // send another RA to tap interface, to verify that we should not see any IPv6 provisioning + // although accept_ra_defrtr is set to 1. + sendBasicRouterAdvertisement(false /* waitForRs */); + verify(mCb, never()).onLinkPropertiesChange(argThat(x -> x.isIpv6Provisioned())); + } + + @Test public void testIPv6LinkLocalOnlyAndThenGlobal() throws Exception { ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() .withoutIPv4() @@ -4354,7 +4472,7 @@ // Speed up provisioning by enabling rapid commit. TODO: why is this necessary? setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */, - false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */); + false /* isDhcpIpConflictDetectEnabled */); config = new ProvisioningConfiguration.Builder() .build(); startIpClientProvisioning(config); @@ -4511,9 +4629,6 @@ .withoutIPv4() .build(); - setFeatureEnabled(NetworkStackUtils.IPCLIENT_MULTICAST_NS_VERSION, - true /* isUnsolicitedNsEnabled */); - assertTrue(isFeatureEnabled(NetworkStackUtils.IPCLIENT_MULTICAST_NS_VERSION, false)); startIpClientProvisioning(config); doIpv6OnlyProvisioning(); @@ -4585,6 +4700,7 @@ } @Test + @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) public void testMaxDtimMultiplier_IPv6OnlyNetwork() throws Exception { ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() .withoutIPv4() @@ -4602,6 +4718,7 @@ } @Test + @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) public void testMaxDtimMultiplier_IPv6LinkLocalOnlyMode() throws Exception { final InOrder inOrder = inOrder(mCb); ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() @@ -4619,6 +4736,7 @@ } @Test + @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) public void testMaxDtimMultiplier_IPv4OnlyNetwork() throws Exception { performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S, true /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */, @@ -4632,7 +4750,7 @@ } private void runDualStackNetworkDtimMultiplierSetting(final InOrder inOrder) throws Exception { - doDualStackProvisioning(false /* shouldDisableAcceptRa */); + doDualStackProvisioning(); inOrder.verify(mCb).setMaxDtimMultiplier( IpClient.DEFAULT_BEFORE_IPV6_PROV_MAX_DTIM_MULTIPLIER); inOrder.verify(mCb, timeout(TEST_TIMEOUT_MS)).setMaxDtimMultiplier( @@ -4640,12 +4758,14 @@ } @Test + @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) public void testMaxDtimMultiplier_DualStackNetwork() throws Exception { final InOrder inOrder = inOrder(mCb); runDualStackNetworkDtimMultiplierSetting(inOrder); } @Test + @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) public void testMaxDtimMultiplier_MulticastLock() throws Exception { final InOrder inOrder = inOrder(mCb); runDualStackNetworkDtimMultiplierSetting(inOrder); @@ -4663,6 +4783,7 @@ } @Test + @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) public void testMaxDtimMultiplier_MulticastLockEnabled_StoppedState() throws Exception { // Simulate to hold the multicast lock by disabling the multicast filter at StoppedState, // verify no callback to be sent, start dual-stack provisioning and verify the multiplier @@ -4671,12 +4792,13 @@ verify(mCb, after(10).never()).setMaxDtimMultiplier( IpClient.DEFAULT_MULTICAST_LOCK_MAX_DTIM_MULTIPLIER); - doDualStackProvisioning(false /* shouldDisableAcceptRa */); + doDualStackProvisioning(); verify(mCb, times(1)).setMaxDtimMultiplier( IpClient.DEFAULT_MULTICAST_LOCK_MAX_DTIM_MULTIPLIER); } @Test + @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) public void testMaxDtimMultiplier_resetMultiplier() throws Exception { final InOrder inOrder = inOrder(mCb); runDualStackNetworkDtimMultiplierSetting(inOrder); @@ -4688,18 +4810,27 @@ inOrder.verify(mCb, timeout(TEST_TIMEOUT_MS)).setMaxDtimMultiplier(DTIM_MULTIPLIER_RESET); } - private void handleDhcp6Packets(final IpPrefix prefix, boolean shouldReplyRapidCommit) - throws Exception { - handleDhcp6Packets(prefix, 3600 /* t1 */, 4500 /* t2 */, 4500 /* preferred */, - 7200 /* valid */, shouldReplyRapidCommit); + private IaPrefixOption buildIaPrefixOption(final IpPrefix prefix, int preferred, + int valid) { + return new IaPrefixOption((short) IaPrefixOption.LENGTH, preferred, valid, + (byte) RFC7421_PREFIX_LENGTH, prefix.getRawAddress() /* prefix */); } - private void handleDhcp6Packets(final IpPrefix prefix, int t1, int t2, int preferred, int valid, + private void handleDhcp6Packets(final IpPrefix prefix, boolean shouldReplyRapidCommit) + throws Exception { + final IaPrefixOption ipo = buildIaPrefixOption(prefix, 4500 /* preferred */, + 7200 /* valid */); + handleDhcp6Packets(Collections.singletonList(ipo), 3600 /* t1 */, 4500 /* t2 */, + shouldReplyRapidCommit); + } + + private void handleDhcp6Packets(final List<IaPrefixOption> ipos, int t1, int t2, boolean shouldReplyRapidCommit) throws Exception { + ByteBuffer iapd; Dhcp6Packet packet; while ((packet = getNextDhcp6Packet()) != null) { - final ByteBuffer iapd = Dhcp6Packet.buildIaPdOption(packet.getIaId(), t1, t2, - preferred, valid, prefix.getRawAddress(), (byte) prefix.getPrefixLength()); + final PrefixDelegation pd = new PrefixDelegation(packet.getIaId(), t1, t2, ipos); + iapd = pd.build(); if (packet instanceof Dhcp6SolicitPacket) { if (shouldReplyRapidCommit) { mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac, @@ -4759,7 +4890,11 @@ @Test public void testDhcp6Pd_longPrefixLength() throws Exception { prepareDhcp6PdTest(); - handleDhcp6Packets(new IpPrefix("2001:db8:1::/80"), true /* shouldReplyRapidCommit */); + final IpPrefix prefix = new IpPrefix("2001:db8:1::/80"); + final IaPrefixOption ipo = buildIaPrefixOption(prefix, 3600 /* preferred */, + 4000 /* valid */); + handleDhcp6Packets(Collections.singletonList(ipo), 3600 /* t1 */, 4500 /* t2 */, + true /* shouldReplyRapidCommit */); verify(mCb, never()).onProvisioningSuccess(any()); } @@ -4776,46 +4911,98 @@ @Test public void testDhcp6Pd_T1GreaterThanT2() throws Exception { prepareDhcp6PdTest(); - handleDhcp6Packets(new IpPrefix("2001:db8:1::/80"), 4500 /* t1 */, 3600 /* t2 */, - 4500 /* preferred */, 7200 /* valid */, true /* shouldReplyRapidCommit */); + final IpPrefix prefix = new IpPrefix("2001:db8:1::/64"); + final IaPrefixOption ipo = buildIaPrefixOption(prefix, 3600 /* preferred */, + 4000 /* valid */); + handleDhcp6Packets(Collections.singletonList(ipo), 4500 /* t1 */, 3600 /* t2 */, + true /* shouldReplyRapidCommit */); verify(mCb, never()).onProvisioningSuccess(any()); } @Test public void testDhcp6Pd_preferredLifetimeGreaterThanValidLifetime() throws Exception { prepareDhcp6PdTest(); - handleDhcp6Packets(new IpPrefix("2001:db8:1::/80"), 3600 /* t1 */, 4500 /* t2 */, - 7200 /* preferred */, 4500 /* valid */, true /* shouldReplyRapidCommit */); + final IpPrefix prefix = new IpPrefix("2001:db8:1::/64"); + final IaPrefixOption ipo = buildIaPrefixOption(prefix, 7200 /* preferred */, + 4500 /* valid */); + handleDhcp6Packets(Collections.singletonList(ipo), 3600 /* t1 */, 4500 /* t2 */, + true /* shouldReplyRapidCommit */); verify(mCb, never()).onProvisioningSuccess(any()); } @Test public void testDhcp6Pd_preferredLifetimeLessThanT2() throws Exception { prepareDhcp6PdTest(); - handleDhcp6Packets(new IpPrefix("2001:db8:1::/80"), 3600 /* t1 */, 4500 /* t2 */, - 3600 /* preferred */, 4000 /* valid */, true /* shouldReplyRapidCommit */); + final IpPrefix prefix = new IpPrefix("2001:db8:1::/64"); + final IaPrefixOption ipo = buildIaPrefixOption(prefix, 3600 /* preferred */, + 4000 /* valid */); + handleDhcp6Packets(Collections.singletonList(ipo), 3600 /* t1 */, 4500 /* t2 */, + true /* shouldReplyRapidCommit */); verify(mCb, never()).onProvisioningSuccess(any()); } - @Test - public void testDhcp6Pd_notStart() throws Exception { - final ByteBuffer pio = buildPioOption(3600, 1800, "2001:db8:1::/64"); - final ByteBuffer rdnss = buildRdnssOption(3600, "2001:4860:4860::64"); - final ByteBuffer slla = buildSllaOption(); - final ByteBuffer ra = buildRaPacket(pio, rdnss, slla); + private void runDhcp6PdNotStartInDualStackTest(final String prefix, final String dnsServer) + throws Exception { + final List<ByteBuffer> options = new ArrayList<>(); + if (prefix != null) { + options.add(buildPioOption(3600, 1800, prefix)); + } + if (dnsServer != null) { + options.add(buildRdnssOption(3600, dnsServer)); + } + options.add(buildSllaOption()); + final ByteBuffer ra = buildRaPacket(options.toArray(new ByteBuffer[options.size()])); ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() - .withoutIPv4() .build(); + setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */, + false /* isDhcpIpConflictDetectEnabled */); startIpClientProvisioning(config); waitForRouterSolicitation(); mPacketReader.sendResponse(ra); - // Response an normal RA for IPv6 provisioning, then DHCPv6 prefix delegation - // should not start. - assertNull(getNextDhcp6Packet(TEST_TIMEOUT_MS)); - verify(mCb).onProvisioningSuccess(any()); + // Start IPv4 provisioning and wait until entire provisioning completes. + handleDhcpPackets(true /* isSuccessLease */, TEST_LEASE_DURATION_S, + true /* shouldReplyRapidCommitAck */, TEST_DEFAULT_MTU, null /* serverSentUrl */); + verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningSuccess(any()); + } + + @Test + public void testDhcp6Pd_notStartWithGlobalPio() throws Exception { + runDhcp6PdNotStartInDualStackTest("2001:db8:1::/64" /* prefix */, + "2001:4860:4860::64" /* dnsServer */); + // Reply with a normal RA with global prefix and an off-link DNS for IPv6 provisioning, + // DHCPv6 prefix delegation should not start. + assertNull(getNextDhcp6Packet(PACKET_TIMEOUT_MS)); + } + + @Test + public void testDhcp6Pd_notStartWithUlaPioAndDns() throws Exception { + runDhcp6PdNotStartInDualStackTest("fd7c:9df8:7f39:dc89::/64" /* prefix */, + "fd7c:9df8:7f39:dc89::1" /* dnsServer */); + // Reply with a normal RA even with ULA prefix and on-link ULA DNS for IPv6 provisioning, + // DHCPv6 prefix delegation should not start. + assertNull(getNextDhcp6Packet(PACKET_TIMEOUT_MS)); + } + + @Test + public void testDhcp6Pd_notStartWithUlaPioAndOffLinkDns() throws Exception { + runDhcp6PdNotStartInDualStackTest("fd7c:9df8:7f39:dc89::/64" /* prefix */, + "2001:4860:4860::64" /* dnsServer */); + // Reply with a normal RA even with ULA prefix and off-link DNS for IPv6 provisioning, + // DHCPv6 prefix delegation should not start. + assertNull(getNextDhcp6Packet(PACKET_TIMEOUT_MS)); + } + + @Test + public void testDhcp6Pd_startWithNoNonIpv6LinkLocalAddresses() throws Exception { + runDhcp6PdNotStartInDualStackTest(null /* prefix */, + "2001:4860:4860::64" /* dnsServer */); + // Reply with a normal RA with only RDNSS but no PIO for IPv6 provisioning, + // DHCPv6 prefix delegation should start. + final Dhcp6Packet packet = getNextDhcp6Packet(PACKET_TIMEOUT_MS); + assertTrue(packet instanceof Dhcp6SolicitPacket); } @Test @@ -4827,7 +5014,7 @@ ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() .build(); setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */, - false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */); + false /* isDhcpIpConflictDetectEnabled */); startIpClientProvisioning(config); waitForRouterSolicitation(); @@ -4845,9 +5032,49 @@ x -> x.isIpv6Provisioned() && hasIpv6AddressPrefixedWith(x, prefix) && hasRouteTo(x, "2001:db8:1::/64", RTN_UNREACHABLE) + // IPv4 address, IPv6 link-local, two global delegated IPv6 addresses + && x.getLinkAddresses().size() == 4 )); } + @Test + public void testDhcp6Pd_multiplePrefixesWithInvalidPrefix() throws Exception { + final IpPrefix valid = new IpPrefix("2001:db8:1::/64"); + final IpPrefix invalid = new IpPrefix("2001:db8:2::/64"); // preferred lft > valid lft + final IaPrefixOption validIpo = buildIaPrefixOption(valid, 4500 /* preferred */, + 7200 /* valid */); + final IaPrefixOption invalidIpo = buildIaPrefixOption(invalid, 4500 /* preferred */, + 3000 /* valid */); + + prepareDhcp6PdTest(); + handleDhcp6Packets(Arrays.asList(invalidIpo, validIpo), 3600 /* t1 */, 4500 /* t2 */, + true /* shouldReplyRapidCommit */); + final ArgumentCaptor<LinkProperties> captor = ArgumentCaptor.forClass(LinkProperties.class); + verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningSuccess(captor.capture()); + final LinkProperties lp = captor.getValue(); + assertTrue(hasIpv6AddressPrefixedWith(lp, valid)); + assertFalse(hasIpv6AddressPrefixedWith(lp, invalid)); + } + + @Test + public void testDhcp6Pd_multiplePrefixesWithPrefixValidLifetimeOfZero() throws Exception { + final IpPrefix valid = new IpPrefix("2001:db8:1::/64"); + final IpPrefix invalid = new IpPrefix("2001:db8:2::/64"); // preferred/valid lft 0 + final IaPrefixOption validIpo = buildIaPrefixOption(valid, 4500 /* preferred */, + 7200 /* valid */); + final IaPrefixOption invalidIpo = buildIaPrefixOption(invalid, 0 /* preferred */, + 0 /* valid */); + + prepareDhcp6PdTest(); + handleDhcp6Packets(Arrays.asList(invalidIpo, validIpo), 3600 /* t1 */, 4500 /* t2 */, + true /* shouldReplyRapidCommit */); + final ArgumentCaptor<LinkProperties> captor = ArgumentCaptor.forClass(LinkProperties.class); + verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningSuccess(captor.capture()); + final LinkProperties lp = captor.getValue(); + assertTrue(hasIpv6AddressPrefixedWith(lp, valid)); + assertFalse(hasIpv6AddressPrefixedWith(lp, invalid)); + } + private void prepareDhcp6PdRenewTest() throws Exception { final IpPrefix prefix = new IpPrefix("2001:db8:1::/64"); prepareDhcp6PdTest(); @@ -4880,9 +5107,9 @@ assertTrue(packet instanceof Dhcp6RebindPacket); } - @Test @SignatureRequiredTest(reason = "Need to mock the DHCP6 renew/rebind alarms") - public void testDhcp6Pd_prefixMismatchOnRenew() throws Exception { + @Test + public void testDhcp6Pd_prefixMismatchOnRenew_newPrefix() throws Exception { prepareDhcp6PdRenewTest(); final InOrder inOrder = inOrder(mAlarm); @@ -4895,14 +5122,500 @@ Dhcp6Packet packet = getNextDhcp6Packet(); assertTrue(packet instanceof Dhcp6RenewPacket); - // Reply with a different prefix with requested one, check if all global IPv6 addresses - // will be deleted and loss the IPv6 provisioning. + // Reply with a new prefix apart of the requested one, per RFC8415#section-18.2.10.1 + // any new prefix should be added. + final IpPrefix prefix = new IpPrefix("2001:db8:1::/64"); final IpPrefix prefix1 = new IpPrefix("2001:db8:2::/64"); - final ByteBuffer iapd = Dhcp6Packet.buildIaPdOption(packet.getIaId(), 3600 /* t1*/, - 4500 /* t2 */, 4500 /* preferred */, 7200 /* valid */, prefix1.getRawAddress(), - (byte) 64 /* prefix length */); + final IaPrefixOption ipo = buildIaPrefixOption(prefix, 4500 /* preferred */, + 7200 /* valid */); + final IaPrefixOption ipo1 = buildIaPrefixOption(prefix1, 5000 /* preferred */, + 6000 /* valid */); + final PrefixDelegation pd = new PrefixDelegation(packet.getIaId(), 3600 /* t1 */, + 4500 /* t2 */, Arrays.asList(ipo, ipo1)); + final ByteBuffer iapd = pd.build(); mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac, (Inet6Address) mClientIpAddress, false /* rapidCommit */)); + verify(mCb, never()).onProvisioningFailure(any()); + verify(mCb, timeout(TEST_TIMEOUT_MS)).onLinkPropertiesChange(argThat( + x -> x.isIpv6Provisioned() + && hasIpv6AddressPrefixedWith(x, prefix) + && hasIpv6AddressPrefixedWith(x, prefix1) + && hasRouteTo(x, "2001:db8:1::/64", RTN_UNREACHABLE) + && hasRouteTo(x, "2001:db8:2::/64", RTN_UNREACHABLE) + // IPv6 link-local, four global delegated IPv6 addresses + && x.getLinkAddresses().size() == 5 + )); + } + + @SignatureRequiredTest(reason = "Need to mock the DHCP6 renew/rebind alarms") + @Test + public void testDhcp6Pd_prefixMismatchOnRenew_requestedPrefixAbsent() throws Exception { + prepareDhcp6PdRenewTest(); + + final InOrder inOrder = inOrder(mAlarm); + final Handler handler = mDependencies.mDhcp6Client.getHandler(); + final OnAlarmListener renewAlarm = expectAlarmSet(inOrder, "RENEW", 3600, handler); + + handler.post(() -> renewAlarm.onAlarm()); + HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS); + + Dhcp6Packet packet = getNextDhcp6Packet(); + assertTrue(packet instanceof Dhcp6RenewPacket); + + // Reply with a new prefix but the requested one is absent, per RFC8415#section-18.2.10.1 + // the new prefix should be added and the absent prefix will expire in nature. + final IpPrefix prefix = new IpPrefix("2001:db8:1::/64"); + final IpPrefix prefix1 = new IpPrefix("2001:db8:2::/64"); + final IaPrefixOption ipo = buildIaPrefixOption(prefix1, 4500 /* preferred */, + 7200 /* valid */); + final PrefixDelegation pd = new PrefixDelegation(packet.getIaId(), 3600 /* t1 */, + 4500 /* t2 */, Arrays.asList(ipo)); + final ByteBuffer iapd = pd.build(); + mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac, + (Inet6Address) mClientIpAddress, false /* rapidCommit */)); + verify(mCb, never()).onProvisioningFailure(any()); + verify(mCb, timeout(TEST_TIMEOUT_MS)).onLinkPropertiesChange(argThat( + x -> x.isIpv6Provisioned() + && hasIpv6AddressPrefixedWith(x, prefix) + && hasIpv6AddressPrefixedWith(x, prefix1) + && hasRouteTo(x, "2001:db8:1::/64", RTN_UNREACHABLE) + && hasRouteTo(x, "2001:db8:2::/64", RTN_UNREACHABLE) + // IPv6 link-local, four global delegated IPv6 addresses + && x.getLinkAddresses().size() == 5 + )); + } + + @SignatureRequiredTest(reason = "Need to mock the DHCP6 renew/rebind alarms") + @Test + public void testDhcp6Pd_prefixMismatchOnRenew_allPrefixesAbsent() throws Exception { + prepareDhcp6PdRenewTest(); + + final InOrder inOrder = inOrder(mAlarm); + final Handler handler = mDependencies.mDhcp6Client.getHandler(); + final OnAlarmListener renewAlarm = expectAlarmSet(inOrder, "RENEW", 3600, handler); + + handler.post(() -> renewAlarm.onAlarm()); + HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS); + + Dhcp6Packet packet = getNextDhcp6Packet(); + assertTrue(packet instanceof Dhcp6RenewPacket); + + clearInvocations(mCb); + + // Reply with IA_PD but IA_Prefix is absent, client should still stay at the RenewState + // and restransmit the Renew message, that should not result in any LinkProperties update. + final PrefixDelegation pd = new PrefixDelegation(packet.getIaId(), 3600 /* t1 */, + 4500 /* t2 */, new ArrayList<IaPrefixOption>(0)); + final ByteBuffer iapd = pd.build(); + mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac, + (Inet6Address) mClientIpAddress, false /* rapidCommit */)); + verify(mCb, never()).onLinkPropertiesChange(any()); + } + + @SignatureRequiredTest(reason = "Need to mock the DHCP6 renew/rebind alarms") + @Test + public void testDhcp6Pd_renewInvalidPrefixes_zeroPreferredAndValidLifetime() throws Exception { + prepareDhcp6PdRenewTest(); + + final InOrder inOrder = inOrder(mAlarm); + final Handler handler = mDependencies.mDhcp6Client.getHandler(); + final OnAlarmListener renewAlarm = expectAlarmSet(inOrder, "RENEW", 3600, handler); + + handler.post(() -> renewAlarm.onAlarm()); + HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS); + + Dhcp6Packet packet = getNextDhcp6Packet(); + assertTrue(packet instanceof Dhcp6RenewPacket); + + // Reply with the requested prefix with preferred/valid lifetime of 0. + final IpPrefix prefix = new IpPrefix("2001:db8:1::/64"); + final IpPrefix prefix1 = new IpPrefix("2001:db8:2::/64"); + final IaPrefixOption ipo = buildIaPrefixOption(prefix, 0 /* preferred */, + 0 /* valid */); + final IaPrefixOption ipo1 = buildIaPrefixOption(prefix1, 5000 /* preferred */, + 6000 /* valid */); + final PrefixDelegation pd = new PrefixDelegation(packet.getIaId(), 3600 /* t1 */, + 4500 /* t2 */, Arrays.asList(ipo, ipo1)); + final ByteBuffer iapd = pd.build(); + mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac, + (Inet6Address) mClientIpAddress, false /* rapidCommit */)); + verify(mCb, never()).onProvisioningFailure(any()); + // IPv6 addresses derived from prefix with 0 preferred/valid lifetime should be deleted. + verify(mCb, timeout(TEST_TIMEOUT_MS)).onLinkPropertiesChange(argThat( + x -> x.isIpv6Provisioned() + && !hasIpv6AddressPrefixedWith(x, prefix) + && hasIpv6AddressPrefixedWith(x, prefix1) + && !hasRouteTo(x, "2001:db8:1::/64", RTN_UNREACHABLE) + && hasRouteTo(x, "2001:db8:2::/64", RTN_UNREACHABLE) + // IPv6 link-local, two global delegated IPv6 addresses with prefix1 + && x.getLinkAddresses().size() == 3 + )); + + handler.post(() -> renewAlarm.onAlarm()); + HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS); + + packet = getNextDhcp6Packet(); + assertTrue(packet instanceof Dhcp6RenewPacket); + final List<IaPrefixOption> renewIpos = packet.getPrefixDelegation().ipos; + assertEquals(1, renewIpos.size()); // don't renew prefix 2001:db8:1::/64 with 0 + // preferred/valid lifetime + assertEquals(prefix1, renewIpos.get(0).getIpPrefix()); + } + + @SignatureRequiredTest(reason = "Need to mock the DHCP6 renew/rebind alarms") + @Test + public void testDhcp6Pd_renewInvalidPrefixes_theSameT1T2ValidLifetime() throws Exception { + prepareDhcp6PdRenewTest(); + + final InOrder inOrder = inOrder(mAlarm); + final Handler handler = mDependencies.mDhcp6Client.getHandler(); + final OnAlarmListener renewAlarm = expectAlarmSet(inOrder, "RENEW", 3600, handler); + + handler.post(() -> renewAlarm.onAlarm()); + HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS); + + Dhcp6Packet packet = getNextDhcp6Packet(); + assertTrue(packet instanceof Dhcp6RenewPacket); + + clearInvocations(mCb); + + // Reply with the requested prefix with the same t1/t2/lifetime. + final IpPrefix prefix = new IpPrefix("2001:db8:1::/64"); + final IaPrefixOption ipo = buildIaPrefixOption(prefix, 3600 /* preferred */, + 3600 /* valid */); + final PrefixDelegation pd = new PrefixDelegation(packet.getIaId(), 3600 /* t1 */, + 3600 /* t2 */, Collections.singletonList(ipo)); + final ByteBuffer iapd = pd.build(); + mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac, + (Inet6Address) mClientIpAddress, false /* rapidCommit */)); + // The prefix doesn't change only the lifetime is updated, therefore, LinkProperties update + // isn't expected. + verify(mCb, never()).onProvisioningFailure(any()); + verify(mCb, never()).onLinkPropertiesChange(any()); + + handler.post(() -> renewAlarm.onAlarm()); + HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS); + + packet = getNextDhcp6Packet(TEST_TIMEOUT_MS); + assertNull(packet); + } + + @Test + public void testDhcp6Pd_multipleIaPrefixOptions() throws Exception { + final InOrder inOrder = inOrder(mCb); + final IpPrefix prefix1 = new IpPrefix("2001:db8:1::/64"); + final IpPrefix prefix2 = new IpPrefix("2400:db8:100::/64"); + final IpPrefix prefix3 = new IpPrefix("fd7c:9df8:7f39:dc89::/64"); + final IaPrefixOption ipo1 = buildIaPrefixOption(prefix1, 4500 /* preferred */, + 7200 /* valid */); + final IaPrefixOption ipo2 = buildIaPrefixOption(prefix2, 5600 /* preferred */, + 6000 /* valid */); + final IaPrefixOption ipo3 = buildIaPrefixOption(prefix3, 7200 /* preferred */, + 14400 /* valid */); + prepareDhcp6PdTest(); + handleDhcp6Packets(Arrays.asList(ipo1, ipo2, ipo3), 3600 /* t1 */, 4500 /* t2 */, + true /* shouldReplyRapidCommit */); + + final ArgumentCaptor<LinkProperties> captor = ArgumentCaptor.forClass(LinkProperties.class); + verifyWithTimeout(inOrder, mCb).onProvisioningSuccess(captor.capture()); + LinkProperties lp = captor.getValue(); + + // Sometimes privacy address or route may appear later along with onLinkPropertiesChange + // callback, in this case we wait a bit longer to see all of these properties appeared and + // then verify if they are what we are looking for. + if (lp.getLinkAddresses().size() < 5 || lp.getRoutes().size() < 4) { + final CompletableFuture<LinkProperties> lpFuture = new CompletableFuture<>(); + verifyWithTimeout(inOrder, mCb).onLinkPropertiesChange(argThat(x -> { + if (!x.isIpv6Provisioned()) return false; + if (x.getLinkAddresses().size() != 5) return false; + if (x.getRoutes().size() != 4) return false; + lpFuture.complete(x); + return true; + })); + lp = lpFuture.get(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS); + } + assertNotNull(lp); + assertTrue(hasIpv6AddressPrefixedWith(lp, prefix1)); + assertTrue(hasIpv6AddressPrefixedWith(lp, prefix2)); + assertFalse(hasIpv6AddressPrefixedWith(lp, prefix3)); + assertTrue(hasRouteTo(lp, prefix1.toString(), RTN_UNREACHABLE)); + assertTrue(hasRouteTo(lp, prefix2.toString(), RTN_UNREACHABLE)); + assertFalse(hasRouteTo(lp, prefix3.toString(), RTN_UNREACHABLE)); + } + + private void runDhcp6PacketWithNoPrefixAvailStatusCodeTest(boolean shouldReplyWithAdvertise) + throws Exception { + prepareDhcp6PdTest(); + Dhcp6Packet packet = getNextDhcp6Packet(PACKET_TIMEOUT_MS); + assertTrue(packet instanceof Dhcp6SolicitPacket); + + final PrefixDelegation pd = new PrefixDelegation(packet.getIaId(), 0 /* t1 */, 0 /* t2 */, + new ArrayList<IaPrefixOption>() /* ipos */, Dhcp6Packet.STATUS_NO_PREFIX_AVAIL); + final ByteBuffer iapd = pd.build(); + if (shouldReplyWithAdvertise) { + mPacketReader.sendResponse(buildDhcp6Advertise(packet, iapd.array(), mClientMac, + (Inet6Address) mClientIpAddress)); + } else { + mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac, + (Inet6Address) mClientIpAddress, true /* rapidCommit */)); + } + + // Check if client will ignore Advertise or Reply for Rapid Commit Solicit and + // retransmit Solicit. + packet = getNextDhcp6Packet(PACKET_TIMEOUT_MS); + assertTrue(packet instanceof Dhcp6SolicitPacket); + } + + @Test + public void testDhcp6AdvertiseWithNoPrefixAvailStatusCode() throws Exception { + // Advertise + runDhcp6PacketWithNoPrefixAvailStatusCodeTest(true /* shouldReplyWithAdvertise */); + } + + @Test + public void testDhcp6ReplyForRapidCommitSolicitWithNoPrefixAvailStatusCode() throws Exception { + // Reply + runDhcp6PacketWithNoPrefixAvailStatusCodeTest(false /* shouldReplyWithAdvertise */); + } + + @Test + public void testDhcp6ReplyForRequestWithNoPrefixAvailStatusCode() throws Exception { + prepareDhcp6PdTest(); + Dhcp6Packet packet = getNextDhcp6Packet(PACKET_TIMEOUT_MS); + assertTrue(packet instanceof Dhcp6SolicitPacket); + + final IpPrefix prefix = new IpPrefix("2001:db8:1::/64"); + final IaPrefixOption ipo = buildIaPrefixOption(prefix, 4500 /* preferred */, + 7200 /* valid */); + PrefixDelegation pd = new PrefixDelegation(packet.getIaId(), 1000 /* t1 */, + 2000 /* t2 */, Arrays.asList(ipo)); + ByteBuffer iapd = pd.build(); + mPacketReader.sendResponse(buildDhcp6Advertise(packet, iapd.array(), mClientMac, + (Inet6Address) mClientIpAddress)); + + packet = getNextDhcp6Packet(PACKET_TIMEOUT_MS); + assertTrue(packet instanceof Dhcp6RequestPacket); + + // Reply for Request with NoPrefixAvail status code. Not sure if this is reasonable in + // practice, but Server can do everything it wants. + pd = new PrefixDelegation(packet.getIaId(), 0 /* t1 */, 0 /* t2 */, + new ArrayList<IaPrefixOption>() /* ipos */, Dhcp6Packet.STATUS_NO_PREFIX_AVAIL); + iapd = pd.build(); + mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac, + (Inet6Address) mClientIpAddress, false /* rapidCommit */)); + + // Check if client will ignore Reply for Request with NoPrefixAvail status code, and + // rollback to SolicitState. + packet = getNextDhcp6Packet(PACKET_TIMEOUT_MS); + assertTrue(packet instanceof Dhcp6SolicitPacket); + } + + @Test + @SignatureRequiredTest(reason = "Need to mock the DHCP6 renew/rebind alarms") + public void testDhcp6ReplyForRenewWithNoPrefixAvailStatusCode() throws Exception { + prepareDhcp6PdRenewTest(); + + final InOrder inOrder = inOrder(mAlarm); + final Handler handler = mDependencies.mDhcp6Client.getHandler(); + final OnAlarmListener renewAlarm = expectAlarmSet(inOrder, "RENEW", 3600, handler); + + handler.post(() -> renewAlarm.onAlarm()); + HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS); + + Dhcp6Packet packet = getNextDhcp6Packet(); + assertTrue(packet instanceof Dhcp6RenewPacket); + + // Reply with normal IA_PD. + final IpPrefix prefix = new IpPrefix("2001:db8:1::/64"); + final IaPrefixOption ipo = buildIaPrefixOption(prefix, 4500 /* preferred */, + 7200 /* valid */); + PrefixDelegation pd = new PrefixDelegation(packet.getIaId(), 1000 /* t1 */, + 2000 /* t2 */, Arrays.asList(ipo)); + ByteBuffer iapd = pd.build(); + mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac, + (Inet6Address) mClientIpAddress, false /* rapidCommit */)); + HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS); + + // Trigger another Renew message. + handler.post(() -> renewAlarm.onAlarm()); + HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS); + + packet = getNextDhcp6Packet(); + assertTrue(packet instanceof Dhcp6RenewPacket); + + // Reply for Renew with NoPrefixAvail status code, check if client will retransmit the + // Renew message. + pd = new PrefixDelegation(packet.getIaId(), 3600 /* t1 */, 4500 /* t2 */, + new ArrayList<IaPrefixOption>(0) /* ipos */, Dhcp6Packet.STATUS_NO_PREFIX_AVAIL); + iapd = pd.build(); + mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac, + (Inet6Address) mClientIpAddress, false /* rapidCommit */)); + + packet = getNextDhcp6Packet(TEST_WAIT_RENEW_REBIND_RETRANSMIT_MS); + assertTrue(packet instanceof Dhcp6RenewPacket); + } + + @Test + @SignatureRequiredTest(reason = "Need to mock the DHCP6 renew/rebind alarms") + public void testDhcp6ReplyForRebindWithNoPrefixAvailStatusCode() throws Exception { + prepareDhcp6PdRenewTest(); + + final InOrder inOrder = inOrder(mAlarm); + final Handler handler = mDependencies.mDhcp6Client.getHandler(); + final OnAlarmListener renewAlarm = expectAlarmSet(inOrder, "RENEW", 3600, handler); + final OnAlarmListener rebindAlarm = expectAlarmSet(inOrder, "REBIND", 4500, handler); + + handler.post(() -> renewAlarm.onAlarm()); + HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS); + + Dhcp6Packet packet = getNextDhcp6Packet(); + assertTrue(packet instanceof Dhcp6RenewPacket); + + handler.post(() -> rebindAlarm.onAlarm()); + HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS); + + packet = getNextDhcp6Packet(); + assertTrue(packet instanceof Dhcp6RebindPacket); + + // Reply with normal IA_PD. + final IpPrefix prefix = new IpPrefix("2001:db8:1::/64"); + final IaPrefixOption ipo = buildIaPrefixOption(prefix, 4500 /* preferred */, + 7200 /* valid */); + PrefixDelegation pd = new PrefixDelegation(packet.getIaId(), 1000 /* t1 */, + 2000 /* t2 */, Arrays.asList(ipo)); + ByteBuffer iapd = pd.build(); + mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac, + (Inet6Address) mClientIpAddress, false /* rapidCommit */)); + HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS); + + // Trigger another Rebind message. + handler.post(() -> renewAlarm.onAlarm()); + HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS); + + packet = getNextDhcp6Packet(); + assertTrue(packet instanceof Dhcp6RenewPacket); + + handler.post(() -> rebindAlarm.onAlarm()); + HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS); + + packet = getNextDhcp6Packet(); + assertTrue(packet instanceof Dhcp6RebindPacket); + + // Reply for Rebind with NoPrefixAvail status code, check if client will retransmit the + // Rebind message. + pd = new PrefixDelegation(packet.getIaId(), 3600 /* t1 */, + 4500 /* t2 */, new ArrayList<IaPrefixOption>(0) /* ipos */, + Dhcp6Packet.STATUS_NO_PREFIX_AVAIL); + iapd = pd.build(); + mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac, + (Inet6Address) mClientIpAddress, false /* rapidCommit */)); + + packet = getNextDhcp6Packet(TEST_WAIT_RENEW_REBIND_RETRANSMIT_MS); + assertTrue(packet instanceof Dhcp6RebindPacket); + } + + @Test + @SignatureRequiredTest(reason = "InterfaceParams.getByName requires CAP_NET_ADMIN") + public void testSendRtmDelAddressMethod() throws Exception { + ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() + .withoutIPv4() + .build(); + startIpClientProvisioning(config); + + final LinkProperties lp = doIpv6OnlyProvisioning(); + assertNotNull(lp); + assertEquals(3, lp.getLinkAddresses().size()); // IPv6 privacy, stable privacy, link-local + + clearInvocations(mCb); + + // Delete all global IPv6 addresses, then that will trigger onProvisioningFailure callback. + final InterfaceParams params = InterfaceParams.getByName(mIfaceName); + for (LinkAddress la : lp.getLinkAddresses()) { + if (la.isGlobalPreferred()) { + NetlinkUtils.sendRtmDelAddressRequest(params.index, (Inet6Address) la.getAddress(), + (short) la.getPrefixLength()); + verify(mCb, timeout(TEST_TIMEOUT_MS)).onLinkPropertiesChange(argThat( + x -> !x.getLinkAddresses().contains(la) + )); + } + } verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningFailure(any()); } + + @Test + @SignatureRequiredTest(reason = "requires mocked netd to read/write IPv6 sysctl") + public void testIpv6SysctlsRestAfterStoppingIpClient() throws Exception { + ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() + .withoutIPv4() + .build(); + // dad_transmits has been set to 0 in disableIpv6ProvisioningDelays, re-enable + // dad_transmits for testing, production code will restore all IPv6 sysctls at + // StoppedState#enter anyway, read this parameter value after IpClient shutdown + // to check if that's default value 1. + mNetd.setProcSysNet(INetd.IPV6, INetd.CONF, mIfaceName, "dad_transmits", "1"); + startIpClientProvisioning(config); + verify(mNetd, timeout(TEST_TIMEOUT_MS)).interfaceSetEnableIPv6(mIfaceName, true); + doIpv6OnlyProvisioning(); + + // Shutdown IpClient and check if the IPv6 sysctls: accept_ra, accept_ra_defrtr and + // dad_transmits have been reset to the default values. + mIpc.shutdown(); + awaitIpClientShutdown(); + final int dadTransmits = Integer.parseUnsignedInt( + mNetd.getProcSysNet(INetd.IPV6, INetd.CONF, mIfaceName, "dad_transmits")); + assertEquals(1, dadTransmits); + final int acceptRa = Integer.parseUnsignedInt( + mNetd.getProcSysNet(INetd.IPV6, INetd.CONF, mIfaceName, "accept_ra")); + assertEquals(2, acceptRa); + final int acceptRaDefRtr = Integer.parseUnsignedInt( + mNetd.getProcSysNet(INetd.IPV6, INetd.CONF, mIfaceName, "accept_ra_defrtr")); + assertEquals(1, acceptRaDefRtr); + } + private void runDhcpDomainSearchListOptionTest(final String domainName, + final List<String> domainSearchList, final String expectedDomain) throws Exception { + when(mResources.getBoolean(R.bool.config_dhcp_client_domain_search_list)).thenReturn(true); + final ProvisioningConfiguration cfg = new ProvisioningConfiguration.Builder() + .withoutIpReachabilityMonitor() + .withoutIPv6() + .withCreatorUid(TEST_DEVICE_OWNER_APP_UID) + .build(); + + startIpClientProvisioning(cfg); + handleDhcpPackets(true /* isSuccessLease */, TEST_LEASE_DURATION_S, + false /* shouldReplyRapidCommitAck */, TEST_DEFAULT_MTU, + null /* captivePortalApiUrl */, null /* ipv6OnlyWaitTime */, + domainName, domainSearchList); + + final ArgumentCaptor<LinkProperties> captor = ArgumentCaptor.forClass(LinkProperties.class); + verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningSuccess(captor.capture()); + final LinkProperties lp = captor.getValue(); + assertNotNull(lp); + assertEquals(expectedDomain, lp.getDomains()); + } + + @Test + @SignatureRequiredTest(reason = "requires mocked DevicePolicyManager") + public void testDhcpDomainSearchListOption() throws Exception { + final String domainName = "google.com"; + final List<String> searchList = List.of("suffix1.google.com", "suffix2.google.com"); + final String expectedDomain = "google.com suffix1.google.com suffix2.google.com"; + runDhcpDomainSearchListOptionTest(domainName, searchList, expectedDomain); + } + + @Test + @SignatureRequiredTest(reason = "requires mocked DevicePolicyManager") + public void testDhcpDomainSearchListOption_invalidSuffix() throws Exception { + final String domainName = "google.com"; + final List<String> searchList = List.of("google com"); + runDhcpDomainSearchListOptionTest(domainName, searchList, domainName /* expectedDomain */); + } + + @Test + @SignatureRequiredTest(reason = "requires mocked DevicePolicyManager") + public void testDhcpDomainSearchListOption_onlySearchList() throws Exception { + final List<String> searchList = List.of("google.com", "example.com"); + final String expectedDomain = "google.com example.com"; + runDhcpDomainSearchListOptionTest(null /* domainName */, searchList, + expectedDomain); + } }
diff --git a/tests/integration/common/android/net/networkstack/TestNetworkStackServiceClient.kt b/tests/integration/common/android/net/networkstack/TestNetworkStackServiceClient.kt index bff1088..3bba529 100644 --- a/tests/integration/common/android/net/networkstack/TestNetworkStackServiceClient.kt +++ b/tests/integration/common/android/net/networkstack/TestNetworkStackServiceClient.kt
@@ -58,7 +58,7 @@ return networkStackVersion == 300000000L || networkStackVersion >= 301100000L } - private fun getNetworkStackComponent(connectorAction: String): ComponentName { + private fun getNetworkStackComponent(connectorAction: String?): ComponentName { val connectorIntent = Intent(connectorAction) return connectorIntent.resolveSystemService(context.packageManager, MATCH_SYSTEM_ONLY) ?: fail("TestNetworkStackService not found")
diff --git a/tests/integration/root/android/net/ip/IpClientRootTest.kt b/tests/integration/root/android/net/ip/IpClientRootTest.kt index 0f7ec0c..77d327f 100644 --- a/tests/integration/root/android/net/ip/IpClientRootTest.kt +++ b/tests/integration/root/android/net/ip/IpClientRootTest.kt
@@ -27,8 +27,6 @@ import android.net.ipmemorystore.Status import android.net.networkstack.TestNetworkStackServiceClient import android.os.Process -import android.provider.DeviceConfig -import android.util.ArrayMap import android.util.Log import androidx.test.platform.app.InstrumentationRegistry import com.android.net.module.util.DeviceConfigUtils @@ -138,8 +136,6 @@ } } - private val originalPropertyValues = ArrayMap<String, String>() - /** * Wrapper class for IIpClientCallbacks. * @@ -154,21 +150,6 @@ } @After - fun tearDownDeviceConfigProperties() { - if (testSkipped()) return - automation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG) - try { - for ((key, value) in originalPropertyValues.entries) { - if (key == null) continue - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, key, - value, false /* makeDefault */) - } - } finally { - automation.dropShellPermissionIdentity() - } - } - - @After fun tearDownIpMemoryStore() { if (testSkipped()) return val latch = CountDownLatch(1) @@ -195,22 +176,6 @@ return ipClientCaptor.value } - private fun setDeviceConfigProperty(name: String, value: String) { - automation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG) - try { - // Do not use computeIfAbsent as it would overwrite null values, - // property originally unset. - if (!originalPropertyValues.containsKey(name)) { - originalPropertyValues[name] = - DeviceConfig.getProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, name) - } - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, name, value, - false /* makeDefault */) - } finally { - automation.dropShellPermissionIdentity() - } - } - override fun setFeatureEnabled(feature: String, enabled: Boolean) { // The feature is enabled if the flag is lower than the package version. // Package versions follow a standard format with 9 digits. @@ -219,15 +184,19 @@ setDeviceConfigProperty(feature, if (enabled) "1" else "999999999") } - override fun setDeviceConfigProperty(name: String, value: Int) { - setDeviceConfigProperty(name, value.toString()) - } - - override fun isFeatureEnabled(name: String, defaultEnabled: Boolean): Boolean { + override fun isFeatureEnabled(name: String): Boolean { automation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG) try { - return DeviceConfigUtils.isFeatureEnabled(mContext, DeviceConfig.NAMESPACE_CONNECTIVITY, - name, defaultEnabled) + return DeviceConfigUtils.isNetworkStackFeatureEnabled(mContext, name) + } finally { + automation.dropShellPermissionIdentity() + } + } + + override fun isFeatureNotChickenedOut(name: String): Boolean { + automation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG) + try { + return DeviceConfigUtils.isNetworkStackFeatureNotChickenedOut(mContext, name) } finally { automation.dropShellPermissionIdentity() }
diff --git a/tests/integration/signature/android/net/NetworkStatsIntegrationTest.kt b/tests/integration/signature/android/net/NetworkStatsIntegrationTest.kt index dcc0b1f..b10e6e1 100644 --- a/tests/integration/signature/android/net/NetworkStatsIntegrationTest.kt +++ b/tests/integration/signature/android/net/NetworkStatsIntegrationTest.kt
@@ -28,12 +28,15 @@ import android.net.NetworkStatsIntegrationTest.Direction.UPLOAD import android.net.NetworkTemplate.MATCH_TEST import android.os.Build +import android.os.ParcelFileDescriptor.AutoCloseInputStream import android.os.Process +import android.util.Log import androidx.test.platform.app.InstrumentationRegistry import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo import com.android.testutils.DevSdkIgnoreRunner import com.android.testutils.PacketBridge import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged +import com.android.testutils.SkipPresubmit import com.android.testutils.TestDnsServer import com.android.testutils.TestHttpServer import com.android.testutils.TestableNetworkCallback @@ -41,6 +44,7 @@ import fi.iki.elonen.NanoHTTPD import java.io.BufferedInputStream import java.io.BufferedOutputStream +import java.io.BufferedReader import java.net.HttpURLConnection import java.net.HttpURLConnection.HTTP_OK import java.net.InetSocketAddress @@ -48,6 +52,7 @@ import java.nio.charset.Charset import kotlin.math.ceil import kotlin.test.assertEquals +import kotlin.test.assertNotNull import kotlin.test.assertTrue import org.junit.After import org.junit.Assume.assumeTrue @@ -61,6 +66,7 @@ @TargetApi(Build.VERSION_CODES.S) @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) class NetworkStatsIntegrationTest { + private val TAG = NetworkStatsIntegrationTest::class.java.simpleName private val INTERNAL_V6ADDR = LinkAddress(InetAddresses.parseNumericAddress("2001:db8::1234"), 64) private val EXTERNAL_V6ADDR = @@ -97,7 +103,7 @@ private val packetBridge = runAsShell(MANAGE_TEST_NETWORKS) { PacketBridge(context, INTERNAL_V6ADDR, EXTERNAL_V6ADDR, REMOTE_V6ADDR.address) } - private val cm = context.getSystemService(ConnectivityManager::class.java) + private val cm = context.getSystemService(ConnectivityManager::class.java)!! // Set up DNS server for testing server and DNS64. private val fakeDns = TestDnsServer( @@ -128,7 +134,7 @@ // network stats being counted, which can only be achieved when they are marked as TYPE_TEST. // If the tethering module does not support TYPE_TEST stats, then these tests will need // to be skipped. - fun shouldRunTests() = cm.getNetworkInfo(packetBridge.internalNetwork).type == TYPE_TEST + fun shouldRunTests() = cm.getNetworkInfo(packetBridge.internalNetwork)!!.type == TYPE_TEST @After fun tearDown() { @@ -138,7 +144,7 @@ } private fun waitFor464XlatReady(network: Network): String { - val iface = cm.getLinkProperties(network).interfaceName + val iface = cm.getLinkProperties(network)!!.interfaceName!! // Make a network request to listen to the specific test network. val nr = NetworkRequest.Builder() @@ -151,14 +157,14 @@ // Wait for the stacked address to be available. testCb.eventuallyExpect<LinkPropertiesChanged> { - it.lp.stackedLinks?.getOrNull(0)?.linkAddresses?.getOrNull(0) != null + it.lp.stackedLinks.getOrNull(0)?.linkAddresses?.getOrNull(0) != null } return iface } private val Network.mtu: Int get() { - val lp = cm.getLinkProperties(this) + val lp = cm.getLinkProperties(this)!! val mtuStacked = if (lp.stackedLinks[0]?.mtu != 0) lp.stackedLinks[0].mtu else DEFAULT_MTU val mtuInterface = if (lp.mtu != 0) lp.mtu else DEFAULT_MTU return mtuInterface.coerceAtMost(mtuStacked) @@ -178,6 +184,7 @@ * While the packets are being forwarded to the external interface, the servers will see * the packets originated from the mocked v6 address, and destined to a local v6 address. */ + @SkipPresubmit(reason = "Out of SLO flakiness") @Test fun test464XlatTcpStats() { // Wait for 464Xlat to be ready. @@ -379,6 +386,7 @@ val taggedUid = getUidDetail(iface, TEST_TAG) val trafficStatsIface = getTrafficStatsIface(iface) val trafficStatsUid = getTrafficStatsUid(Process.myUid()) + val xtBpfStats = getXtBpfStatsInternal() private fun getUidDetail(iface: String, tag: Int): BareStats { return getNetworkStatsThat(iface, tag) { nsm, template -> @@ -406,7 +414,7 @@ tag: Int, queryApi: (nsm: NetworkStatsManager, template: NetworkTemplate) -> NetworkStats ): BareStats { - val nsm = context.getSystemService(NetworkStatsManager::class.java) + val nsm = context.getSystemService(NetworkStatsManager::class.java)!! nsm.forceUpdate() val testTemplate = NetworkTemplate.Builder(MATCH_TEST) .setWifiNetworkKeys(setOf(iface)).build() @@ -445,6 +453,40 @@ TrafficStats.getUidTxBytes(uid), TrafficStats.getUidTxPackets(uid) ) + + private fun getXtBpfStatsInternal(): BareStats { + // The following pattern matches ip(6)tables-save -c output like below: + // [119:37802] -A bw_raw_PREROUTING -m bpf --object-pinned + // /sys/fs/bpf/netd_shared/prog_netd_skfilter_ingress_xtbpf + // [141:26439] -A bw_mangle_POSTROUTING -m bpf --object-pinned + // /sys/fs/bpf/netd_shared/prog_netd_skfilter_egress_xtbpf + val ingressRegex = Regex("""\[(?<rxPackets>\d+):(?<rxBytes>\d+)\]""" + + """.*prog_netd_skfilter_ingress_xtbpf""") + val egressRegex = Regex("""\[(?<txPackets>\d+):(?<txBytes>\d+)\]""" + + """.*prog_netd_skfilter_egress_xtbpf""") + val (v4Stats, v6Stats) = listOf("iptables-save -c", "ip6tables-save -c").map { + val output = runShellCommand(it) + val rxMatches = ingressRegex.find(output) + val txMatches = egressRegex.find(output) + assertNotNull(rxMatches) + assertNotNull(txMatches) + + BareStats( + rxBytes = rxMatches.groups["rxBytes"]!!.value.toLong(), + rxPackets = rxMatches.groups["rxPackets"]!!.value.toLong(), + txBytes = txMatches.groups["txBytes"]!!.value.toLong(), + txPackets = txMatches.groups["txPackets"]!!.value.toLong() + ) + } + return v4Stats.plus(v6Stats) + } + + private fun runShellCommand(cmd: String): String { + return InstrumentationRegistry.getInstrumentation().getUiAutomation() + .executeShellCommand(cmd).use { pfd -> + AutoCloseInputStream(pfd).bufferedReader().use(BufferedReader::readText) + } + } } private fun assertAllStatsIncreases( @@ -473,6 +515,15 @@ lower: BareStats, upper: BareStats ) { + // XtBpf iptables hook counted traffic on all interfaces. Thus, this might see traffic + // on other interfaces as well. Also, other thread/process could reload the relevant + // iptables table. Thus, instead of asserting the readings, print logs when it is + // unexpected to provide more debug information when failing other items. + if (!checkInRange(before.xtBpfStats, after.xtBpfStats, + lower + lower.reverse(), upper + upper.reverse())) { + Log.d(TAG, "Unexpected xtbpf stats: ${after.xtBpfStats} - ${before.xtBpfStats} " + + "is not within range [$lower, $upper]") + } assertInRange( "Unexpected iface traffic stats", after.iface, @@ -548,16 +599,24 @@ ) { // Passing the value after operation and the value before operation to dump the actual // numbers if it fails. - val value = after - before - assertTrue( - value.rxBytes in lower.rxBytes..upper.rxBytes && - value.rxPackets in lower.rxPackets..upper.rxPackets && - value.txBytes in lower.txBytes..upper.txBytes && - value.txPackets in lower.txPackets..upper.txPackets, + assertTrue(checkInRange(before, after, lower, upper), "$tag on $iface: $after - $before is not within range [$lower, $upper]" ) } + private fun checkInRange( + before: BareStats, + after: BareStats, + lower: BareStats, + upper: BareStats + ): Boolean { + val value = after - before + return value.rxBytes in lower.rxBytes..upper.rxBytes && + value.rxPackets in lower.rxPackets..upper.rxPackets && + value.txBytes in lower.txBytes..upper.txBytes && + value.txPackets in lower.txPackets..upper.txPackets + } + fun getRandomString(length: Long): String { val allowedChars = ('A'..'Z') + ('a'..'z') + ('0'..'9') return (1..length)
diff --git a/tests/integration/signature/android/net/ip/IpClientSignatureTest.kt b/tests/integration/signature/android/net/ip/IpClientSignatureTest.kt index b494732..2f91f4f 100644 --- a/tests/integration/signature/android/net/ip/IpClientSignatureTest.kt +++ b/tests/integration/signature/android/net/ip/IpClientSignatureTest.kt
@@ -44,8 +44,12 @@ override fun useNetworkStackSignature() = true - override fun isFeatureEnabled(name: String, defaultEnabled: Boolean): Boolean { - return mEnabledFeatures.get(name) ?: defaultEnabled + override fun isFeatureEnabled(name: String): Boolean { + return mEnabledFeatures.get(name) ?: false + } + + override fun isFeatureNotChickenedOut(name: String): Boolean { + return mEnabledFeatures.get(name) ?: true } override fun setFeatureEnabled(name: String, enabled: Boolean) {
diff --git a/tests/integration/signature/android/net/util/NetworkStackUtilsIntegrationTest.kt b/tests/integration/signature/android/net/util/NetworkStackUtilsIntegrationTest.kt index cf514e9..3f01bea 100644 --- a/tests/integration/signature/android/net/util/NetworkStackUtilsIntegrationTest.kt +++ b/tests/integration/signature/android/net/util/NetworkStackUtilsIntegrationTest.kt
@@ -33,6 +33,7 @@ import android.system.OsConstants.ARPHRD_ETHER import android.system.OsConstants.ETH_P_IPV6 import android.system.OsConstants.IPPROTO_UDP +import android.system.OsConstants.SOCK_CLOEXEC import android.system.OsConstants.SOCK_DGRAM import android.system.OsConstants.SOCK_NONBLOCK import android.system.OsConstants.SOCK_RAW @@ -159,9 +160,8 @@ assertArrayEquals("Sent packet != original packet", originalPacket, sentDhcpPacket) } - @Test - fun testAttachRaFilter() { - val socket = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IPV6) + private fun doTestAttachRaFilter(generic: Boolean) { + val socket = Os.socket(AF_PACKET, SOCK_RAW or SOCK_CLOEXEC, 0) val ifParams = InterfaceParams.getByName(iface.interfaceName) ?: fail("Could not obtain interface params for ${iface.interfaceName}") val socketAddr = SocketUtils.makePacketSocketAddress(ETH_P_IPV6, ifParams.index) @@ -176,7 +176,11 @@ echo.rewind() assertNextPacketEquals(socket, echo.readAsArray(), "ICMPv6 echo") - NetworkStackUtils.attachRaFilter(socket, ARPHRD_ETHER) + if (generic) { + NetworkStackUtils.attachControlPacketFilter(socket) + } else { + NetworkStackUtils.attachRaFilter(socket) + } // Send another echo, then an RA. After setting the filter expect only the RA. echo.rewind() reader.sendResponse(echo) @@ -192,6 +196,16 @@ assertNextPacketEquals(socket, ra.readAsArray(), "ICMPv6 RA") } + @Test + fun testAttachRaFilter() { + doTestAttachRaFilter(false) + } + + @Test + fun testRaViaAttachControlPacketFilter() { + doTestAttachRaFilter(true) + } + private fun assertNextPacketEquals(socket: FileDescriptor, expected: ByteArray, descr: String) { val buffer = ByteArray(TEST_MTU) val readPacket = Os.read(socket, buffer, 0 /* byteOffset */, buffer.size) @@ -293,12 +307,15 @@ packet.putShort(checksumOffset, IpUtils.ipChecksum(packet, ETHER_HEADER_LEN)) } - @Test - fun testDhcpResponseWithMfBitDropped() { + private fun doTestDhcpResponseWithMfBitDropped(generic: Boolean) { val ifindex = InterfaceParams.getByName(iface.interfaceName).index val packetSock = Os.socket(AF_PACKET, SOCK_RAW or SOCK_NONBLOCK, /*protocol=*/0) try { - NetworkStackUtils.attachDhcpFilter(packetSock) + if (generic) { + NetworkStackUtils.attachControlPacketFilter(packetSock) + } else { + NetworkStackUtils.attachDhcpFilter(packetSock) + } val addr = SocketUtils.makePacketSocketAddress(OsConstants.ETH_P_IP, ifindex) Os.bind(packetSock, addr) val packet = DhcpPacket.buildNakPacket(DhcpPacket.ENCAP_L2, 42, @@ -319,6 +336,16 @@ Os.close(packetSock) } } + + @Test + fun testDhcpResponseWithMfBitDropped() { + doTestDhcpResponseWithMfBitDropped(false) + } + + @Test + fun testGenericDhcpResponseWithMfBitDropped() { + doTestDhcpResponseWithMfBitDropped(true) + } } private fun ByteBuffer.readAsArray(): ByteArray {
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp index 1233ae5..ea29714 100644 --- a/tests/unit/Android.bp +++ b/tests/unit/Android.bp
@@ -21,7 +21,10 @@ java_defaults { name: "NetworkStackTestsDefaults", platform_apis: true, - srcs: ["src/**/*.java", "src/**/*.kt"], + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], resource_dirs: ["res"], static_libs: [ "androidx.test.ext.junit", @@ -39,7 +42,7 @@ ], defaults: [ "framework-connectivity-test-defaults", - "libnetworkstackutilsjni_deps" + "libnetworkstackutilsjni_deps", ], jni_libs: [ // For mockito extended @@ -66,6 +69,9 @@ static_libs: ["NetworkStackApiCurrentLib"], compile_multilib: "both", // Workaround for b/147785146 for mainline-presubmit jarjar_rules: ":NetworkStackJarJarRules", + lint: { + baseline_filename: "lint-baseline.xml", + }, } // Library containing the unit tests. This is used by the coverage test target to pull in the @@ -76,17 +82,24 @@ min_sdk_version: "30", defaults: ["NetworkStackTestsDefaults"], static_libs: ["NetworkStackApiStableLib"], + lint: { + test: true, + baseline_filename: "lint-baseline.xml", + }, visibility: [ "//packages/modules/NetworkStack/tests/integration", "//packages/modules/Connectivity/tests:__subpackages__", "//packages/modules/Connectivity/Tethering/tests:__subpackages__", - ] + ], } android_test { name: "NetworkStackTests", min_sdk_version: "30", - test_suites: ["general-tests", "mts"], + test_suites: [ + "general-tests", + "mts", + ], defaults: [ "NetworkStackTestsDefaults", "connectivity-mainline-presubmit-java-defaults", @@ -94,6 +107,9 @@ static_libs: ["NetworkStackApiStableLib"], compile_multilib: "both", jarjar_rules: ":NetworkStackJarJarRules", + lint: { + baseline_filename: "lint-baseline.xml", + }, } // Additional dependencies of libnetworkstackutilsjni that are not provided by the system when
diff --git a/tests/unit/jni/Android.bp b/tests/unit/jni/Android.bp index 1dbfbbe..50576cc 100644 --- a/tests/unit/jni/Android.bp +++ b/tests/unit/jni/Android.bp
@@ -37,6 +37,8 @@ ], static_libs: [ "libapf", + "libapf_v5", + "libapfdisassembler", "libpcap", ], sdk_version: "30",
diff --git a/tests/unit/jni/apf_jni.cpp b/tests/unit/jni/apf_jni.cpp index ff30bd1..8e14b3a 100644 --- a/tests/unit/jni/apf_jni.cpp +++ b/tests/unit/jni/apf_jni.cpp
@@ -24,15 +24,32 @@ #include <vector> #include "apf_interpreter.h" +#include "disassembler.h" #include "nativehelper/scoped_primitive_array.h" +#include "v5/apf_interpreter.h" +#include "v5/test_buf_allocator.h" #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) #define LOG_TAG "NetworkStackUtils-JNI" +static int run_apf_interpreter(int apf_version, uint8_t* program, + uint32_t program_len, uint32_t ram_len, + const uint8_t* packet, uint32_t packet_len, + uint32_t filter_age) { + if (apf_version == 4) { + return accept_packet(program, program_len, ram_len, packet, packet_len, + filter_age); + } else { + return apf_run(nullptr, program, program_len, ram_len, packet, packet_len, + filter_age << 14); + } +} + // JNI function acting as simply call-through to native APF interpreter. -static jint com_android_server_ApfTest_apfSimulate( - JNIEnv* env, jclass, jbyteArray jprogram, jbyteArray jpacket, - jbyteArray jdata, jint filter_age) { +static jint +com_android_server_ApfTest_apfSimulate(JNIEnv* env, jclass, jint apf_version, + jbyteArray jprogram, jbyteArray jpacket, + jbyteArray jdata, jint filter_age) { ScopedByteArrayRO packet(env, jpacket); uint32_t packet_len = (uint32_t)packet.size(); @@ -47,9 +64,10 @@ reinterpret_cast<jbyte*>(buf.data() + program_len)); } - jint result = - accept_packet(buf.data(), program_len, program_len + data_len, - reinterpret_cast<const uint8_t*>(packet.get()), packet_len, filter_age); + jint result = run_apf_interpreter( + apf_version, buf.data(), program_len, program_len + data_len, + reinterpret_cast<const uint8_t *>(packet.get()), packet_len, + filter_age); if (jdata) { env->SetByteArrayRegion(jdata, 0, data_len, @@ -118,8 +136,9 @@ return env->NewStringUTF(bpf_string.c_str()); } -static jboolean com_android_server_ApfTest_compareBpfApf(JNIEnv* env, jclass, jstring jfilter, - jstring jpcap_filename, jbyteArray japf_program) { +static jboolean com_android_server_ApfTest_compareBpfApf( + JNIEnv* env, jclass, jint apf_version, jstring jfilter, + jstring jpcap_filename, jbyteArray japf_program) { ScopedUtfChars filter(env, jfilter); ScopedUtfChars pcap_filename(env, jpcap_filename); ScopedByteArrayRO apf_program(env, japf_program); @@ -163,7 +182,7 @@ const uint8_t* apf_packet; do { apf_packet = pcap_next(apf_pcap.get(), &apf_header); - } while (apf_packet != NULL && !accept_packet( + } while (apf_packet != NULL && !run_apf_interpreter(apf_version, reinterpret_cast<uint8_t*>(const_cast<int8_t*>(apf_program.get())), apf_program.size(), 0 /* data_len */, apf_packet, apf_header.len, 0 /* filter_age */)); @@ -182,8 +201,9 @@ return true; } -static jboolean com_android_server_ApfTest_dropsAllPackets(JNIEnv* env, jclass, jbyteArray jprogram, - jbyteArray jdata, jstring jpcap_filename) { +static jboolean com_android_server_ApfTest_dropsAllPackets( + JNIEnv* env, jclass, jint apf_version, jbyteArray jprogram, + jbyteArray jdata, jstring jpcap_filename) { ScopedUtfChars pcap_filename(env, jpcap_filename); ScopedByteArrayRO apf_program(env, jprogram); uint32_t apf_program_len = (uint32_t)apf_program.size(); @@ -208,8 +228,9 @@ } while ((apf_packet = pcap_next(apf_pcap.get(), &apf_header)) != NULL) { - int result = accept_packet(buf.data(), apf_program_len, - apf_program_len + data_len, apf_packet, apf_header.len, 0); + int result = run_apf_interpreter( + apf_version, buf.data(), apf_program_len, + apf_program_len + data_len, apf_packet, apf_header.len, 0); // Return false once packet passes the filter if (result) { @@ -224,6 +245,52 @@ return true; } +static char output_buffer[512]; + +static jobjectArray com_android_server_ApfTest_disassembleApf( + JNIEnv* env, jclass, jbyteArray jprogram) { + uint32_t program_len = env->GetArrayLength(jprogram); + std::vector<uint8_t> buf(program_len, 0); + + env->GetByteArrayRegion(jprogram, 0, program_len, + reinterpret_cast<jbyte*>(buf.data())); + std::vector<std::string> disassemble_output; + for (uint32_t pc = 0; pc < program_len;) { + pc = apf_disassemble(buf.data(), program_len, pc, output_buffer, + sizeof(output_buffer) / sizeof(output_buffer[0])); + disassemble_output.emplace_back(output_buffer); + } + jclass stringClass = env->FindClass("java/lang/String"); + jobjectArray disassembleOutput = + env->NewObjectArray(disassemble_output.size(), stringClass, nullptr); + + for (jsize i = 0; i < (jsize) disassemble_output.size(); i++) { + jstring j_disassemble_output = + env->NewStringUTF(disassemble_output[i].c_str()); + env->SetObjectArrayElement(disassembleOutput, i, j_disassemble_output); + env->DeleteLocalRef(j_disassemble_output); + } + + return disassembleOutput; +} + +jbyteArray com_android_server_ApfTest_getTransmittedPacket(JNIEnv* env, + jclass) { + jbyteArray jdata = env->NewByteArray((jint) apf_test_tx_packet_len); + if (jdata == NULL) { return NULL; } + if (apf_test_tx_packet_len == 0) { return jdata; } + + env->SetByteArrayRegion(jdata, 0, (jint) apf_test_tx_packet_len, + reinterpret_cast<jbyte*>(apf_test_tx_packet)); + + return jdata; +} + +void com_android_server_ApfTest_resetTransmittedPacketMemory(JNIEnv, jclass) { + apf_test_tx_packet_len = 0; + memset(apf_test_tx_packet, 0, APF_TX_BUFFER_SIZE); +} + extern "C" jint JNI_OnLoad(JavaVM* vm, void*) { JNIEnv *env; if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { @@ -232,17 +299,23 @@ } static JNINativeMethod gMethods[] = { - { "apfSimulate", "([B[B[BI)I", + { "apfSimulate", "(I[B[B[BI)I", (void*)com_android_server_ApfTest_apfSimulate }, { "compileToBpf", "(Ljava/lang/String;)Ljava/lang/String;", (void*)com_android_server_ApfTest_compileToBpf }, - { "compareBpfApf", "(Ljava/lang/String;Ljava/lang/String;[B)Z", + { "compareBpfApf", "(ILjava/lang/String;Ljava/lang/String;[B)Z", (void*)com_android_server_ApfTest_compareBpfApf }, - { "dropsAllPackets", "([B[BLjava/lang/String;)Z", + { "dropsAllPackets", "(I[B[BLjava/lang/String;)Z", (void*)com_android_server_ApfTest_dropsAllPackets }, + { "disassembleApf", "([B)[Ljava/lang/String;", + (void*)com_android_server_ApfTest_disassembleApf }, + { "getTransmittedPacket", "()[B", + (void*)com_android_server_ApfTest_getTransmittedPacket }, + { "resetTransmittedPacketMemory", "()V", + (void*)com_android_server_ApfTest_resetTransmittedPacketMemory }, }; - jniRegisterNativeMethods(env, "android/net/apf/ApfTest", + jniRegisterNativeMethods(env, "android/net/apf/ApfJniUtils", gMethods, ARRAY_SIZE(gMethods)); return JNI_VERSION_1_6;
diff --git a/tests/unit/res/raw/apfPcap.pcap b/tests/unit/res/raw/apfPcap.pcap index 6f69c4a..0206d25 100644 --- a/tests/unit/res/raw/apfPcap.pcap +++ b/tests/unit/res/raw/apfPcap.pcap Binary files differ
diff --git a/tests/unit/src/android/net/apf/ApfJniUtils.java b/tests/unit/src/android/net/apf/ApfJniUtils.java new file mode 100644 index 0000000..e6a7ad7 --- /dev/null +++ b/tests/unit/src/android/net/apf/ApfJniUtils.java
@@ -0,0 +1,72 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.net.apf; + +/** + * The class contains the helper method for interacting with native apf code. + */ +public class ApfJniUtils { + + static { + // Load up native shared library containing APF interpreter exposed via JNI. + System.loadLibrary("networkstacktestsjni"); + } + + /** + * Call the APF interpreter to run {@code program} on {@code packet} with persistent memory + * segment {@data} pretending the filter was installed {@code filter_age} seconds ago. + */ + public static native int apfSimulate(int apfVersion, byte[] program, byte[] packet, + byte[] data, int filterAge); + + /** + * Compile a tcpdump human-readable filter (e.g. "icmp" or "tcp port 54") into a BPF + * prorgam and return a human-readable dump of the BPF program identical to "tcpdump -d". + */ + public static native String compileToBpf(String filter); + + /** + * Open packet capture file {@code pcap_filename} and filter the packets using tcpdump + * human-readable filter (e.g. "icmp" or "tcp port 54") compiled to a BPF program and + * at the same time using APF program {@code apf_program}. Return {@code true} if + * both APF and BPF programs filter out exactly the same packets. + */ + public static native boolean compareBpfApf(int apfVersion, String filter, + String pcapFilename, byte[] apfProgram); + + /** + * Open packet capture file {@code pcapFilename} and run it through APF filter. Then + * checks whether all the packets are dropped and populates data[] {@code data} with + * the APF counters. + */ + public static native boolean dropsAllPackets(int apfVersion, byte[] program, byte[] data, + String pcapFilename); + + /** + * Disassemble the Apf program into human-readable text. + */ + public static native String[] disassembleApf(byte[] program); + + /** + * Get the transmitted packet. + */ + public static native byte[] getTransmittedPacket(); + + /** + * Reset the memory region that stored the transmitted packet. + */ + public static native void resetTransmittedPacketMemory(); +}
diff --git a/tests/unit/src/android/net/apf/ApfTest.java b/tests/unit/src/android/net/apf/ApfTest.java index 285fcb4..4e1187b 100644 --- a/tests/unit/src/android/net/apf/ApfTest.java +++ b/tests/unit/src/android/net/apf/ApfTest.java
@@ -16,9 +16,18 @@ package android.net.apf; +import static android.net.apf.ApfGenerator.APF_VERSION_4; import static android.net.apf.ApfGenerator.Register.R0; import static android.net.apf.ApfGenerator.Register.R1; -import static android.system.OsConstants.AF_UNIX; +import static android.net.apf.ApfJniUtils.compareBpfApf; +import static android.net.apf.ApfJniUtils.compileToBpf; +import static android.net.apf.ApfJniUtils.dropsAllPackets; +import static android.net.apf.ApfTestUtils.DROP; +import static android.net.apf.ApfTestUtils.MIN_PKT_SIZE; +import static android.net.apf.ApfTestUtils.PASS; +import static android.net.apf.ApfTestUtils.assertProgramEquals; +import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED; +import static android.os.PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED; import static android.system.OsConstants.ARPHRD_ETHER; import static android.system.OsConstants.ETH_P_ARP; import static android.system.OsConstants.ETH_P_IP; @@ -27,24 +36,24 @@ import static android.system.OsConstants.IPPROTO_IPV6; import static android.system.OsConstants.IPPROTO_TCP; import static android.system.OsConstants.IPPROTO_UDP; -import static android.system.OsConstants.SOCK_STREAM; import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE; -import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_LEN; -import static com.android.networkstack.util.NetworkStackUtils.APF_USE_RA_LIFETIME_CALCULATION_FIX_VERSION; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import static org.mockito.Mockito.anyBoolean; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.eq; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; import android.net.InetAddresses; import android.net.IpPrefix; import android.net.LinkAddress; @@ -52,49 +61,52 @@ import android.net.MacAddress; import android.net.NattKeepalivePacketDataParcelable; import android.net.TcpKeepalivePacketDataParcelable; +import android.net.apf.ApfCounterTracker.Counter; import android.net.apf.ApfFilter.ApfConfiguration; import android.net.apf.ApfGenerator.IllegalInstructionException; -import android.net.ip.IIpClientCallbacks; -import android.net.ip.IpClient.IpClientCallbacksWrapper; +import android.net.apf.ApfTestUtils.MockIpClientCallback; +import android.net.apf.ApfTestUtils.TestApfFilter; +import android.net.apf.ApfTestUtils.TestLegacyApfFilter; import android.net.metrics.IpConnectivityLog; -import android.net.metrics.RaEvent; -import android.os.ConditionVariable; -import android.os.Parcelable; -import android.os.SystemClock; +import android.os.Build; +import android.os.PowerManager; +import android.stats.connectivity.NetworkQuirkEvent; import android.system.ErrnoException; -import android.system.Os; import android.text.TextUtils; import android.text.format.DateUtils; +import android.util.ArrayMap; import android.util.Log; import android.util.Pair; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.internal.util.HexDump; import com.android.net.module.util.DnsPacket; import com.android.net.module.util.Inet4AddressUtils; -import com.android.net.module.util.InterfaceParams; import com.android.net.module.util.NetworkStackConstants; import com.android.net.module.util.PacketBuilder; -import com.android.net.module.util.SharedLog; -import com.android.networkstack.apishim.NetworkInformationShimImpl; +import com.android.networkstack.metrics.ApfSessionInfoMetrics; +import com.android.networkstack.metrics.IpClientRaInfoMetrics; +import com.android.networkstack.metrics.NetworkQuirkMetrics; import com.android.server.networkstack.tests.R; +import com.android.testutils.DevSdkIgnoreRule; +import com.android.testutils.DevSdkIgnoreRunner; -import libcore.io.IoUtils; import libcore.io.Streams; import org.junit.Assert; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.io.ByteArrayOutputStream; import java.io.File; -import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -108,41 +120,49 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Random; /** * Tests for APF program generator and interpreter. * - * Build, install and run with: - * runtest frameworks-net -c android.net.apf.ApfTest + * The test cases will be executed by both APFv4 and APFv6 interpreter. */ -@RunWith(AndroidJUnit4.class) +@RunWith(DevSdkIgnoreRunner.class) @SmallTest public class ApfTest { - private static final int TIMEOUT_MS = 500; private static final int MIN_APF_VERSION = 2; - @Mock IpConnectivityLog mLog; - @Mock ApfFilter.Dependencies mDependencies; - @Mock Context mContext; + @Rule + public DevSdkIgnoreRule mDevSdkIgnoreRule = new DevSdkIgnoreRule(); + // Indicates which apf interpreter to run. + @Parameterized.Parameter() + public int mApfVersion; + @Parameterized.Parameters + public static Iterable<? extends Object> data() { + return Arrays.asList(4, 6); + } + + @Mock private Context mContext; + @Mock + private ApfFilter.Dependencies mDependencies; + @Mock private PowerManager mPowerManager; + @Mock private IpConnectivityLog mIpConnectivityLog; + @Mock private NetworkQuirkMetrics mNetworkQuirkMetrics; + @Mock private ApfSessionInfoMetrics mApfSessionInfoMetrics; + @Mock private IpClientRaInfoMetrics mIpClientRaInfoMetrics; + @Mock private ApfFilter.Clock mClock; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - when(mDependencies.isFeatureEnabled(eq(mContext), - eq(APF_USE_RA_LIFETIME_CALCULATION_FIX_VERSION), anyBoolean())).thenReturn(true); - // Load up native shared library containing APF interpreter exposed via JNI. - System.loadLibrary("networkstacktestsjni"); + doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class); + doReturn(mApfSessionInfoMetrics).when(mDependencies).getApfSessionInfoMetrics(); + doReturn(mIpClientRaInfoMetrics).when(mDependencies).getIpClientRaInfoMetrics(); } private static final String TAG = "ApfTest"; // Expected return codes from APF interpreter. - private static final int PASS = 1; - private static final int DROP = 0; - // Interpreter will just accept packets without link layer headers, so pad fake packet to at - // least the minimum packet size. - private static final int MIN_PKT_SIZE = 15; - private static final ApfCapabilities MOCK_APF_CAPABILITIES = new ApfCapabilities(2, 4096, ARPHRD_ETHER); @@ -153,6 +173,7 @@ private static final boolean ALLOW_802_3_FRAMES = false; private static final int MIN_RDNSS_LIFETIME_SEC = 0; + private static final int MIN_METRICS_SESSION_DURATIONS_MS = 300_000; // Constants for opcode encoding private static final byte LI_OP = (byte)(13 << 3); @@ -172,104 +193,57 @@ config.ethTypeBlackList = new int[0]; config.minRdnssLifetimeSec = MIN_RDNSS_LIFETIME_SEC; config.minRdnssLifetimeSec = 67; + config.minMetricsSessionDurationMs = MIN_METRICS_SESSION_DURATIONS_MS; return config; } - private static String label(int code) { - switch (code) { - case PASS: return "PASS"; - case DROP: return "DROP"; - default: return "UNKNOWN"; - } + private void assertPass(ApfGenerator gen) throws ApfGenerator.IllegalInstructionException { + ApfTestUtils.assertPass(mApfVersion, gen); } - private static void assertReturnCodesEqual(String msg, int expected, int got) { - assertEquals(msg, label(expected), label(got)); - } - - private static void assertReturnCodesEqual(int expected, int got) { - assertEquals(label(expected), label(got)); - } - - private void assertVerdict(int expected, byte[] program, byte[] packet, int filterAge) { - final String msg = "Unexpected APF verdict. To debug:\n" - + " apf_run --program " + HexDump.toHexString(program) - + " --packet " + HexDump.toHexString(packet) + " --trace | less\n "; - assertReturnCodesEqual(msg, expected, apfSimulate(program, packet, null, filterAge)); - } - - private void assertVerdict(String msg, int expected, byte[] program, byte[] packet, - int filterAge) { - assertReturnCodesEqual(msg, expected, apfSimulate(program, packet, null, filterAge)); - } - - private void assertVerdict(int expected, byte[] program, byte[] packet) { - assertVerdict(expected, program, packet, 0); - } - - private void assertPass(byte[] program, byte[] packet, int filterAge) { - assertVerdict(PASS, program, packet, filterAge); + private void assertDrop(ApfGenerator gen) throws ApfGenerator.IllegalInstructionException { + ApfTestUtils.assertDrop(mApfVersion, gen); } private void assertPass(byte[] program, byte[] packet) { - assertVerdict(PASS, program, packet); - } - - private void assertDrop(byte[] program, byte[] packet, int filterAge) { - assertVerdict(DROP, program, packet, filterAge); + ApfTestUtils.assertPass(mApfVersion, program, packet); } private void assertDrop(byte[] program, byte[] packet) { - assertVerdict(DROP, program, packet); + ApfTestUtils.assertDrop(mApfVersion, program, packet); } - private void assertProgramEquals(byte[] expected, byte[] program) throws AssertionError { - // assertArrayEquals() would only print one byte, making debugging difficult. - if (!Arrays.equals(expected, program)) { - throw new AssertionError( - "\nexpected: " + HexDump.toHexString(expected) + - "\nactual: " + HexDump.toHexString(program)); - } + private void assertPass(byte[] program, byte[] packet, int filterAge) { + ApfTestUtils.assertPass(mApfVersion, program, packet, filterAge); } - private void assertDataMemoryContents( - int expected, byte[] program, byte[] packet, byte[] data, byte[] expected_data) - throws IllegalInstructionException, Exception { - assertReturnCodesEqual(expected, apfSimulate(program, packet, data, 0 /* filterAge */)); - - // assertArrayEquals() would only print one byte, making debugging difficult. - if (!Arrays.equals(expected_data, data)) { - throw new Exception( - "\nprogram: " + HexDump.toHexString(program) + - "\ndata memory: " + HexDump.toHexString(data) + - "\nexpected: " + HexDump.toHexString(expected_data)); - } - } - - private void assertVerdict(int expected, ApfGenerator gen, byte[] packet, int filterAge) - throws IllegalInstructionException { - assertReturnCodesEqual(expected, apfSimulate(gen.generate(), packet, null, - filterAge)); + private void assertDrop(byte[] program, byte[] packet, int filterAge) { + ApfTestUtils.assertDrop(mApfVersion, program, packet, filterAge); } private void assertPass(ApfGenerator gen, byte[] packet, int filterAge) - throws IllegalInstructionException { - assertVerdict(PASS, gen, packet, filterAge); + throws ApfGenerator.IllegalInstructionException { + ApfTestUtils.assertPass(mApfVersion, gen, packet, filterAge); } private void assertDrop(ApfGenerator gen, byte[] packet, int filterAge) - throws IllegalInstructionException { - assertVerdict(DROP, gen, packet, filterAge); + throws ApfGenerator.IllegalInstructionException { + ApfTestUtils.assertDrop(mApfVersion, gen, packet, filterAge); } - private void assertPass(ApfGenerator gen) - throws IllegalInstructionException { - assertVerdict(PASS, gen, new byte[MIN_PKT_SIZE], 0); + private void assertDataMemoryContents(int expected, byte[] program, byte[] packet, + byte[] data, byte[] expectedData) throws Exception { + ApfTestUtils.assertDataMemoryContents(mApfVersion, expected, program, packet, data, + expectedData); } - private void assertDrop(ApfGenerator gen) - throws IllegalInstructionException { - assertVerdict(DROP, gen, new byte[MIN_PKT_SIZE], 0); + private void assertVerdict(String msg, int expected, byte[] program, + byte[] packet, int filterAge) { + ApfTestUtils.assertVerdict(mApfVersion, msg, expected, program, packet, filterAge); + } + + private void assertVerdict(int expected, byte[] program, byte[] packet) { + ApfTestUtils.assertVerdict(mApfVersion, expected, program, packet); } /** @@ -342,6 +316,12 @@ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL); assertDrop(gen); + // Test add with a small signed negative value. + gen = new ApfGenerator(MIN_APF_VERSION); + gen.addAdd(-1); + gen.addJumpIfR0Equals(-1, gen.DROP_LABEL); + assertDrop(gen); + // Test subtract. gen = new ApfGenerator(MIN_APF_VERSION); gen.addAdd(-1234567890); @@ -596,8 +576,8 @@ // Test filter age pre-filled memory. gen = new ApfGenerator(MIN_APF_VERSION); gen.addLoadFromMemory(R0, gen.FILTER_AGE_MEMORY_SLOT); - gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL); - assertDrop(gen, new byte[MIN_PKT_SIZE], 1234567890); + gen.addJumpIfR0Equals(123, gen.DROP_LABEL); + assertDrop(gen, new byte[MIN_PKT_SIZE], 123); // Test packet size pre-filled memory. gen = new ApfGenerator(MIN_APF_VERSION); @@ -652,7 +632,7 @@ // Test jump if bytes not equal. gen = new ApfGenerator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 1); - gen.addJumpIfBytesNotEqual(R0, new byte[]{123}, gen.DROP_LABEL); + gen.addJumpIfBytesAtR0NotEqual(new byte[]{123}, gen.DROP_LABEL); program = gen.generate(); assertEquals(6, program.length); assertEquals((13 << 3) | (1 << 1) | 0, program[0]); @@ -664,20 +644,20 @@ assertDrop(program, new byte[MIN_PKT_SIZE], 0); gen = new ApfGenerator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 1); - gen.addJumpIfBytesNotEqual(R0, new byte[]{123}, gen.DROP_LABEL); + gen.addJumpIfBytesAtR0NotEqual(new byte[]{123}, gen.DROP_LABEL); byte[] packet123 = {0,123,0,0,0,0,0,0,0,0,0,0,0,0,0}; assertPass(gen, packet123, 0); gen = new ApfGenerator(MIN_APF_VERSION); - gen.addJumpIfBytesNotEqual(R0, new byte[]{123}, gen.DROP_LABEL); + gen.addJumpIfBytesAtR0NotEqual(new byte[]{123}, gen.DROP_LABEL); assertDrop(gen, packet123, 0); gen = new ApfGenerator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 1); - gen.addJumpIfBytesNotEqual(R0, new byte[]{1,2,30,4,5}, gen.DROP_LABEL); + gen.addJumpIfBytesAtR0NotEqual(new byte[]{1, 2, 30, 4, 5}, gen.DROP_LABEL); byte[] packet12345 = {0,1,2,3,4,5,0,0,0,0,0,0,0,0,0}; assertDrop(gen, packet12345, 0); gen = new ApfGenerator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 1); - gen.addJumpIfBytesNotEqual(R0, new byte[]{1,2,3,4,5}, gen.DROP_LABEL); + gen.addJumpIfBytesAtR0NotEqual(new byte[]{1, 2, 3, 4, 5}, gen.DROP_LABEL); assertPass(gen, packet12345, 0); } @@ -768,23 +748,23 @@ ApfGenerator gen; // Load data with no offset: lddw R0, [0 + r1] - gen = new ApfGenerator(3); + gen = new ApfGenerator(APF_VERSION_4); gen.addLoadData(R0, 0); assertProgramEquals(new byte[]{LDDW_OP | SIZE0}, gen.generate()); // Store data with 8bit negative offset: lddw r0, [-42 + r1] - gen = new ApfGenerator(3); + gen = new ApfGenerator(APF_VERSION_4); gen.addStoreData(R0, -42); assertProgramEquals(new byte[]{STDW_OP | SIZE8, -42}, gen.generate()); // Store data to R1 with 16bit negative offset: stdw r1, [-0x1122 + r0] - gen = new ApfGenerator(3); + gen = new ApfGenerator(APF_VERSION_4); gen.addStoreData(R1, -0x1122); assertProgramEquals(new byte[]{STDW_OP | SIZE16 | R1_REG, (byte)0xEE, (byte)0xDE}, gen.generate()); // Load data to R1 with 32bit negative offset: lddw r1, [0xDEADBEEF + r0] - gen = new ApfGenerator(3); + gen = new ApfGenerator(APF_VERSION_4); gen.addLoadData(R1, 0xDEADBEEF); assertProgramEquals( new byte[]{LDDW_OP | SIZE32 | R1_REG, @@ -802,12 +782,12 @@ byte[] expected_data = data.clone(); // No memory access instructions: should leave the data segment untouched. - ApfGenerator gen = new ApfGenerator(3); + ApfGenerator gen = new ApfGenerator(APF_VERSION_4); assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data); // Expect value 0x87654321 to be stored starting from address -11 from the end of the // data buffer, in big-endian order. - gen = new ApfGenerator(3); + gen = new ApfGenerator(APF_VERSION_4); gen.addLoadImmediate(R0, 0x87654321); gen.addLoadImmediate(R1, -5); gen.addStoreData(R0, -6); // -5 + -6 = -11 (offset +5 with data_len=16) @@ -824,7 +804,7 @@ @Test public void testApfDataRead() throws IllegalInstructionException, Exception { // Program that DROPs if address 10 (-6) contains 0x87654321. - ApfGenerator gen = new ApfGenerator(3); + ApfGenerator gen = new ApfGenerator(APF_VERSION_4); gen.addLoadImmediate(R1, 1000); gen.addLoadData(R0, -1006); // 1000 + -1006 = -6 (offset +10 with data_len=16) gen.addJumpIfR0Equals(0x87654321, gen.DROP_LABEL); @@ -853,7 +833,7 @@ */ @Test public void testApfDataReadModifyWrite() throws IllegalInstructionException, Exception { - ApfGenerator gen = new ApfGenerator(3); + ApfGenerator gen = new ApfGenerator(APF_VERSION_4); gen.addLoadImmediate(R1, -22); gen.addLoadData(R0, 0); // Load from address 32 -22 + 0 = 10 gen.addAdd(0x78453412); // 87654321 + 78453412 = FFAA7733 @@ -880,35 +860,35 @@ byte[] expected_data = data; // Program that DROPs unconditionally. This is our the baseline. - ApfGenerator gen = new ApfGenerator(3); + ApfGenerator gen = new ApfGenerator(APF_VERSION_4); gen.addLoadImmediate(R0, 3); gen.addLoadData(R1, 7); gen.addJump(gen.DROP_LABEL); assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data); // Same program as before, but this time we're trying to load past the end of the data. - gen = new ApfGenerator(3); + gen = new ApfGenerator(APF_VERSION_4); gen.addLoadImmediate(R0, 20); gen.addLoadData(R1, 15); // 20 + 15 > 32 gen.addJump(gen.DROP_LABEL); // Not reached. assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data); // Subtracting an immediate should work... - gen = new ApfGenerator(3); + gen = new ApfGenerator(APF_VERSION_4); gen.addLoadImmediate(R0, 20); gen.addLoadData(R1, -4); gen.addJump(gen.DROP_LABEL); assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data); // ...and underflowing simply wraps around to the end of the buffer... - gen = new ApfGenerator(3); + gen = new ApfGenerator(APF_VERSION_4); gen.addLoadImmediate(R0, 20); gen.addLoadData(R1, -30); gen.addJump(gen.DROP_LABEL); assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data); // ...but doesn't allow accesses before the start of the buffer - gen = new ApfGenerator(3); + gen = new ApfGenerator(APF_VERSION_4); gen.addLoadImmediate(R0, 20); gen.addLoadData(R1, -1000); gen.addJump(gen.DROP_LABEL); // Not reached. @@ -929,7 +909,7 @@ for (String tcpdump_filter : tcpdump_filters) { byte[] apf_program = Bpf2Apf.convert(compileToBpf(tcpdump_filter)); assertTrue("Failed to match for filter: " + tcpdump_filter, - compareBpfApf(tcpdump_filter, pcap_filename, apf_program)); + compareBpfApf(mApfVersion, tcpdump_filter, pcap_filename, apf_program)); } } @@ -951,102 +931,19 @@ config.apfCapabilities = MOCK_APF_PCAP_CAPABILITIES; config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; - TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog, - mDependencies); + TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + mNetworkQuirkMetrics); apfFilter.setLinkProperties(lp); - byte[] program = ipClientCallback.getApfProgram(); - byte[] data = new byte[ApfFilter.Counter.totalSize()]; + byte[] program = ipClientCallback.assertProgramUpdateAndGet(); + byte[] data = new byte[Counter.totalSize()]; final boolean result; - result = dropsAllPackets(program, data, pcapFilename); + result = dropsAllPackets(mApfVersion, program, data, pcapFilename); Log.i(TAG, "testApfFilterPcapFile(): Data counters: " + HexDump.toHexString(data, false)); assertTrue("Failed to drop all packets by filter. \nAPF counters:" + HexDump.toHexString(data, false), result); - } - - private class MockIpClientCallback extends IpClientCallbacksWrapper { - private final ConditionVariable mGotApfProgram = new ConditionVariable(); - private byte[] mLastApfProgram; - - MockIpClientCallback() { - super(mock(IIpClientCallbacks.class), mock(SharedLog.class), - NetworkInformationShimImpl.newInstance()); - } - - @Override - public void installPacketFilter(byte[] filter) { - mLastApfProgram = filter; - mGotApfProgram.open(); - } - - public void resetApfProgramWait() { - mGotApfProgram.close(); - } - - public byte[] getApfProgram() { - assertTrue(mGotApfProgram.block(TIMEOUT_MS)); - return mLastApfProgram; - } - - public void assertNoProgramUpdate() { - assertFalse(mGotApfProgram.block(TIMEOUT_MS)); - } - } - - private static class TestApfFilter extends ApfFilter { - public static final byte[] MOCK_MAC_ADDR = {1,2,3,4,5,6}; - - private FileDescriptor mWriteSocket; - private long mCurrentTimeMs = SystemClock.elapsedRealtime(); - - public TestApfFilter(Context context, ApfConfiguration config, - IpClientCallbacksWrapper ipClientCallback, IpConnectivityLog log, - ApfFilter.Dependencies deps) throws Exception { - super(context, config, InterfaceParams.getByName("lo"), ipClientCallback, log, deps); - } - - // Pretend an RA packet has been received and show it to ApfFilter. - public void pretendPacketReceived(byte[] packet) throws IOException, ErrnoException { - // ApfFilter's ReceiveThread will be waiting to read this. - Os.write(mWriteSocket, packet, 0, packet.length); - } - - // Simulate current time changes - public void increaseCurrentTimeSeconds(int delta) { - mCurrentTimeMs += delta * DateUtils.SECOND_IN_MILLIS; - } - - @Override - protected long currentTimeSeconds() { - return mCurrentTimeMs / DateUtils.SECOND_IN_MILLIS; - } - - @Override - public synchronized void maybeStartFilter() { - mHardwareAddress = MOCK_MAC_ADDR; - installNewProgramLocked(); - - // Create two sockets, "readSocket" and "mWriteSocket" and connect them together. - FileDescriptor readSocket = new FileDescriptor(); - mWriteSocket = new FileDescriptor(); - try { - Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mWriteSocket, readSocket); - } catch (ErrnoException e) { - fail(); - return; - } - // Now pass readSocket to ReceiveThread as if it was setup to read raw RAs. - // This allows us to pretend RA packets have been recieved via pretendPacketReceived(). - mReceiveThread = new ReceiveThread(readSocket); - mReceiveThread.start(); - } - - @Override - public void shutdown() { - super.shutdown(); - IoUtils.closeQuietly(mWriteSocket); - } + apfFilter.shutdown(); } private static final int ETH_HEADER_LEN = 14; @@ -1178,18 +1075,6 @@ private static final int IPV6_UDP_DEST_PORT_OFFSET = IPV6_PAYLOAD_OFFSET + 2; private static final int MDNS_UDP_PORT = 5353; - // Helper to initialize a default apfFilter. - private ApfFilter setupApfFilter( - IpClientCallbacksWrapper ipClientCallback, ApfConfiguration config) throws Exception { - LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19); - LinkProperties lp = new LinkProperties(); - lp.addLinkAddress(link); - TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog, - mDependencies); - apfFilter.setLinkProperties(lp); - return apfFilter; - } - private static void setIpv4VersionFields(ByteBuffer packet) { packet.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IP); packet.put(IP_HEADER_OFFSET, (byte) 0x45); @@ -1223,11 +1108,11 @@ ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; - TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog, - mDependencies); + TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + mNetworkQuirkMetrics); apfFilter.setLinkProperties(lp); - byte[] program = ipClientCallback.getApfProgram(); + byte[] program = ipClientCallback.assertProgramUpdateAndGet(); // Verify empty packet of 100 zero bytes is passed ByteBuffer packet = ByteBuffer.wrap(new byte[100]); @@ -1276,9 +1161,9 @@ public void testApfFilterIPv6() throws Exception { MockIpClientCallback ipClientCallback = new MockIpClientCallback(); ApfConfiguration config = getDefaultConfig(); - TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog, - mDependencies); - byte[] program = ipClientCallback.getApfProgram(); + TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + mNetworkQuirkMetrics); + byte[] program = ipClientCallback.assertProgramUpdateAndGet(); // Verify empty IPv6 packet is passed ByteBuffer packet = makeIpv6Packet(IPPROTO_UDP); @@ -1495,7 +1380,7 @@ /** Adds to the program a no-op instruction that is one byte long. */ private void addOneByteNoop(ApfGenerator gen) { - gen.addOr(0); + gen.addLeftShift(0); } @Test @@ -1532,14 +1417,14 @@ lp.addLinkAddress(link); ApfConfiguration config = getDefaultConfig(); - TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog, - mDependencies); + TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + mNetworkQuirkMetrics); apfFilter.setLinkProperties(lp); // Construct IPv4 mDNS packet byte[] mdnsv4packet = makeMdnsV4Packet("test.local"); byte[] mdnsv6packet = makeMdnsV6Packet("test.local"); - byte[] program = ipClientCallback.getApfProgram(); + byte[] program = ipClientCallback.assertProgramUpdateAndGet(); // mDNSv4 packet is passed if no mDns filter is turned on assertPass(program, mdnsv4packet); // mDNSv6 packet is passed if no mDNS filter is turned on @@ -1549,7 +1434,17 @@ apfFilter.addToMdnsAllowList(new String[]{"test", "local"}); apfFilter.addToMdnsAllowList(new String[]{"abcd", "local"}); apfFilter.setMulticastFilter(true); - program = ipClientCallback.getApfProgram(); + program = ipClientCallback.assertProgramUpdateAndGet(); + assertPass(program, mdnsv4packet); + assertPass(program, mdnsv6packet); + // If packet contains more than one qname, pass the packet + mdnsv4packet = makeMdnsV4Packet("cccc.local", "dddd.local"); + mdnsv6packet = makeMdnsV6Packet("cccc.local", "dddd.local"); + assertPass(program, mdnsv4packet); + assertPass(program, mdnsv6packet); + // If packet doesn't contain any qname, pass the packet + mdnsv4packet = makeMdnsV4Packet(); + mdnsv6packet = makeMdnsV6Packet(); assertPass(program, mdnsv4packet); assertPass(program, mdnsv6packet); @@ -1565,7 +1460,7 @@ assertDrop(program, mdnsv6packet); apfFilter.removeFromAllowList(new String[]{"abcd", "local"}); - program = ipClientCallback.getApfProgram(); + program = ipClientCallback.assertProgramUpdateAndGet(); mdnsv4packet = makeMdnsV4Packet("abcd.local"); mdnsv6packet = makeMdnsV6Packet("abcd.local"); assertDrop(program, mdnsv4packet); @@ -1760,11 +1655,11 @@ ApfConfiguration config = getDefaultConfig(); config.ieee802_3Filter = DROP_802_3_FRAMES; - TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog, - mDependencies); + TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + mNetworkQuirkMetrics); apfFilter.setLinkProperties(lp); - byte[] program = ipClientCallback.getApfProgram(); + byte[] program = ipClientCallback.assertProgramUpdateAndGet(); // Construct IPv4 and IPv6 multicast packets. ByteBuffer mcastv4packet = makeIpv4Packet(IPPROTO_UDP); @@ -1800,7 +1695,7 @@ // Turn on multicast filter and verify it works ipClientCallback.resetApfProgramWait(); apfFilter.setMulticastFilter(true); - program = ipClientCallback.getApfProgram(); + program = ipClientCallback.assertProgramUpdateAndGet(); assertDrop(program, mcastv4packet.array()); assertDrop(program, mcastv6packet.array()); assertDrop(program, bcastv4packet1.array()); @@ -1810,7 +1705,7 @@ // Turn off multicast filter and verify it's off ipClientCallback.resetApfProgramWait(); apfFilter.setMulticastFilter(false); - program = ipClientCallback.getApfProgram(); + program = ipClientCallback.assertProgramUpdateAndGet(); assertPass(program, mcastv4packet.array()); assertPass(program, mcastv6packet.array()); assertPass(program, bcastv4packet1.array()); @@ -1822,9 +1717,9 @@ apfFilter.shutdown(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; - apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog, mDependencies); + apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mNetworkQuirkMetrics); apfFilter.setLinkProperties(lp); - program = ipClientCallback.getApfProgram(); + program = ipClientCallback.assertProgramUpdateAndGet(); assertDrop(program, mcastv4packet.array()); assertDrop(program, mcastv6packet.array()); assertDrop(program, bcastv4packet1.array()); @@ -1839,37 +1734,83 @@ @Test public void testApfFilterMulticastPingWhileDozing() throws Exception { - MockIpClientCallback ipClientCallback = new MockIpClientCallback(); - ApfFilter apfFilter = setupApfFilter(ipClientCallback, getDefaultConfig()); + doTestApfFilterMulticastPingWhileDozing(false /* isLightDozing */); + } + + @Test + @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) + public void testApfFilterMulticastPingWhileLightDozing() throws Exception { + doTestApfFilterMulticastPingWhileDozing(true /* isLightDozing */); + } + + @Test + @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) + public void testShouldHandleLightDozeKillSwitch() throws Exception { + final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); + final ApfConfiguration configuration = getDefaultConfig(); + configuration.shouldHandleLightDoze = false; + final ApfFilter apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, + configuration, mNetworkQuirkMetrics, mDependencies); + final ArgumentCaptor<BroadcastReceiver> receiverCaptor = + ArgumentCaptor.forClass(BroadcastReceiver.class); + verify(mDependencies).addDeviceIdleReceiver(receiverCaptor.capture(), anyBoolean()); + final BroadcastReceiver receiver = receiverCaptor.getValue(); + doReturn(true).when(mPowerManager).isDeviceLightIdleMode(); + receiver.onReceive(mContext, new Intent(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED)); + assertFalse(apfFilter.isInDozeMode()); + } + + private void doTestApfFilterMulticastPingWhileDozing(boolean isLightDozing) throws Exception { + final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); + final ApfConfiguration configuration = getDefaultConfig(); + configuration.shouldHandleLightDoze = true; + final ApfFilter apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, + configuration, mNetworkQuirkMetrics, mDependencies); + final ArgumentCaptor<BroadcastReceiver> receiverCaptor = + ArgumentCaptor.forClass(BroadcastReceiver.class); + verify(mDependencies).addDeviceIdleReceiver(receiverCaptor.capture(), anyBoolean()); + final BroadcastReceiver receiver = receiverCaptor.getValue(); // Construct a multicast ICMPv6 ECHO request. final byte[] multicastIpv6Addr = {(byte)0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,(byte)0xfb}; - ByteBuffer packet = makeIpv6Packet(IPPROTO_ICMPV6); + final ByteBuffer packet = makeIpv6Packet(IPPROTO_ICMPV6); packet.put(ICMP6_TYPE_OFFSET, (byte)ICMPV6_ECHO_REQUEST_TYPE); put(packet, IPV6_DEST_ADDR_OFFSET, multicastIpv6Addr); // Normally, we let multicast pings alone... - assertPass(ipClientCallback.getApfProgram(), packet.array()); + assertPass(ipClientCallback.assertProgramUpdateAndGet(), packet.array()); + if (isLightDozing) { + doReturn(true).when(mPowerManager).isDeviceLightIdleMode(); + receiver.onReceive(mContext, new Intent(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED)); + } else { + doReturn(true).when(mPowerManager).isDeviceIdleMode(); + receiver.onReceive(mContext, new Intent(ACTION_DEVICE_IDLE_MODE_CHANGED)); + } // ...and even while dozing... - apfFilter.setDozeMode(true); - assertPass(ipClientCallback.getApfProgram(), packet.array()); + assertPass(ipClientCallback.assertProgramUpdateAndGet(), packet.array()); // ...but when the multicast filter is also enabled, drop the multicast pings to save power. apfFilter.setMulticastFilter(true); - assertDrop(ipClientCallback.getApfProgram(), packet.array()); + assertDrop(ipClientCallback.assertProgramUpdateAndGet(), packet.array()); // However, we should still let through all other ICMPv6 types. ByteBuffer raPacket = ByteBuffer.wrap(packet.array().clone()); setIpv6VersionFields(packet); packet.put(IPV6_NEXT_HEADER_OFFSET, (byte) IPPROTO_ICMPV6); raPacket.put(ICMP6_TYPE_OFFSET, (byte) NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT); - assertPass(ipClientCallback.getApfProgram(), raPacket.array()); + assertPass(ipClientCallback.assertProgramUpdateAndGet(), raPacket.array()); // Now wake up from doze mode to ensure that we no longer drop the packets. // (The multicast filter is still enabled at this point). - apfFilter.setDozeMode(false); - assertPass(ipClientCallback.getApfProgram(), packet.array()); + if (isLightDozing) { + doReturn(false).when(mPowerManager).isDeviceLightIdleMode(); + receiver.onReceive(mContext, new Intent(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED)); + } else { + doReturn(false).when(mPowerManager).isDeviceIdleMode(); + receiver.onReceive(mContext, new Intent(ACTION_DEVICE_IDLE_MODE_CHANGED)); + } + assertPass(ipClientCallback.assertProgramUpdateAndGet(), packet.array()); apfFilter.shutdown(); } @@ -1878,8 +1819,9 @@ public void testApfFilter802_3() throws Exception { MockIpClientCallback ipClientCallback = new MockIpClientCallback(); ApfConfiguration config = getDefaultConfig(); - ApfFilter apfFilter = setupApfFilter(ipClientCallback, config); - byte[] program = ipClientCallback.getApfProgram(); + ApfFilter apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config, + mNetworkQuirkMetrics, mDependencies); + byte[] program = ipClientCallback.assertProgramUpdateAndGet(); // Verify empty packet of 100 zero bytes is passed // Note that eth-type = 0 makes it an IEEE802.3 frame @@ -1898,8 +1840,9 @@ ipClientCallback.resetApfProgramWait(); apfFilter.shutdown(); config.ieee802_3Filter = DROP_802_3_FRAMES; - apfFilter = setupApfFilter(ipClientCallback, config); - program = ipClientCallback.getApfProgram(); + apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config, + mNetworkQuirkMetrics, mDependencies); + program = ipClientCallback.assertProgramUpdateAndGet(); // Verify that IEEE802.3 frame is dropped // In this case ethtype is used for payload length @@ -1925,8 +1868,9 @@ MockIpClientCallback ipClientCallback = new MockIpClientCallback(); ApfConfiguration config = getDefaultConfig(); - ApfFilter apfFilter = setupApfFilter(ipClientCallback, config); - byte[] program = ipClientCallback.getApfProgram(); + ApfFilter apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config, + mNetworkQuirkMetrics, mDependencies); + byte[] program = ipClientCallback.assertProgramUpdateAndGet(); // Verify empty packet of 100 zero bytes is passed // Note that eth-type = 0 makes it an IEEE802.3 frame @@ -1945,8 +1889,9 @@ ipClientCallback.resetApfProgramWait(); apfFilter.shutdown(); config.ethTypeBlackList = ipv4BlackList; - apfFilter = setupApfFilter(ipClientCallback, config); - program = ipClientCallback.getApfProgram(); + apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config, + mNetworkQuirkMetrics, mDependencies); + program = ipClientCallback.assertProgramUpdateAndGet(); // Verify that IPv4 frame will be dropped setIpv4VersionFields(packet); @@ -1960,8 +1905,9 @@ ipClientCallback.resetApfProgramWait(); apfFilter.shutdown(); config.ethTypeBlackList = ipv4Ipv6BlackList; - apfFilter = setupApfFilter(ipClientCallback, config); - program = ipClientCallback.getApfProgram(); + apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config, + mNetworkQuirkMetrics, mDependencies); + program = ipClientCallback.assertProgramUpdateAndGet(); // Verify that IPv4 frame will be dropped setIpv4VersionFields(packet); @@ -1977,7 +1923,7 @@ private byte[] getProgram(MockIpClientCallback cb, ApfFilter filter, LinkProperties lp) { cb.resetApfProgramWait(); filter.setLinkProperties(lp); - return cb.getApfProgram(); + return cb.assertProgramUpdateAndGet(); } private void verifyArpFilter(byte[] program, int filterResult) { @@ -2007,11 +1953,11 @@ ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; - TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog, - mDependencies); + TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + mNetworkQuirkMetrics); // Verify initially ARP request filter is off, and GARP filter is on. - verifyArpFilter(ipClientCallback.getApfProgram(), PASS); + verifyArpFilter(ipClientCallback.assertProgramUpdateAndGet(), PASS); // Inform ApfFilter of our address and verify ARP filtering is on LinkAddress linkAddress = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 24); @@ -2068,8 +2014,8 @@ final ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; - final TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mLog, - mDependencies); + final TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, + mNetworkQuirkMetrics); byte[] program; final int srcPort = 12345; final int dstPort = 54321; @@ -2098,7 +2044,7 @@ parcel.ack = ackNum; apfFilter.addTcpKeepalivePacketFilter(slot1, parcel); - program = cb.getApfProgram(); + program = cb.assertProgramUpdateAndGet(); // Verify IPv4 keepalive ack packet is dropped // src: 10.0.0.6, port: 54321 @@ -2137,7 +2083,7 @@ ipv6Parcel.ack = ackNum; apfFilter.addTcpKeepalivePacketFilter(slot1, ipv6Parcel); - program = cb.getApfProgram(); + program = cb.assertProgramUpdateAndGet(); // Verify IPv6 keepalive ack packet is dropped // src: 2404:0:0:0:0:0:faf2, port: 54321 @@ -2160,7 +2106,7 @@ // Verify multiple filters apfFilter.addTcpKeepalivePacketFilter(slot1, parcel); apfFilter.addTcpKeepalivePacketFilter(slot2, ipv6Parcel); - program = cb.getApfProgram(); + program = cb.assertProgramUpdateAndGet(); // Verify IPv4 keepalive ack packet is dropped // src: 10.0.0.6, port: 54321 @@ -2199,7 +2145,7 @@ // TODO: support V6 packets } - program = cb.getApfProgram(); + program = cb.assertProgramUpdateAndGet(); // Verify IPv4, IPv6 packets are passed assertPass(program, @@ -2262,8 +2208,8 @@ final ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; - final TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mLog, - mDependencies); + final TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, + mNetworkQuirkMetrics); byte[] program; final int srcPort = 1024; final int dstPort = 4500; @@ -2284,7 +2230,7 @@ parcel.dstPort = dstPort; apfFilter.addNattKeepalivePacketFilter(slot1, parcel); - program = cb.getApfProgram(); + program = cb.assertProgramUpdateAndGet(); // Verify IPv4 keepalive packet is dropped // src: 10.0.0.6, port: 4500 @@ -2331,100 +2277,188 @@ return packet.array(); } - private void addRdnssOption(ByteBuffer packet, int lifetime, String... servers) - throws Exception { - int optionLength = 1 + 2 * servers.length; // In 8-byte units - packet.put((byte) ICMP6_RDNSS_OPTION_TYPE); // Type - packet.put((byte) optionLength); // Length - packet.putShort((short) 0); // Reserved - packet.putInt(lifetime); // Lifetime - for (String server : servers) { - packet.put(InetAddress.getByName(server).getAddress()); - } - } + private static class RaPacketBuilder { + final ByteArrayOutputStream mPacket = new ByteArrayOutputStream(); + int mFlowLabel = 0x12345; + int mReachableTime = 30_000; + int mRetransmissionTimer = 1000; - private void addRioOption(ByteBuffer packet, int lifetime, String prefixString) - throws Exception { - IpPrefix prefix = new IpPrefix(prefixString); + public RaPacketBuilder(int routerLft) throws Exception { + InetAddress src = InetAddress.getByName("fe80::1234:abcd"); + ByteBuffer buffer = ByteBuffer.allocate(ICMP6_RA_OPTION_OFFSET); - int optionLength; - if (prefix.getPrefixLength() == 0) { - optionLength = 1; - } else if (prefix.getPrefixLength() <= 64) { - optionLength = 2; - } else { - optionLength = 3; + buffer.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IPV6); + buffer.position(ETH_HEADER_LEN); + + // skip version, tclass, flowlabel; set in build() + buffer.position(buffer.position() + 4); + + buffer.putShort((short) 0); // Payload length; updated later + buffer.put((byte) IPPROTO_ICMPV6); // Next header + buffer.put((byte) 0xff); // Hop limit + buffer.put(src.getAddress()); // Source address + buffer.put(IPV6_ALL_NODES_ADDRESS); // Destination address + + buffer.put((byte) ICMP6_ROUTER_ADVERTISEMENT); // Type + buffer.put((byte) 0); // Code (0) + buffer.putShort((short) 0); // Checksum (ignored) + buffer.put((byte) 64); // Hop limit + buffer.put((byte) 0); // M/O, reserved + buffer.putShort((short) routerLft); // Router lifetime + // skip reachable time; set in build() + // skip retransmission timer; set in build(); + + mPacket.write(buffer.array(), 0, buffer.capacity()); } - packet.put((byte) ICMP6_ROUTE_INFO_OPTION_TYPE); // Type - packet.put((byte) optionLength); // Length in 8-byte units - packet.put((byte) prefix.getPrefixLength()); // Prefix length - packet.put((byte) 0b00011000); // Pref = high - packet.putInt(lifetime); // Lifetime + public RaPacketBuilder setFlowLabel(int flowLabel) { + mFlowLabel = flowLabel; + return this; + } - byte[] prefixBytes = prefix.getRawAddress(); - packet.put(prefixBytes, 0, (optionLength - 1) * 8); - } + public RaPacketBuilder setReachableTime(int reachable) { + mReachableTime = reachable; + return this; + } - private void addPioOption(ByteBuffer packet, int valid, int preferred, String prefixString) { - IpPrefix prefix = new IpPrefix(prefixString); - packet.put((byte) ICMP6_PREFIX_OPTION_TYPE); // Type - packet.put((byte) 4); // Length in 8-byte units - packet.put((byte) prefix.getPrefixLength()); // Prefix length - packet.put((byte) 0b11000000); // L = 1, A = 1 - packet.putInt(valid); - packet.putInt(preferred); - packet.putInt(0); // Reserved - packet.put(prefix.getRawAddress()); + public RaPacketBuilder setRetransmissionTimer(int retrans) { + mRetransmissionTimer = retrans; + return this; + } + + public RaPacketBuilder addPioOption(int valid, int preferred, String prefixString) + throws Exception { + ByteBuffer buffer = ByteBuffer.allocate(ICMP6_PREFIX_OPTION_LEN); + + IpPrefix prefix = new IpPrefix(prefixString); + buffer.put((byte) ICMP6_PREFIX_OPTION_TYPE); // Type + buffer.put((byte) 4); // Length in 8-byte units + buffer.put((byte) prefix.getPrefixLength()); // Prefix length + buffer.put((byte) 0b11000000); // L = 1, A = 1 + buffer.putInt(valid); + buffer.putInt(preferred); + buffer.putInt(0); // Reserved + buffer.put(prefix.getRawAddress()); + + mPacket.write(buffer.array(), 0, buffer.capacity()); + return this; + } + + public RaPacketBuilder addRioOption(int lifetime, String prefixString) throws Exception { + IpPrefix prefix = new IpPrefix(prefixString); + + int optionLength; + if (prefix.getPrefixLength() == 0) { + optionLength = 1; + } else if (prefix.getPrefixLength() <= 64) { + optionLength = 2; + } else { + optionLength = 3; + } + + ByteBuffer buffer = ByteBuffer.allocate(optionLength * 8); + + buffer.put((byte) ICMP6_ROUTE_INFO_OPTION_TYPE); // Type + buffer.put((byte) optionLength); // Length in 8-byte units + buffer.put((byte) prefix.getPrefixLength()); // Prefix length + buffer.put((byte) 0b00011000); // Pref = high + buffer.putInt(lifetime); // Lifetime + + byte[] prefixBytes = prefix.getRawAddress(); + buffer.put(prefixBytes, 0, (optionLength - 1) * 8); + + mPacket.write(buffer.array(), 0, buffer.capacity()); + return this; + } + + public RaPacketBuilder addDnsslOption(int lifetime, String... domains) { + ByteArrayOutputStream dnssl = new ByteArrayOutputStream(); + for (String domain : domains) { + for (String label : domain.split(".")) { + final byte[] bytes = label.getBytes(StandardCharsets.UTF_8); + dnssl.write((byte) bytes.length); + dnssl.write(bytes, 0, bytes.length); + } + dnssl.write((byte) 0); + } + + // Extend with 0s to make it 8-byte aligned. + while (dnssl.size() % 8 != 0) { + dnssl.write((byte) 0); + } + + final int length = ICMP6_4_BYTE_OPTION_LEN + dnssl.size(); + ByteBuffer buffer = ByteBuffer.allocate(length); + + buffer.put((byte) ICMP6_DNSSL_OPTION_TYPE); // Type + buffer.put((byte) (length / 8)); // Length + // skip past reserved bytes + buffer.position(buffer.position() + 2); + buffer.putInt(lifetime); // Lifetime + buffer.put(dnssl.toByteArray()); // Domain names + + mPacket.write(buffer.array(), 0, buffer.capacity()); + return this; + } + + public RaPacketBuilder addRdnssOption(int lifetime, String... servers) throws Exception { + int optionLength = 1 + 2 * servers.length; // In 8-byte units + ByteBuffer buffer = ByteBuffer.allocate(optionLength * 8); + + buffer.put((byte) ICMP6_RDNSS_OPTION_TYPE); // Type + buffer.put((byte) optionLength); // Length + buffer.putShort((short) 0); // Reserved + buffer.putInt(lifetime); // Lifetime + for (String server : servers) { + buffer.put(InetAddress.getByName(server).getAddress()); + } + + mPacket.write(buffer.array(), 0, buffer.capacity()); + return this; + } + + public RaPacketBuilder addZeroLengthOption() throws Exception { + ByteBuffer buffer = ByteBuffer.allocate(ICMP6_4_BYTE_OPTION_LEN); + buffer.put((byte) ICMP6_PREFIX_OPTION_TYPE); + buffer.put((byte) 0); + + mPacket.write(buffer.array(), 0, buffer.capacity()); + return this; + } + + public byte[] build() { + ByteBuffer buffer = ByteBuffer.wrap(mPacket.toByteArray()); + // IPv6, traffic class = 0, flow label = mFlowLabel + buffer.putInt(IP_HEADER_OFFSET, 0x60000000 | (0xFFFFF & mFlowLabel)); + buffer.putShort(IPV6_PAYLOAD_LENGTH_OFFSET, (short) buffer.capacity()); + + buffer.position(ICMP6_RA_REACHABLE_TIME_OFFSET); + buffer.putInt(mReachableTime); + buffer.putInt(mRetransmissionTimer); + + return buffer.array(); + } } private byte[] buildLargeRa() throws Exception { - InetAddress src = InetAddress.getByName("fe80::1234:abcd"); + RaPacketBuilder builder = new RaPacketBuilder(1800 /* router lft */); - ByteBuffer packet = ByteBuffer.wrap(new byte[1514]); - packet.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IPV6); - packet.position(ETH_HEADER_LEN); + builder.addRioOption(1200, "64:ff9b::/96"); + builder.addRdnssOption(7200, "2001:db8:1::1", "2001:db8:1::2"); + builder.addRioOption(2100, "2000::/3"); + builder.addRioOption(2400, "::/0"); + builder.addPioOption(600, 300, "2001:db8:a::/64"); + builder.addRioOption(1500, "2001:db8:c:d::/64"); + builder.addPioOption(86400, 43200, "fd95:d1e:12::/64"); - packet.putInt(0x60012345); // Version, tclass, flowlabel - packet.putShort((short) 0); // Payload length; updated later - packet.put((byte) IPPROTO_ICMPV6); // Next header - packet.put((byte) 0xff); // Hop limit - packet.put(src.getAddress()); // Source address - packet.put(IPV6_ALL_NODES_ADDRESS); // Destination address - - packet.put((byte) ICMP6_ROUTER_ADVERTISEMENT); // Type - packet.put((byte) 0); // Code (0) - packet.putShort((short) 0); // Checksum (ignored) - packet.put((byte) 64); // Hop limit - packet.put((byte) 0); // M/O, reserved - packet.putShort((short) 1800); // Router lifetime - packet.putInt(30_000); // Reachable time - packet.putInt(1000); // Retrans timer - - addRioOption(packet, 1200, "64:ff9b::/96"); - addRdnssOption(packet, 7200, "2001:db8:1::1", "2001:db8:1::2"); - addRioOption(packet, 2100, "2000::/3"); - addRioOption(packet, 2400, "::/0"); - addPioOption(packet, 600, 300, "2001:db8:a::/64"); - addRioOption(packet, 1500, "2001:db8:c:d::/64"); - addPioOption(packet, 86400, 43200, "fd95:d1e:12::/64"); - - int length = packet.position(); - packet.putShort(IPV6_PAYLOAD_LENGTH_OFFSET, (short) length); - - // Don't pass the Ra constructor a packet that is longer than the actual RA. - // This relies on the fact that all the relative writes to the byte buffer are at the end. - byte[] packetArray = new byte[length]; - packet.rewind(); - packet.get(packetArray); - return packetArray; + return builder.build(); } @Test public void testRaToString() throws Exception { MockIpClientCallback cb = new MockIpClientCallback(); ApfConfiguration config = getDefaultConfig(); - TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mLog, mDependencies); + TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mNetworkQuirkMetrics); byte[] packet = buildLargeRa(); ApfFilter.Ra ra = apfFilter.new Ra(packet, packet.length); @@ -2477,77 +2511,26 @@ private void verifyRaLifetime(TestApfFilter apfFilter, MockIpClientCallback ipClientCallback, ByteBuffer packet, int lifetime) throws IOException, ErrnoException { // Verify new program generated if ApfFilter witnesses RA - ipClientCallback.resetApfProgramWait(); apfFilter.pretendPacketReceived(packet.array()); - byte[] program = ipClientCallback.getApfProgram(); + byte[] program = ipClientCallback.assertProgramUpdateAndGet(); verifyRaLifetime(program, packet, lifetime); } - private void verifyRaEvent(RaEvent expected) { - ArgumentCaptor<IpConnectivityLog.Event> captor = - ArgumentCaptor.forClass(IpConnectivityLog.Event.class); - verify(mLog, atLeastOnce()).log(captor.capture()); - RaEvent got = lastRaEvent(captor.getAllValues()); - if (!raEventEquals(expected, got)) { - assertEquals(expected, got); // fail for printing an assertion error message. - } - } - - private RaEvent lastRaEvent(List<IpConnectivityLog.Event> events) { - RaEvent got = null; - for (Parcelable ev : events) { - if (ev instanceof RaEvent) { - got = (RaEvent) ev; - } - } - return got; - } - - private boolean raEventEquals(RaEvent ev1, RaEvent ev2) { - return (ev1 != null) && (ev2 != null) - && (ev1.routerLifetime == ev2.routerLifetime) - && (ev1.prefixValidLifetime == ev2.prefixValidLifetime) - && (ev1.prefixPreferredLifetime == ev2.prefixPreferredLifetime) - && (ev1.routeInfoLifetime == ev2.routeInfoLifetime) - && (ev1.rdnssLifetime == ev2.rdnssLifetime) - && (ev1.dnsslLifetime == ev2.dnsslLifetime); - } - private void assertInvalidRa(TestApfFilter apfFilter, MockIpClientCallback ipClientCallback, ByteBuffer packet) throws IOException, ErrnoException { - ipClientCallback.resetApfProgramWait(); apfFilter.pretendPacketReceived(packet.array()); ipClientCallback.assertNoProgramUpdate(); } - private ByteBuffer makeBaseRaPacket() { - ByteBuffer basePacket = ByteBuffer.wrap(new byte[ICMP6_RA_OPTION_OFFSET]); - final int ROUTER_LIFETIME = 1000; - final int VERSION_TRAFFIC_CLASS_FLOW_LABEL_OFFSET = ETH_HEADER_LEN; - // IPv6, traffic class = 0, flow label = 0x12345 - final int VERSION_TRAFFIC_CLASS_FLOW_LABEL = 0x60012345; - - basePacket.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IPV6); - basePacket.putInt(VERSION_TRAFFIC_CLASS_FLOW_LABEL_OFFSET, - VERSION_TRAFFIC_CLASS_FLOW_LABEL); - basePacket.put(IPV6_NEXT_HEADER_OFFSET, (byte) IPPROTO_ICMPV6); - basePacket.put(ICMP6_TYPE_OFFSET, (byte) ICMP6_ROUTER_ADVERTISEMENT); - basePacket.putShort(ICMP6_RA_ROUTER_LIFETIME_OFFSET, (short) ROUTER_LIFETIME); - basePacket.position(IPV6_DEST_ADDR_OFFSET); - basePacket.put(IPV6_ALL_NODES_ADDRESS); - - return basePacket; - } - @Test public void testApfFilterRa() throws Exception { MockIpClientCallback ipClientCallback = new MockIpClientCallback(); ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; - TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog, - mDependencies); - byte[] program = ipClientCallback.getApfProgram(); + TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + mNetworkQuirkMetrics); + byte[] program = ipClientCallback.assertProgramUpdateAndGet(); final int ROUTER_LIFETIME = 1000; final int PREFIX_VALID_LIFETIME = 200; @@ -2556,102 +2539,69 @@ final int ROUTE_LIFETIME = 400; // Note that lifetime of 2000 will be ignored in favor of shorter route lifetime of 1000. final int DNSSL_LIFETIME = 2000; - final int VERSION_TRAFFIC_CLASS_FLOW_LABEL_OFFSET = ETH_HEADER_LEN; - // IPv6, traffic class = 0, flow label = 0x12345 - final int VERSION_TRAFFIC_CLASS_FLOW_LABEL = 0x60012345; // Verify RA is passed the first time - ByteBuffer basePacket = makeBaseRaPacket(); + RaPacketBuilder ra = new RaPacketBuilder(ROUTER_LIFETIME); + ByteBuffer basePacket = ByteBuffer.wrap(ra.build()); assertPass(program, basePacket.array()); verifyRaLifetime(apfFilter, ipClientCallback, basePacket, ROUTER_LIFETIME); - verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, -1, -1, -1)); - ByteBuffer newFlowLabelPacket = ByteBuffer.wrap(new byte[ICMP6_RA_OPTION_OFFSET]); - basePacket.clear(); - newFlowLabelPacket.put(basePacket); + ra = new RaPacketBuilder(ROUTER_LIFETIME); // Check that changes are ignored in every byte of the flow label. - newFlowLabelPacket.putInt(VERSION_TRAFFIC_CLASS_FLOW_LABEL_OFFSET, - VERSION_TRAFFIC_CLASS_FLOW_LABEL + 0x11111); + ra.setFlowLabel(0x56789); + ByteBuffer newFlowLabelPacket = ByteBuffer.wrap(ra.build()); // Ensure zero-length options cause the packet to be silently skipped. // Do this before we test other packets. http://b/29586253 - ByteBuffer zeroLengthOptionPacket = ByteBuffer.wrap( - new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]); - basePacket.clear(); - zeroLengthOptionPacket.put(basePacket); - zeroLengthOptionPacket.put((byte)ICMP6_PREFIX_OPTION_TYPE); - zeroLengthOptionPacket.put((byte)0); + ra = new RaPacketBuilder(ROUTER_LIFETIME); + ra.addZeroLengthOption(); + ByteBuffer zeroLengthOptionPacket = ByteBuffer.wrap(ra.build()); assertInvalidRa(apfFilter, ipClientCallback, zeroLengthOptionPacket); // Generate several RAs with different options and lifetimes, and verify when // ApfFilter is shown these packets, it generates programs to filter them for the // appropriate lifetime. - ByteBuffer prefixOptionPacket = ByteBuffer.wrap( - new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_LEN]); - basePacket.clear(); - prefixOptionPacket.put(basePacket); - addPioOption(prefixOptionPacket, PREFIX_VALID_LIFETIME, PREFIX_PREFERRED_LIFETIME, - "2001:db8::/64"); + ra = new RaPacketBuilder(ROUTER_LIFETIME); + ra.addPioOption(PREFIX_VALID_LIFETIME, PREFIX_PREFERRED_LIFETIME, "2001:db8::/64"); + ByteBuffer prefixOptionPacket = ByteBuffer.wrap(ra.build()); verifyRaLifetime( apfFilter, ipClientCallback, prefixOptionPacket, PREFIX_PREFERRED_LIFETIME); - verifyRaEvent(new RaEvent( - ROUTER_LIFETIME, PREFIX_VALID_LIFETIME, PREFIX_PREFERRED_LIFETIME, -1, -1, -1)); - ByteBuffer rdnssOptionPacket = ByteBuffer.wrap( - new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN + 2 * IPV6_ADDR_LEN]); - basePacket.clear(); - rdnssOptionPacket.put(basePacket); - addRdnssOption(rdnssOptionPacket, RDNSS_LIFETIME, - "2001:4860:4860::8888", "2001:4860:4860::8844"); + ra = new RaPacketBuilder(ROUTER_LIFETIME); + ra.addRdnssOption(RDNSS_LIFETIME, "2001:4860:4860::8888", "2001:4860:4860::8844"); + ByteBuffer rdnssOptionPacket = ByteBuffer.wrap(ra.build()); verifyRaLifetime(apfFilter, ipClientCallback, rdnssOptionPacket, RDNSS_LIFETIME); - verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, -1, RDNSS_LIFETIME, -1)); final int lowLifetime = 60; - ByteBuffer lowLifetimeRdnssOptionPacket = ByteBuffer.wrap( - new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN + IPV6_ADDR_LEN]); - basePacket.clear(); - lowLifetimeRdnssOptionPacket.put(basePacket); - addRdnssOption(lowLifetimeRdnssOptionPacket, lowLifetime, "2620:fe::9"); + ra = new RaPacketBuilder(ROUTER_LIFETIME); + ra.addRdnssOption(lowLifetime, "2620:fe::9"); + ByteBuffer lowLifetimeRdnssOptionPacket = ByteBuffer.wrap(ra.build()); verifyRaLifetime(apfFilter, ipClientCallback, lowLifetimeRdnssOptionPacket, ROUTER_LIFETIME); - verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, -1, lowLifetime, -1)); - ByteBuffer routeInfoOptionPacket = ByteBuffer.wrap( - new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN + IPV6_ADDR_LEN]); - basePacket.clear(); - routeInfoOptionPacket.put(basePacket); - addRioOption(routeInfoOptionPacket, ROUTE_LIFETIME, "64:ff9b::/96"); + ra = new RaPacketBuilder(ROUTER_LIFETIME); + ra.addRioOption(ROUTE_LIFETIME, "64:ff9b::/96"); + ByteBuffer routeInfoOptionPacket = ByteBuffer.wrap(ra.build()); verifyRaLifetime(apfFilter, ipClientCallback, routeInfoOptionPacket, ROUTE_LIFETIME); - verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, ROUTE_LIFETIME, -1, -1)); // Check that RIOs differing only in the first 4 bytes are different. - ByteBuffer similarRouteInfoOptionPacket = ByteBuffer.wrap( - new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN + IPV6_ADDR_LEN]); - basePacket.clear(); - similarRouteInfoOptionPacket.put(basePacket); - addRioOption(similarRouteInfoOptionPacket, ROUTE_LIFETIME, "64:ff9b::/64"); + ra = new RaPacketBuilder(ROUTER_LIFETIME); + ra.addRioOption(ROUTE_LIFETIME, "64:ff9b::/64"); // Packet should be passed because it is different. - program = ipClientCallback.getApfProgram(); - assertPass(program, similarRouteInfoOptionPacket.array()); + program = ipClientCallback.assertProgramUpdateAndGet(); + assertPass(program, ra.build()); - ByteBuffer dnsslOptionPacket = ByteBuffer.wrap( - new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]); - basePacket.clear(); - dnsslOptionPacket.put(basePacket); - dnsslOptionPacket.put((byte)ICMP6_DNSSL_OPTION_TYPE); - dnsslOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8)); - dnsslOptionPacket.putInt( - ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, DNSSL_LIFETIME); + ra = new RaPacketBuilder(ROUTER_LIFETIME); + ra.addDnsslOption(DNSSL_LIFETIME, "test.example.com", "one.more.example.com"); + ByteBuffer dnsslOptionPacket = ByteBuffer.wrap(ra.build()); verifyRaLifetime(apfFilter, ipClientCallback, dnsslOptionPacket, ROUTER_LIFETIME); - verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, -1, -1, DNSSL_LIFETIME)); ByteBuffer largeRaPacket = ByteBuffer.wrap(buildLargeRa()); verifyRaLifetime(apfFilter, ipClientCallback, largeRaPacket, 300); - verifyRaEvent(new RaEvent(1800, 600, 300, 1200, 7200, -1)); // Verify that current program filters all the RAs (note: ApfFilter.MAX_RAS == 10). - program = ipClientCallback.getApfProgram(); + program = ipClientCallback.assertProgramUpdateAndGet(); verifyRaLifetime(program, basePacket, ROUTER_LIFETIME); verifyRaLifetime(program, newFlowLabelPacket, ROUTER_LIFETIME); verifyRaLifetime(program, prefixOptionPacket, PREFIX_PREFERRED_LIFETIME); @@ -2670,39 +2620,38 @@ final ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; - final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog, - mDependencies); - byte[] program = ipClientCallback.getApfProgram(); + final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + mNetworkQuirkMetrics); + byte[] program = ipClientCallback.assertProgramUpdateAndGet(); final int RA_REACHABLE_TIME = 1800; final int RA_RETRANSMISSION_TIMER = 1234; // Create an Ra packet without options // Reachable time = 1800, retransmission timer = 1234 - ByteBuffer raPacket = makeBaseRaPacket(); - raPacket.position(ICMP6_RA_REACHABLE_TIME_OFFSET); - raPacket.putInt(RA_REACHABLE_TIME); - raPacket.putInt(RA_RETRANSMISSION_TIMER); + RaPacketBuilder ra = new RaPacketBuilder(1800 /* router lft */); + ra.setReachableTime(RA_REACHABLE_TIME); + ra.setRetransmissionTimer(RA_RETRANSMISSION_TIMER); + byte[] raPacket = ra.build(); // First RA passes filter - assertPass(program, raPacket.array()); + assertPass(program, raPacket); // Assume apf is shown the given RA, it generates program to filter it. - ipClientCallback.resetApfProgramWait(); - apfFilter.pretendPacketReceived(raPacket.array()); - program = ipClientCallback.getApfProgram(); - assertDrop(program, raPacket.array()); + apfFilter.pretendPacketReceived(raPacket); + program = ipClientCallback.assertProgramUpdateAndGet(); + assertDrop(program, raPacket); // A packet with different reachable time should be passed. // Reachable time = 2300, retransmission timer = 1234 - raPacket.clear(); - raPacket.putInt(ICMP6_RA_REACHABLE_TIME_OFFSET, RA_REACHABLE_TIME + 500); - assertPass(program, raPacket.array()); + ra.setReachableTime(RA_REACHABLE_TIME + 500); + raPacket = ra.build(); + assertPass(program, raPacket); // A packet with different retransmission timer should be passed. // Reachable time = 1800, retransmission timer = 2234 - raPacket.clear(); - raPacket.putInt(ICMP6_RA_REACHABLE_TIME_OFFSET, RA_REACHABLE_TIME); - raPacket.putInt(ICMP6_RA_RETRANSMISSION_TIMER_OFFSET, RA_RETRANSMISSION_TIMER + 1000); - assertPass(program, raPacket.array()); + ra.setReachableTime(RA_REACHABLE_TIME); + ra.setRetransmissionTimer(RA_RETRANSMISSION_TIMER + 1000); + raPacket = ra.build(); + assertPass(program, raPacket); } // The ByteBuffer is always created by ByteBuffer#wrap in the helper functions @@ -2713,22 +2662,22 @@ final ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; - final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog, - mDependencies); - byte[] program = ipClientCallback.getApfProgram(); + final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + mNetworkQuirkMetrics); + byte[] program = ipClientCallback.assertProgramUpdateAndGet(); final int routerLifetime = 1000; final int timePassedSeconds = 12; // Verify that when the program is generated and installed some time after RA is last seen // it should be installed with the correct remaining lifetime. - ByteBuffer basePacket = makeBaseRaPacket(); + ByteBuffer basePacket = ByteBuffer.wrap(new RaPacketBuilder(routerLifetime).build()); verifyRaLifetime(apfFilter, ipClientCallback, basePacket, routerLifetime); apfFilter.increaseCurrentTimeSeconds(timePassedSeconds); synchronized (apfFilter) { apfFilter.installNewProgramLocked(); } - program = ipClientCallback.getApfProgram(); + program = ipClientCallback.assertProgramUpdateAndGet(); verifyRaLifetime(program, basePacket, routerLifetime, timePassedSeconds); // Packet should be passed if the program is installed after 1/6 * lifetime from last seen @@ -2736,54 +2685,18 @@ synchronized (apfFilter) { apfFilter.installNewProgramLocked(); } - program = ipClientCallback.getApfProgram(); + program = ipClientCallback.assertProgramUpdateAndGet(); assertDrop(program, basePacket.array()); apfFilter.increaseCurrentTimeSeconds(1); synchronized (apfFilter) { apfFilter.installNewProgramLocked(); } - program = ipClientCallback.getApfProgram(); + program = ipClientCallback.assertProgramUpdateAndGet(); assertPass(program, basePacket.array()); apfFilter.shutdown(); } - // The ByteBuffer is always created by ByteBuffer#wrap in the helper functions - @SuppressWarnings("ByteBufferBackingArray") - @Test - public void testRaWithoutLifetimeCalculationFix() throws Exception { - final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); - final ApfConfiguration config = getDefaultConfig(); - config.multicastFilter = DROP_MULTICAST; - config.ieee802_3Filter = DROP_802_3_FRAMES; - // Disable the RA lifetime calculation fix in aosp/2276160 - when(mDependencies.isFeatureEnabled(eq(mContext), - eq(APF_USE_RA_LIFETIME_CALCULATION_FIX_VERSION), anyBoolean())).thenReturn(false); - final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog, - mDependencies); - byte[] program = ipClientCallback.getApfProgram(); - - final int routerLifetime = 1000; - final int timePassedSeconds = 12; - - // Verify that when the program is generated and installed without the RA lifetime - // calculation fix, it should be installed with the old buggy behavior. - ByteBuffer basePacket = makeBaseRaPacket(); - verifyRaLifetime(apfFilter, ipClientCallback, basePacket, routerLifetime); - apfFilter.increaseCurrentTimeSeconds(timePassedSeconds); - synchronized (apfFilter) { - apfFilter.installNewProgramLocked(); - } - program = ipClientCallback.getApfProgram(); - final int ageLimit = (routerLifetime - timePassedSeconds) / 6; - assertDrop(program, basePacket.array()); - assertDrop(program, basePacket.array(), ageLimit); - assertPass(program, basePacket.array(), ageLimit + 1); - assertPass(program, basePacket.array(), routerLifetime); - - apfFilter.shutdown(); - } - /** * Stage a file for testing, i.e. make it native accessible. Given a resource ID, * copy that resource into the app's data directory and return the path to it. @@ -2819,7 +2732,7 @@ ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; - TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mLog, mDependencies); + TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mNetworkQuirkMetrics); for (int i = 0; i < 1000; i++) { byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)]; r.nextBytes(packet); @@ -2830,6 +2743,7 @@ throw new Exception("bad packet: " + HexDump.toHexString(packet), e); } } + apfFilter.shutdown(); } @Test @@ -2840,7 +2754,7 @@ ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; - TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mLog, mDependencies); + TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mNetworkQuirkMetrics); for (int i = 0; i < 1000; i++) { byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)]; r.nextBytes(packet); @@ -2850,37 +2764,300 @@ throw new Exception("bad packet: " + HexDump.toHexString(packet), e); } } + apfFilter.shutdown(); } - /** - * Call the APF interpreter to run {@code program} on {@code packet} with persistent memory - * segment {@data} pretending the filter was installed {@code filter_age} seconds ago. - */ - private native static int apfSimulate(byte[] program, byte[] packet, byte[] data, - int filter_age); + @Test + public void testMatchedRaUpdatesLifetime() throws Exception { + final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); + final TestApfFilter apfFilter = new TestApfFilter(mContext, getDefaultConfig(), + ipClientCallback, mNetworkQuirkMetrics); - /** - * Compile a tcpdump human-readable filter (e.g. "icmp" or "tcp port 54") into a BPF - * prorgam and return a human-readable dump of the BPF program identical to "tcpdump -d". - */ - private native static String compileToBpf(String filter); + // Create an RA and build an APF program + byte[] ra = new RaPacketBuilder(1800 /* router lifetime */).build(); + apfFilter.pretendPacketReceived(ra); + byte[] program = ipClientCallback.assertProgramUpdateAndGet(); - /** - * Open packet capture file {@code pcap_filename} and filter the packets using tcpdump - * human-readable filter (e.g. "icmp" or "tcp port 54") compiled to a BPF program and - * at the same time using APF program {@code apf_program}. Return {@code true} if - * both APF and BPF programs filter out exactly the same packets. - */ - private native static boolean compareBpfApf(String filter, String pcap_filename, - byte[] apf_program); + // lifetime dropped significantly, assert pass + ra = new RaPacketBuilder(200 /* router lifetime */).build(); + assertPass(program, ra); + + // update program with the new RA + apfFilter.pretendPacketReceived(ra); + program = ipClientCallback.assertProgramUpdateAndGet(); + + // assert program was updated and new lifetimes were taken into account. + assertDrop(program, ra); + apfFilter.shutdown(); + } + + // Test for go/apf-ra-filter Case 1a. + // Old lifetime is 0 + @Test + public void testAcceptRaMinLftCase1a() throws Exception { + final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); + // configure accept_ra_min_lft + final ApfConfiguration config = getDefaultConfig(); + config.acceptRaMinLft = 180; + final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + mNetworkQuirkMetrics); + + // Create an initial RA and build an APF program + byte[] ra = new RaPacketBuilder(1800 /* router lifetime */) + .addPioOption(1800 /*valid*/, 0 /*preferred*/, "2001:db8::/64") + .build(); + + apfFilter.pretendPacketReceived(ra); + byte[] program = ipClientCallback.assertProgramUpdateAndGet(); + + // repeated RA is dropped + assertDrop(program, ra); + + // PIO preferred lifetime increases + ra = new RaPacketBuilder(1800 /* router lifetime */) + .addPioOption(1800 /*valid*/, 1 /*preferred*/, "2001:db8::/64") + .build(); + assertPass(program, ra); + apfFilter.shutdown(); + } + + // Test for go/apf-ra-filter Case 2a. + // Old lifetime is > 0 + @Test + public void testAcceptRaMinLftCase2a() throws Exception { + final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); + // configure accept_ra_min_lft + final ApfConfiguration config = getDefaultConfig(); + config.acceptRaMinLft = 180; + final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + mNetworkQuirkMetrics); + + // Create an initial RA and build an APF program + byte[] ra = new RaPacketBuilder(1800 /* router lifetime */) + .addPioOption(1800 /*valid*/, 100 /*preferred*/, "2001:db8::/64") + .build(); + + apfFilter.pretendPacketReceived(ra); + byte[] program = ipClientCallback.assertProgramUpdateAndGet(); + + // repeated RA is dropped + assertDrop(program, ra); + + // PIO preferred lifetime increases + ra = new RaPacketBuilder(1800 /* router lifetime */) + .addPioOption(1800 /*valid*/, 101 /*preferred*/, "2001:db8::/64") + .build(); + assertPass(program, ra); + + // PIO preferred lifetime decreases significantly + ra = new RaPacketBuilder(1800 /* router lifetime */) + .addPioOption(1800 /*valid*/, 33 /*preferred*/, "2001:db8::/64") + .build(); + assertPass(program, ra); + apfFilter.shutdown(); + } - /** - * Open packet capture file {@code pcapFilename} and run it through APF filter. Then - * checks whether all the packets are dropped and populates data[] {@code data} with - * the APF counters. - */ - private native static boolean dropsAllPackets(byte[] program, byte[] data, String pcapFilename); + // Test for go/apf-ra-filter Case 1b. + // Old lifetime is 0 + @Test + public void testAcceptRaMinLftCase1b() throws Exception { + final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); + // configure accept_ra_min_lft + final ApfConfiguration config = getDefaultConfig(); + config.acceptRaMinLft = 180; + final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + mNetworkQuirkMetrics); + + // Create an initial RA and build an APF program + byte[] ra = new RaPacketBuilder(0 /* router lifetime */).build(); + + apfFilter.pretendPacketReceived(ra); + byte[] program = ipClientCallback.assertProgramUpdateAndGet(); + + // repeated RA is dropped + assertDrop(program, ra); + + // lifetime increases below accept_ra_min_lft + ra = new RaPacketBuilder(179 /* router lifetime */).build(); + assertDrop(program, ra); + + // lifetime increases to accept_ra_min_lft + ra = new RaPacketBuilder(180 /* router lifetime */).build(); + assertPass(program, ra); + apfFilter.shutdown(); + } + + + // Test for go/apf-ra-filter Case 2b. + // Old lifetime is < accept_ra_min_lft (but not 0). + @Test + public void testAcceptRaMinLftCase2b() throws Exception { + final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); + // configure accept_ra_min_lft + final ApfConfiguration config = getDefaultConfig(); + config.acceptRaMinLft = 180; + final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + mNetworkQuirkMetrics); + + // Create an initial RA and build an APF program + byte[] ra = new RaPacketBuilder(100 /* router lifetime */).build(); + + apfFilter.pretendPacketReceived(ra); + byte[] program = ipClientCallback.assertProgramUpdateAndGet(); + + // repeated RA is dropped + assertDrop(program, ra); + + // lifetime increases + ra = new RaPacketBuilder(101 /* router lifetime */).build(); + assertDrop(program, ra); + + // lifetime decreases significantly + ra = new RaPacketBuilder(1 /* router lifetime */).build(); + assertDrop(program, ra); + + // equals accept_ra_min_lft + ra = new RaPacketBuilder(180 /* router lifetime */).build(); + assertPass(program, ra); + + // lifetime is 0 + ra = new RaPacketBuilder(0 /* router lifetime */).build(); + assertPass(program, ra); + apfFilter.shutdown(); + } + + // Test for go/apf-ra-filter Case 3b. + // Old lifetime is >= accept_ra_min_lft and <= 3 * accept_ra_min_lft + @Test + public void testAcceptRaMinLftCase3b() throws Exception { + final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); + // configure accept_ra_min_lft + final ApfConfiguration config = getDefaultConfig(); + config.acceptRaMinLft = 180; + final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + mNetworkQuirkMetrics); + + // Create an initial RA and build an APF program + byte[] ra = new RaPacketBuilder(200 /* router lifetime */).build(); + + apfFilter.pretendPacketReceived(ra); + byte[] program = ipClientCallback.assertProgramUpdateAndGet(); + + // repeated RA is dropped + assertDrop(program, ra); + + // lifetime increases + ra = new RaPacketBuilder(201 /* router lifetime */).build(); + assertPass(program, ra); + + // lifetime is below accept_ra_min_lft (but not 0) + ra = new RaPacketBuilder(1 /* router lifetime */).build(); + assertDrop(program, ra); + + // lifetime is 0 + ra = new RaPacketBuilder(0 /* router lifetime */).build(); + assertPass(program, ra); + apfFilter.shutdown(); + } + + // Test for go/apf-ra-filter Case 4b. + // Old lifetime is > 3 * accept_ra_min_lft + @Test + public void testAcceptRaMinLftCase4b() throws Exception { + final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); + // configure accept_ra_min_lft + final ApfConfiguration config = getDefaultConfig(); + config.acceptRaMinLft = 180; + final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + mNetworkQuirkMetrics); + + // Create an initial RA and build an APF program + byte[] ra = new RaPacketBuilder(1800 /* router lifetime */).build(); + + apfFilter.pretendPacketReceived(ra); + byte[] program = ipClientCallback.assertProgramUpdateAndGet(); + + // repeated RA is dropped + assertDrop(program, ra); + + // lifetime increases + ra = new RaPacketBuilder(1801 /* router lifetime */).build(); + assertPass(program, ra); + + // lifetime is 1/3 of old lft + ra = new RaPacketBuilder(600 /* router lifetime */).build(); + assertDrop(program, ra); + + // lifetime is below 1/3 of old lft + ra = new RaPacketBuilder(599 /* router lifetime */).build(); + assertPass(program, ra); + + // lifetime is below accept_ra_min_lft (but not 0) + ra = new RaPacketBuilder(1 /* router lifetime */).build(); + assertDrop(program, ra); + + // lifetime is 0 + ra = new RaPacketBuilder(0 /* router lifetime */).build(); + assertPass(program, ra); + apfFilter.shutdown(); + } + + @Test + public void testRaFilterIsUpdated() throws Exception { + final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); + // configure accept_ra_min_lft + final ApfConfiguration config = getDefaultConfig(); + config.acceptRaMinLft = 180; + final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + mNetworkQuirkMetrics); + + // Create an initial RA and build an APF program + byte[] ra = new RaPacketBuilder(1800 /* router lifetime */).build(); + apfFilter.pretendPacketReceived(ra); + byte[] program = ipClientCallback.assertProgramUpdateAndGet(); + + // repeated RA is dropped. + assertDrop(program, ra); + + // updated RA is passed, repeated RA is dropped after program update. + ra = new RaPacketBuilder(599 /* router lifetime */).build(); + assertPass(program, ra); + apfFilter.pretendPacketReceived(ra); + program = ipClientCallback.assertProgramUpdateAndGet(); + assertDrop(program, ra); + + ra = new RaPacketBuilder(180 /* router lifetime */).build(); + assertPass(program, ra); + apfFilter.pretendPacketReceived(ra); + program = ipClientCallback.assertProgramUpdateAndGet(); + assertDrop(program, ra); + + ra = new RaPacketBuilder(0 /* router lifetime */).build(); + assertPass(program, ra); + apfFilter.pretendPacketReceived(ra); + program = ipClientCallback.assertProgramUpdateAndGet(); + assertDrop(program, ra); + + ra = new RaPacketBuilder(180 /* router lifetime */).build(); + assertPass(program, ra); + apfFilter.pretendPacketReceived(ra); + program = ipClientCallback.assertProgramUpdateAndGet(); + assertDrop(program, ra); + + ra = new RaPacketBuilder(599 /* router lifetime */).build(); + assertPass(program, ra); + apfFilter.pretendPacketReceived(ra); + program = ipClientCallback.assertProgramUpdateAndGet(); + assertDrop(program, ra); + + ra = new RaPacketBuilder(1800 /* router lifetime */).build(); + assertPass(program, ra); + apfFilter.pretendPacketReceived(ra); + program = ipClientCallback.assertProgramUpdateAndGet(); + assertDrop(program, ra); + apfFilter.shutdown(); + } @Test public void testBroadcastAddress() throws Exception { @@ -2900,4 +3077,328 @@ (Inet4Address) InetAddresses.parseNumericAddress(expected)); assertEquals(want, got); } + + private TestAndroidPacketFilter makeTestApfFilter(ApfConfiguration config, + MockIpClientCallback ipClientCallback, boolean isLegacy) throws Exception { + final TestAndroidPacketFilter apfFilter; + if (isLegacy) { + apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback, + mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); + } else { + apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mNetworkQuirkMetrics, + mDependencies, mClock); + } + return apfFilter; + } + + // LegacyApfFilter ignores zero lifetime RAs (doesn't update program) but ApfFilter won't. + private void verifyUpdateProgramForZeroLifetimeRa(MockIpClientCallback ipClientCallback, + boolean isLegacy) { + if (isLegacy) { + ipClientCallback.assertNoProgramUpdate(); + } else { + ipClientCallback.assertProgramUpdateAndGet(); + } + } + + private void verifyInstallPacketFilterFailure(boolean isLegacy) throws Exception { + final MockIpClientCallback ipClientCallback = new MockIpClientCallback(false); + final ApfConfiguration config = getDefaultConfig(); + final TestAndroidPacketFilter apfFilter = + makeTestApfFilter(config, ipClientCallback, isLegacy); + verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE); + verify(mNetworkQuirkMetrics).statsWrite(); + reset(mNetworkQuirkMetrics); + synchronized (apfFilter) { + apfFilter.installNewProgramLocked(); + } + verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE); + verify(mNetworkQuirkMetrics).statsWrite(); + apfFilter.shutdown(); + } + + @Test + public void testInstallPacketFilterFailure() throws Exception { + verifyInstallPacketFilterFailure(false /* isLegacy */); + } + + @Test + public void testInstallPacketFilterFailure_LegacyApfFilter() throws Exception { + verifyInstallPacketFilterFailure(true /* isLegacy */); + } + + private void verifyApfProgramOverSize(boolean isLegacy) throws Exception { + final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); + final ApfConfiguration config = getDefaultConfig(); + final ApfCapabilities capabilities = new ApfCapabilities(2, 512, ARPHRD_ETHER); + config.apfCapabilities = capabilities; + final TestAndroidPacketFilter apfFilter = + makeTestApfFilter(config, ipClientCallback, isLegacy); + byte[] program = ipClientCallback.assertProgramUpdateAndGet(); + final byte[] ra = buildLargeRa(); + apfFilter.pretendPacketReceived(ra); + // The generated program size will be 529, which is larger than 512 + program = ipClientCallback.assertProgramUpdateAndGet(); + verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_OVER_SIZE_FAILURE); + verify(mNetworkQuirkMetrics).statsWrite(); + apfFilter.shutdown(); + } + + @Test + public void testApfProgramOverSize() throws Exception { + verifyApfProgramOverSize(false /* isLegacy */); + } + + @Test + public void testApfProgramOverSize_LegacyApfFilter() throws Exception { + verifyApfProgramOverSize(true /* isLegacy */); + } + + private void verifyGenerateApfProgramException(boolean isLegacy) throws Exception { + final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); + final ApfConfiguration config = getDefaultConfig(); + final TestAndroidPacketFilter apfFilter; + if (isLegacy) { + apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback, + mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, + true /* throwsExceptionWhenGeneratesProgram */); + } else { + apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mNetworkQuirkMetrics, + mDependencies, true /* throwsExceptionWhenGeneratesProgram */); + } + synchronized (apfFilter) { + apfFilter.installNewProgramLocked(); + } + verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_GENERATE_FILTER_EXCEPTION); + verify(mNetworkQuirkMetrics).statsWrite(); + apfFilter.shutdown(); + } + + @Test + public void testGenerateApfProgramException() throws Exception { + verifyGenerateApfProgramException(false /* isLegacy */); + } + + @Test + public void testGenerateApfProgramException_LegacyApfFilter() throws Exception { + verifyGenerateApfProgramException(true /* isLegacy */); + } + + private void verifyApfSessionInfoMetrics(boolean isLegacy) throws Exception { + final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); + final ApfConfiguration config = getDefaultConfig(); + final ApfCapabilities capabilities = new ApfCapabilities(4, 4096, ARPHRD_ETHER); + config.apfCapabilities = capabilities; + final long startTimeMs = 12345; + final long durationTimeMs = config.minMetricsSessionDurationMs; + doReturn(startTimeMs).when(mClock).elapsedRealtime(); + final TestAndroidPacketFilter apfFilter = + makeTestApfFilter(config, ipClientCallback, isLegacy); + int maxProgramSize = 0; + int numProgramUpdated = 0; + byte[] program = ipClientCallback.assertProgramUpdateAndGet(); + maxProgramSize = Math.max(maxProgramSize, program.length); + numProgramUpdated++; + + final byte[] data = new byte[Counter.totalSize()]; + final byte[] expectedData = data.clone(); + final int totalPacketsCounterIdx = Counter.totalSize() + Counter.TOTAL_PACKETS.offset(); + final int passedIpv6IcmpCounterIdx = + Counter.totalSize() + Counter.PASSED_IPV6_ICMP.offset(); + final int droppedIpv4MulticastIdx = + Counter.totalSize() + Counter.DROPPED_IPV4_MULTICAST.offset(); + + // Receive an RA packet (passed). + final byte[] ra = buildLargeRa(); + expectedData[totalPacketsCounterIdx + 3] += 1; + expectedData[passedIpv6IcmpCounterIdx + 3] += 1; + assertDataMemoryContents(PASS, program, ra, data, expectedData); + apfFilter.pretendPacketReceived(ra); + program = ipClientCallback.assertProgramUpdateAndGet(); + maxProgramSize = Math.max(maxProgramSize, program.length); + numProgramUpdated++; + + apfFilter.setMulticastFilter(true); + // setMulticastFilter will trigger program installation. + program = ipClientCallback.assertProgramUpdateAndGet(); + maxProgramSize = Math.max(maxProgramSize, program.length); + numProgramUpdated++; + + // Receive IPv4 multicast packet (dropped). + final byte[] multicastIpv4Addr = {(byte) 224, 0, 0, 1}; + ByteBuffer mcastv4packet = makeIpv4Packet(IPPROTO_UDP); + put(mcastv4packet, IPV4_DEST_ADDR_OFFSET, multicastIpv4Addr); + expectedData[totalPacketsCounterIdx + 3] += 1; + expectedData[droppedIpv4MulticastIdx + 3] += 1; + assertDataMemoryContents(DROP, program, mcastv4packet.array(), data, expectedData); + + // Set data snapshot and update counters. + apfFilter.setDataSnapshot(data); + + // Write metrics data to statsd pipeline when shutdown. + doReturn(startTimeMs + durationTimeMs).when(mClock).elapsedRealtime(); + apfFilter.shutdown(); + verify(mApfSessionInfoMetrics).setVersion(4); + verify(mApfSessionInfoMetrics).setMemorySize(4096); + + // Verify Counters + final Map<Counter, Long> expectedCounters = Map.of(Counter.TOTAL_PACKETS, 2L, + Counter.PASSED_IPV6_ICMP, 1L, Counter.DROPPED_IPV4_MULTICAST, 1L); + final ArgumentCaptor<Counter> counterCaptor = ArgumentCaptor.forClass(Counter.class); + final ArgumentCaptor<Long> valueCaptor = ArgumentCaptor.forClass(Long.class); + verify(mApfSessionInfoMetrics, times(expectedCounters.size())).addApfCounter( + counterCaptor.capture(), valueCaptor.capture()); + final List<Counter> counters = counterCaptor.getAllValues(); + final List<Long> values = valueCaptor.getAllValues(); + final ArrayMap<Counter, Long> capturedCounters = new ArrayMap<>(); + for (int i = 0; i < counters.size(); i++) { + capturedCounters.put(counters.get(i), values.get(i)); + } + assertEquals(expectedCounters, capturedCounters); + + verify(mApfSessionInfoMetrics).setApfSessionDurationSeconds( + (int) (durationTimeMs / DateUtils.SECOND_IN_MILLIS)); + verify(mApfSessionInfoMetrics).setNumOfTimesApfProgramUpdated(numProgramUpdated); + verify(mApfSessionInfoMetrics).setMaxProgramSize(maxProgramSize); + verify(mApfSessionInfoMetrics).statsWrite(); + } + + @Test + public void testApfSessionInfoMetrics() throws Exception { + verifyApfSessionInfoMetrics(false /* isLegacy */); + } + + @Test + public void testApfSessionInfoMetrics_LegacyApfFilter() throws Exception { + verifyApfSessionInfoMetrics(true /* isLegacy */); + } + + private void verifyIpClientRaInfoMetrics(boolean isLegacy) throws Exception { + final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); + final ApfConfiguration config = getDefaultConfig(); + final long startTimeMs = 12345; + final long durationTimeMs = config.minMetricsSessionDurationMs; + doReturn(startTimeMs).when(mClock).elapsedRealtime(); + final TestAndroidPacketFilter apfFilter = + makeTestApfFilter(config, ipClientCallback, isLegacy); + byte[] program = ipClientCallback.assertProgramUpdateAndGet(); + + final int routerLifetime = 1000; + final int prefixValidLifetime = 200; + final int prefixPreferredLifetime = 100; + final int rdnssLifetime = 300; + final int routeLifetime = 400; + + // Construct 2 RAs with partial lifetimes larger than predefined constants + final RaPacketBuilder ra1 = new RaPacketBuilder(routerLifetime); + ra1.addPioOption(prefixValidLifetime + 123, prefixPreferredLifetime, "2001:db8::/64"); + ra1.addRdnssOption(rdnssLifetime, "2001:4860:4860::8888", "2001:4860:4860::8844"); + ra1.addRioOption(routeLifetime + 456, "64:ff9b::/96"); + final RaPacketBuilder ra2 = new RaPacketBuilder(routerLifetime + 123); + ra2.addPioOption(prefixValidLifetime, prefixPreferredLifetime, "2001:db9::/64"); + ra2.addRdnssOption(rdnssLifetime + 456, "2001:4860:4860::8888", "2001:4860:4860::8844"); + ra2.addRioOption(routeLifetime, "64:ff9b::/96"); + + // Construct an invalid RA packet + final RaPacketBuilder raInvalid = new RaPacketBuilder(routerLifetime); + raInvalid.addZeroLengthOption(); + + // Construct 4 different kinds of zero lifetime RAs + final RaPacketBuilder raZeroRouterLifetime = new RaPacketBuilder(0 /* routerLft */); + final RaPacketBuilder raZeroPioValidLifetime = new RaPacketBuilder(routerLifetime); + raZeroPioValidLifetime.addPioOption(0, prefixPreferredLifetime, "2001:db10::/64"); + final RaPacketBuilder raZeroRdnssLifetime = new RaPacketBuilder(routerLifetime); + raZeroRdnssLifetime.addPioOption( + prefixValidLifetime, prefixPreferredLifetime, "2001:db11::/64"); + raZeroRdnssLifetime.addRdnssOption(0, "2001:4860:4860::8888", "2001:4860:4860::8844"); + final RaPacketBuilder raZeroRioRouteLifetime = new RaPacketBuilder(routerLifetime); + raZeroRioRouteLifetime.addPioOption( + prefixValidLifetime, prefixPreferredLifetime, "2001:db12::/64"); + raZeroRioRouteLifetime.addRioOption(0, "64:ff9b::/96"); + + // Inject RA packets. Calling assertProgramUpdateAndGet()/assertNoProgramUpdate() is to make + // sure that the RA packet has been processed. + apfFilter.pretendPacketReceived(ra1.build()); + program = ipClientCallback.assertProgramUpdateAndGet(); + apfFilter.pretendPacketReceived(ra2.build()); + program = ipClientCallback.assertProgramUpdateAndGet(); + apfFilter.pretendPacketReceived(raInvalid.build()); + ipClientCallback.assertNoProgramUpdate(); + apfFilter.pretendPacketReceived(raZeroRouterLifetime.build()); + verifyUpdateProgramForZeroLifetimeRa(ipClientCallback, isLegacy); + apfFilter.pretendPacketReceived(raZeroPioValidLifetime.build()); + verifyUpdateProgramForZeroLifetimeRa(ipClientCallback, isLegacy); + apfFilter.pretendPacketReceived(raZeroRdnssLifetime.build()); + verifyUpdateProgramForZeroLifetimeRa(ipClientCallback, isLegacy); + apfFilter.pretendPacketReceived(raZeroRioRouteLifetime.build()); + verifyUpdateProgramForZeroLifetimeRa(ipClientCallback, isLegacy); + + // Write metrics data to statsd pipeline when shutdown. + doReturn(startTimeMs + durationTimeMs).when(mClock).elapsedRealtime(); + apfFilter.shutdown(); + + // Verify each metric fields in IpClientRaInfoMetrics. + if (isLegacy) { + // LegacyApfFilter will purge expired RAs before adding new RA. Every time a new zero + // lifetime RA is received, zero lifetime RAs except the newly added one will be + // cleared, so the number of distinct RAs is 3 (ra1, ra2 and the newly added RA). + verify(mIpClientRaInfoMetrics).setMaxNumberOfDistinctRas(3); + } else { + verify(mIpClientRaInfoMetrics).setMaxNumberOfDistinctRas(6); + } + verify(mIpClientRaInfoMetrics).setNumberOfZeroLifetimeRas(4); + verify(mIpClientRaInfoMetrics).setNumberOfParsingErrorRas(1); + verify(mIpClientRaInfoMetrics).setLowestRouterLifetimeSeconds(routerLifetime); + verify(mIpClientRaInfoMetrics).setLowestPioValidLifetimeSeconds(prefixValidLifetime); + verify(mIpClientRaInfoMetrics).setLowestRioRouteLifetimeSeconds(routeLifetime); + verify(mIpClientRaInfoMetrics).setLowestRdnssLifetimeSeconds(rdnssLifetime); + verify(mIpClientRaInfoMetrics).statsWrite(); + } + + @Test + public void testIpClientRaInfoMetrics() throws Exception { + verifyIpClientRaInfoMetrics(false /* isLegacy */); + } + + @Test + public void testIpClientRaInfoMetrics_LegacyApfFilter() throws Exception { + verifyIpClientRaInfoMetrics(true /* isLegacy */); + } + + private void verifyNoMetricsWrittenForShortDuration(boolean isLegacy) throws Exception { + final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); + final ApfConfiguration config = getDefaultConfig(); + final long startTimeMs = 12345; + final long durationTimeMs = config.minMetricsSessionDurationMs; + + // Verify no metrics data written to statsd for duration less than durationTimeMs. + doReturn(startTimeMs).when(mClock).elapsedRealtime(); + final TestAndroidPacketFilter apfFilter = + makeTestApfFilter(config, ipClientCallback, isLegacy); + doReturn(startTimeMs + durationTimeMs - 1).when(mClock).elapsedRealtime(); + apfFilter.shutdown(); + verify(mApfSessionInfoMetrics, never()).statsWrite(); + verify(mIpClientRaInfoMetrics, never()).statsWrite(); + + // Verify metrics data written to statsd for duration greater than or equal to + // durationTimeMs. + ApfFilter.Clock clock = mock(ApfFilter.Clock.class); + doReturn(startTimeMs).when(clock).elapsedRealtime(); + final TestAndroidPacketFilter apfFilter2 = new TestApfFilter(mContext, config, + ipClientCallback, mNetworkQuirkMetrics, mDependencies, clock); + doReturn(startTimeMs + durationTimeMs).when(clock).elapsedRealtime(); + apfFilter2.shutdown(); + verify(mApfSessionInfoMetrics).statsWrite(); + verify(mIpClientRaInfoMetrics).statsWrite(); + } + + @Test + public void testNoMetricsWrittenForShortDuration() throws Exception { + verifyNoMetricsWrittenForShortDuration(false /* isLegacy */); + } + + @Test + public void testNoMetricsWrittenForShortDuration_LegacyApfFilter() throws Exception { + verifyNoMetricsWrittenForShortDuration(true /* isLegacy */); + } }
diff --git a/tests/unit/src/android/net/apf/ApfTestUtils.java b/tests/unit/src/android/net/apf/ApfTestUtils.java new file mode 100644 index 0000000..abbdd6b --- /dev/null +++ b/tests/unit/src/android/net/apf/ApfTestUtils.java
@@ -0,0 +1,489 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.net.apf; + +import static android.net.apf.ApfJniUtils.apfSimulate; +import static android.system.OsConstants.AF_UNIX; +import static android.system.OsConstants.SOCK_STREAM; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; + +import android.content.Context; +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.apf.ApfGenerator.IllegalInstructionException; +import android.net.ip.IIpClientCallbacks; +import android.net.ip.IpClient; +import android.net.metrics.IpConnectivityLog; +import android.os.ConditionVariable; +import android.os.SystemClock; +import android.system.ErrnoException; +import android.system.Os; +import android.text.format.DateUtils; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.HexDump; +import com.android.net.module.util.InterfaceParams; +import com.android.net.module.util.SharedLog; +import com.android.networkstack.apishim.NetworkInformationShimImpl; +import com.android.networkstack.metrics.NetworkQuirkMetrics; + +import libcore.io.IoUtils; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.net.InetAddress; +import java.util.Arrays; + +/** + * The util class for calling the APF interpreter and check the return value + */ +public class ApfTestUtils { + public static final int TIMEOUT_MS = 500; + public static final int PASS = 1; + public static final int DROP = 0; + // Interpreter will just accept packets without link layer headers, so pad fake packet to at + // least the minimum packet size. + public static final int MIN_PKT_SIZE = 15; + + private ApfTestUtils() { + } + + private static String label(int code) { + switch (code) { + case PASS: + return "PASS"; + case DROP: + return "DROP"; + default: + return "UNKNOWN"; + } + } + + private static void assertReturnCodesEqual(String msg, int expected, int got) { + assertEquals(msg, label(expected), label(got)); + } + + private static void assertReturnCodesEqual(int expected, int got) { + assertEquals(label(expected), label(got)); + } + + private static void assertVerdict(int apfVersion, int expected, byte[] program, byte[] packet, + int filterAge) { + final String msg = "Unexpected APF verdict. To debug:\n" + " apf_run --program " + + HexDump.toHexString(program) + " --packet " + HexDump.toHexString(packet) + + " --trace | less\n "; + assertReturnCodesEqual(msg, expected, + apfSimulate(apfVersion, program, packet, null, filterAge)); + } + + /** + * Runs the APF program and checks the return code is equals to expected value. If not, the + * customized message is printed. + */ + public static void assertVerdict(int apfVersion, String msg, int expected, byte[] program, + byte[] packet, int filterAge) { + assertReturnCodesEqual(msg, expected, + apfSimulate(apfVersion, program, packet, null, filterAge)); + } + + /** + * Runs the APF program and checks the return code is equals to expected value. + */ + public static void assertVerdict(int apfVersion, int expected, byte[] program, byte[] packet) { + assertVerdict(apfVersion, expected, program, packet, 0); + } + + /** + * Runs the APF program and checks the return code is PASS. + */ + public static void assertPass(int apfVersion, byte[] program, byte[] packet, int filterAge) { + assertVerdict(apfVersion, PASS, program, packet, filterAge); + } + + /** + * Runs the APF program and checks the return code is PASS. + */ + public static void assertPass(int apfVersion, byte[] program, byte[] packet) { + assertVerdict(apfVersion, PASS, program, packet); + } + + /** + * Runs the APF program and checks the return code is DROP. + */ + public static void assertDrop(int apfVersion, byte[] program, byte[] packet, int filterAge) { + assertVerdict(apfVersion, DROP, program, packet, filterAge); + } + + /** + * Runs the APF program and checks the return code is DROP. + */ + public static void assertDrop(int apfVersion, byte[] program, byte[] packet) { + assertVerdict(apfVersion, DROP, program, packet); + } + + /** + * Checks the generated APF program equals to the expected value. + */ + public static void assertProgramEquals(byte[] expected, byte[] program) throws AssertionError { + // assertArrayEquals() would only print one byte, making debugging difficult. + if (!Arrays.equals(expected, program)) { + throw new AssertionError("\nexpected: " + HexDump.toHexString(expected) + "\nactual: " + + HexDump.toHexString(program)); + } + } + + /** + * Runs the APF program and checks the return code and data regions equals to expected value. + */ + public static void assertDataMemoryContents(int apfVersion, int expected, byte[] program, + byte[] packet, byte[] data, byte[] expectedData) + throws ApfGenerator.IllegalInstructionException, Exception { + assertReturnCodesEqual(expected, + apfSimulate(apfVersion, program, packet, data, 0 /* filterAge */)); + + // assertArrayEquals() would only print one byte, making debugging difficult. + if (!Arrays.equals(expectedData, data)) { + throw new Exception("\nprogram: " + HexDump.toHexString(program) + "\ndata memory: " + + HexDump.toHexString(data) + "\nexpected: " + HexDump.toHexString( + expectedData)); + } + } + + /** + * Runs the APF program with customized data region and checks the return code. + */ + public static void assertVerdict(int apfVersion, int expected, byte[] program, byte[] packet, + byte[] data) { + assertReturnCodesEqual(expected, + apfSimulate(apfVersion, program, packet, data, 0 /* filterAge */)); + } + + private static void assertVerdict(int apfVersion, int expected, ApfGenerator gen, byte[] packet, + int filterAge) throws ApfGenerator.IllegalInstructionException { + assertReturnCodesEqual(expected, + apfSimulate(apfVersion, gen.generate(), packet, null, filterAge)); + } + + /** + * Runs the APF program and checks the return code is PASS. + */ + public static void assertPass(int apfVersion, ApfGenerator gen, byte[] packet, int filterAge) + throws ApfGenerator.IllegalInstructionException { + assertVerdict(apfVersion, PASS, gen, packet, filterAge); + } + + /** + * Runs the APF program and checks the return code is DROP. + */ + public static void assertDrop(int apfVersion, ApfGenerator gen, byte[] packet, int filterAge) + throws ApfGenerator.IllegalInstructionException { + assertVerdict(apfVersion, DROP, gen, packet, filterAge); + } + + /** + * Runs the APF program and checks the return code is PASS. + */ + public static void assertPass(int apfVersion, ApfGenerator gen) + throws ApfGenerator.IllegalInstructionException { + assertVerdict(apfVersion, PASS, gen, new byte[MIN_PKT_SIZE], 0); + } + + /** + * Runs the APF program and checks the return code is DROP. + */ + public static void assertDrop(int apfVersion, ApfGenerator gen) + throws ApfGenerator.IllegalInstructionException { + assertVerdict(apfVersion, DROP, gen, new byte[MIN_PKT_SIZE], 0); + } + + /** + * The Mock ip client callback class. + */ + public static class MockIpClientCallback extends IpClient.IpClientCallbacksWrapper { + private final ConditionVariable mGotApfProgram = new ConditionVariable(); + private byte[] mLastApfProgram; + private boolean mInstallPacketFilterReturn = true; + + MockIpClientCallback() { + super(mock(IIpClientCallbacks.class), mock(SharedLog.class), + NetworkInformationShimImpl.newInstance()); + } + + MockIpClientCallback(boolean installPacketFilterReturn) { + super(mock(IIpClientCallbacks.class), mock(SharedLog.class), + NetworkInformationShimImpl.newInstance()); + mInstallPacketFilterReturn = installPacketFilterReturn; + } + + @Override + public boolean installPacketFilter(byte[] filter) { + mLastApfProgram = filter; + mGotApfProgram.open(); + return mInstallPacketFilterReturn; + } + + /** + * Reset the apf program and wait for the next update. + */ + public void resetApfProgramWait() { + mGotApfProgram.close(); + } + + /** + * Assert the program is update within TIMEOUT_MS and return the program. + */ + public byte[] assertProgramUpdateAndGet() { + assertTrue(mGotApfProgram.block(TIMEOUT_MS)); + return mLastApfProgram; + } + + /** + * Assert the program is not update within TIMEOUT_MS. + */ + public void assertNoProgramUpdate() { + assertFalse(mGotApfProgram.block(TIMEOUT_MS)); + } + } + + /** + * The test apf filter class. + */ + public static class TestApfFilter extends ApfFilter implements TestAndroidPacketFilter { + public static final byte[] MOCK_MAC_ADDR = {1, 2, 3, 4, 5, 6}; + private static final byte[] MOCK_IPV4_ADDR = {10, 0, 0, 1}; + + private FileDescriptor mWriteSocket; + private long mCurrentTimeMs = SystemClock.elapsedRealtime(); + private final MockIpClientCallback mMockIpClientCb; + private final boolean mThrowsExceptionWhenGeneratesProgram; + + public TestApfFilter(Context context, ApfConfiguration config, + MockIpClientCallback ipClientCallback, NetworkQuirkMetrics networkQuirkMetrics) + throws Exception { + this(context, config, ipClientCallback, networkQuirkMetrics, new Dependencies(context), + false /* throwsExceptionWhenGeneratesProgram */, new ApfFilter.Clock()); + } + + public TestApfFilter(Context context, ApfConfiguration config, + MockIpClientCallback ipClientCallback, NetworkQuirkMetrics networkQuirkMetrics, + Dependencies dependencies) throws Exception { + this(context, config, ipClientCallback, networkQuirkMetrics, dependencies, + false /* throwsExceptionWhenGeneratesProgram */, new ApfFilter.Clock()); + } + + public TestApfFilter(Context context, ApfConfiguration config, + MockIpClientCallback ipClientCallback, NetworkQuirkMetrics networkQuirkMetrics, + Dependencies dependencies, boolean throwsExceptionWhenGeneratesProgram) + throws Exception { + this(context, config, ipClientCallback, networkQuirkMetrics, dependencies, + throwsExceptionWhenGeneratesProgram, new ApfFilter.Clock()); + } + + public TestApfFilter(Context context, ApfConfiguration config, + MockIpClientCallback ipClientCallback, NetworkQuirkMetrics networkQuirkMetrics, + Dependencies dependencies, ApfFilter.Clock clock) throws Exception { + this(context, config, ipClientCallback, networkQuirkMetrics, dependencies, + false /* throwsExceptionWhenGeneratesProgram */, clock); + } + + public TestApfFilter(Context context, ApfConfiguration config, + MockIpClientCallback ipClientCallback, NetworkQuirkMetrics networkQuirkMetrics, + Dependencies dependencies, boolean throwsExceptionWhenGeneratesProgram, + ApfFilter.Clock clock) throws Exception { + super(context, config, InterfaceParams.getByName("lo"), ipClientCallback, + networkQuirkMetrics, dependencies, clock); + mMockIpClientCb = ipClientCallback; + mThrowsExceptionWhenGeneratesProgram = throwsExceptionWhenGeneratesProgram; + } + + /** + * Create a new test ApfFiler. + */ + public static ApfFilter createTestApfFilter(Context context, + MockIpClientCallback ipClientCallback, ApfConfiguration config, + NetworkQuirkMetrics networkQuirkMetrics, ApfFilter.Dependencies dependencies) + throws Exception { + LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19); + LinkProperties lp = new LinkProperties(); + lp.addLinkAddress(link); + TestApfFilter apfFilter = new TestApfFilter(context, config, ipClientCallback, + networkQuirkMetrics, dependencies); + apfFilter.setLinkProperties(lp); + return apfFilter; + } + + /** + * Pretend an RA packet has been received and show it to ApfFilter. + */ + public void pretendPacketReceived(byte[] packet) throws IOException, ErrnoException { + mMockIpClientCb.resetApfProgramWait(); + // ApfFilter's ReceiveThread will be waiting to read this. + Os.write(mWriteSocket, packet, 0, packet.length); + } + + /** + * Simulate current time changes. + */ + public void increaseCurrentTimeSeconds(int delta) { + mCurrentTimeMs += delta * DateUtils.SECOND_IN_MILLIS; + } + + @Override + protected int secondsSinceBoot() { + return (int) (mCurrentTimeMs / DateUtils.SECOND_IN_MILLIS); + } + + @Override + public synchronized void maybeStartFilter() { + mHardwareAddress = MOCK_MAC_ADDR; + installNewProgramLocked(); + + // Create two sockets, "readSocket" and "mWriteSocket" and connect them together. + FileDescriptor readSocket = new FileDescriptor(); + mWriteSocket = new FileDescriptor(); + try { + Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mWriteSocket, readSocket); + } catch (ErrnoException e) { + fail(); + return; + } + // Now pass readSocket to ReceiveThread as if it was setup to read raw RAs. + // This allows us to pretend RA packets have been received via pretendPacketReceived(). + mReceiveThread = new ReceiveThread(readSocket); + mReceiveThread.start(); + } + + @Override + public synchronized void shutdown() { + super.shutdown(); + if (mReceiveThread != null) { + mReceiveThread.halt(); + mReceiveThread = null; + } + IoUtils.closeQuietly(mWriteSocket); + } + + @Override + @GuardedBy("this") + protected ApfGenerator emitPrologueLocked() throws IllegalInstructionException { + if (mThrowsExceptionWhenGeneratesProgram) { + throw new IllegalStateException(); + } + return super.emitPrologueLocked(); + } + } + + /** + * The test legacy apf filter class. + */ + public static class TestLegacyApfFilter extends LegacyApfFilter + implements TestAndroidPacketFilter { + public static final byte[] MOCK_MAC_ADDR = {1, 2, 3, 4, 5, 6}; + private static final byte[] MOCK_IPV4_ADDR = {10, 0, 0, 1}; + + private FileDescriptor mWriteSocket; + private final MockIpClientCallback mMockIpClientCb; + private final boolean mThrowsExceptionWhenGeneratesProgram; + + public TestLegacyApfFilter(Context context, ApfFilter.ApfConfiguration config, + MockIpClientCallback ipClientCallback, IpConnectivityLog ipConnectivityLog, + NetworkQuirkMetrics networkQuirkMetrics) throws Exception { + this(context, config, ipClientCallback, ipConnectivityLog, networkQuirkMetrics, + new ApfFilter.Dependencies(context), + false /* throwsExceptionWhenGeneratesProgram */, new ApfFilter.Clock()); + } + + public TestLegacyApfFilter(Context context, ApfFilter.ApfConfiguration config, + MockIpClientCallback ipClientCallback, IpConnectivityLog ipConnectivityLog, + NetworkQuirkMetrics networkQuirkMetrics, ApfFilter.Dependencies dependencies, + boolean throwsExceptionWhenGeneratesProgram) throws Exception { + this(context, config, ipClientCallback, ipConnectivityLog, networkQuirkMetrics, + dependencies, throwsExceptionWhenGeneratesProgram, new ApfFilter.Clock()); + } + + public TestLegacyApfFilter(Context context, ApfFilter.ApfConfiguration config, + MockIpClientCallback ipClientCallback, IpConnectivityLog ipConnectivityLog, + NetworkQuirkMetrics networkQuirkMetrics, ApfFilter.Dependencies dependencies, + ApfFilter.Clock clock) throws Exception { + this(context, config, ipClientCallback, ipConnectivityLog, networkQuirkMetrics, + dependencies, false /* throwsExceptionWhenGeneratesProgram */, clock); + } + + public TestLegacyApfFilter(Context context, ApfFilter.ApfConfiguration config, + MockIpClientCallback ipClientCallback, IpConnectivityLog ipConnectivityLog, + NetworkQuirkMetrics networkQuirkMetrics, ApfFilter.Dependencies dependencies, + boolean throwsExceptionWhenGeneratesProgram, ApfFilter.Clock clock) + throws Exception { + super(context, config, InterfaceParams.getByName("lo"), ipClientCallback, + ipConnectivityLog, networkQuirkMetrics, dependencies, clock); + mMockIpClientCb = ipClientCallback; + mThrowsExceptionWhenGeneratesProgram = throwsExceptionWhenGeneratesProgram; + } + + /** + * Pretend an RA packet has been received and show it to LegacyApfFilter. + */ + public void pretendPacketReceived(byte[] packet) throws IOException, ErrnoException { + mMockIpClientCb.resetApfProgramWait(); + // ApfFilter's ReceiveThread will be waiting to read this. + Os.write(mWriteSocket, packet, 0, packet.length); + } + + @Override + public synchronized void maybeStartFilter() { + mHardwareAddress = MOCK_MAC_ADDR; + installNewProgramLocked(); + + // Create two sockets, "readSocket" and "mWriteSocket" and connect them together. + FileDescriptor readSocket = new FileDescriptor(); + mWriteSocket = new FileDescriptor(); + try { + Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mWriteSocket, readSocket); + } catch (ErrnoException e) { + fail(); + return; + } + // Now pass readSocket to ReceiveThread as if it was setup to read raw RAs. + // This allows us to pretend RA packets have been received via pretendPacketReceived(). + mReceiveThread = new ReceiveThread(readSocket); + mReceiveThread.start(); + } + + @Override + public synchronized void shutdown() { + super.shutdown(); + if (mReceiveThread != null) { + mReceiveThread.halt(); + mReceiveThread = null; + } + IoUtils.closeQuietly(mWriteSocket); + } + + @Override + @GuardedBy("this") + protected ApfGenerator emitPrologueLocked() throws IllegalInstructionException { + if (mThrowsExceptionWhenGeneratesProgram) { + throw new IllegalStateException(); + } + return super.emitPrologueLocked(); + } + } +}
diff --git a/tests/unit/src/android/net/apf/ApfV5Test.kt b/tests/unit/src/android/net/apf/ApfV5Test.kt new file mode 100644 index 0000000..162feef --- /dev/null +++ b/tests/unit/src/android/net/apf/ApfV5Test.kt
@@ -0,0 +1,364 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.net.apf + +import android.net.apf.ApfGenerator.IllegalInstructionException +import android.net.apf.ApfGenerator.Register.R0 +import android.net.apf.ApfGenerator.Register.R1 +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import java.lang.IllegalArgumentException +import kotlin.test.assertContentEquals +import kotlin.test.assertFailsWith +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Tests for APFv6 specific instructions. + */ +@RunWith(AndroidJUnit4::class) +@SmallTest +class ApfV5Test { + + @Test + fun testApfInstructionVersionCheck() { + var gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION) + assertFailsWith<IllegalInstructionException> { gen.addDrop() } + assertFailsWith<IllegalInstructionException> { gen.addCountAndDrop(12) } + assertFailsWith<IllegalInstructionException> { gen.addCountAndPass(1000) } + assertFailsWith<IllegalInstructionException> { gen.addTransmit() } + assertFailsWith<IllegalInstructionException> { gen.addDiscard() } + assertFailsWith<IllegalInstructionException> { gen.addAllocateR0() } + assertFailsWith<IllegalInstructionException> { gen.addAllocate(100) } + assertFailsWith<IllegalInstructionException> { gen.addData(ByteArray(3) { 0x01 }) } + assertFailsWith<IllegalInstructionException> { gen.addWriteU8(100) } + assertFailsWith<IllegalInstructionException> { gen.addWriteU16(100) } + assertFailsWith<IllegalInstructionException> { gen.addWriteU32(100) } + assertFailsWith<IllegalInstructionException> { gen.addPacketCopy(100, 100) } + assertFailsWith<IllegalInstructionException> { gen.addDataCopy(100, 100) } + assertFailsWith<IllegalInstructionException> { gen.addWriteU8(R0) } + assertFailsWith<IllegalInstructionException> { gen.addWriteU16(R0) } + assertFailsWith<IllegalInstructionException> { gen.addWriteU32(R0) } + assertFailsWith<IllegalInstructionException> { gen.addWriteU8(R1) } + assertFailsWith<IllegalInstructionException> { gen.addWriteU16(R1) } + assertFailsWith<IllegalInstructionException> { gen.addWriteU32(R1) } + assertFailsWith<IllegalInstructionException> { gen.addPacketCopyFromR0LenR1() } + assertFailsWith<IllegalInstructionException> { gen.addDataCopyFromR0LenR1() } + assertFailsWith<IllegalInstructionException> { gen.addPacketCopyFromR0(10) } + assertFailsWith<IllegalInstructionException> { gen.addDataCopyFromR0(10) } + assertFailsWith<IllegalInstructionException> { + gen.addJumpIfBytesAtR0Equal(byteArrayOf('A'.code.toByte()), ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalInstructionException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( + byteArrayOf(1, 'A'.code.toByte()), 0x0c, ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalInstructionException> { gen.addJumpIfPktAtR0ContainDnsQ( + byteArrayOf(1, 'A'.code.toByte()), 0x0c, ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalInstructionException> { gen.addJumpIfPktAtR0DoesNotContainDnsA( + byteArrayOf(1, 'A'.code.toByte()), ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalInstructionException> { gen.addJumpIfPktAtR0ContainDnsA( + byteArrayOf(1, 'A'.code.toByte()), ApfGenerator.DROP_LABEL) } + } + + @Test + fun testDataInstructionMustComeFirst() { + var gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + gen.addAllocateR0() + assertFailsWith<IllegalInstructionException> { gen.addData(ByteArray(3) { 0x01 }) } + } + + @Test + fun testApfInstructionEncodingSizeCheck() { + var gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + assertFailsWith<IllegalArgumentException> { gen.addAllocate(65536) } + assertFailsWith<IllegalArgumentException> { gen.addAllocate(-1) } + assertFailsWith<IllegalArgumentException> { gen.addDataCopy(-1, 1) } + assertFailsWith<IllegalArgumentException> { gen.addPacketCopy(-1, 1) } + assertFailsWith<IllegalArgumentException> { gen.addDataCopy(1, 256) } + assertFailsWith<IllegalArgumentException> { gen.addPacketCopy(1, 256) } + assertFailsWith<IllegalArgumentException> { gen.addDataCopy(1, -1) } + assertFailsWith<IllegalArgumentException> { gen.addPacketCopy(1, -1) } + assertFailsWith<IllegalArgumentException> { gen.addPacketCopyFromR0(256) } + assertFailsWith<IllegalArgumentException> { gen.addDataCopyFromR0(256) } + assertFailsWith<IllegalArgumentException> { gen.addPacketCopyFromR0(-1) } + assertFailsWith<IllegalArgumentException> { gen.addDataCopyFromR0(-1) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( + byteArrayOf(1, 'A'.code.toByte(), 0, 0), 256, ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( + byteArrayOf(1, 'a'.code.toByte(), 0, 0), 0x0c, ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( + byteArrayOf(1, '.'.code.toByte(), 0, 0), 0x0c, ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( + byteArrayOf(0, 0), 0xc0, ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( + byteArrayOf(1, 'A'.code.toByte()), 0xc0, ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( + byteArrayOf(64) + ByteArray(64) { 'A'.code.toByte() } + byteArrayOf(0, 0), + 0xc0, ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( + byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte(), 0), + 0xc0, ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( + byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte()), + 0xc0, ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ( + byteArrayOf(1, 'A'.code.toByte(), 0, 0), 256, ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ( + byteArrayOf(1, 'a'.code.toByte(), 0, 0), 0x0c, ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ( + byteArrayOf(1, '.'.code.toByte(), 0, 0), 0x0c, ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ( + byteArrayOf(0, 0), 0xc0, ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ( + byteArrayOf(1, 'A'.code.toByte()), 0xc0, ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ( + byteArrayOf(64) + ByteArray(64) { 'A'.code.toByte() } + byteArrayOf(0, 0), + 0xc0, ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ( + byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte(), 0), + 0xc0, ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ( + byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte()), + 0xc0, ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA( + byteArrayOf(1, 'a'.code.toByte(), 0, 0), ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA( + byteArrayOf(1, '.'.code.toByte(), 0, 0), ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA( + byteArrayOf(0, 0), ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA( + byteArrayOf(1, 'A'.code.toByte()), ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA( + byteArrayOf(64) + ByteArray(64) { 'A'.code.toByte() } + byteArrayOf(0, 0), + ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA( + byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte(), 0), + ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA( + byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte()), + ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA( + byteArrayOf(1, 'a'.code.toByte(), 0, 0), ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA( + byteArrayOf(1, '.'.code.toByte(), 0, 0), ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA( + byteArrayOf(0, 0), ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA( + byteArrayOf(1, 'A'.code.toByte()), ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA( + byteArrayOf(64) + ByteArray(64) { 'A'.code.toByte() } + byteArrayOf(0, 0), + ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA( + byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte(), 0), + ApfGenerator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA( + byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte()), + ApfGenerator.DROP_LABEL) } + } + + @Test + fun testApfInstructionsEncoding() { + var gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION) + gen.addPass() + var program = gen.generate() + // encoding PASS opcode: opcode=0, imm_len=0, R=0 + assertContentEquals( + byteArrayOf(encodeInstruction(opcode = 0, immLength = 0, register = 0)), program) + + gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + gen.addDrop() + program = gen.generate() + // encoding DROP opcode: opcode=0, imm_len=0, R=1 + assertContentEquals( + byteArrayOf(encodeInstruction(opcode = 0, immLength = 0, register = 1)), program) + + gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + gen.addCountAndPass(129) + program = gen.generate() + // encoding COUNT(PASS) opcode: opcode=0, imm_len=size_of(imm), R=0, imm=counterNumber + assertContentEquals( + byteArrayOf(encodeInstruction(opcode = 0, immLength = 1, register = 0), + 0x81.toByte()), program) + + gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + gen.addCountAndDrop(1000) + program = gen.generate() + // encoding COUNT(DROP) opcode: opcode=0, imm_len=size_of(imm), R=1, imm=counterNumber + assertContentEquals( + byteArrayOf(encodeInstruction(opcode = 0, immLength = 2, register = 1), + 0x03, 0xe8.toByte()), program) + + gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + gen.addAllocateR0() + gen.addAllocate(1500) + program = gen.generate() + // encoding ALLOC opcode: opcode=21(EXT opcode number), imm=36(TRANS opcode number). + // R=0 means length stored in R0. R=1 means the length stored in imm1. + assertContentEquals(byteArrayOf( + encodeInstruction(opcode = 21, immLength = 1, register = 0), 36, + encodeInstruction(opcode = 21, immLength = 1, register = 1), 36, 0x05, + 0xDC.toByte()), + program) + // TODO: add back disassembling test check after we update the apf_disassembler + // assertContentEquals(arrayOf(" 0: alloc"), ApfJniUtils.disassembleApf(program)) + + gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + gen.addTransmit() + gen.addDiscard() + program = gen.generate() + // encoding TRANSMIT/DISCARD opcode: opcode=21(EXT opcode number), + // imm=37(TRANSMIT/DISCARD opcode number), + // R=0 means discard the buffer. R=1 means transmit the buffer. + assertContentEquals(byteArrayOf( + encodeInstruction(opcode = 21, immLength = 1, register = 0), 37, + encodeInstruction(opcode = 21, immLength = 1, register = 1), 37, + ), program) + // TODO: add back disassembling test check after we update the apf_disassembler + // assertContentEquals(arrayOf(" 0: trans"), ApfJniUtils.disassembleApf(program)) + + gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + val largeByteArray = ByteArray(256) { 0x01 } + gen.addData(largeByteArray) + program = gen.generate() + // encoding DATA opcode: opcode=14(JMP), R=1 + assertContentEquals(byteArrayOf( + encodeInstruction(opcode = 14, immLength = 2, register = 1), 0x01, 0x00) + + largeByteArray, program) + + gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + gen.addWriteU8(0x01) + gen.addWriteU16(0x0102) + gen.addWriteU32(0x01020304) + gen.addWriteU8(0x00) + gen.addWriteU8(0x80) + gen.addWriteU16(0x0000) + gen.addWriteU16(0x8000) + gen.addWriteU32(0x00000000) + gen.addWriteU32(0x80000000) + program = gen.generate() + assertContentEquals(byteArrayOf( + encodeInstruction(24, 1, 0), 0x01, + encodeInstruction(24, 2, 0), 0x01, 0x02, + encodeInstruction(24, 4, 0), 0x01, 0x02, 0x03, 0x04, + encodeInstruction(24, 1, 0), 0x00, + encodeInstruction(24, 1, 0), 0x80.toByte(), + encodeInstruction(24, 2, 0), 0x00, 0x00, + encodeInstruction(24, 2, 0), 0x80.toByte(), 0x00, + encodeInstruction(24, 4, 0), 0x00, 0x00, 0x00, 0x00, + encodeInstruction(24, 4, 0), 0x80.toByte(), 0x00, 0x00, + 0x00), program) + assertContentEquals(arrayOf( + " 0: write 0x01", + " 2: write 0x0102", + " 5: write 0x01020304", + " 10: write 0x00", + " 12: write 0x80", + " 14: write 0x0000", + " 17: write 0x8000", + " 20: write 0x00000000", + " 25: write 0x80000000"), + ApfJniUtils.disassembleApf(program)) + + gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + gen.addWriteU8(R0) + gen.addWriteU16(R0) + gen.addWriteU32(R0) + gen.addWriteU8(R1) + gen.addWriteU16(R1) + gen.addWriteU32(R1) + program = gen.generate() + assertContentEquals(byteArrayOf( + encodeInstruction(21, 1, 0), 38, + encodeInstruction(21, 1, 0), 39, + encodeInstruction(21, 1, 0), 40, + encodeInstruction(21, 1, 1), 38, + encodeInstruction(21, 1, 1), 39, + encodeInstruction(21, 1, 1), 40 + ), program) + // TODO: add back disassembling test check after we update the apf_disassembler +// assertContentEquals(arrayOf( +// " 0: ewrite1 r0", +// " 2: ewrite2 r0", +// " 4: ewrite4 r0", +// " 6: ewrite1 r1", +// " 8: ewrite2 r1", +// " 10: ewrite4 r1"), ApfJniUtils.disassembleApf(program)) + + gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + gen.addDataCopy(0, 10) + gen.addDataCopy(1, 5) + gen.addPacketCopy(1000, 255) + program = gen.generate() + assertContentEquals(byteArrayOf( + encodeInstruction(25, 0, 1), 10, + encodeInstruction(25, 1, 1), 1, 5, + encodeInstruction(25, 2, 0), + 0x03.toByte(), 0xe8.toByte(), 0xff.toByte(), + ), program) + // TODO: add back disassembling test check after we update the apf_disassembler +// assertContentEquals(arrayOf( +// " 0: dcopy 0, 5", +// " 3: pcopy 1000, 255"), ApfJniUtils.disassembleApf(program)) + + gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + gen.addPacketCopyFromR0LenR1() + gen.addPacketCopyFromR0(5) + gen.addDataCopyFromR0LenR1() + gen.addDataCopyFromR0(5) + program = gen.generate() + assertContentEquals(byteArrayOf( + encodeInstruction(21, 1, 1), 41, + encodeInstruction(21, 1, 0), 41, 5, + encodeInstruction(21, 1, 1), 42, + encodeInstruction(21, 1, 0), 42, 5, + ), program) + // TODO: add back the following test case when implementing EPKTCOPY, EDATACOPY opcodes. +// assertContentEquals(arrayOf( +// " 0: dcopy [r1+0], 5", +// " 4: pcopy [r0+1000], 255"), ApfJniUtils.disassembleApf(program)) + + gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + gen.addJumpIfBytesAtR0Equal(byteArrayOf('a'.code.toByte()), ApfGenerator.DROP_LABEL) + program = gen.generate() + assertContentEquals( + byteArrayOf(encodeInstruction(opcode = 20, immLength = 1, register = 1), + 1, 1, 'a'.code.toByte()), program) + + val qnames = byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte(), 0, 0) + gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + gen.addJumpIfPktAtR0DoesNotContainDnsQ(qnames, 0x0c, ApfGenerator.DROP_LABEL) + gen.addJumpIfPktAtR0ContainDnsQ(qnames, 0x0c, ApfGenerator.DROP_LABEL) + program = gen.generate() + assertContentEquals(byteArrayOf( + encodeInstruction(21, 1, 0), 43, 11, 0x0c.toByte(), + ) + qnames + byteArrayOf( + encodeInstruction(21, 1, 1), 43, 1, 0x0c.toByte(), + ) + qnames, program) + + gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + gen.addJumpIfPktAtR0DoesNotContainDnsA(qnames, ApfGenerator.DROP_LABEL) + gen.addJumpIfPktAtR0ContainDnsA(qnames, ApfGenerator.DROP_LABEL) + program = gen.generate() + assertContentEquals(byteArrayOf( + encodeInstruction(21, 1, 0), 44, 10, + ) + qnames + byteArrayOf( + encodeInstruction(21, 1, 1), 44, 1, + ) + qnames, program) + } + + private fun encodeInstruction(opcode: Int, immLength: Int, register: Int): Byte { + val immLengthEncoding = if (immLength == 4) 3 else immLength + return opcode.shl(3).or(immLengthEncoding.shl(1)).or(register).toByte() + } +}
diff --git a/tests/unit/src/android/net/apf/TestAndroidPacketFilter.java b/tests/unit/src/android/net/apf/TestAndroidPacketFilter.java new file mode 100644 index 0000000..39386cd --- /dev/null +++ b/tests/unit/src/android/net/apf/TestAndroidPacketFilter.java
@@ -0,0 +1,35 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.net.apf; + +import android.system.ErrnoException; + +import java.io.IOException; + +/** + * The interface for TestAndroidPacketFilter + */ +public interface TestAndroidPacketFilter extends AndroidPacketFilter { + /** + * Pretend an RA packet has been received and show it to ApfFilter. + */ + void pretendPacketReceived(byte[] packet) throws IOException, ErrnoException; + + /** + * Generate and install a new filter program. + */ + void installNewProgramLocked(); +}
diff --git a/tests/unit/src/android/net/dhcp/DhcpPacketTest.java b/tests/unit/src/android/net/dhcp/DhcpPacketTest.java index 1a1f6c3..42ea54b 100644 --- a/tests/unit/src/android/net/dhcp/DhcpPacketTest.java +++ b/tests/unit/src/android/net/dhcp/DhcpPacketTest.java
@@ -1300,6 +1300,44 @@ checkBuildOfferPacket(3600, null); } + @Test + public void testInvalidLengthIpv6OnlyPreferredOption() throws Exception { + // CHECKSTYLE:OFF Generated code + final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( + // IP header. + "45100158000040004011B5CEC0A80164C0A80102" + + // UDP header + "004300440144CE63" + + // BOOTP header + "02010600B8BF41E60000000000000000C0A80102C0A8016400000000" + + // MAC address. + "22B3614EE01200000000000000000000" + + // Server name and padding. + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + // File. + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + // Options + "638253633501023604C0A80164330400000E103A04000007083B0400000C4E01" + + "04FFFFFF001C04C0A801FF0304C0A801640604C0A801640C0C74657374686F73" + + "746E616D651A0205DC" + + // Option 108 (0x6c, IPv6-Only preferred option), length 8 (0x08) + "6C080102030405060708" + + // End of options. + "FF")); + // CHECKSTYLE:ON Generated code + + final DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3, + TEST_EMPTY_OPTIONS_SKIP_LIST); + // rfc8925#section-3.1: The client MUST ignore the IPv6-Only Preferred option if the length + // field value is not 4. + assertTrue(offerPacket instanceof DhcpOfferPacket); + assertEquals(offerPacket.mIpv6OnlyWaitTime, null); + } + private static byte[] intToByteArray(int val) { return ByteBuffer.allocate(4).putInt(val).array(); }
diff --git a/tests/unit/src/android/net/dhcp/DhcpResultsParcelableUtilTest.java b/tests/unit/src/android/net/dhcp/DhcpResultsParcelableUtilTest.java index 5e160ad..2d0916f 100644 --- a/tests/unit/src/android/net/dhcp/DhcpResultsParcelableUtilTest.java +++ b/tests/unit/src/android/net/dhcp/DhcpResultsParcelableUtilTest.java
@@ -17,8 +17,8 @@ package android.net.dhcp; import static android.net.InetAddresses.parseNumericAddress; -import static android.net.dhcp.DhcpResultsParcelableUtil.fromStableParcelable; import static android.net.dhcp.DhcpResultsParcelableUtil.toStableParcelable; +import static android.net.shared.IpConfigurationParcelableUtil.unparcelAddress; import static com.android.testutils.MiscAsserts.assertFieldCountEquals; @@ -26,9 +26,12 @@ import static org.junit.Assert.assertTrue; import android.net.DhcpResults; +import android.net.DhcpResultsParcelable; import android.net.LinkAddress; import android.net.shared.IpConfigurationParcelableUtil; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -37,6 +40,7 @@ import org.junit.runner.RunWith; import java.net.Inet4Address; +import java.util.Arrays; /** * Tests for {@link IpConfigurationParcelableUtil}. @@ -60,68 +64,102 @@ mDhcpResults.serverHostName = "dhcp.example.com"; mDhcpResults.mtu = 1450; mDhcpResults.captivePortalApiUrl = "https://example.com/testapi"; + mDhcpResults.dmnsrchList.addAll(Arrays.asList("google.com", "example.com")); // Any added DhcpResults field must be included in equals() to be tested properly - assertFieldCountEquals(10, DhcpResults.class); + assertFieldCountEquals(11, DhcpResults.class); } @Test - public void testParcelUnparcelDhcpResults() { - doDhcpResultsParcelUnparcelTest(); + public void testParcelDhcpResults() { + doDhcpResultsParcelTest(); } @Test public void testParcelUnparcelDhcpResults_NullIpAddress() { mDhcpResults.ipAddress = null; - doDhcpResultsParcelUnparcelTest(); + doDhcpResultsParcelTest(); } @Test public void testParcelUnparcelDhcpResults_NullGateway() { mDhcpResults.gateway = null; - doDhcpResultsParcelUnparcelTest(); + doDhcpResultsParcelTest(); } @Test public void testParcelUnparcelDhcpResults_NullDomains() { mDhcpResults.domains = null; - doDhcpResultsParcelUnparcelTest(); + doDhcpResultsParcelTest(); } @Test public void testParcelUnparcelDhcpResults_EmptyDomains() { mDhcpResults.domains = ""; - doDhcpResultsParcelUnparcelTest(); + doDhcpResultsParcelTest(); } @Test public void testParcelUnparcelDhcpResults_NullServerAddress() { mDhcpResults.serverAddress = null; - doDhcpResultsParcelUnparcelTest(); + doDhcpResultsParcelTest(); } @Test public void testParcelUnparcelDhcpResults_NullVendorInfo() { mDhcpResults.vendorInfo = null; - doDhcpResultsParcelUnparcelTest(); + doDhcpResultsParcelTest(); } @Test public void testParcelUnparcelDhcpResults_NullServerHostName() { mDhcpResults.serverHostName = null; - doDhcpResultsParcelUnparcelTest(); + doDhcpResultsParcelTest(); } @Test public void testParcelUnparcelDhcpResults_NullCaptivePortalApiUrl() { mDhcpResults.captivePortalApiUrl = null; - doDhcpResultsParcelUnparcelTest(); + doDhcpResultsParcelTest(); } - private void doDhcpResultsParcelUnparcelTest() { + private void doDhcpResultsParcelTest() { final DhcpResults unparceled = fromStableParcelable(toStableParcelable(mDhcpResults)); + setFieldsLostWhileParceling(unparceled); assertEquals(mDhcpResults, unparceled); } + private void setFieldsLostWhileParceling(@NonNull DhcpResults unparceledResults) { + // TODO: add other fields that are not part of DhcpResultsParcelable here + // e.g. if the dmnsrchList field is added, + unparceledResults.dmnsrchList.clear(); + unparceledResults.dnsServers.clear(); + unparceledResults.dmnsrchList.addAll(mDhcpResults.dmnsrchList); + unparceledResults.domains = mDhcpResults.domains; + unparceledResults.dnsServers.addAll(mDhcpResults.dnsServers); + unparceledResults.gateway = mDhcpResults.gateway; + unparceledResults.ipAddress = mDhcpResults.ipAddress; + } + + /** + * Convert a DhcpResultsParcelable to DhcpResults. + */ + private static DhcpResults fromStableParcelable(@Nullable DhcpResultsParcelable p) { + if (p == null) return null; + final DhcpResults results = new DhcpResults(p.baseConfiguration); + results.leaseDuration = p.leaseDuration; + results.mtu = p.mtu; + results.serverAddress = (Inet4Address) unparcelAddress(p.serverAddress); + results.vendorInfo = p.vendorInfo; + results.serverHostName = p.serverHostName; + results.captivePortalApiUrl = p.captivePortalApiUrl; + // DhcpResultsParcelable is only used to fill the legacy DhcpInfo class in Wifi, so it + // should not be extended with any new field. Some fields maybe part of DhcpResults, but + // not DhcpResultsParcelable, as DhcpResults is used internally in NetworkStack, but + // DhcpResultsParcelable is used to provide info to wifi (for building DhcpInfo) + + return results; + } + @Test public void testToString() { final String str = toStableParcelable(mDhcpResults).toString();
diff --git a/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt b/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt index 2d093f7..32cf464 100644 --- a/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt +++ b/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt
@@ -20,7 +20,7 @@ import androidx.test.runner.AndroidJUnit4 import com.android.net.module.util.HexDump import com.android.testutils.assertThrows -import java.nio.ByteBuffer +import kotlin.test.assertEquals import kotlin.test.assertTrue import org.junit.Test import org.junit.runner.RunWith @@ -29,6 +29,21 @@ @SmallTest class Dhcp6PacketTest { @Test + fun testDecodeDhcp6PacketWithoutIaPdOption() { + val solicitHex = + // Solicit, Transaction ID + "01000F51" + + // client identifier option(option_len=12) + "0001000C0003001B024CCBFFFE5F6EA9" + + // elapsed time option(option_len=2) + "000800020000" + val bytes = HexDump.hexStringToByteArray(solicitHex) + assertThrows(Dhcp6Packet.ParseException::class.java) { + Dhcp6Packet.decode(bytes, bytes.size) + } + } + + @Test fun testDecodeDhcp6SolicitPacket() { val solicitHex = // Solicit, Transaction ID @@ -42,7 +57,7 @@ // IA prefix option(option_len=25) "001A001900000000000000004000000000000000000000000000000000" val bytes = HexDump.hexStringToByteArray(solicitHex) - val packet = Dhcp6Packet.decodePacket(ByteBuffer.wrap(bytes)) + val packet = Dhcp6Packet.decode(bytes, bytes.size) assertTrue(packet is Dhcp6SolicitPacket) } @@ -61,7 +76,7 @@ "001A001900000000000000004000000000000000000000000000000000" val bytes = HexDump.hexStringToByteArray(solicitHex) assertThrows(Dhcp6Packet.ParseException::class.java) { - Dhcp6Packet.decodePacket(ByteBuffer.wrap(bytes)) + Dhcp6Packet.decode(bytes, bytes.size) } } @@ -80,7 +95,7 @@ "001A0019000000000000000040000000000000000000000000000000" val bytes = HexDump.hexStringToByteArray(solicitHex) assertThrows(Dhcp6Packet.ParseException::class.java) { - Dhcp6Packet.decodePacket(ByteBuffer.wrap(bytes)) + Dhcp6Packet.decode(bytes, bytes.size) } } @@ -99,7 +114,7 @@ "001A001900000000000000004000000000000000000000000000000000" val bytes = HexDump.hexStringToByteArray(solicitHex) assertThrows(Dhcp6Packet.ParseException::class.java) { - Dhcp6Packet.decodePacket(ByteBuffer.wrap(bytes)) + Dhcp6Packet.decode(bytes, bytes.size) } } @@ -119,7 +134,7 @@ // IA prefix option(option_len=25, prefix="fdfd:9ed6:7950:2::/64") "001A00190000019F0000A8C040FDFD9ED6795000010000000000000000" val bytes = HexDump.hexStringToByteArray(advertiseHex) - val packet = Dhcp6Packet.decodePacket(ByteBuffer.wrap(bytes)) + val packet = Dhcp6Packet.decode(bytes, bytes.size) assertTrue(packet is Dhcp6AdvertisePacket) } @@ -142,7 +157,231 @@ "001A00190000019F0000A8C040FDFD9ED6795000010000000000000000" val bytes = HexDump.hexStringToByteArray(advertiseHex) // The unsupported option will be skipped normally and won't throw ParseException. - val packet = Dhcp6Packet.decodePacket(ByteBuffer.wrap(bytes)) + val packet = Dhcp6Packet.decode(bytes, bytes.size) assertTrue(packet is Dhcp6AdvertisePacket) } + + @Test + fun testDecodeDhcp6ReplyPacket() { + val replyHex = + // Reply, Transaction ID + "07000A47" + + // server identifier option(option_len=10) + "0002000A0003000186C9B26AED4D" + + // client identifier option(option_len=12) + "0001000C0003001B02FBBAFFFEB7BC71" + + // SOL_MAX_RT (don't support this option yet) + "005200040000003c" + + // Rapid Commit + "000e0000" + + // DNS recursive server (don't support this opton yet) + "00170010fdfd9ed6795000000000000000000001" + + // IA_PD option(option_len=70, including IA prefix option) + "0019004629cc56c7000000d300000152" + + // IA prefix option(option_len=25, prefix="2401:fa00:49c:412::/64", preferred=400, + // valid=1623) + "001a00190000019000000657402401fa00049c04120000000000000000" + + // IA prefix option(option_len=25, prefix="fdfd:9ed6:7950:2::/64", preferred=423, + // valid=43200) + "001a0019000001a70000a8c040fdfd9ed6795000020000000000000000" + val bytes = HexDump.hexStringToByteArray(replyHex) + val packet = Dhcp6Packet.decode(bytes, bytes.size) + assertTrue(packet is Dhcp6ReplyPacket) + assertEquals(400, packet.prefixDelegation.minimalPreferredLifetime) + assertEquals(1623, packet.prefixDelegation.minimalValidLifetime) + } + + @Test + fun testGetMinimalPreferredValidLifetime() { + val replyHex = + // Reply, Transaction ID + "07000A47" + + // server identifier option(option_len=10) + "0002000A0003000186C9B26AED4D" + + // client identifier option(option_len=12) + "0001000C0003001B02FBBAFFFEB7BC71" + + // SOL_MAX_RT (don't support this option yet) + "005200040000003c" + + // Rapid Commit + "000e0000" + + // DNS recursive server (don't support this opton yet) + "00170010fdfd9ed6795000000000000000000001" + + // IA_PD option(option_len=70, including IA prefix option) + "0019004629cc56c7000000d300000152" + + // IA prefix option(option_len=25, prefix="2401:fa00:49c:412::/64", preferred=0, + // valid=0) + "001a00190000000000000000402401fa00049c04120000000000000000" + + // IA prefix option(option_len=25, prefix="fdfd:9ed6:7950:2::/64", preferred=423, + // valid=43200) + "001a0019000001a70000a8c040fdfd9ed6795000020000000000000000" + val bytes = HexDump.hexStringToByteArray(replyHex) + val packet = Dhcp6Packet.decode(bytes, bytes.size) + assertTrue(packet is Dhcp6ReplyPacket) + assertEquals(423, packet.prefixDelegation.minimalPreferredLifetime) + assertEquals(43200, packet.prefixDelegation.minimalValidLifetime) + } + + @Test + fun testStatusCodeOptionWithStatusMessage() { + val replyHex = + // Reply, Transaction ID + "07000A47" + + // server identifier option(option_len=10) + "0002000A0003000186C9B26AED4D" + + // client identifier option(option_len=12) + "0001000C0003001B02FBBAFFFEB7BC71" + + // SOL_MAX_RT (don't support this option yet) + "005200040000003c" + + // Rapid Commit + "000e0000" + + // DNS recursive server (don't support this opton yet) + "00170010fdfd9ed6795000000000000000000001" + + // IA_PD option (t1=t2=0, empty prefix) + "0019000c000000000000000000000000" + + // Status code option: status code=NoPrefixAvail + "000d00150006" + + // Status code option: status message="no prefix available" + "6e6f2070726566697820617661696c61626c65" + val bytes = HexDump.hexStringToByteArray(replyHex) + val packet = Dhcp6Packet.decode(bytes, bytes.size) + assertTrue(packet is Dhcp6ReplyPacket) + assertEquals(0, packet.mPrefixDelegation.iaid) + assertEquals(0, packet.mPrefixDelegation.t1) + assertEquals(0, packet.mPrefixDelegation.t2) + assertEquals(Dhcp6Packet.STATUS_NO_PREFIX_AVAIL, packet.mStatusCode) + } + + @Test + fun testStatusCodeOptionWithoutStatusMessage() { + val replyHex = + // Reply, Transaction ID + "07000A47" + + // server identifier option(option_len=10) + "0002000A0003000186C9B26AED4D" + + // client identifier option(option_len=12) + "0001000C0003001B02FBBAFFFEB7BC71" + + // SOL_MAX_RT (don't support this option yet) + "005200040000003c" + + // Rapid Commit + "000e0000" + + // DNS recursive server (don't support this opton yet) + "00170010fdfd9ed6795000000000000000000001" + + // IA_PD option (t1=t2=0, empty prefix) + "0019000c000000000000000000000000" + + // Status code option: status code=NoPrefixAvail + "000d00020006" + val bytes = HexDump.hexStringToByteArray(replyHex) + val packet = Dhcp6Packet.decode(bytes, bytes.size) + assertTrue(packet is Dhcp6ReplyPacket) + assertEquals(0, packet.mPrefixDelegation.iaid) + assertEquals(0, packet.mPrefixDelegation.t1) + assertEquals(0, packet.mPrefixDelegation.t2) + assertEquals(Dhcp6Packet.STATUS_NO_PREFIX_AVAIL, packet.mStatusCode) + } + + @Test + fun testStatusCodeOptionInIaPdWithStatusMessage() { + val replyHex = + // Reply, Transaction ID + "07000A47" + + // server identifier option(option_len=10) + "0002000A0003000186C9B26AED4D" + + // client identifier option(option_len=12) + "0001000C0003001B02FBBAFFFEB7BC71" + + // SOL_MAX_RT (don't support this option yet) + "005200040000003c" + + // Rapid Commit + "000e0000" + + // DNS recursive server (don't support this opton yet) + "00170010fdfd9ed6795000000000000000000001" + + // IA_PD option (t1=t2=0, status code=NoPrefixAvail, + // status message="no prefix available") + "00190025000000000000000000000000000d00150006" + + "6e6f2070726566697820617661696c61626c65" + val bytes = HexDump.hexStringToByteArray(replyHex) + val packet = Dhcp6Packet.decode(bytes, bytes.size) + assertTrue(packet is Dhcp6ReplyPacket) + assertEquals(0, packet.mPrefixDelegation.iaid) + assertEquals(0, packet.mPrefixDelegation.t1) + assertEquals(0, packet.mPrefixDelegation.t2) + assertEquals(Dhcp6Packet.STATUS_NO_PREFIX_AVAIL, packet.mPrefixDelegation.statusCode) + } + + @Test + fun testStatusCodeOptionInIaPdWithoutStatusMessage() { + val replyHex = + // Reply, Transaction ID + "07000A47" + + // server identifier option(option_len=10) + "0002000A0003000186C9B26AED4D" + + // client identifier option(option_len=12) + "0001000C0003001B02FBBAFFFEB7BC71" + + // SOL_MAX_RT (don't support this option yet) + "005200040000003c" + + // Rapid Commit + "000e0000" + + // DNS recursive server (don't support this opton yet) + "00170010fdfd9ed6795000000000000000000001" + + // IA_PD option (t1=t2=0, status code=NoPrefixAvail) + "00190012000000000000000000000000000d00020006" + val bytes = HexDump.hexStringToByteArray(replyHex) + val packet = Dhcp6Packet.decode(bytes, bytes.size) + assertTrue(packet is Dhcp6ReplyPacket) + assertEquals(0, packet.mPrefixDelegation.iaid) + assertEquals(0, packet.mPrefixDelegation.t1) + assertEquals(0, packet.mPrefixDelegation.t2) + assertEquals(Dhcp6Packet.STATUS_NO_PREFIX_AVAIL, packet.mPrefixDelegation.statusCode) + } + + @Test + fun testStatusCodeOptionWithTruncatedStatusMessage() { + val replyHex = + // Reply, Transaction ID + "07000A47" + + // server identifier option(option_len=10) + "0002000A0003000186C9B26AED4D" + + // client identifier option(option_len=12) + "0001000C0003001B02FBBAFFFEB7BC71" + + // SOL_MAX_RT (don't support this option yet) + "005200040000003c" + + // Rapid Commit + "000e0000" + + // DNS recursive server (don't support this opton yet) + "00170010fdfd9ed6795000000000000000000001" + + // Status code option: len=21, status code=NoPrefixAvail + "000d00150006" + + // Status code option: truncated status message="no prefix available" + "6e6f2070726566697820617661696c6162" + val bytes = HexDump.hexStringToByteArray(replyHex) + assertThrows(Dhcp6Packet.ParseException::class.java) { + Dhcp6Packet.decode(bytes, bytes.size) + } + } + + @Test + fun testStatusCodeOptionInIaPdWithTruncatedStatusMessage() { + val replyHex = + // Reply, Transaction ID + "07000A47" + + // server identifier option(option_len=10) + "0002000A0003000186C9B26AED4D" + + // client identifier option(option_len=12) + "0001000C0003001B02FBBAFFFEB7BC71" + + // SOL_MAX_RT (don't support this option yet) + "005200040000003c" + + // Rapid Commit + "000e0000" + + // DNS recursive server (don't support this opton yet) + "00170010fdfd9ed6795000000000000000000001" + + // IA_PD option (t1=t2=0, empty prefix) + "00190025000000000000000000000000" + + // Status code option: len=21, status code=NoPrefixAvail + "000d00150006" + + // truncated status message="no prefix available") + "6e6f2070726566697820617661696c6162" + val bytes = HexDump.hexStringToByteArray(replyHex) + assertThrows(Dhcp6Packet.ParseException::class.java) { + Dhcp6Packet.decode(bytes, bytes.size) + } + } }
diff --git a/tests/unit/src/android/net/ip/IpClientTest.java b/tests/unit/src/android/net/ip/IpClientTest.java index 4ed8081..1849776 100644 --- a/tests/unit/src/android/net/ip/IpClientTest.java +++ b/tests/unit/src/android/net/ip/IpClientTest.java
@@ -18,8 +18,6 @@ import static android.system.OsConstants.RT_SCOPE_UNIVERSE; -import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_PARSE_NETLINK_EVENTS_VERSION; - import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -27,8 +25,8 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; @@ -89,6 +87,8 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; @@ -150,6 +150,8 @@ @Mock private IpMemoryStoreService mIpMemoryStoreService; @Mock private InterfaceParams mInterfaceParams; @Mock private IpConnectivityLog mMetricsLog; + @Mock private FileDescriptor mFd; + @Mock private PrintWriter mWriter; private NetworkObserver mObserver; private InterfaceParams mIfParams; @@ -170,8 +172,6 @@ when(mDependencies.getIpMemoryStore(mContext, mNetworkStackServiceManager)) .thenReturn(mIpMemoryStore); when(mDependencies.getIpConnectivityLog()).thenReturn(mMetricsLog); - when(mDependencies.isFeatureEnabled(eq(mContext), - eq(IPCLIENT_PARSE_NETLINK_EVENTS_VERSION), anyBoolean())).thenReturn(false); mIfParams = null; } @@ -427,6 +427,8 @@ verifyNetworkAttributesStored(l2Key, new NetworkAttributes.Builder() .setCluster(cluster) .build()); + + verifyShutdown(ipc); } private void verifyShutdown(IpClient ipc) throws Exception { @@ -486,6 +488,8 @@ fail(testcase.errorMessage()); } } + + ipc.shutdown(); } static class IsProvisionedTestCase { @@ -703,7 +707,7 @@ final ArgumentCaptor<ApfConfiguration> configCaptor = ArgumentCaptor.forClass( ApfConfiguration.class); verify(mDependencies, timeout(TEST_TIMEOUT_MS)).maybeCreateApfFilter( - any(), configCaptor.capture(), any(), any()); + any(), configCaptor.capture(), any(), any(), any(), anyBoolean()); return configCaptor.getValue(); } @@ -772,7 +776,7 @@ final ArgumentCaptor<ApfConfiguration> configCaptor = ArgumentCaptor.forClass( ApfConfiguration.class); verify(mDependencies, timeout(TEST_TIMEOUT_MS)).maybeCreateApfFilter( - any(), configCaptor.capture(), any(), any()); + any(), configCaptor.capture(), any(), any(), any(), anyBoolean()); final ApfConfiguration actual = configCaptor.getValue(); assertNotNull(actual); assertEquals(4, actual.apfCapabilities.apfVersionSupported); @@ -783,6 +787,17 @@ } @Test + public void testDumpApfFilter_withNoException() throws Exception { + final IpClient ipc = makeIpClient(TEST_IFNAME); + final ApfConfiguration config = verifyApfFilterCreatedOnStart(ipc, + false /* isApfSupported */); + assertNull(config.apfCapabilities); + clearInvocations(mDependencies); + ipc.dump(mFd, mWriter, null /* args */); + verifyShutdown(ipc); + } + + @Test public void testApfUpdateCapabilities_nonNullInitialApfCapabilities() throws Exception { final IpClient ipc = makeIpClient(TEST_IFNAME); final ApfConfiguration config = verifyApfFilterCreatedOnStart(ipc, @@ -794,7 +809,8 @@ 8192 /* maxProgramSize */, 4 /* format */); ipc.updateApfCapabilities(newApfCapabilities); HandlerUtils.waitForIdle(ipc.getHandler(), TEST_TIMEOUT_MS); - verify(mDependencies, never()).maybeCreateApfFilter(any(), any(), any(), any()); + verify(mDependencies, never()).maybeCreateApfFilter(any(), any(), any(), any(), any(), + anyBoolean()); verifyShutdown(ipc); } @@ -808,7 +824,8 @@ ipc.updateApfCapabilities(null /* apfCapabilities */); HandlerUtils.waitForIdle(ipc.getHandler(), TEST_TIMEOUT_MS); - verify(mDependencies, never()).maybeCreateApfFilter(any(), any(), any(), any()); + verify(mDependencies, never()).maybeCreateApfFilter(any(), any(), any(), any(), any(), + anyBoolean()); verifyShutdown(ipc); } @@ -834,6 +851,7 @@ final MacAddress bssid = ipc.getInitialBssid(layer2Info, scanResultInfo, true /* isAtLeastS */); assertEquals(bssid, MacAddress.fromString(TEST_BSSID)); + ipc.shutdown(); } @Test @@ -844,6 +862,7 @@ final MacAddress bssid = ipc.getInitialBssid(layer2Info, null /* ScanResultInfo */, true /* isAtLeastS */); assertEquals(bssid, MacAddress.fromString(TEST_BSSID)); + ipc.shutdown(); } @Test @@ -855,6 +874,7 @@ final MacAddress bssid = ipc.getInitialBssid(layer2Info, scanResultInfo, true /* isAtLeastS */); assertNull(bssid); + ipc.shutdown(); } @Test @@ -864,6 +884,7 @@ final MacAddress bssid = ipc.getInitialBssid(null /* layer2Info */, scanResultInfo, true /* isAtLeastS */); assertNull(bssid); + ipc.shutdown(); } @Test @@ -875,6 +896,7 @@ final MacAddress bssid = ipc.getInitialBssid(layer2Info, scanResultInfo, false /* isAtLeastS */); assertEquals(bssid, MacAddress.fromString(TEST_BSSID)); + ipc.shutdown(); } @Test @@ -884,6 +906,7 @@ final MacAddress bssid = ipc.getInitialBssid(null /* layer2Info */, scanResultInfo, false /* isAtLeastS */); assertEquals(bssid, MacAddress.fromString(TEST_BSSID)); + ipc.shutdown(); } @Test @@ -893,6 +916,7 @@ final MacAddress bssid = ipc.getInitialBssid(null /* layer2Info */, scanResultInfo, false /* isAtLeastS */); assertNull(bssid); + ipc.shutdown(); } @Test @@ -904,6 +928,7 @@ final MacAddress bssid = ipc.getInitialBssid(layer2Info, scanResultInfo, false /* isAtLeastS */); assertEquals(bssid, MacAddress.fromString(TEST_BSSID)); + ipc.shutdown(); } @Test @@ -914,6 +939,7 @@ final MacAddress bssid = ipc.getInitialBssid(layer2Info, null /* scanResultInfo */, false /* isAtLeastS */); assertEquals(bssid, MacAddress.fromString(TEST_BSSID)); + ipc.shutdown(); } @Test @@ -922,6 +948,7 @@ final MacAddress bssid = ipc.getInitialBssid(null /* layer2Info */, null /* scanResultInfo */, false /* isAtLeastS */); assertNull(bssid); + ipc.shutdown(); } interface Fn<A,B> {
diff --git a/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt b/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt index 3800752..4d57df5 100644 --- a/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt +++ b/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt
@@ -24,7 +24,6 @@ import android.net.LinkProperties import android.net.RouteInfo import android.net.metrics.IpConnectivityLog -import com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_MCAST_RESOLICIT_VERSION import android.os.Handler import android.os.HandlerThread import android.os.MessageQueue @@ -36,12 +35,12 @@ import android.stats.connectivity.NudEventType.NUD_CONFIRM_FAILED import android.stats.connectivity.NudEventType.NUD_CONFIRM_FAILED_CRITICAL import android.stats.connectivity.NudEventType.NUD_CONFIRM_MAC_ADDRESS_CHANGED -import android.stats.connectivity.NudEventType.NUD_POST_ROAMING_FAILED -import android.stats.connectivity.NudEventType.NUD_POST_ROAMING_FAILED_CRITICAL -import android.stats.connectivity.NudEventType.NUD_POST_ROAMING_MAC_ADDRESS_CHANGED import android.stats.connectivity.NudEventType.NUD_ORGANIC_FAILED import android.stats.connectivity.NudEventType.NUD_ORGANIC_FAILED_CRITICAL import android.stats.connectivity.NudEventType.NUD_ORGANIC_MAC_ADDRESS_CHANGED +import android.stats.connectivity.NudEventType.NUD_POST_ROAMING_FAILED +import android.stats.connectivity.NudEventType.NUD_POST_ROAMING_FAILED_CRITICAL +import android.stats.connectivity.NudEventType.NUD_POST_ROAMING_MAC_ADDRESS_CHANGED import android.stats.connectivity.NudNeighborType import android.stats.connectivity.NudNeighborType.NUD_NEIGHBOR_BOTH import android.stats.connectivity.NudNeighborType.NUD_NEIGHBOR_DNS @@ -50,32 +49,15 @@ import android.system.OsConstants.EAGAIN import androidx.test.filters.SmallTest import androidx.test.runner.AndroidJUnit4 -import com.android.networkstack.metrics.IpReachabilityMonitorMetrics import com.android.net.module.util.InterfaceParams import com.android.net.module.util.SharedLog import com.android.net.module.util.ip.IpNeighborMonitor import com.android.net.module.util.netlink.StructNdMsg.NUD_FAILED import com.android.net.module.util.netlink.StructNdMsg.NUD_REACHABLE import com.android.net.module.util.netlink.StructNdMsg.NUD_STALE +import com.android.networkstack.metrics.IpReachabilityMonitorMetrics import com.android.testutils.makeNewNeighMessage import com.android.testutils.waitForIdle -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentCaptor -import org.mockito.ArgumentMatchers.any -import org.mockito.ArgumentMatchers.anyBoolean -import org.mockito.ArgumentMatchers.anyInt -import org.mockito.ArgumentMatchers.anyObject -import org.mockito.ArgumentMatchers.anyString -import org.mockito.ArgumentMatchers.eq -import org.mockito.Mockito.doAnswer -import org.mockito.Mockito.doReturn -import org.mockito.Mockito.mock -import org.mockito.Mockito.never -import org.mockito.Mockito.timeout -import org.mockito.Mockito.verify import java.io.FileDescriptor import java.net.Inet4Address import java.net.Inet6Address @@ -86,6 +68,21 @@ import kotlin.test.assertFalse import kotlin.test.assertTrue import kotlin.test.fail +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyString +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mockito.doAnswer +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.timeout +import org.mockito.Mockito.verify private const val TEST_TIMEOUT_MS = 10_000L @@ -261,8 +258,6 @@ }.`when`(dependencies).makeIpNeighborMonitor(any(), any(), any()) doReturn(mIpReachabilityMonitorMetrics) .`when`(dependencies).getIpReachabilityMonitorMetrics() - doReturn(true).`when`(dependencies).isFeatureEnabled(anyObject(), - eq(IP_REACHABILITY_MCAST_RESOLICIT_VERSION), anyBoolean()) val monitorFuture = CompletableFuture<IpReachabilityMonitor>() // IpReachabilityMonitor needs to be started from the handler thread
diff --git a/tests/unit/src/android/net/shared/PrivateDnsConfigTest.java b/tests/unit/src/android/net/shared/PrivateDnsConfigTest.java new file mode 100644 index 0000000..94f04d5 --- /dev/null +++ b/tests/unit/src/android/net/shared/PrivateDnsConfigTest.java
@@ -0,0 +1,133 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.shared; + +import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OFF; +import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; +import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import android.net.PrivateDnsConfigParcel; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.net.InetAddress; + +@RunWith(JUnit4.class) +public final class PrivateDnsConfigTest { + private static final int OFF_MODE = PRIVATE_DNS_MODE_OFF; + private static final int OPPORTUNISTIC_MODE = PRIVATE_DNS_MODE_OPPORTUNISTIC; + private static final int STRICT_MODE = PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; + + private static final InetAddress[] TEST_ADDRS = new InetAddress[] { + InetAddress.parseNumericAddress("1.2.3.4"), + InetAddress.parseNumericAddress("2001:db8::2"), + }; + + private String[] toStringArray(InetAddress[] ips) { + String[] out = new String[ips.length]; + int i = 0; + for (InetAddress ip : ips) { + out[i++] = ip.getHostAddress(); + } + return out; + } + + private void assertPrivateDnsConfigEquals(PrivateDnsConfig a, PrivateDnsConfig b) { + assertEquals(a.mode, b.mode); + assertEquals(a.hostname, b.hostname); + assertArrayEquals(a.ips, b.ips); + assertEquals(a.dohName, b.dohName); + assertArrayEquals(a.dohIps, b.dohIps); + assertEquals(a.dohPath, b.dohPath); + assertEquals(a.dohPort, b.dohPort); + } + + private void assertParcelEquals(PrivateDnsConfig cfg, PrivateDnsConfigParcel parcel) { + assertEquals(parcel.privateDnsMode, cfg.mode); + assertEquals(parcel.hostname, cfg.hostname); + assertArrayEquals(parcel.ips, toStringArray(cfg.ips)); + assertEquals(parcel.dohName, cfg.dohName); + assertEquals(parcel.dohPath, cfg.dohPath); + assertEquals(parcel.dohPort, cfg.dohPort); + assertArrayEquals(parcel.dohIps, toStringArray(cfg.dohIps)); + } + + // Tests both toParcel() and fromParcel() together. + private void testPrivateDnsConfigConversion(PrivateDnsConfig cfg) { + final PrivateDnsConfigParcel parcel = cfg.toParcel(); + assertParcelEquals(cfg, parcel); + + final PrivateDnsConfig convertedCfg = PrivateDnsConfig.fromParcel(parcel); + assertPrivateDnsConfigEquals(cfg, convertedCfg); + } + + // Tests that a PrivateDnsConfig and a PrivateDnsConfig that is converted from + // PrivateDnsConfigParcel are equal. + @Test + public void testParcelableConversion() { + // Test the constructor: PrivateDnsConfig() + testPrivateDnsConfigConversion(new PrivateDnsConfig()); + + // Test the constructor: PrivateDnsConfig(boolean useTls) + testPrivateDnsConfigConversion(new PrivateDnsConfig(true)); + testPrivateDnsConfigConversion(new PrivateDnsConfig(false)); + + // Test the constructor: PrivateDnsConfig(String hostname, InetAddress[] ips) + testPrivateDnsConfigConversion(new PrivateDnsConfig(null, null)); + testPrivateDnsConfigConversion(new PrivateDnsConfig(null, TEST_ADDRS)); + testPrivateDnsConfigConversion(new PrivateDnsConfig("dns.com", null)); + testPrivateDnsConfigConversion(new PrivateDnsConfig("dns.com", TEST_ADDRS)); + + // Test the constructor: + // PrivateDnsConfig(int mode, String hostname, InetAddress[] ips, + // String dohName, InetAddress[] dohIps, String dohPath, int dohPort) + for (int mode : new int[] { OFF_MODE, OPPORTUNISTIC_MODE, STRICT_MODE }) { + testPrivateDnsConfigConversion(new PrivateDnsConfig(mode, null, null, + null, null, null, -1)); + testPrivateDnsConfigConversion(new PrivateDnsConfig(mode, "dns.com", null, + null, null, null, -1)); + testPrivateDnsConfigConversion(new PrivateDnsConfig(mode, "dns.com", TEST_ADDRS, + null, null, null, -1)); + testPrivateDnsConfigConversion(new PrivateDnsConfig(mode, "dns.com", TEST_ADDRS, + "doh.com", null, null, -1)); + testPrivateDnsConfigConversion(new PrivateDnsConfig(mode, "dns.com", TEST_ADDRS, + "doh.com", TEST_ADDRS, null, -1)); + testPrivateDnsConfigConversion(new PrivateDnsConfig(mode, "dns.com", TEST_ADDRS, + "doh.com", TEST_ADDRS, "dohpath=/some-path{?dns}", -1)); + testPrivateDnsConfigConversion(new PrivateDnsConfig(mode, "dns.com", TEST_ADDRS, + "doh.com", TEST_ADDRS, "dohpath=/some-path{?dns}", 443)); + } + } + + @Test + public void testIpAddressArrayIsCopied() { + final InetAddress ip = InetAddress.parseNumericAddress("1.2.3.4"); + final InetAddress[] ipArray = new InetAddress[] { ip }; + final PrivateDnsConfig cfg = new PrivateDnsConfig(OPPORTUNISTIC_MODE, null /* hostname */, + ipArray /* ips */, null /* dohName */, ipArray /* dohIps */, null /* dohPath */, + -1 /* dohPort */); + + ipArray[0] = InetAddress.parseNumericAddress("2001:db8::2"); + assertArrayEquals(new InetAddress[] { ip }, cfg.ips); + assertArrayEquals(new InetAddress[] { ip }, cfg.dohIps); + } +}
diff --git a/tests/unit/src/android/net/shared/ProvisioningConfigurationTest.java b/tests/unit/src/android/net/shared/ProvisioningConfigurationTest.java index 033cdbc..29ddf21 100644 --- a/tests/unit/src/android/net/shared/ProvisioningConfigurationTest.java +++ b/tests/unit/src/android/net/shared/ProvisioningConfigurationTest.java
@@ -112,6 +112,7 @@ config.mIPv4ProvisioningMode = PROV_IPV4_DHCP; config.mIPv6ProvisioningMode = PROV_IPV6_SLAAC; config.mUniqueEui64AddressesOnly = false; + config.mCreatorUid = 10136; return config; } @@ -142,6 +143,7 @@ MacAddress.fromString("00:01:02:03:04:05")); p.layer2Info = layer2Info.toStableParcelable(); p.options = makeCustomizedDhcpOptions((byte) 60, new String("android-dhcp-11").getBytes()); + p.creatorUid = 10136; return p; } @@ -149,7 +151,7 @@ public void setUp() { mConfig = makeTestProvisioningConfiguration(); // Any added field must be included in equals() to be tested properly - assertFieldCountEquals(17, ProvisioningConfiguration.class); + assertFieldCountEquals(18, ProvisioningConfiguration.class); } @Test @@ -281,7 +283,8 @@ assertNotEqualsAfterChange(c -> c.mIPv6ProvisioningMode = PROV_IPV6_DISABLED); assertNotEqualsAfterChange(c -> c.mIPv6ProvisioningMode = PROV_IPV6_LINKLOCAL); assertNotEqualsAfterChange(c -> c.mUniqueEui64AddressesOnly = true); - assertFieldCountEquals(17, ProvisioningConfiguration.class); + assertNotEqualsAfterChange(c -> c.mCreatorUid = 10138); + assertFieldCountEquals(18, ProvisioningConfiguration.class); } private void assertNotEqualsAfterChange(Consumer<ProvisioningConfiguration> mutator) {
diff --git a/tests/unit/src/com/android/networkstack/arp/ArpPacketTest.java b/tests/unit/src/com/android/networkstack/arp/ArpPacketTest.java deleted file mode 100644 index dd7ba6a..0000000 --- a/tests/unit/src/com/android/networkstack/arp/ArpPacketTest.java +++ /dev/null
@@ -1,199 +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.arp; - -import static com.android.net.module.util.NetworkStackConstants.ARP_REQUEST; -import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN; -import static com.android.testutils.MiscAsserts.assertThrows; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; - -import android.net.InetAddresses; -import android.net.MacAddress; -import android.net.dhcp.DhcpPacket; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.net.Inet4Address; -import java.nio.ByteBuffer; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public final class ArpPacketTest { - - private static final Inet4Address TEST_IPV4_ADDR = - (Inet4Address) InetAddresses.parseNumericAddress("192.168.1.2"); - private static final Inet4Address INADDR_ANY = - (Inet4Address) InetAddresses.parseNumericAddress("0.0.0.0"); - private static final byte[] TEST_SENDER_MAC_ADDR = new byte[] { - 0x00, 0x1a, 0x11, 0x22, 0x33, 0x33 }; - private static final byte[] TEST_TARGET_MAC_ADDR = new byte[] { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; - private static final byte[] TEST_ARP_PROBE = new byte[] { - // dst mac address - (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, - // src mac address - (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33, - // ether type - (byte) 0x08, (byte) 0x06, - // hardware type - (byte) 0x00, (byte) 0x01, - // protocol type - (byte) 0x08, (byte) 0x00, - // hardware address size - (byte) 0x06, - // protocol address size - (byte) 0x04, - // opcode - (byte) 0x00, (byte) 0x01, - // sender mac address - (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33, - // sender IP address - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - // target mac address - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - // target IP address - (byte) 0xc0, (byte) 0xa8, (byte) 0x01, (byte) 0x02, - }; - - private static final byte[] TEST_ARP_ANNOUNCE = new byte[] { - // dst mac address - (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, - // src mac address - (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33, - // ether type - (byte) 0x08, (byte) 0x06, - // hardware type - (byte) 0x00, (byte) 0x01, - // protocol type - (byte) 0x08, (byte) 0x00, - // hardware address size - (byte) 0x06, - // protocol address size - (byte) 0x04, - // opcode - (byte) 0x00, (byte) 0x01, - // sender mac address - (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33, - // sender IP address - (byte) 0xc0, (byte) 0xa8, (byte) 0x01, (byte) 0x02, - // target mac address - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - // target IP address - (byte) 0xc0, (byte) 0xa8, (byte) 0x01, (byte) 0x02, - }; - - private static final byte[] TEST_ARP_PROBE_TRUNCATED = new byte[] { - // dst mac address - (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, - // src mac address - (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33, - // ether type - (byte) 0x08, (byte) 0x06, - // hardware type - (byte) 0x00, (byte) 0x01, - // protocol type - (byte) 0x08, (byte) 0x00, - // hardware address size - (byte) 0x06, - // protocol address size - (byte) 0x04, - // opcode - (byte) 0x00, - }; - - private static final byte[] TEST_ARP_PROBE_TRUNCATED_MAC = new byte[] { - // dst mac address - (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, - // src mac address - (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33, - // ether type - (byte) 0x08, (byte) 0x06, - // hardware type - (byte) 0x00, (byte) 0x01, - // protocol type - (byte) 0x08, (byte) 0x00, - // hardware address size - (byte) 0x06, - // protocol address size - (byte) 0x04, - // opcode - (byte) 0x00, (byte) 0x01, - // sender mac address - (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, - }; - - @Test - public void testBuildArpProbePacket() throws Exception { - final ByteBuffer arpProbe = ArpPacket.buildArpPacket(DhcpPacket.ETHER_BROADCAST, - TEST_SENDER_MAC_ADDR, TEST_IPV4_ADDR.getAddress(), new byte[ETHER_ADDR_LEN], - INADDR_ANY.getAddress(), (short) ARP_REQUEST); - assertArrayEquals(arpProbe.array(), TEST_ARP_PROBE); - } - - @Test - public void testBuildArpAnnouncePacket() throws Exception { - final ByteBuffer arpAnnounce = ArpPacket.buildArpPacket(DhcpPacket.ETHER_BROADCAST, - TEST_SENDER_MAC_ADDR, TEST_IPV4_ADDR.getAddress(), new byte[ETHER_ADDR_LEN], - TEST_IPV4_ADDR.getAddress(), (short) ARP_REQUEST); - assertArrayEquals(arpAnnounce.array(), TEST_ARP_ANNOUNCE); - } - - @Test - public void testParseArpProbePacket() throws Exception { - final ArpPacket packet = ArpPacket.parseArpPacket(TEST_ARP_PROBE, TEST_ARP_PROBE.length); - assertEquals(packet.opCode, ARP_REQUEST); - assertEquals(packet.senderHwAddress, MacAddress.fromBytes(TEST_SENDER_MAC_ADDR)); - assertEquals(packet.targetHwAddress, MacAddress.fromBytes(TEST_TARGET_MAC_ADDR)); - assertEquals(packet.senderIp, INADDR_ANY); - assertEquals(packet.targetIp, TEST_IPV4_ADDR); - } - - @Test - public void testParseArpAnnouncePacket() throws Exception { - final ArpPacket packet = ArpPacket.parseArpPacket(TEST_ARP_ANNOUNCE, - TEST_ARP_ANNOUNCE.length); - assertEquals(packet.opCode, ARP_REQUEST); - assertEquals(packet.senderHwAddress, MacAddress.fromBytes(TEST_SENDER_MAC_ADDR)); - assertEquals(packet.targetHwAddress, MacAddress.fromBytes(TEST_TARGET_MAC_ADDR)); - assertEquals(packet.senderIp, TEST_IPV4_ADDR); - assertEquals(packet.targetIp, TEST_IPV4_ADDR); - } - - @Test - public void testParseArpPacket_invalidByteBufferParameters() throws Exception { - assertThrows(ArpPacket.ParseException.class, () -> ArpPacket.parseArpPacket( - TEST_ARP_PROBE, 0)); - } - - @Test - public void testParseArpPacket_truncatedPacket() throws Exception { - assertThrows(ArpPacket.ParseException.class, () -> ArpPacket.parseArpPacket( - TEST_ARP_PROBE_TRUNCATED, TEST_ARP_PROBE_TRUNCATED.length)); - } - - @Test - public void testParseArpPacket_truncatedMacAddress() throws Exception { - assertThrows(ArpPacket.ParseException.class, () -> ArpPacket.parseArpPacket( - TEST_ARP_PROBE_TRUNCATED_MAC, TEST_ARP_PROBE_TRUNCATED.length)); - } -}
diff --git a/tests/unit/src/com/android/networkstack/metrics/ApfSessionInfoMetricsTest.java b/tests/unit/src/com/android/networkstack/metrics/ApfSessionInfoMetricsTest.java new file mode 100644 index 0000000..332b42f --- /dev/null +++ b/tests/unit/src/com/android/networkstack/metrics/ApfSessionInfoMetricsTest.java
@@ -0,0 +1,147 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.networkstack.metrics; + +import static org.junit.Assert.assertEquals; + +import android.net.apf.ApfCounterTracker.Counter; +import android.stats.connectivity.CounterName; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for ApfSessionInfoMetrics. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ApfSessionInfoMetricsTest { + @Test + public void testApfSessionInfoMetrics_VerifyCollectMetrics() throws Exception { + ApfSessionInfoReported mStats; + final ApfSessionInfoMetrics mMetrics = new ApfSessionInfoMetrics(); + mMetrics.setVersion(4); + mMetrics.setMemorySize(4096); + mMetrics.setApfSessionDurationSeconds(123); + mMetrics.setNumOfTimesApfProgramUpdated(456); + mMetrics.setMaxProgramSize(1234); + mMetrics.addApfCounter(Counter.TOTAL_PACKETS, 5678); + mMetrics.addApfCounter(Counter.PASSED_ARP_UNICAST_REPLY, 1010); + mMetrics.addApfCounter(Counter.DROPPED_MDNS, 333); + mStats = mMetrics.statsWrite(); + assertEquals(4, mStats.getVersion()); + assertEquals(4096, mStats.getMemorySize()); + assertEquals(123, mStats.getApfSessionDurationSeconds()); + assertEquals(456, mStats.getNumOfTimesApfProgramUpdated()); + assertEquals(1234, mStats.getMaxProgramSize()); + + // ApfCounter count: 3 (CN_TOTAL_PACKETS, CN_PASSED_ARP_UNICAST_REPLY, CN_DROPPED_MDNS) + final ApfCounterList apfCounterList = mStats.getApfCounterList(); + assertEquals(3, apfCounterList.getApfCounterCount()); + + // Verify 1st ApfCounter: CounterName = CN_TOTAL_PACKETS, CounterValue = 5678 + ApfCounter apfCounter = apfCounterList.getApfCounter(0); + assertEquals(CounterName.CN_TOTAL_PACKETS, apfCounter.getCounterName()); + assertEquals(5678, apfCounter.getCounterValue()); + + // Verify 1st ApfCounter: CounterName = CN_PASSED_ARP_UNICAST_REPLY, CounterValue = 1010 + apfCounter = apfCounterList.getApfCounter(1); + assertEquals(CounterName.CN_PASSED_ARP_UNICAST_REPLY, apfCounter.getCounterName()); + assertEquals(1010, apfCounter.getCounterValue()); + + // Verify 1st ApfCounter: CounterName = CN_DROPPED_MDNS, CounterValue = 333 + apfCounter = apfCounterList.getApfCounter(2); + assertEquals(CounterName.CN_DROPPED_MDNS, apfCounter.getCounterName()); + assertEquals(333, apfCounter.getCounterValue()); + } + + @Test + public void testApfSessionInfoMetrics_VerifyMaxApfCounter() throws Exception { + ApfSessionInfoReported mStats; + final ApfSessionInfoMetrics mMetrics = new ApfSessionInfoMetrics(); + for (Counter counter : Counter.class.getEnumConstants()) { + mMetrics.addApfCounter(counter, 1); + } + final int expectedApfCounterCount = Counter.class.getEnumConstants().length - 1; + mStats = mMetrics.statsWrite(); + final ApfCounterList apfCounterList = mStats.getApfCounterList(); + assertEquals(expectedApfCounterCount, apfCounterList.getApfCounterCount()); + } + + private void verifyCounterName(Counter counter, + CounterName expectedCounterName) { + assertEquals(expectedCounterName, ApfSessionInfoMetrics.apfFilterCounterToEnum(counter)); + } + + @Test + public void testApfSessionInfoMetrics_VerifyApfCounterToEnum() throws Exception { + verifyCounterName(Counter.RESERVED_OOB, CounterName.CN_UNKNOWN); + verifyCounterName(Counter.TOTAL_PACKETS, CounterName.CN_TOTAL_PACKETS); + verifyCounterName(Counter.PASSED_ARP, CounterName.CN_PASSED_ARP); + verifyCounterName(Counter.PASSED_DHCP, CounterName.CN_PASSED_DHCP); + verifyCounterName(Counter.PASSED_IPV4, CounterName.CN_PASSED_IPV4); + verifyCounterName(Counter.PASSED_IPV6_NON_ICMP, CounterName.CN_PASSED_IPV6_NON_ICMP); + verifyCounterName(Counter.PASSED_IPV4_UNICAST, CounterName.CN_PASSED_IPV4_UNICAST); + verifyCounterName(Counter.PASSED_IPV6_ICMP, CounterName.CN_PASSED_IPV6_ICMP); + verifyCounterName(Counter.PASSED_IPV6_UNICAST_NON_ICMP, + CounterName.CN_PASSED_IPV6_UNICAST_NON_ICMP); + verifyCounterName(Counter.PASSED_ARP_NON_IPV4, CounterName.CN_UNKNOWN); + verifyCounterName(Counter.PASSED_ARP_UNKNOWN, CounterName.CN_UNKNOWN); + verifyCounterName(Counter.PASSED_ARP_UNICAST_REPLY, + CounterName.CN_PASSED_ARP_UNICAST_REPLY); + verifyCounterName(Counter.PASSED_NON_IP_UNICAST, CounterName.CN_PASSED_NON_IP_UNICAST); + verifyCounterName(Counter.PASSED_MDNS, CounterName.CN_PASSED_MDNS); + verifyCounterName(Counter.DROPPED_ETH_BROADCAST, CounterName.CN_DROPPED_ETH_BROADCAST); + verifyCounterName(Counter.DROPPED_RA, CounterName.CN_DROPPED_RA); + verifyCounterName(Counter.DROPPED_GARP_REPLY, CounterName.CN_DROPPED_GARP_REPLY); + verifyCounterName(Counter.DROPPED_ARP_OTHER_HOST, CounterName.CN_DROPPED_ARP_OTHER_HOST); + verifyCounterName(Counter.DROPPED_IPV4_L2_BROADCAST, + CounterName.CN_DROPPED_IPV4_L2_BROADCAST); + verifyCounterName(Counter.DROPPED_IPV4_BROADCAST_ADDR, + CounterName.CN_DROPPED_IPV4_BROADCAST_ADDR); + verifyCounterName(Counter.DROPPED_IPV4_BROADCAST_NET, + CounterName.CN_DROPPED_IPV4_BROADCAST_NET); + verifyCounterName(Counter.DROPPED_IPV4_MULTICAST, CounterName.CN_DROPPED_IPV4_MULTICAST); + verifyCounterName(Counter.DROPPED_IPV6_ROUTER_SOLICITATION, + CounterName.CN_DROPPED_IPV6_ROUTER_SOLICITATION); + verifyCounterName(Counter.DROPPED_IPV6_MULTICAST_NA, + CounterName.CN_DROPPED_IPV6_MULTICAST_NA); + verifyCounterName(Counter.DROPPED_IPV6_MULTICAST, CounterName.CN_DROPPED_IPV6_MULTICAST); + verifyCounterName(Counter.DROPPED_IPV6_MULTICAST_PING, + CounterName.CN_DROPPED_IPV6_MULTICAST_PING); + verifyCounterName(Counter.DROPPED_IPV6_NON_ICMP_MULTICAST, + CounterName.CN_DROPPED_IPV6_NON_ICMP_MULTICAST); + verifyCounterName(Counter.DROPPED_802_3_FRAME, CounterName.CN_DROPPED_802_3_FRAME); + verifyCounterName(Counter.DROPPED_ETHERTYPE_DENYLISTED, + CounterName.CN_DROPPED_ETHERTYPE_DENYLISTED); + verifyCounterName(Counter.DROPPED_ARP_REPLY_SPA_NO_HOST, + CounterName.CN_DROPPED_ARP_REPLY_SPA_NO_HOST); + verifyCounterName(Counter.DROPPED_IPV4_KEEPALIVE_ACK, + CounterName.CN_DROPPED_IPV4_KEEPALIVE_ACK); + verifyCounterName(Counter.DROPPED_IPV6_KEEPALIVE_ACK, + CounterName.CN_DROPPED_IPV6_KEEPALIVE_ACK); + verifyCounterName(Counter.DROPPED_IPV4_NATT_KEEPALIVE, + CounterName.CN_DROPPED_IPV4_NATT_KEEPALIVE); + verifyCounterName(Counter.DROPPED_MDNS, CounterName.CN_DROPPED_MDNS); + verifyCounterName(Counter.DROPPED_IPV4_TCP_PORT7_UNICAST, CounterName.CN_UNKNOWN); + verifyCounterName(Counter.DROPPED_ARP_NON_IPV4, CounterName.CN_DROPPED_ARP_NON_IPV4); + verifyCounterName(Counter.DROPPED_ARP_UNKNOWN, CounterName.CN_DROPPED_ARP_UNKNOWN); + } +}
diff --git a/tests/unit/src/com/android/networkstack/metrics/IpClientRaInfoMetricsTest.java b/tests/unit/src/com/android/networkstack/metrics/IpClientRaInfoMetricsTest.java new file mode 100644 index 0000000..2c16f86 --- /dev/null +++ b/tests/unit/src/com/android/networkstack/metrics/IpClientRaInfoMetricsTest.java
@@ -0,0 +1,53 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.networkstack.metrics; + +import static org.junit.Assert.assertEquals; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for IpClientRaInfoMetrics. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class IpClientRaInfoMetricsTest { + @Test + public void testIpClientRaInfoMetrics_VerifyCollectMetrics() throws Exception { + IpClientRaInfoReported mStats; + final IpClientRaInfoMetrics mMetrics = new IpClientRaInfoMetrics(); + mMetrics.setMaxNumberOfDistinctRas(12); + mMetrics.setNumberOfZeroLifetimeRas(34); + mMetrics.setNumberOfParsingErrorRas(56); + mMetrics.setLowestRouterLifetimeSeconds(78); + mMetrics.setLowestPioValidLifetimeSeconds(123); + mMetrics.setLowestRioRouteLifetimeSeconds(456); + mMetrics.setLowestRdnssLifetimeSeconds(789); + mStats = mMetrics.statsWrite(); + assertEquals(12, mStats.getMaxNumberOfDistinctRas()); + assertEquals(34, mStats.getNumberOfZeroLifetimeRas()); + assertEquals(56, mStats.getNumberOfParsingErrorRas()); + assertEquals(78, mStats.getLowestRouterLifetimeSeconds()); + assertEquals(123, mStats.getLowestPioValidLifetimeSeconds()); + assertEquals(456, mStats.getLowestRioRouteLifetimeSeconds()); + assertEquals(789, mStats.getLowestRdnssLifetimeSeconds()); + } +}
diff --git a/tests/unit/src/com/android/networkstack/netlink/TcpInfoTest.java b/tests/unit/src/com/android/networkstack/netlink/TcpInfoTest.java index ddab8c7..ff56f5f 100644 --- a/tests/unit/src/com/android/networkstack/netlink/TcpInfoTest.java +++ b/tests/unit/src/com/android/networkstack/netlink/TcpInfoTest.java
@@ -17,6 +17,8 @@ package com.android.networkstack.netlink; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -27,6 +29,7 @@ import org.junit.runner.RunWith; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.LinkedHashMap; import java.util.Map; @@ -49,38 +52,38 @@ "07" + // option = TCPI_OPT_WSCALE|TCPI_OPT_SACK|TCPI_OPT_TIMESTAMPS "88" + // wscale = 8 "00" + // delivery_rate_app_limited = 0 - "001B914A" + // rto = 1806666 + "4A911B00" + // rto = 1806666 "00000000" + // ato = 0 - "0000052E" + // sndMss = 1326 - "00000218" + // rcvMss = 536 + "2E050000" + // sndMss = 1326 + "18020000" + // rcvMss = 536 "00000000" + // unsacked = 0 "00000000" + // acked = 0 "00000000" + // lost = 0 "00000000" + // retrans = 0 "00000000" + // fackets = 0 - "000000BB" + // lastDataSent = 187 + "BB000000" + // lastDataSent = 187 "00000000" + // lastAckSent = 0 - "000000BB" + // lastDataRecv = 187 - "000000BB" + // lastDataAckRecv = 187 - "000005DC" + // pmtu = 1500 - "00015630" + // rcvSsthresh = 87600 - "00092C3E" + // rttt = 601150 - "0004961F" + // rttvar = 300575 - "00000578" + // sndSsthresh = 1400 - "0000000A" + // sndCwnd = 10 - "000005A8" + // advmss = 1448 - "00000003" + // reordering = 3 + "BB000000" + // lastDataRecv = 187 + "BB00000000" + // lastDataAckRecv = 187 + "DC0500" + // pmtu = 1500 + "30560100" + // rcvSsthresh = 87600 + "3E2C0900" + // rttt = 601150 + "1F960400" + // rttvar = 300575 + "78050000" + // sndSsthresh = 1400 + "0A000000" + // sndCwnd = 10 + "A8050000" + // advmss = 1448 + "02000000" + // reordering = 3 "00000000" + // rcvrtt = 0 - "00015630" + // rcvspace = 87600 - "00000000" + // totalRetrans = 0 - "000000000000AC53" + // pacingRate = 44115 + "30560100" + // rcvspace = 87600 + "05000000" + // totalRetrans = 5 + "53AC000000000000" + // pacingRate = 44115 "FFFFFFFFFFFFFFFF" + // maxPacingRate = 18446744073709551615 - "0000000000000001" + // bytesAcked = 1 + "0100000000000001" + // bytesAcked = 1 "0000000000000000" + // bytesReceived = 0 - "00000002" + // SegsOut = 2 - "00000001" + // SegsIn = 1 + "02000000" + // SegsOut = 2 + "01000000" + // SegsIn = 1 "00000000" + // NotSentBytes = 0 - "00092C3E" + // minRtt = 601150 + "3E2C0900" + // minRtt = 601150 "00000000" + // DataSegsIn = 0 "00000000" + // DataSegsOut = 0 "0000000000000000" + // deliverRate = 0 @@ -90,7 +93,7 @@ private static final byte[] TCP_INFO_BYTES = HexEncoding.decode(TCP_INFO_HEX.toCharArray(), false); private static final TcpInfo TEST_TCPINFO = - new TcpInfo(0 /* retransmits */, 0 /* lost */, 2 /* segsOut */, 1 /* segsIn */); + new TcpInfo(2 /* segsOut */, 1 /* segsIn */, 5 /* totalRetrans */); private static final String EXPANDED_TCP_INFO_HEX = TCP_INFO_HEX + "00000000" // tcpi_delivered @@ -102,27 +105,36 @@ @Test public void testParseTcpInfo() { final ByteBuffer buffer = ByteBuffer.wrap(TCP_INFO_BYTES); + // Android is always little-endian. Refer to https://developer.android.com/ndk/guides/abis. + buffer.order(ByteOrder.nativeOrder()); // Length is less than required - final TcpInfo nullInfo = TcpInfo.parse(buffer, SHORT_TEST_TCP_INFO); - assertEquals(nullInfo, null); + assertNull(TcpInfo.parse(buffer, SHORT_TEST_TCP_INFO)); + assertEquals(TEST_TCPINFO, TcpInfo.parse(buffer, TCP_INFO_LENGTH_V1)); - final TcpInfo parsedInfo = TcpInfo.parse(buffer, TCP_INFO_LENGTH_V1); - assertEquals(parsedInfo, TEST_TCPINFO); + // Make a data that TcpInfo is not started from the beginning of the buffer. + final ByteBuffer buffer2 = ByteBuffer.wrap(TCP_INFO_BYTES); + buffer2.order(ByteOrder.nativeOrder()); + // Move to certain position. + buffer2.position(2); + // Parsing is started in an incorrect position. This results in a failed parsing. + assertNotEquals(TEST_TCPINFO, TcpInfo.parse(buffer2, TCP_INFO_LENGTH_V1)); - // Make a data that TcpInfo is not started from the begining of the buffer. - final ByteBuffer bufferWithHeader = + // Make a TcpInfo with extra tcp info fields. Parsing is only performed with + // TCP_INFO_LENGTH_V1 length. Result is the same as parsing with TCP_INFO_BYTES. + final ByteBuffer bufferExtraInfo = ByteBuffer.allocate(EXPANDED_TCP_INFO_BYTES.length + TCP_INFO_BYTES.length); - bufferWithHeader.put(EXPANDED_TCP_INFO_BYTES); - bufferWithHeader.put(TCP_INFO_BYTES); - final TcpInfo infoWithHeader = TcpInfo.parse(buffer, TCP_INFO_LENGTH_V1); - bufferWithHeader.position(EXPANDED_TCP_INFO_BYTES.length); - assertEquals(parsedInfo, TEST_TCPINFO); + bufferExtraInfo.order(ByteOrder.nativeOrder()); + bufferExtraInfo.put(TCP_INFO_BYTES); + bufferExtraInfo.put(EXPANDED_TCP_INFO_BYTES); + bufferExtraInfo.position(0); + assertEquals(TEST_TCPINFO, TcpInfo.parse(bufferExtraInfo, TCP_INFO_LENGTH_V1)); } @Test public void testFieldOffset() { assertEquals(TcpInfo.RETRANSMITS_OFFSET, 2); assertEquals(TcpInfo.LOST_OFFSET, 32); + assertEquals(TcpInfo.TOTAL_RETRANS_OFFSET, 100); assertEquals(TcpInfo.SEGS_OUT_OFFSET, 136); assertEquals(TcpInfo.SEGS_IN_OFFSET, 140); } @@ -130,25 +142,27 @@ @Test public void testParseTcpInfoExpanded() { final ByteBuffer buffer = ByteBuffer.wrap(EXPANDED_TCP_INFO_BYTES); + // Android is always little-endian. Refer to https://developer.android.com/ndk/guides/abis. + buffer.order(ByteOrder.nativeOrder()); final TcpInfo parsedInfo = TcpInfo.parse(buffer, TCP_INFO_LENGTH_V1 + EXPANDED_TCP_INFO_LENGTH); - assertEquals(parsedInfo, TEST_TCPINFO); + assertEquals(TEST_TCPINFO, parsedInfo); assertEquals(buffer.limit(), buffer.position()); // reset the index. buffer.position(0); final TcpInfo parsedInfoShorterLen = TcpInfo.parse(buffer, TCP_INFO_LENGTH_V1); - assertEquals(parsedInfoShorterLen, TEST_TCPINFO); + assertEquals(TEST_TCPINFO, parsedInfoShorterLen); assertEquals(TCP_INFO_LENGTH_V1, buffer.position()); } @Test public void testTcpStateName() { - assertEquals(TcpInfo.getTcpStateName(4), TCP_FIN_WAIT1); - assertEquals(TcpInfo.getTcpStateName(1), TCP_ESTABLISHED); - assertEquals(TcpInfo.getTcpStateName(2), TCP_SYN_SENT); - assertEquals(TcpInfo.getTcpStateName(20), UNKNOWN_20); + assertEquals(TCP_FIN_WAIT1, TcpInfo.getTcpStateName(4)); + assertEquals(TCP_ESTABLISHED, TcpInfo.getTcpStateName(1)); + assertEquals(TCP_SYN_SENT, TcpInfo.getTcpStateName(2)); + assertEquals(UNKNOWN_20, TcpInfo.getTcpStateName(20)); } private static final String MALFORMED_TCP_INFO_HEX = @@ -166,12 +180,8 @@ @Test public void testMalformedTcpInfo() { final ByteBuffer buffer = ByteBuffer.wrap(MALFORMED_TCP_INFO_BYTES); - - TcpInfo parsedInfo = TcpInfo.parse(buffer, SHORT_TEST_TCP_INFO); - assertEquals(parsedInfo, null); - - parsedInfo = TcpInfo.parse(buffer, TCP_INFO_LENGTH_V1); - assertEquals(parsedInfo, null); + assertNull(TcpInfo.parse(buffer, SHORT_TEST_TCP_INFO)); + assertNull(TcpInfo.parse(buffer, TCP_INFO_LENGTH_V1)); } // Make a TcpInfo contains only first 8 bytes. @@ -232,4 +242,20 @@ return info; } + + @Test + public void testHashCode() { + final TcpInfo info = new TcpInfo(2, 1, 5); + final TcpInfo info2 = new TcpInfo(2, 1, 5); + + assertEquals(info, info2); + assertEquals(info.hashCode(), info2.hashCode()); + } + + @Test + public void testDecodeWscale() { + assertEquals("0:0", TcpInfo.decodeWscale((byte) 0)); + assertEquals("15:15", TcpInfo.decodeWscale((byte) 255)); + assertEquals("15:0", TcpInfo.decodeWscale((byte) 240)); + } }
diff --git a/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java b/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java index 1d15719..3857b04 100644 --- a/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java +++ b/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java
@@ -16,8 +16,13 @@ package com.android.networkstack.netlink; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.util.DataStallUtils.CONFIG_TCP_PACKETS_FAIL_PERCENTAGE; import static android.net.util.DataStallUtils.DEFAULT_TCP_PACKETS_FAIL_PERCENTAGE; +import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED; +import static android.os.PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED; import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; import static android.system.OsConstants.AF_INET; @@ -27,26 +32,27 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; -import static org.junit.Assume.assumeTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; -import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; 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.annotation.IntDef; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.net.ConnectivityManager; import android.net.INetd; import android.net.InetAddresses; import android.net.LinkProperties; import android.net.MarkMaskParcel; import android.net.Network; +import android.net.NetworkCapabilities; import android.os.Build; import android.os.PowerManager; import android.util.Log; @@ -58,7 +64,6 @@ import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.netlink.NetlinkUtils; import com.android.net.module.util.netlink.StructNlMsgHdr; -import com.android.networkstack.apishim.ConstantsShim; import com.android.testutils.DevSdkIgnoreRule; import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter; import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; @@ -75,6 +80,8 @@ import org.mockito.MockitoAnnotations; import java.io.FileDescriptor; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.net.InetAddress; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -95,12 +102,12 @@ private static final byte[] SOCK_DIAG_MSG_BYTES = HexEncoding.decode(DIAG_MSG_HEX.toCharArray(), false); // Hexadecimal representation of a SOCK_DIAG response with tcp info. - private static final String SOCK_DIAG_TCP_ZERO_LOST_HEX = - composeSockDiagTcpHex(0 /* lost */, 10 /* sent */); - private static final byte[] SOCK_DIAG_TCP_INET_ZERO_LOST_BYTES = - HexEncoding.decode(SOCK_DIAG_TCP_ZERO_LOST_HEX.toCharArray(), false); + private static final String SOCK_DIAG_TCP_TEST_HEX = + composeSockDiagTcpHex(5 /* retrans */, 10 /* sent */); + private static final byte[] SOCK_DIAG_TCP_INET_TEST_BYTES = + HexEncoding.decode(SOCK_DIAG_TCP_TEST_HEX.toCharArray(), false); private static final TcpInfo TEST_TCPINFO = - new TcpInfo(5 /* retransmits */, 0 /* lost */, 10 /* segsOut */, 0 /* segsIn */); + new TcpInfo(10 /* segsOut */, 0 /* segsIn */, 5 /* totalRetrans */); private static final String NLMSG_DONE_HEX = // struct nlmsghdr "14000000" // length = 20 @@ -113,7 +120,7 @@ + "06" // state + "00" // timer + "00"; // retrans - private static final String TEST_RESPONSE_HEX = SOCK_DIAG_TCP_ZERO_LOST_HEX + NLMSG_DONE_HEX; + private static final String TEST_RESPONSE_HEX = SOCK_DIAG_TCP_TEST_HEX + NLMSG_DONE_HEX; private static final byte[] TEST_RESPONSE_BYTES = HexEncoding.decode(TEST_RESPONSE_HEX.toCharArray(), false); private static final int TEST_NETID1 = 0xA85; @@ -122,10 +129,22 @@ private static final int TEST_NETID2_FWMARK = 0x1A85; private static final int NETID_MASK = 0xffff; private static final int TEST_UID1 = 1234; + private static final int TEST_UID2 = TEST_UID1 + 1; private static final short TEST_DST_PORT = 29113; private static final long TEST_COOKIE1 = 43387759684916L; private static final long TEST_COOKIE2 = TEST_COOKIE1 + 1; private static final InetAddress TEST_DNS1 = InetAddresses.parseNumericAddress("8.8.8.8"); + + private static final NetworkCapabilities CELL_METERED_CAPABILITIES = + new NetworkCapabilities() + .addTransportType(TRANSPORT_CELLULAR) + .addCapability(NET_CAPABILITY_INTERNET); + + private static final NetworkCapabilities CELL_NOT_METERED_CAPABILITIES = + new NetworkCapabilities() + .addTransportType(TRANSPORT_CELLULAR) + .addCapability(NET_CAPABILITY_INTERNET) + .addCapability(NET_CAPABILITY_NOT_METERED); @Mock private TcpSocketTracker.Dependencies mDependencies; @Mock private INetd mNetd; private final Network mNetwork = new Network(TEST_NETID1); @@ -133,6 +152,7 @@ private TerribleFailureHandler mOldWtfHandler; @Mock private Context mContext; @Mock private PowerManager mPowerManager; + @Mock private ConnectivityManager mCm; @Rule public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule(); @@ -145,16 +165,18 @@ mOldWtfHandler = Log.setWtfHandler((tag, what, system) -> Log.e(tag, what.getMessage(), what)); when(mDependencies.getNetd()).thenReturn(mNetd); - when(mDependencies.isTcpInfoParsingSupported()).thenReturn(true); when(mDependencies.connectToKernel()).thenReturn(new FileDescriptor()); when(mDependencies.getDeviceConfigPropertyInt( eq(NAMESPACE_CONNECTIVITY), eq(CONFIG_TCP_PACKETS_FAIL_PERCENTAGE), anyInt())).thenReturn(DEFAULT_TCP_PACKETS_FAIL_PERCENTAGE); + when(mDependencies.shouldDisableInLightDoze(anyBoolean())).thenReturn(true); when(mNetd.getFwmarkForNetwork(eq(TEST_NETID1))) .thenReturn(makeMarkMaskParcel(NETID_MASK, TEST_NETID1_FWMARK)); + doReturn(mContext).when(mDependencies).getContext(); doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class); + doReturn(mCm).when(mContext).getSystemService(ConnectivityManager.class); } @After @@ -182,9 +204,10 @@ @Test public void testParseSockInfo() { - final ByteBuffer buffer = getByteBuffer(SOCK_DIAG_TCP_INET_ZERO_LOST_BYTES); + final ByteBuffer buffer = getByteBuffer(SOCK_DIAG_TCP_INET_TEST_BYTES); final ArrayList<TcpSocketTracker.SocketInfo> infoList = new ArrayList<>(); - TcpSocketTracker.parseMessage(buffer, AF_INET, infoList, 100L); + final TcpSocketTracker tst = new TcpSocketTracker(mDependencies, mNetwork); + tst.parseMessage(buffer, AF_INET, infoList, 100L); assertEquals(1, infoList.size()); final TcpSocketTracker.SocketInfo parsed = infoList.get(0); @@ -211,15 +234,10 @@ assertFalse(NetlinkUtils.enoughBytesRemainForValidNlMsg(buffer)); } - @Test @IgnoreUpTo(Build.VERSION_CODES.Q) // TCP info parsing is not supported on Q + @Test public void testPollSocketsInfo() throws Exception { - // This test requires shims that provide API 30 access - assumeTrue(ConstantsShim.VERSION >= Build.VERSION_CODES.R); - when(mDependencies.isTcpInfoParsingSupported()).thenReturn(false); final TcpSocketTracker tst = new TcpSocketTracker(mDependencies, mNetwork); - assertFalse(tst.pollSocketsInfo()); - when(mDependencies.isTcpInfoParsingSupported()).thenReturn(true); // No enough bytes remain for a valid NlMsg. final ByteBuffer invalidBuffer = ByteBuffer.allocate(1); invalidBuffer.order(ByteOrder.nativeOrder()); @@ -256,11 +274,11 @@ final TcpSocketTracker tst = new TcpSocketTracker(mDependencies, mNetwork); // Simulate 1 message with data stall happened. doReturn(getByteBufferFromHexString( - composeSockDiagTcpHex(4, 10) + NLMSG_DONE_HEX)) + composeSockDiagTcpHex(9, 10) + NLMSG_DONE_HEX)) .when(mDependencies).recvMessage(any()); assertTrue(tst.pollSocketsInfo()); - // ( Lost 4 + default 5 retransmits in the sample ) / 10 sent = 90 percent. + // 9 retrans / 10 sent = 90 percent. assertEquals(90, tst.getLatestPacketFailPercentage()); assertEquals(10, tst.getSentSinceLastRecv()); assertTrue(tst.isDataStallSuspected()); @@ -271,8 +289,8 @@ final LinkProperties testLp = new LinkProperties(); testLp.addDnsServer(TEST_DNS1); tst.setLinkProperties(testLp); - doReturn(getByteBufferFromHexString(composeSockDiagTcpHex(4, 10) - + composeSockDiagTcpHex(5, 10, DNS_OVER_TLS_PORT, TEST_COOKIE2) + doReturn(getByteBufferFromHexString(composeSockDiagTcpHex(9, 10) + + composeSockDiagTcpHex(9, 10, DNS_OVER_TLS_PORT, TEST_COOKIE2, TEST_UID1) + NLMSG_DONE_HEX)) .when(mDependencies).recvMessage(any()); assertTrue(tst.pollSocketsInfo()); @@ -289,12 +307,12 @@ // will be counted. testLp.addValidatedPrivateDnsServer(TEST_DNS1); tst.setLinkProperties(testLp); - doReturn(getByteBufferFromHexString(composeSockDiagTcpHex(5, 12) - + composeSockDiagTcpHex(7, 12, DNS_OVER_TLS_PORT, TEST_COOKIE2) + doReturn(getByteBufferFromHexString(composeSockDiagTcpHex(10, 12) + + composeSockDiagTcpHex(11, 12, DNS_OVER_TLS_PORT, TEST_COOKIE2, TEST_UID1) + NLMSG_DONE_HEX)) .when(mDependencies).recvMessage(any()); assertTrue(tst.pollSocketsInfo()); - // Lost ( 1 + 2 ) / ( 2 + 2 ) sent = 75 percent. + // Retrans ( 1 + 2 ) / ( 2 + 2 ) sent = 75 percent. assertEquals(75, tst.getLatestPacketFailPercentage()); assertEquals(14, tst.getSentSinceLastRecv()); assertFalse(tst.isDataStallSuspected()); @@ -303,40 +321,113 @@ // counted. And the stat is correctly subtracted from the stat ignored in the previous // polling cycle. tst.setOpportunisticMode(false); - doReturn(getByteBufferFromHexString(composeSockDiagTcpHex(6, 14) - + composeSockDiagTcpHex(9, 14, DNS_OVER_TLS_PORT, TEST_COOKIE2) + doReturn(getByteBufferFromHexString(composeSockDiagTcpHex(11, 14) + + composeSockDiagTcpHex(13, 14, DNS_OVER_TLS_PORT, TEST_COOKIE2, TEST_UID1) + NLMSG_DONE_HEX)) .when(mDependencies).recvMessage(any()); assertTrue(tst.pollSocketsInfo()); - // Lost ( 1 + 2 ) / ( 2 + 2 ) sent = 75 percent. + // Retrans ( 1 + 2 ) / ( 2 + 2 ) sent = 75 percent. assertEquals(75, tst.getLatestPacketFailPercentage()); assertEquals(18, tst.getSentSinceLastRecv()); assertFalse(tst.isDataStallSuspected()); } + @IgnoreAfter(Build.VERSION_CODES.S_V2) @Test - public void testTcpInfoParsingUnsupported() { - doReturn(false).when(mDependencies).isTcpInfoParsingSupported(); - final TcpSocketTracker tst = new TcpSocketTracker(mDependencies, mNetwork); - verify(mDependencies).getNetd(); - - assertFalse(tst.pollSocketsInfo()); - assertEquals(-1, tst.getLatestPacketFailPercentage()); - assertEquals(-1, tst.getLatestReceivedCount()); - assertEquals(-1, tst.getSentSinceLastRecv()); - assertFalse(tst.isDataStallSuspected()); - - verify(mDependencies, atLeastOnce()).isTcpInfoParsingSupported(); - verifyNoMoreInteractions(mDependencies); - - // Verify that no un-registration for the device configuration listener and broadcast - // receiver if TcpInfo parsing is not supported. - tst.quit(); - verify(mDependencies, never()).removeDeviceConfigChangedListener(any()); - verify(mDependencies, never()).removeBroadcastReceiver(any()); + public void testPollSocketsInfo_ignoreBlockedUid_featureDisabled_beforeT() throws Exception { + doTestPollSocketsInfo_ignoreBlockedUid_featureDisabled(); } - @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + @IgnoreUpTo(Build.VERSION_CODES.S_V2) + @Test + public void testPollSocketsInfo_ignoreBlockedUid_featureDisabled_TOrAbove() throws Exception { + doTestPollSocketsInfo_ignoreBlockedUid_featureDisabled(); + verify(mCm, never()).isUidNetworkingBlocked(anyInt(), anyBoolean()); + } + + private void doTestPollSocketsInfo_ignoreBlockedUid_featureDisabled() throws Exception { + doReturn(false).when(mDependencies).shouldIgnoreTcpInfoForBlockedUids(); + final TcpSocketTracker tst = new TcpSocketTracker(mDependencies, mNetwork); + // Simulate 1 message with data stall happened. + doReturn(getByteBufferFromHexString( + composeSockDiagTcpHex(4, 10) + NLMSG_DONE_HEX)) + .when(mDependencies).recvMessage(any()); + assertTrue(tst.pollSocketsInfo()); + // 4 retran / 10 sent = 40 percent. + assertEquals(40, tst.getLatestPacketFailPercentage()); + assertEquals(10, tst.getSentSinceLastRecv()); + assertFalse(tst.isDataStallSuspected()); + + // With the feature disabled, append another message with blocked uid, verify the + // traffic of networking-blocked uid is not filtered. + doReturn(getByteBufferFromHexString(composeSockDiagTcpHex(9, 10) + + composeSockDiagTcpHex(5, 10, DNS_OVER_TLS_PORT, TEST_COOKIE2, TEST_UID2) + + NLMSG_DONE_HEX)) + .when(mDependencies).recvMessage(any()); + assertTrue(tst.pollSocketsInfo()); + // 5 + 5 retrans / 10 sent = 100 percent. + assertEquals(100, tst.getLatestPacketFailPercentage()); + assertEquals(20, tst.getSentSinceLastRecv()); + assertTrue(tst.isDataStallSuspected()); + } + + // The feature is not enabled on pre-T device, because it needs bpf support. + @IgnoreUpTo(Build.VERSION_CODES.S_V2) + @Test + public void testPollSocketsInfo_ignoreBlockedUid_featureEnabled() throws Exception { + doReturn(true).when(mDependencies).shouldIgnoreTcpInfoForBlockedUids(); + final TcpSocketTracker tst = new TcpSocketTracker(mDependencies, mNetwork); + tst.setNetworkCapabilities(CELL_NOT_METERED_CAPABILITIES); + doReturn(true).when(mCm).isUidNetworkingBlocked(TEST_UID2, false /* metered */); + // With the feature enabled, append another message with blocked uid, verify the + // traffic of networking-blocked uid is filtered out. + doReturn(getByteBufferFromHexString(composeSockDiagTcpHex(4, 10) + + composeSockDiagTcpHex(6, 12, DNS_OVER_TLS_PORT, TEST_COOKIE2, TEST_UID2) + + NLMSG_DONE_HEX)) + .when(mDependencies).recvMessage(any()); + assertTrue(tst.pollSocketsInfo()); + assertEquals(40, tst.getLatestPacketFailPercentage()); + assertEquals(10, tst.getSentSinceLastRecv()); + assertFalse(tst.isDataStallSuspected()); + + // Unblock traffic of the uid, verify the traffic of the uid is not filtered. + doReturn(false).when(mCm).isUidNetworkingBlocked(TEST_UID2, false /* metered */); + doReturn(getByteBufferFromHexString(composeSockDiagTcpHex(4, 10) + + composeSockDiagTcpHex(8, 14, DNS_OVER_TLS_PORT, TEST_COOKIE2, TEST_UID2) + + NLMSG_DONE_HEX)) + .when(mDependencies).recvMessage(any()); + assertTrue(tst.pollSocketsInfo()); + // Lost 2 / 2 sent = 100 percent. + assertEquals(100, tst.getLatestPacketFailPercentage()); + assertEquals(12, tst.getSentSinceLastRecv()); + assertTrue(tst.isDataStallSuspected()); + } + + // The feature is not enabled on pre-T device, because it needs bpf support. + @IgnoreUpTo(Build.VERSION_CODES.S_V2) + @Test + public void testPollSocketsInfo_ignoreBlockedUid_featureEnabled_dataSaver() throws Exception { + doReturn(true).when(mDependencies).shouldIgnoreTcpInfoForBlockedUids(); + final TcpSocketTracker tst = new TcpSocketTracker(mDependencies, mNetwork); + + tst.setNetworkCapabilities(CELL_NOT_METERED_CAPABILITIES); + final ByteBuffer mockMessage = getByteBufferFromHexString(composeSockDiagTcpHex(4, 10) + + NLMSG_DONE_HEX); + doReturn(mockMessage).when(mDependencies).recvMessage(any()); + assertTrue(tst.pollSocketsInfo()); + verify(mCm).isUidNetworkingBlocked(TEST_UID1, false /* metered */); + + // Verify the metered parameter will be correctly passed to ConnectivityManager. + tst.setNetworkCapabilities(CELL_METERED_CAPABILITIES); + mockMessage.rewind(); // Reset read position to 0 since the same buffer is used. + assertTrue(tst.pollSocketsInfo()); + verify(mCm).isUidNetworkingBlocked(TEST_UID1, true /* metered */); + + // Correctness of the logic which handling different blocked status is + // verified in other tests, see {@code testPollSocketsInfo_ignoreBlockedUid_featureEnabled}. + } + + @Test public void testTcpInfoParsingWithMultipleMsgs() throws Exception { final TcpSocketTracker tst = new TcpSocketTracker(mDependencies, mNetwork); @@ -345,20 +436,20 @@ // // Mocking 6 return results for different IP families(3 for IPv6; 3 for Ipv4). Use the same // message for different IP families to reduce the complexity. - doReturn(getByteBufferFromHexString(repeat(composeSockDiagTcpHex(0, 10), 5)), - getByteBufferFromHexString(repeat(composeSockDiagTcpHex(0, 10), 2)), + doReturn(getByteBufferFromHexString(repeat(composeSockDiagTcpHex(5, 10), 5)), + getByteBufferFromHexString(repeat(composeSockDiagTcpHex(5, 10), 2)), getByteBufferFromHexString( - repeat(composeSockDiagTcpHex(0, 10), 2) + NLMSG_DONE_HEX), - getByteBufferFromHexString(repeat(composeSockDiagTcpHex(0, 10), 5)), - getByteBufferFromHexString(repeat(composeSockDiagTcpHex(0, 10), 2)), + repeat(composeSockDiagTcpHex(5, 10), 2) + NLMSG_DONE_HEX), + getByteBufferFromHexString(repeat(composeSockDiagTcpHex(5, 10), 5)), + getByteBufferFromHexString(repeat(composeSockDiagTcpHex(5, 10), 2)), getByteBufferFromHexString( - repeat(composeSockDiagTcpHex(0, 10), 2) + NLMSG_DONE_HEX)) + repeat(composeSockDiagTcpHex(5, 10), 2) + NLMSG_DONE_HEX)) .when(mDependencies).recvMessage(any()); assertTrue(tst.pollSocketsInfo()); // Verify that code reads all the messages. (3 times for IPv4, 3 times for IPv6) verify(mDependencies, times(6)).recvMessage(any()); - // Calculated from (retransmits + lost) / segsout. + // Calculated from totalRetrans / segsout. // Note that the counters cannot be verified given that the cookie of the mocked sockets // are the same, the latest SocketInfo would overwrite previous reported ones. assertEquals(50, tst.getLatestPacketFailPercentage()); @@ -371,12 +462,12 @@ // // Mocking 6 return results for different IP families(3 for IPv6; 3 for Ipv4). Use the same // message for different IP families to reduce the complexity. - doReturn(getByteBufferFromHexString(composeSockDiagTcpHex(5, 15)), - getByteBufferFromHexString(repeat(composeSockDiagTcpHex(5, 15), 5)), - getByteBufferFromHexString(composeSockDiagTcpHex(5, 15) + NLMSG_DONE_HEX), - getByteBufferFromHexString(composeSockDiagTcpHex(5, 15)), - getByteBufferFromHexString(repeat(composeSockDiagTcpHex(5, 15), 5)), - getByteBufferFromHexString(composeSockDiagTcpHex(5, 15) + NLMSG_DONE_HEX)) + doReturn(getByteBufferFromHexString(composeSockDiagTcpHex(10, 15)), + getByteBufferFromHexString(repeat(composeSockDiagTcpHex(10, 15), 5)), + getByteBufferFromHexString(composeSockDiagTcpHex(10, 15) + NLMSG_DONE_HEX), + getByteBufferFromHexString(composeSockDiagTcpHex(10, 15)), + getByteBufferFromHexString(repeat(composeSockDiagTcpHex(10, 15), 5)), + getByteBufferFromHexString(composeSockDiagTcpHex(10, 15) + NLMSG_DONE_HEX)) .when(mDependencies).recvMessage(any()); assertTrue(tst.pollSocketsInfo()); @@ -393,12 +484,12 @@ // // Mocking 4 return results for different IP families(2 for IPv6; 2 for Ipv4). Use the same // message for different IP families to reduce the complexity. - doReturn(getByteBufferFromHexString(repeat(composeSockDiagTcpHex(5, 15), 5)), - getByteBufferFromHexString(composeSockDiagTcpHex(5, 15)), - getByteBufferFromHexString(composeSockDiagTcpHex(5, 15) + NLMSG_DONE_HEX), - getByteBufferFromHexString(repeat(composeSockDiagTcpHex(5, 15), 5)), - getByteBufferFromHexString(composeSockDiagTcpHex(5, 15)), - getByteBufferFromHexString(composeSockDiagTcpHex(5, 15) + NLMSG_DONE_HEX)) + doReturn(getByteBufferFromHexString(repeat(composeSockDiagTcpHex(10, 15), 5)), + getByteBufferFromHexString(composeSockDiagTcpHex(10, 15)), + getByteBufferFromHexString(composeSockDiagTcpHex(10, 15) + NLMSG_DONE_HEX), + getByteBufferFromHexString(repeat(composeSockDiagTcpHex(10, 15), 5)), + getByteBufferFromHexString(composeSockDiagTcpHex(10, 15)), + getByteBufferFromHexString(composeSockDiagTcpHex(10, 15) + NLMSG_DONE_HEX)) .when(mDependencies).recvMessage(any()); assertTrue(tst.pollSocketsInfo()); @@ -415,9 +506,9 @@ // Mocking 2 return results for different IP families(1 for IPv6; 1 for Ipv4). Use the same // message for different IP families to reduce the complexity. doReturn(getByteBufferFromHexString( - repeat(composeSockDiagTcpHex(9, 20), 8) + NLMSG_DONE_HEX), + repeat(composeSockDiagTcpHex(14, 20), 8) + NLMSG_DONE_HEX), getByteBufferFromHexString( - repeat(composeSockDiagTcpHex(9, 20), 8) + NLMSG_DONE_HEX)) + repeat(composeSockDiagTcpHex(14, 20), 8) + NLMSG_DONE_HEX)) .when(mDependencies).recvMessage(any()); assertTrue(tst.pollSocketsInfo()); @@ -435,9 +526,9 @@ // Mocking 2 return results for different IP families(1 for IPv6; 1 for Ipv4). Use the same // message for different IP families to reduce the complexity. doReturn(getByteBufferFromHexString( - NLMSG_DONE_HEX + repeat(composeSockDiagTcpHex(15, 26), 2)), + NLMSG_DONE_HEX + repeat(composeSockDiagTcpHex(20, 26), 2)), getByteBufferFromHexString( - NLMSG_DONE_HEX + repeat(composeSockDiagTcpHex(15, 26), 2))) + NLMSG_DONE_HEX + repeat(composeSockDiagTcpHex(20, 26), 2))) .when(mDependencies).recvMessage(any()); assertTrue(tst.pollSocketsInfo()); // Another 1 time for IPv6 and 1 time for IPv4 @@ -480,10 +571,17 @@ final ByteBuffer bb = ByteBuffer.allocate(size); bb.order(order); switch (size) { - case Short.BYTES -> bb.putShort((short) v); - case Integer.BYTES -> bb.putInt((int) v); - case Long.BYTES -> bb.putLong(v); - default -> throw new IllegalArgumentException("Unsupported size: " + size); + case Short.BYTES: + bb.putShort((short) v); + break; + case Integer.BYTES: + bb.putInt((int) v); + break; + case Long.BYTES: + bb.putLong(v); + break; + default: + throw new IllegalArgumentException("Unsupported size: " + size); } String s = ""; for (byte b : bb.array()) { @@ -492,119 +590,181 @@ return s; } - private static String composeSockDiagTcpHex(int lost, int sent) { - return composeSockDiagTcpHex(lost, sent, TEST_DST_PORT, TEST_COOKIE1); + private static String composeSockDiagTcpHex(int retrans, int sent) { + return composeSockDiagTcpHex(retrans, sent, TEST_DST_PORT, TEST_COOKIE1, TEST_UID1); } - private static String composeSockDiagTcpHex(int lost, int sent, short dstPort, long cookie) { + private static String composeSockDiagTcpHex(int retrans, int sent, short dstPort, + long cookie, int uid) { return // struct nlmsghdr. - "14010000" + // length = 276 - "1400" + // type = SOCK_DIAG_BY_FAMILY - "0301" + // flags = NLM_F_REQUEST | NLM_F_DUMP - "00000000" + // seqno - "00000000" + // pid (0 == kernel) + "14010000" // length = 276 + + "1400" // type = SOCK_DIAG_BY_FAMILY + + "0301" // flags = NLM_F_REQUEST | NLM_F_DUMP + + "00000000" // seqno + + "00000000" // pid (0 == kernel) // struct inet_diag_req_v2 - "02" + // family = AF_INET - "06" + // state - "00" + // timer - "00" + // retrans + + "02" // family = AF_INET + + "06" // state + + "00" // timer + + "00" // retrans // inet_diag_sockid: ports and addresses are always in big endian, // see StructInetDiagSockId. - "DEA5" + // idiag_sport = 56997 - getHexStringFromShort(dstPort, ByteOrder.BIG_ENDIAN) + // idiag_dport - "0a006402000000000000000000000000" + // idiag_src = 10.0.100.2 - "08080808000000000000000000000000" + // idiag_dst = 8.8.8.8 - "00000000" + // idiag_if - getHexStringFromLong(cookie) + // idiag_cookie - "00000000" + // idiag_expires - "00000000" + // idiag_rqueue - "00000000" + // idiag_wqueue - getHexStringFromInt(TEST_UID1) + // idiag_uid - "00000000" + // idiag_inode + + "DEA5" // idiag_sport = 56997 + + getHexStringFromShort(dstPort, ByteOrder.BIG_ENDIAN) // idiag_dport + + "0a006402000000000000000000000000" // idiag_src = 10.0.100.2 + + "08080808000000000000000000000000" // idiag_dst = 8.8.8.8 + + "00000000" // idiag_if + + getHexStringFromLong(cookie) // idiag_cookie + + "00000000" // idiag_expires + + "00000000" // idiag_rqueue + + "00000000" // idiag_wqueue + + getHexStringFromInt(uid) // idiag_uid + + "00000000" // idiag_inode // rtattr - "0500" + // len = 5 - "0800" + // type = 8 - "00000000" + // data - "0800" + // len = 8 - "0F00" + // type = 15(INET_DIAG_MARK) - "850A0C00" + // data, socket mark=789125 - "AC00" + // len = 172 - "0200" + // type = 2(INET_DIAG_INFO) + + "0500" // len = 5 + + "0800" // type = 8 + + "00000000" // data + + "0800" // len = 8 + + "0F00" // type = 15(INET_DIAG_MARK) + + "850A0C00" // data, socket mark=789125 + + "AC00" // len = 172 + + "0200" // type = 2(INET_DIAG_INFO) // tcp_info - "01" + // state = TCP_ESTABLISHED - "00" + // ca_state = TCP_CA_OPEN - "05" + // retransmits = 5 - "00" + // probes = 0 - "00" + // backoff = 0 - "07" + // option = TCPI_OPT_WSCALE|TCPI_OPT_SACK|TCPI_OPT_TIMESTAMPS - "88" + // wscale = 8 - "00" + // delivery_rate_app_limited = 0 - "4A911B00" + // rto = 1806666 - "00000000" + // ato = 0 - "2E050000" + // sndMss = 1326 - "18020000" + // rcvMss = 536 - "00000000" + // unsacked = 0 - "00000000" + // acked = 0 - getHexStringFromInt(lost) + // lost - "00000000" + // retrans = 0 - "00000000" + // fackets = 0 - "BB000000" + // lastDataSent = 187 - "00000000" + // lastAckSent = 0 - "BB000000" + // lastDataRecv = 187 - "BB000000" + // lastDataAckRecv = 187 - "DC050000" + // pmtu = 1500 - "30560100" + // rcvSsthresh = 87600 - "3E2C0900" + // rttt = 601150 - "1F960400" + // rttvar = 300575 - "78050000" + // sndSsthresh = 1400 - "0A000000" + // sndCwnd = 10 - "A8050000" + // advmss = 1448 - "03000000" + // reordering = 3 - "00000000" + // rcvrtt = 0 - "30560100" + // rcvspace = 87600 - "00000000" + // totalRetrans = 0 - "53AC000000000000" + // pacingRate = 44115 - "FFFFFFFFFFFFFFFF" + // maxPacingRate = 18446744073709551615 - "0100000000000000" + // bytesAcked = 1 - "0000000000000000" + // bytesReceived = 0 - getHexStringFromInt(sent) + // SegsOut - "00000000" + // SegsIn = 0 - "00000000" + // NotSentBytes = 0 - "3E2C0900" + // minRtt = 601150 - "00000000" + // DataSegsIn = 0 - "00000000" + // DataSegsOut = 0 - "0000000000000000"; // deliverRate = 0 + + "01" // state = TCP_ESTABLISHED + + "00" // ca_state = TCP_CA_OPEN + + "05" // retransmits = 5 + + "00" // probes = 0 + + "00" // backoff = 0 + + "07" // option = TCPI_OPT_WSCALE|TCPI_OPT_SACK|TCPI_OPT_TIMESTAMPS + + "88" // wscale = 8 + + "00" // delivery_rate_app_limited = 0 + + "4A911B00" // rto = 1806666 + + "00000000" // ato = 0 + + "2E050000" // sndMss = 1326 + + "18020000" // rcvMss = 536 + + "00000000" // unsacked = 0 + + "00000000" // acked = 0 + + "00000000" // lost + + "00000000" // retrans = 0 + + "00000000" // fackets = 0 + + "BB000000" // lastDataSent = 187 + + "00000000" // lastAckSent = 0 + + "BB000000" // lastDataRecv = 187 + + "BB000000" // lastDataAckRecv = 187 + + "DC050000" // pmtu = 1500 + + "30560100" // rcvSsthresh = 87600 + + "3E2C0900" // rttt = 601150 + + "1F960400" // rttvar = 300575 + + "78050000" // sndSsthresh = 1400 + + "0A000000" // sndCwnd = 10 + + "A8050000" // advmss = 1448 + + "03000000" // reordering = 3 + + "00000000" // rcvrtt = 0 + + "30560100" // rcvspace = 87600 + + getHexStringFromInt(retrans) // totalRetrans + + "53AC000000000000" // pacingRate = 44115 + + "FFFFFFFFFFFFFFFF" // maxPacingRate = 18446744073709551615 + + "0100000000000000" // bytesAcked = 1 + + "0000000000000000" // bytesReceived = 0 + + getHexStringFromInt(sent) // SegsOut + + "00000000" // SegsIn = 0 + + "00000000" // NotSentBytes = 0 + + "3E2C0900" // minRtt = 601150 + + "00000000" // DataSegsIn = 0 + + "00000000" // DataSegsOut = 0 + + "0000000000000000"; // deliverRate = 0 } - @Test @IgnoreUpTo(Build.VERSION_CODES.Q) - public void testTcpInfoParsingWithDozeMode() throws Exception { - // This test requires shims that provide API 30 access - assumeTrue(ConstantsShim.VERSION >= Build.VERSION_CODES.R); + private static final int DEEP_DOZE = 0; + private static final int LIGHT_DOZE = 1; + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + DEEP_DOZE, + LIGHT_DOZE + }) + private @interface DozeModeType {} + + @Test + public void testTcpInfoParsingWithDozeMode_enabled() throws Exception { + doReturn(false).when(mDependencies).shouldIgnoreTcpInfoForBlockedUids(); + doReturn(false).when(mDependencies).shouldDisableInLightDoze(anyBoolean()); + doTestTcpInfoDisableParsingWithDozeMode(DEEP_DOZE, true /* featureEnabled */); + } + + // Ignore blocked uids is supported on T. Thus, for pre-T device this feature is always + // needed since there is no replacement. + @IgnoreUpTo(Build.VERSION_CODES.S_V2) + @Test + public void testTcpInfoParsingWithDozeMode_disabled() throws Exception { + doReturn(true).when(mDependencies).shouldIgnoreTcpInfoForBlockedUids(); + doReturn(false).when(mDependencies).shouldDisableInLightDoze(anyBoolean()); + doTestTcpInfoDisableParsingWithDozeMode(DEEP_DOZE, false /* featureEnabled */); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2) + public void testTcpInfoDisableParsingWithLightDozeMode_enabled() throws Exception { + doReturn(true).when(mDependencies).shouldDisableInLightDoze(anyBoolean()); + doTestTcpInfoDisableParsingWithDozeMode(LIGHT_DOZE, true /* featureEnabled */); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2) + public void testTcpInfoDisableParsingWithLightDozeMode_disabled() throws Exception { + doReturn(false).when(mDependencies).shouldDisableInLightDoze(anyBoolean()); + doTestTcpInfoDisableParsingWithDozeMode(LIGHT_DOZE, false /* featureEnabled */); + } + + private void doTestTcpInfoDisableParsingWithDozeMode(@DozeModeType int dozeModeType, + boolean featureEnabled) throws Exception { final TcpSocketTracker tst = new TcpSocketTracker(mDependencies, mNetwork); + tst.setNetworkCapabilities(CELL_NOT_METERED_CAPABILITIES); final ArgumentCaptor<BroadcastReceiver> receiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class); - verify(mDependencies).addDeviceIdleReceiver(receiverCaptor.capture()); - setupNormalTestTcpInfo(); - assertTrue(tst.pollSocketsInfo()); - - // Lower the threshold. - when(mDependencies.getDeviceConfigPropertyInt(any(), eq(CONFIG_TCP_PACKETS_FAIL_PERCENTAGE), - anyInt())).thenReturn(40); - - // Trigger a config update - tst.mConfigListener.onPropertiesChanged(null /* properties */); - assertEquals(10, tst.getSentSinceLastRecv()); - assertEquals(50, tst.getLatestPacketFailPercentage()); - assertTrue(tst.isDataStallSuspected()); - - // Enable doze mode - doReturn(true).when(mPowerManager).isDeviceIdleMode(); + // Enable doze mode with 1 netlink message. + verify(mDependencies).addDeviceIdleReceiver(receiverCaptor.capture(), + anyBoolean(), anyBoolean()); final BroadcastReceiver receiver = receiverCaptor.getValue(); - receiver.onReceive(mContext, new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)); + if (dozeModeType == DEEP_DOZE) { + doReturn(true).when(mPowerManager).isDeviceIdleMode(); + receiver.onReceive(mContext, new Intent(ACTION_DEVICE_IDLE_MODE_CHANGED)); + } else { + doReturn(true).when(mPowerManager).isDeviceLightIdleMode(); + receiver.onReceive(mContext, new Intent(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED)); + } + doReturn(getByteBufferFromHexString(composeSockDiagTcpHex(9, 10) + + NLMSG_DONE_HEX)).when(mDependencies).recvMessage(any()); + + if (!featureEnabled) { + // Verify TcpInfo is still processed. + assertTrue(tst.pollSocketsInfo()); + assertEquals(10, tst.getSentSinceLastRecv()); + // Lost 4 + default 5 retrans / 10 sent. + assertEquals(90, tst.getLatestPacketFailPercentage()); + assertTrue(tst.isDataStallSuspected()); + return; + } + + // Verify counters are not updated. assertFalse(tst.pollSocketsInfo()); + assertEquals(0, tst.getSentSinceLastRecv()); + // -1 if not enough packets. + assertEquals(-1, tst.getLatestPacketFailPercentage()); assertFalse(tst.isDataStallSuspected()); + + // Disable deep/light doze mode, verify polling are processed and counters are updated. + if (dozeModeType == DEEP_DOZE) { + doReturn(false).when(mPowerManager).isDeviceIdleMode(); + receiver.onReceive(mContext, new Intent(ACTION_DEVICE_IDLE_MODE_CHANGED)); + } else { + doReturn(false).when(mPowerManager).isDeviceLightIdleMode(); + receiver.onReceive(mContext, new Intent(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED)); + } + assertTrue(tst.pollSocketsInfo()); + assertEquals(10, tst.getSentSinceLastRecv()); + // Lost 4 + default 5 retrans / 10 sent. + assertEquals(90, tst.getLatestPacketFailPercentage()); + assertTrue(tst.isDataStallSuspected()); } private void setupNormalTestTcpInfo() throws Exception { @@ -613,49 +773,35 @@ doReturn(tcpBufferV6, tcpBufferV4).when(mDependencies).recvMessage(any()); } - @Test @IgnoreAfter(Build.VERSION_CODES.Q) - public void testTcpInfoParsingNotSupportedOnQ() { - assertFalse(new TcpSocketTracker.Dependencies(mContext) - .isTcpInfoParsingSupported()); - } - - @Test @IgnoreUpTo(Build.VERSION_CODES.Q) - public void testTcpInfoParsingSupportedFromR() { - assertTrue(new TcpSocketTracker.Dependencies(mContext) - .isTcpInfoParsingSupported()); - } - private static final String BAD_DIAG_MSG_HEX = // struct nlmsghdr. - "00000058" + // length = 1476395008 - "1400" + // type = SOCK_DIAG_BY_FAMILY - "0301" + // flags = NLM_F_REQUEST | NLM_F_DUMP - "00000000" + // seqno - "00000000" + // pid (0 == kernel) + "00000058" // length = 1476395008 + + "1400" // type = SOCK_DIAG_BY_FAMILY + + "0301" // flags = NLM_F_REQUEST | NLM_F_DUMP + + "00000000" // seqno + + "00000000" // pid (0 == kernel) // struct inet_diag_req_v2 - "02" + // family = AF_INET - "06" + // state - "00" + // timer - "00" + // retrans + + "02" // family = AF_INET + + "06" // state + + "00" // timer + + "00" // retrans // inet_diag_sockid - "DEA5" + // idiag_sport = 42462 - "71B9" + // idiag_dport = 47473 - "0a006402000000000000000000000000" + // idiag_src = 10.0.100.2 - "08080808000000000000000000000000" + // idiag_dst = 8.8.8.8 - "00000000" + // idiag_if - "34ED000076270000" + // idiag_cookie = 43387759684916 - "00000000" + // idiag_expires - "00000000" + // idiag_rqueue - "00000000" + // idiag_wqueue - "00000000" + // idiag_uid - "00000000"; // idiag_inode + + "DEA5" // idiag_sport = 42462 + + "71B9" // idiag_dport = 47473 + + "0a006402000000000000000000000000" // idiag_src = 10.0.100.2 + + "08080808000000000000000000000000" // idiag_dst = 8.8.8.8 + + "00000000" // idiag_if + + "34ED000076270000" // idiag_cookie = 43387759684916 + + "00000000" // idiag_expires + + "00000000" // idiag_rqueue + + "00000000" // idiag_wqueue + + "00000000" // idiag_uid + + "00000000"; // idiag_inode private static final byte[] BAD_SOCK_DIAG_MSG_BYTES = HexEncoding.decode(BAD_DIAG_MSG_HEX.toCharArray(), false); - @Test @IgnoreUpTo(Build.VERSION_CODES.Q) // TCP info parsing is not supported on Q + @Test public void testPollSocketsInfo_BadFormat() throws Exception { - // This test requires shims that provide API 30 access - assumeTrue(ConstantsShim.VERSION >= Build.VERSION_CODES.R); final TcpSocketTracker tst = new TcpSocketTracker(mDependencies, mNetwork); setupNormalTestTcpInfo(); assertTrue(tst.pollSocketsInfo());
diff --git a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java index 19c9e5e..77e3a12 100644 --- a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java +++ b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
@@ -32,6 +32,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID; import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED; @@ -62,6 +63,7 @@ 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_PROBE_PRIVATE_IP_NO_INTERNET_VERSION; +import static com.android.networkstack.util.NetworkStackUtils.REEVALUATE_WHEN_RESUME; import static com.android.server.connectivity.NetworkMonitor.INITIAL_REEVALUATE_DELAY_MS; import static com.android.server.connectivity.NetworkMonitor.extractCharset; @@ -76,6 +78,7 @@ import static org.junit.Assert.fail; import static org.junit.Assume.assumeFalse; import static org.junit.Assume.assumeTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.after; @@ -90,7 +93,6 @@ import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; @@ -319,10 +321,17 @@ return lp; } - private static final NetworkCapabilities CELL_METERED_CAPABILITIES = new NetworkCapabilities() + private static final NetworkCapabilities CELL_SUSPENDED_METERED_CAPABILITIES = + new NetworkCapabilities() .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) .addCapability(NET_CAPABILITY_INTERNET); + private static final NetworkCapabilities CELL_METERED_CAPABILITIES = + new NetworkCapabilities() + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .addCapability(NET_CAPABILITY_INTERNET) + .addCapability(NET_CAPABILITY_NOT_SUSPENDED); + private static final NetworkCapabilities CELL_NOT_METERED_CAPABILITIES = new NetworkCapabilities() .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) @@ -551,8 +560,10 @@ return null; } }).when(mCleartextDnsNetwork).openConnection(any()); - doReturn(new ArrayMap<>()).when(mHttpConnection).getRequestProperties(); - doReturn(new ArrayMap<>()).when(mHttpsConnection).getRequestProperties(); + initHttpConnection(mHttpConnection); + initHttpConnection(mHttpsConnection); + initHttpConnection(mFallbackConnection); + initHttpConnection(mOtherFallbackConnection); mFakeDns = new FakeDns(); mFakeDns.startMocking(); @@ -578,7 +589,7 @@ return null; }).when(mContext).unregisterReceiver(any()); - resetCallbacks(); + initCallbacks(11 /* interfaceVersion */); setMinDataStallEvaluateInterval(TEST_MIN_STALL_EVALUATE_INTERVAL_MS); setDataStallEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS); @@ -603,18 +614,23 @@ 0, mCreatedNetworkMonitors.size()); assertEquals("BroadcastReceiver still registered after disconnect", 0, mRegisteredReceivers.size()); - if (mTstDependencies.isTcpInfoParsingSupported()) { - verify(mTstDependencies, times(networkMonitors.length)) - .removeDeviceConfigChangedListener(any()); - } } - private void resetCallbacks() throws Exception { - resetCallbacks(11); + private void initHttpConnection(HttpURLConnection connection) { + doReturn(new ArrayMap<>()).when(connection).getRequestProperties(); + // Explicitly set the HttpURLConnection methods so that these will not interact with real + // methods to prevent threading issue in the test. + doReturn(new HashMap<>()).when(connection).getHeaderFields(); + doReturn(null).when(connection).getHeaderField(eq("location")); + doNothing().when(connection).setInstanceFollowRedirects(anyBoolean()); + doNothing().when(connection).setConnectTimeout(anyInt()); + doNothing().when(connection).setReadTimeout(anyInt()); + doNothing().when(connection).setRequestProperty(any(), any()); + doNothing().when(connection).setUseCaches(anyBoolean()); + doNothing().when(connection).disconnect(); } - private void resetCallbacks(int interfaceVersion) throws Exception { - reset(mCallbacks); + private void initCallbacks(int interfaceVersion) throws Exception { try { doReturn(interfaceVersion).when(mCallbacks).getInterfaceVersion(); } catch (RemoteException e) { @@ -700,7 +716,6 @@ setNetworkCapabilities(nm, nc); HandlerUtils.waitForIdle(nm.getHandler(), HANDLER_TIMEOUT_MS); mCreatedNetworkMonitors.add(nm); - doReturn(false).when(mTstDependencies).isTcpInfoParsingSupported(); return nm; } @@ -1025,7 +1040,6 @@ verify(mContext, times(1)).registerReceiver(receiverCaptor.capture(), argThat(receiver -> ACTION_CONFIGURATION_CHANGED.equals(receiver.getAction(0)))); - resetCallbacks(); // New URLs with partial connectivity doReturn(TEST_HTTPS_OTHER_URL1).when(mResources).getString( R.string.config_captive_portal_https_url); @@ -1039,7 +1053,8 @@ HandlerUtils.waitForIdle(nm.getHandler(), HANDLER_TIMEOUT_MS); verifyNetworkTested(NETWORK_VALIDATION_RESULT_PARTIAL, - NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP); + NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP, + 1 /* interactions */); verify(mOtherHttpsConnection1, times(1)).getResponseCode(); verify(mOtherHttpConnection1, times(1)).getResponseCode(); } @@ -1311,7 +1326,7 @@ assertTrue(INITIAL_REEVALUATE_DELAY_MS < 2000); verify(mOtherFallbackConnection, timeout(INITIAL_REEVALUATE_DELAY_MS + HANDLER_TIMEOUT_MS)) .getResponseCode(); - verifyNetworkTested(VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded */, TEST_LOGIN_URL); + verifyNetworkTestedPortal(TEST_LOGIN_URL, 1 /* interactions */); } @Test @@ -1403,8 +1418,7 @@ + "'user-portal-url': '" + TEST_LOGIN_URL + "'}"); nm.notifyLinkPropertiesChanged(makeCapportLPs()); - verifyNetworkTested(VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded */, - TEST_LOGIN_URL); + verifyNetworkTestedPortal(TEST_LOGIN_URL, 1 /* interactions */); final ArgumentCaptor<CaptivePortalData> capportCaptor = ArgumentCaptor.forClass( CaptivePortalData.class); verify(mCallbacks).notifyCaptivePortalDataChanged(capportCaptor.capture()); @@ -1435,7 +1449,7 @@ // After notifyNetworkConnected, validation uses the capport API contents notifyNetworkConnected(nm, lp, CELL_METERED_CAPABILITIES); - verifyNetworkTested(VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded */, TEST_LOGIN_URL); + verifyNetworkTestedPortal(TEST_LOGIN_URL, 1 /* interactions */); verify(mHttpConnection, never()).getResponseCode(); verify(mCapportApiConnection).getResponseCode(); @@ -1555,25 +1569,19 @@ NETWORK_VALIDATION_RESULT_VALID, NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS, null); - resetCallbacks(); // Underlying network changed. notifyUnderlyingNetworkChange(nm, nc , List.of(new Network(TEST_NETID))); // The underlying network change should cause a re-validation - verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID, - NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS); + verifyNetworkTestedValidFromHttps(1 /* interactions */); - resetCallbacks(); notifyUnderlyingNetworkChange(nm, nc , List.of(new Network(TEST_NETID))); - // Identical networks should not cause revalidation. - verify(mCallbacks, never()).notifyNetworkTestedWithExtras(matchNetworkTestResultParcelable( - NETWORK_VALIDATION_RESULT_VALID, - NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS)); + // Identical networks should not cause revalidation. The interaction stays in 1 time which + // is verified in runNetworkTest. + verifyNetworkTestedValidFromHttps(1 /* interactions */); - resetCallbacks(); // Change to another network notifyUnderlyingNetworkChange(nm, nc , List.of(new Network(TEST_NETID2))); - verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID, - NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS); + verifyNetworkTestedValidFromHttps(2 /* interactions */); } private void notifyUnderlyingNetworkChange(NetworkMonitor nm, NetworkCapabilities nc, @@ -1584,7 +1592,7 @@ HandlerUtils.waitForIdle(nm.getHandler(), HANDLER_TIMEOUT_MS); } - @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + @Test public void testIsCaptivePortal_CapportApiNotSupported() throws Exception { // Test that on a R+ device, if NetworkStack was compiled without CaptivePortalData support // (built against Q), NetworkMonitor behaves as expected. @@ -1859,7 +1867,6 @@ public void testIsDataStall_SkipEvaluateOnValidationNotRequiredNetwork() { // Make DNS and TCP stall condition satisfied. setDataStallEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS | DATA_STALL_EVALUATION_TYPE_TCP); - doReturn(true).when(mTstDependencies).isTcpInfoParsingSupported(); doReturn(0).when(mTst).getLatestReceivedCount(); doReturn(true).when(mTst).isDataStallSuspected(); final WrappedNetworkMonitor nm = makeMonitor(CELL_NO_INTERNET_CAPABILITIES); @@ -1894,7 +1901,6 @@ @Test public void testIsDataStall_EvaluationTcp() throws Exception { - doReturn(true).when(mTstDependencies).isTcpInfoParsingSupported(); // Evaluate TCP only. Expect ignoring DNS signal. setDataStallEvaluationType(DATA_STALL_EVALUATION_TYPE_TCP); WrappedNetworkMonitor wrappedMonitor = makeMonitor(CELL_METERED_CAPABILITIES); @@ -1974,7 +1980,7 @@ @Test public void testNoInternetCapabilityValidated_OlderPlatform() throws Exception { // Before callbacks version 11, NETWORK_VALIDATION_RESULT_SKIPPED is not sent - resetCallbacks(10); + initCallbacks(10); doValidationSkippedTest(CELL_NO_INTERNET_CAPABILITIES, NETWORK_VALIDATION_RESULT_VALID); } @@ -2127,8 +2133,6 @@ // Portal URL should be detection URL. final String redirectUrl = bundle.getString(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL); assertEquals(expectedUrl, redirectUrl); - - resetCallbacks(); } @@ -2206,29 +2210,25 @@ wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns6.google", new InetAddress[0])); notifyNetworkConnected(wnm, CELL_NOT_METERED_CAPABILITIES); - verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID, PROBES_PRIVDNS_VALID); - verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyProbeStatusChanged( - eq(PROBES_PRIVDNS_VALID), eq(PROBES_PRIVDNS_VALID)); + verifyNetworkTestedValidFromPrivateDns(1 /* interactions */); + verifyProbeStatusChangedPrivateDnsCompleteAndSucceeded(1 /* interaction */); // Verify dns query only get v4 address. - resetCallbacks(); mFakeDns.setAnswer("dns4.google", new String[]{"192.0.2.1"}, TYPE_A); wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns4.google", new InetAddress[0])); - verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID, PROBES_PRIVDNS_VALID); + verifyNetworkTestedValidFromPrivateDns(2 /* interactions */); // NetworkMonitor will check if the probes has changed or not, if the probes has not - // changed, the callback won't be fired. - verify(mCallbacks, never()).notifyProbeStatusChanged( - eq(PROBES_PRIVDNS_VALID), eq(PROBES_PRIVDNS_VALID)); + // changed, the callback won't be fired. The interaction stays in 1 time. + verifyProbeStatusChangedPrivateDnsCompleteAndSucceeded(1 /* interaction */); // Verify dns query get both v4 and v6 address. - resetCallbacks(); mFakeDns.setAnswer("dns.google", new String[]{"2001:db8::54"}, TYPE_AAAA); mFakeDns.setAnswer("dns.google", new String[]{"192.0.2.3"}, TYPE_A); wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0])); - verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID, PROBES_PRIVDNS_VALID); - verify(mCallbacks, never()).notifyProbeStatusChanged( - eq(PROBES_PRIVDNS_VALID), eq(PROBES_PRIVDNS_VALID)); + verifyNetworkTestedValidFromPrivateDns(3 /* interactions */); + // Verify no further interaction. + verifyProbeStatusChangedPrivateDnsCompleteAndSucceeded(1 /* interaction */); } @Test @@ -2240,22 +2240,18 @@ WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor(); wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0])); notifyNetworkConnected(wnm, CELL_NOT_METERED_CAPABILITIES); - verifyNetworkTested(VALIDATION_RESULT_INVALID, - NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS); - verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS)).notifyProbeStatusChanged( - eq(PROBES_PRIVDNS_VALID), eq(NETWORK_VALIDATION_PROBE_DNS - | NETWORK_VALIDATION_PROBE_HTTPS)); + verifyNetworkTestedInvalidFromHttps(1 /* interactions */); + verifyProbeStatusChangedPrivateDnsCompleteAndHttpsSucceeded(1 /* interaction */); + // Fix DNS and retry, expect validation to succeed. - resetCallbacks(); mFakeDns.setAnswer("dns.google", new String[]{"2001:db8::1"}, TYPE_AAAA); wnm.forceReevaluation(Process.myUid()); // ProbeCompleted should be reset to 0 HandlerUtils.waitForIdle(wnm.getHandler(), HANDLER_TIMEOUT_MS); assertEquals(wnm.getEvaluationState().getProbeCompletedResult(), 0); - verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID, PROBES_PRIVDNS_VALID); - verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS)).notifyProbeStatusChanged( - eq(PROBES_PRIVDNS_VALID), eq(PROBES_PRIVDNS_VALID)); + verifyNetworkTestedValidFromPrivateDns(1 /* interactions */); + verifyProbeStatusChangedPrivateDnsCompleteAndSucceeded(1 /* interaction */); } @Test @@ -2267,63 +2263,102 @@ WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor(); wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0])); notifyNetworkConnected(wnm, CELL_NOT_METERED_CAPABILITIES); - verifyNetworkTested(VALIDATION_RESULT_INVALID, - NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS); - verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyProbeStatusChanged( - eq(PROBES_PRIVDNS_VALID), eq(NETWORK_VALIDATION_PROBE_DNS - | NETWORK_VALIDATION_PROBE_HTTPS)); + verifyNetworkTestedInvalidFromHttps(1 /* interactions */); + verifyProbeStatusChangedPrivateDnsCompleteAndHttpsSucceeded(1 /* interactions */); // Fix DNS and retry, expect validation to succeed. - resetCallbacks(); mFakeDns.setAnswer("dns.google", new String[]{"2001:db8::1"}, TYPE_AAAA); wnm.forceReevaluation(Process.myUid()); - verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).atLeastOnce()) - .notifyNetworkTestedWithExtras(matchNetworkTestResultParcelable( - NETWORK_VALIDATION_RESULT_VALID, PROBES_PRIVDNS_VALID)); - verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyProbeStatusChanged( - eq(PROBES_PRIVDNS_VALID), eq(PROBES_PRIVDNS_VALID)); + verifyNetworkTestedValidFromPrivateDns(1 /* interactions */); + verifyProbeStatusChangedPrivateDnsCompleteAndSucceeded(1 /* interaction */); // Change configuration to an invalid DNS name, expect validation to fail. - resetCallbacks(); mFakeDns.setAnswer("dns.bad", new String[0], TYPE_A); mFakeDns.setAnswer("dns.bad", new String[0], TYPE_AAAA); wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.bad", new InetAddress[0])); // Strict mode hostname resolve fail. Expect only notification for evaluation fail. No probe // notification. - verifyNetworkTested(VALIDATION_RESULT_INVALID, - NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS); - verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyProbeStatusChanged( - eq(PROBES_PRIVDNS_VALID), eq(NETWORK_VALIDATION_PROBE_DNS - | NETWORK_VALIDATION_PROBE_HTTPS)); + verifyNetworkTestedInvalidFromHttps(2 /* interactions */); + verifyProbeStatusChangedPrivateDnsCompleteAndHttpsSucceeded(2 /* interaction */); // Change configuration back to working again, but make private DNS not work. // Expect validation to fail. - resetCallbacks(); mFakeDns.setNonBypassPrivateDnsWorking(false); wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0])); - verifyNetworkTested(VALIDATION_RESULT_INVALID, - NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS); + verifyNetworkTestedInvalidFromHttps(3 /* interactions */); // NetworkMonitor will check if the probes has changed or not, if the probes has not - // changed, the callback won't be fired. - verify(mCallbacks, never()).notifyProbeStatusChanged( - eq(PROBES_PRIVDNS_VALID), eq(NETWORK_VALIDATION_PROBE_DNS - | NETWORK_VALIDATION_PROBE_HTTPS)); + // changed, the callback won't be fired. No further interaction. + verifyProbeStatusChangedPrivateDnsCompleteAndHttpsSucceeded(2 /* interaction */); // Make private DNS work again. Expect validation to succeed. - resetCallbacks(); mFakeDns.setNonBypassPrivateDnsWorking(true); wnm.forceReevaluation(Process.myUid()); - verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID, PROBES_PRIVDNS_VALID); - verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyProbeStatusChanged( - eq(PROBES_PRIVDNS_VALID), eq(PROBES_PRIVDNS_VALID)); + verifyNetworkTestedValidFromPrivateDns(1 /* interactions */); + verifyProbeStatusChangedPrivateDnsCompleteAndSucceeded(1 /* interaction */); + } + + @Test + public void testReevaluationInterval_networkResume() throws Exception { + // Setup nothing and expect validation to fail. + doReturn(true).when(mDependencies).isFeatureEnabled(any(), eq(REEVALUATE_WHEN_RESUME)); + final NetworkMonitor nm = runFailedNetworkTest(); + verifyNetworkTested(VALIDATION_RESULT_INVALID, 0 /* probesSucceeded */, + 1 /* interactions */); + // Reevaluation delay doubled right after 1st validation failure. + assertEquals(INITIAL_REEVALUATE_DELAY_MS * 2, nm.getReevaluationDelayMs()); + + // Suspend the network. Verify re-evaluation count does not increase. + setNetworkCapabilities(nm, CELL_SUSPENDED_METERED_CAPABILITIES); + verifyNetworkTested(VALIDATION_RESULT_INVALID, 0 /* probesSucceeded */, + 1 /* interactions */); + // Verify the count does not increase. + assertEquals(INITIAL_REEVALUATE_DELAY_MS * 2, nm.getReevaluationDelayMs()); + + // Resume the network, verify re-evaluation runs immediately and the timer resets. + setNetworkCapabilities(nm, CELL_METERED_CAPABILITIES); + // Wait for another idle to prevent from flaky because the handler fires another message + // to re-evaluate. + HandlerUtils.waitForIdle(nm.getHandler(), HANDLER_TIMEOUT_MS); + assertEquals(INITIAL_REEVALUATE_DELAY_MS, nm.getReevaluationDelayMs()); + verifyNetworkTested(VALIDATION_RESULT_INVALID, 0 /* probesSucceeded */, + 2 /* interactions */); + } + + @Test + public void testReevaluationInterval_verifiedNetwork() throws Exception { + final WrappedNetworkMonitor wnm = prepareValidatedStateNetworkMonitor( + CELL_METERED_CAPABILITIES); + assertEquals(INITIAL_REEVALUATE_DELAY_MS, wnm.getReevaluationDelayMs()); + + // Suspend the network. Verify re-evaluation count does not increase. + setNetworkCapabilities(wnm, CELL_SUSPENDED_METERED_CAPABILITIES); + verifyNetworkTestedValidFromHttps(1 /* interactions */); + assertEquals(INITIAL_REEVALUATE_DELAY_MS, wnm.getReevaluationDelayMs()); + + // Resume the network. Verify re-evaluation count does not increase. + setNetworkCapabilities(wnm, CELL_METERED_CAPABILITIES); + verifyNetworkTestedValidFromHttps(1 /* interactions */); + assertEquals(INITIAL_REEVALUATE_DELAY_MS, wnm.getReevaluationDelayMs()); + } + + @Test + public void testTcpSocketTracker_setCapabilities() throws Exception { + setDataStallEvaluationType(DATA_STALL_EVALUATION_TYPE_TCP); + final InOrder inOrder = inOrder(mTst); + final WrappedNetworkMonitor wnm = prepareValidatedStateNetworkMonitor( + CELL_METERED_CAPABILITIES); + inOrder.verify(mTst).setNetworkCapabilities(eq(CELL_METERED_CAPABILITIES)); + + // Suspend the network. Verify the capabilities would be passed to TcpSocketTracker. + setNetworkCapabilities(wnm, CELL_SUSPENDED_METERED_CAPABILITIES); + inOrder.verify(mTst).setNetworkCapabilities(eq(CELL_SUSPENDED_METERED_CAPABILITIES)); } @Test public void testDataStall_setOpportunisticMode() { setDataStallEvaluationType(DATA_STALL_EVALUATION_TYPE_TCP); - doReturn(true).when(mTstDependencies).isTcpInfoParsingSupported(); WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor(); InOrder inOrder = inOrder(mTst); // Initialized with default value. @@ -2360,7 +2395,7 @@ private void testDataStall_StallDnsSuspectedAndSendMetrics(int transport, NetworkCapabilities nc) throws Exception { // NM suspects data stall from DNS signal and sends data stall metrics. - final WrappedNetworkMonitor nm = prepareNetworkMonitorForVerifyDataStall(nc); + final WrappedNetworkMonitor nm = prepareValidatedStateNetworkMonitor(nc); makeDnsTimeoutEvent(nm, 5); // Trigger a dns signal to start evaluate data stall and upload metrics. nm.notifyDnsResponse(RETURN_CODE_DNS_TIMEOUT); @@ -2370,7 +2405,7 @@ @Test public void testDataStall_NoStallSuspectedAndSendMetrics() throws Exception { - final WrappedNetworkMonitor nm = prepareNetworkMonitorForVerifyDataStall( + final WrappedNetworkMonitor nm = prepareValidatedStateNetworkMonitor( CELL_METERED_CAPABILITIES); // Setup no data stall dns signal. makeDnsTimeoutEvent(nm, 3); @@ -2392,12 +2427,11 @@ private void testDataStall_StallTcpSuspectedAndSendMetrics(NetworkCapabilities nc) throws Exception { - assumeTrue(ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q)); setupTcpDataStall(); - setTcpPollingInterval(0); + setTcpPollingInterval(1); // NM suspects data stall from TCP signal and sends data stall metrics. setDataStallEvaluationType(DATA_STALL_EVALUATION_TYPE_TCP); - final WrappedNetworkMonitor nm = prepareNetworkMonitorForVerifyDataStall(nc); + final WrappedNetworkMonitor nm = prepareValidatedStateNetworkMonitor(nc); // Trigger a tcp event immediately. nm.sendTcpPollingEvent(); // Allow only one transport type in the context of this test for simplification. @@ -2406,7 +2440,7 @@ verifySendDataStallDetectionStats(nm, DATA_STALL_EVALUATION_TYPE_TCP, transports[0]); } - private WrappedNetworkMonitor prepareNetworkMonitorForVerifyDataStall(NetworkCapabilities nc) + private WrappedNetworkMonitor prepareValidatedStateNetworkMonitor(NetworkCapabilities nc) throws Exception { // Connect a VALID network to simulate the data stall detection because data stall // evaluation will only start from validated state. @@ -2423,14 +2457,12 @@ fail("Undefined transport type"); } notifyNetworkConnected(nm, nc); - verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID, - NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS); + verifyNetworkTestedValidFromHttps(1 /* interactions */); nm.setLastProbeTime(SystemClock.elapsedRealtime() - STALL_EXPECTED_LAST_PROBE_TIME_MS); return nm; } private void setupTcpDataStall() { - doReturn(true).when(mTstDependencies).isTcpInfoParsingSupported(); doReturn(0).when(mTst).getLatestReceivedCount(); doReturn(TEST_TCP_FAIL_RATE).when(mTst).getLatestPacketFailPercentage(); doReturn(TEST_TCP_PACKET_COUNT).when(mTst).getSentSinceLastRecv(); @@ -2445,7 +2477,9 @@ ArgumentCaptor.forClass(CaptivePortalProbeResult.class); final ArgumentCaptor<DataStallDetectionStats> statsCaptor = ArgumentCaptor.forClass(DataStallDetectionStats.class); - verify(mDependencies, timeout(HANDLER_TIMEOUT_MS).times(1)) + // TCP data stall detection may be triggered more than once because NM stays in the + // ValidatedState and polling timer is set to 0. + verify(mDependencies, timeout(HANDLER_TIMEOUT_MS).atLeast(1)) .writeDataStallDetectionStats(statsCaptor.capture(), probeResultCaptor.capture()); // Ensure probe will not stop due to rate-limiting mechanism. nm.setLastProbeTime(SystemClock.elapsedRealtime() - STALL_EXPECTED_LAST_PROBE_TIME_MS); @@ -2600,26 +2634,22 @@ @Test public void testCollectDataStallMetrics_TcpWithCellular() { - assumeTrue(ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q)); testDataStallMetricsWithCellular(DATA_STALL_EVALUATION_TYPE_TCP); } @Test public void testCollectDataStallMetrics_TcpWithWiFi() { - assumeTrue(ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q)); testDataStallMetricsWithWiFi(DATA_STALL_EVALUATION_TYPE_TCP); } @Test public void testCollectDataStallMetrics_TcpAndDnsWithWifi() { - assumeTrue(ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q)); testDataStallMetricsWithWiFi( DATA_STALL_EVALUATION_TYPE_TCP | DATA_STALL_EVALUATION_TYPE_DNS); } @Test public void testCollectDataStallMetrics_TcpAndDnsWithCellular() { - assumeTrue(ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q)); testDataStallMetricsWithCellular( DATA_STALL_EVALUATION_TYPE_TCP | DATA_STALL_EVALUATION_TYPE_DNS); } @@ -2633,11 +2663,11 @@ NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP, null /* redirectUrl */); - resetCallbacks(); nm.setAcceptPartialConnectivity(); // Expect to update evaluation result notifications to CS. verifyNetworkTested(NETWORK_VALIDATION_RESULT_PARTIAL | NETWORK_VALIDATION_RESULT_VALID, - NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP); + NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP, + 1 /* interactions */); } @Test @@ -2706,27 +2736,24 @@ @Test public void testNotifyNetwork_WithforceReevaluation() throws Exception { + // Set validated result for both HTTP and HTTPS probes. setValidProbes(); final NetworkMonitor nm = runValidatedNetworkTest(); // Verify forceReevaluation will not reset the validation result but only probe result until // getting the validation result. - resetCallbacks(); setSslException(mHttpsConnection); - setStatus(mHttpConnection, 500); - setStatus(mFallbackConnection, 204); nm.forceReevaluation(Process.myUid()); // Expect to send HTTP, HTTPs, FALLBACK and evaluation results. - verifyNetworkTested(VALIDATION_RESULT_INVALID, - NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_FALLBACK, - null /* redirectUrl */); - HandlerUtils.waitForIdle(nm.getHandler(), HANDLER_TIMEOUT_MS); + verifyNetworkTested(NETWORK_VALIDATION_RESULT_PARTIAL, + NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP, + 1 /* interactions */); } @Test public void testNotifyNetwork_NotifyNetworkTestedOldInterfaceVersion() throws Exception { // Use old interface version so notifyNetworkTested is used over // notifyNetworkTestedWithExtras - resetCallbacks(4); + initCallbacks(4); // Trigger Network validation setStatus(mHttpsConnection, 204); @@ -2813,8 +2840,6 @@ setValidProbes(); final NetworkMonitor nm = runValidatedNetworkTest(); - resetCallbacks(); - nm.reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, CaptivePortalProbeResult.success(1 << PROBE_HTTP)); // Verify result should be appended and notifyNetworkTestedWithExtras callback is triggered @@ -2842,20 +2867,22 @@ nm.getEvaluationState().reportEvaluationResult(NETWORK_VALIDATION_RESULT_VALID, null /* redirectUrl */); verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID, - NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP); + NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP, + 1 /* interactions */); nm.getEvaluationState().reportEvaluationResult( NETWORK_VALIDATION_RESULT_VALID | NETWORK_VALIDATION_RESULT_PARTIAL, null /* redirectUrl */); verifyNetworkTested( NETWORK_VALIDATION_RESULT_VALID | NETWORK_VALIDATION_RESULT_PARTIAL, - NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP); + NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP, + 1 /* interactions */); nm.getEvaluationState().reportEvaluationResult(VALIDATION_RESULT_INVALID, TEST_REDIRECT_URL); verifyNetworkTested(VALIDATION_RESULT_INVALID, NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP, - TEST_REDIRECT_URL); + TEST_REDIRECT_URL, 1 /* interactions */); } @Test @@ -2997,14 +3024,13 @@ verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)) .showProvisioningNotification(any(), any()); assertCaptivePortalAppReceiverRegistered(true /* isPortal */); - verifyNetworkTested(VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded */, TEST_LOGIN_URL); + verifyNetworkTestedPortal(TEST_LOGIN_URL, 1 /* interactions */); // Force reevaluation and confirm that the network is still captive HandlerUtils.waitForIdle(monitor.getHandler(), HANDLER_TIMEOUT_MS); - resetCallbacks(); monitor.forceReevaluation(Process.myUid()); assertEquals(monitor.getEvaluationState().getProbeCompletedResult(), 0); - verifyNetworkTested(VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded */, TEST_LOGIN_URL); + verifyNetworkTestedPortal(TEST_LOGIN_URL, 2 /* interactions */); // Check that startCaptivePortalApp sends the expected intent. monitor.launchCaptivePortalApp(); @@ -3197,21 +3223,59 @@ int testResult, int probesSucceeded, String redirectUrl) throws Exception { final NetworkMonitor monitor = makeMonitor(nc); notifyNetworkConnected(monitor, config, lp, nc); - verifyNetworkTested(testResult, probesSucceeded, redirectUrl); + verifyNetworkTested(testResult, probesSucceeded, redirectUrl, 1 /* interactions */); HandlerUtils.waitForIdle(monitor.getHandler(), HANDLER_TIMEOUT_MS); return monitor; } - private void verifyNetworkTested(int testResult, int probesSucceeded) throws Exception { - verifyNetworkTested(testResult, probesSucceeded, null /* redirectUrl */); + private void verifyProbeStatusChangedPrivateDnsCompleteAndSucceeded(int interactions) + throws Exception { + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(interactions)) + .notifyProbeStatusChanged(eq(PROBES_PRIVDNS_VALID), eq(PROBES_PRIVDNS_VALID)); } - private void verifyNetworkTested(int testResult, int probesSucceeded, String redirectUrl) - throws RemoteException { + private void verifyProbeStatusChangedPrivateDnsCompleteAndHttpsSucceeded(int interactions) + throws Exception { + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(interactions)) + .notifyProbeStatusChanged( + eq(PROBES_PRIVDNS_VALID), + eq(NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS)); + } + + private void verifyNetworkTestedInvalidFromHttps(int interactions) throws Exception { + verifyNetworkTested(VALIDATION_RESULT_INVALID, + NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS, + interactions); + } + + private void verifyNetworkTestedPortal(String redirectUrl, int interactions) throws Exception { + verifyNetworkTested(VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded */, redirectUrl, + interactions); + } + + private void verifyNetworkTestedValidFromHttps(int interactions) throws Exception { + verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID, + NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS, + interactions); + } + + private void verifyNetworkTestedValidFromPrivateDns(int interactions) throws Exception { + verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID, PROBES_PRIVDNS_VALID, interactions); + } + + private void verifyNetworkTested(int testResult, int probesSucceeded, int interactions) + throws Exception { + verifyNetworkTested(testResult, probesSucceeded, null /* redirectUrl */, interactions); + } + + private void verifyNetworkTested(int testResult, int probesSucceeded, String redirectUrl, + int interactions) throws RemoteException { try { - verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS)).notifyNetworkTestedWithExtras( - matchNetworkTestResultParcelable(testResult, probesSucceeded, redirectUrl)); + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(interactions)) + .notifyNetworkTestedWithExtras( + matchNetworkTestResultParcelable( + testResult, probesSucceeded, redirectUrl)); } catch (AssertionFailedError e) { // Capture the callbacks up to now to give a better error message final ArgumentCaptor<NetworkTestResultParcelable> captor = @@ -3221,14 +3285,15 @@ // call which failed, but this time use a captor to log the exact parcel sent by // NetworkMonitor. // This assertion will fail if notifyNetworkTested was not called at all. - verify(mCallbacks).notifyNetworkTestedWithExtras(captor.capture()); + verify(mCallbacks, times(interactions)).notifyNetworkTestedWithExtras(captor.capture()); - final NetworkTestResultParcelable lastResult = captor.getValue(); - fail(String.format("notifyNetworkTestedWithExtras was not called with the " + final List<NetworkTestResultParcelable> results = captor.getAllValues(); + final NetworkTestResultParcelable lastResult = results.get(results.size() - 1); + fail(String.format("notifyNetworkTestedWithExtras was not called %d times with the " + "expected result within timeout. " + "Expected result %d, probes succeeded %d, redirect URL %s, " + "last result was (%d, %d, %s).", - testResult, probesSucceeded, redirectUrl, + interactions, testResult, probesSucceeded, redirectUrl, lastResult.result, lastResult.probesSucceeded, lastResult.redirectUrl)); } }