Snap for 11273583 from 499764925927c69d361dc754cedd2def1bafc26c to mainline-wifi-release Change-Id: If91b6adb34094709d454a6c110815ac58613da5d
diff --git a/Android.bp b/Android.bp index 8ee5d6f..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,7 +86,7 @@ java_defaults { name: "NetworkStackReleaseApiLevel", - defaults:["NetworkStackReleaseTargetSdk"], + defaults: ["NetworkStackReleaseTargetSdk"], sdk_version: module_34_version, libs: [ "framework-configinfrastructure", @@ -93,7 +94,7 @@ "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 { @@ -196,6 +207,9 @@ ], sdk_version: "module_33", visibility: ["//visibility:private"], + lint: { + baseline_filename: "lint-baseline.xml", + }, } java_library { @@ -217,6 +231,9 @@ ], 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 @@ -228,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", ], @@ -243,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. @@ -274,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 @@ -281,7 +307,10 @@ // the networkstack code. java_library { name: "NetworkStackApiStableShims", - defaults: ["NetworkStackShimsDefaults", "NetworkStackReleaseApiLevel"], + defaults: [ + "NetworkStackShimsDefaults", + "NetworkStackReleaseApiLevel", + ], static_libs: [ "NetworkStackShimsCommon", "NetworkStackApi29Shims", @@ -297,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 @@ -320,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", ], } @@ -336,11 +368,10 @@ ], srcs: [ "src/**/*.java", - ":statslog-networkstack-java-gen-current" + ":statslog-networkstack-java-gen-current", ], static_libs: [ "NetworkStackApiCurrentShims", - "net-utils-device-common-struct", ], manifest: "AndroidManifestBase.xml", visibility: [ @@ -349,19 +380,24 @@ "//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", - "net-utils-device-common-struct", ], manifest: "AndroidManifestBase.xml", visibility: [ @@ -372,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 { @@ -390,6 +429,7 @@ "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. @@ -397,6 +437,9 @@ "//packages/modules/Connectivity/Tethering/tests/integration", "//packages/modules/Connectivity/tests/cts/net", ], + lint: { + baseline_filename: "lint-baseline.xml", + }, } java_genrule { @@ -456,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 @@ -473,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 @@ -491,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", @@ -505,13 +563,16 @@ "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", @@ -549,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"], } @@ -558,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" @@ -571,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 @@ -602,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/common/networkstackclient/Android.bp b/common/networkstackclient/Android.bp index e056e3b..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 { @@ -164,12 +197,13 @@ 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 { @@ -178,7 +212,7 @@ min_sdk_version: "30", static_libs: [ "ipmemorystore-aidl-interfaces-V10-java", - "networkstack-aidl-interfaces-V19-java", + "networkstack-aidl-interfaces-V20-java", ], visibility: ["//packages/modules/NetworkStack:__subpackages__"], apex_available: [ @@ -187,6 +221,9 @@ "com.android.tethering", "com.android.wifi", ], + lint: { + baseline_filename: "lint-baseline.xml", + }, } java_library { @@ -231,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/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/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/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/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/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/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 3ab5e0e..ee2990b 100644 --- a/src/android/net/apf/ApfFilter.java +++ b/src/android/net/apf/ApfFilter.java
@@ -45,11 +45,13 @@ 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.os.PowerManager; import android.os.SystemClock; +import android.stats.connectivity.NetworkQuirkEvent; import android.system.ErrnoException; import android.system.Os; import android.text.format.DateUtils; @@ -69,6 +71,9 @@ 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; @@ -87,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} @@ -117,61 +123,16 @@ public int minRdnssLifetimeSec; public int acceptRaMinLft; public boolean shouldHandleLightDoze; + public long minMetricsSessionDurationMs; } - /** - * 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_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, - 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; + /** 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(); } } @@ -238,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; @@ -262,7 +224,7 @@ 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; @@ -293,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 @@ -303,7 +267,6 @@ ETH_HEADER_LEN + UDP_HEADER_LEN + DNS_HEADER_LEN; - private final ApfCapabilities mApfCapabilities; private final IpClientCallbacksWrapper mIpClientCallback; private final InterfaceParams mInterfaceParams; @@ -322,14 +285,38 @@ 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; + // 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()); } @@ -386,13 +373,23 @@ private final Dependencies mDependencies; public ApfFilter(Context context, ApfConfiguration config, InterfaceParams ifParams, - IpClientCallbacksWrapper ipClientCallback) { - this(context, config, ifParams, ipClientCallback, new Dependencies(context)); + 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, Dependencies dependencies) { + 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; @@ -403,6 +400,12 @@ 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"; @@ -457,10 +460,25 @@ 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) { @@ -516,7 +534,9 @@ // 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 @@ -537,7 +557,7 @@ // Returns seconds since device boot. @VisibleForTesting protected int secondsSinceBoot() { - return (int) (SystemClock.elapsedRealtime() / DateUtils.SECOND_IN_MILLIS); + return (int) (mClock.elapsedRealtime() / DateUtils.SECOND_IN_MILLIS); } public static class InvalidRaException extends Exception { @@ -642,6 +662,14 @@ // 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 private final int mMinLifetime; // When the packet was last captured, in seconds since Unix Epoch @@ -825,6 +853,34 @@ 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; + } + // 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 @@ -860,8 +916,9 @@ // 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, routerLifetime, mAcceptRaMinLft); + 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); @@ -885,6 +942,9 @@ lifetime = getUint32(mPacket, mPacket.position()); addLifetimeSection(ICMP6_PREFIX_OPTION_VALID_LIFETIME_LEN, lifetime, mAcceptRaMinLft); + mMinPioValidLifetime = getMinForPositiveValue( + mMinPioValidLifetime, lifetime); + if (lifetime == 0) mNumZeroLifetimeRas++; // Parse preferred lifetime lifetime = getUint32(mPacket, mPacket.position()); @@ -901,10 +961,15 @@ case ICMP6_RDNSS_OPTION_TYPE: mRdnssOptionOffsets.add(position); lifetime = add4ByteLifetimeOption(optionLength, mMinRdnssLifetimeSec); + mMinRdnssLifetime = getMinForPositiveValue(mMinRdnssLifetime, lifetime); + if (lifetime == 0) mNumZeroLifetimeRas++; break; case ICMP6_ROUTE_INFO_OPTION_TYPE: mRioOptionOffsets.add(position); lifetime = add4ByteLifetimeOption(optionLength, mAcceptRaMinLft); + mMinRioRouteLifetime = getMinForPositiveValue( + mMinRioRouteLifetime, lifetime); + if (lifetime == 0) mNumZeroLifetimeRas++; break; case ICMP6_SOURCE_LL_ADDRESS_OPTION_TYPE: case ICMP6_MTU_OPTION_TYPE: @@ -1083,7 +1148,7 @@ // 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); @@ -1218,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 @@ -1233,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); @@ -1333,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 @@ -1355,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); @@ -1416,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; @@ -1451,7 +1522,7 @@ // Drop if not ARP IPv4. gen.addLoadImmediate(Register.R0, ARP_HEADER_OFFSET); maybeSetupCounter(gen, Counter.DROPPED_ARP_NON_IPV4); - gen.addJumpIfBytesNotEqual(Register.R0, ARP_IPV4_HEADER, mCountAndDropLabel); + gen.addJumpIfBytesAtR0NotEqual(ARP_IPV4_HEADER, mCountAndDropLabel); // Drop if unknown ARP opcode. gen.addLoad16(Register.R0, ARP_OPCODE_OFFSET); @@ -1467,7 +1538,7 @@ // 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 request, or a broadcast reply. gen.defineLabel(checkTargetIPv4); @@ -1481,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); @@ -1518,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); @@ -1551,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 @@ -1690,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); @@ -1748,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); @@ -1766,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); @@ -1782,7 +1858,7 @@ // 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); gen.addLoad16Indexed(Register.R0, MDNS_QDCOUNT_OFFSET); @@ -1798,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 @@ -1817,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, @@ -1844,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); @@ -1881,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); } @@ -1912,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); @@ -1983,6 +2091,7 @@ // 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; } @@ -1993,6 +2102,7 @@ // 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; } @@ -2009,13 +2119,19 @@ 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); + // 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); @@ -2026,6 +2142,20 @@ log(msg + HexDump.toHexString(packet, 0, length, false /* lowercase */)); } + // 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. @@ -2039,9 +2169,20 @@ 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. @@ -2077,6 +2218,7 @@ return; } } + mMaxDistinctRas = Math.max(mMaxDistinctRas, mRas.size() + 1); if (mRas.size() >= MAX_RAS) { // Remove the last (i.e. oldest) RA. mRas.remove(mRas.size() - 1); @@ -2096,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; @@ -2113,10 +2256,41 @@ return null; } - return new ApfFilter(context, config, ifParams, ipClientCallback); + 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. @@ -2246,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")); @@ -2347,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); @@ -2406,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 e14364e..c6ff441 100644 --- a/src/android/net/apf/ApfGenerator.java +++ b/src/android/net/apf/ApfGenerator.java
@@ -16,6 +16,11 @@ 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 java.util.ArrayList; @@ -26,7 +31,7 @@ * 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 */ @@ -41,6 +46,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]" @@ -54,7 +68,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" @@ -64,12 +83,17 @@ 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" - WRITE(24), // Write 1, 2 or 4 bytes imm to the output buffer, e.g. "WRITE 5" - // Copy the data from input packet or APF data region to output buffer. Register bit is - // used to specify the source of data copy: R=0 means copy from packet, R=1 means copy - // from APF data region. The source offset is encoded in the first imm and the copy length - // is encoded in the second imm. "e.g. MEMCOPY(R=0), 5, 5" - MEMCOPY(25); + // 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; @@ -86,16 +110,30 @@ NEG(33), // Negate, e.g. "neg R0" SWAP(34), // Swap, e.g. "swap R0,R1" MOVE(35), // Move, e.g. "move R0,R1" - ALLOC(36), // Allocate buffer, "e.g. ALLOC R0" - TRANS(37), // Transmit buffer, "e.g. TRANS R0" - EWRITE1(38), // Write 1 byte from register to the output buffer, e.g. "EWRITE1 R0" - EWRITE2(39), // Write 2 bytes from register to the output buffer, e.g. "EWRITE2 R0" - EWRITE4(40), // Write 4 bytes from register to the output buffer, e.g. "EWRITE4 R0" - // Copy the data from input packet to output buffer. The source offset is encoded as [Rx - // + second imm]. The copy length is encoded in the third imm. "e.g. EPKTCOPY [R0 + 5], 5" + // 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), - // Copy the data from APF data region to output buffer. The source offset is encoded as [Rx - // + second imm]. The copy length is encoded in the third imm. "e.g. EDATACOPY [R0 + 5], 5" EDATACOPY(42); final int value; @@ -115,79 +153,223 @@ } } - private static class Immediate { - public final boolean mSigned; - public final byte mImmSize; + 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; - Immediate(int value, boolean signed) { - this(value, signed, calculateImmSize(value, signed)); + IntImmediate(int value, IntImmediateType type) { + mImmediateType = type; + mValue = value; } - Immediate(int value, boolean signed, byte size) { - mValue = value; - mSigned = signed; - mImmSize = size; + 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 "Immediate{" + "mSigned=" + mSigned + ", mImmSize=" + mImmSize + ", mValue=" - + mValue + '}'; + return "IntImmediate{" + "mImmediateType=" + mImmediateType + ", mValue=" + mValue + + '}'; } } private class Instruction { private final byte mOpcode; // A "Opcode" value. private final byte mRegister; // A "Register" value. - private final int mMaxSupportedImms; - public final List<Immediate> mImms = new ArrayList<>(); + 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) { - this(opcode, register, 1 /* maxSupportedImm */); - } - - Instruction(Opcodes opcode, Register register, int maxSupportedImms) { mOpcode = (byte) opcode.value; mRegister = (byte) register.value; - mMaxSupportedImms = maxSupportedImms; + } + + 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 addUnsignedImm(int imm) { - addImm(new Immediate(imm, false)); + Instruction(ExtendedOpcodes extendedOpcodes) { + this(extendedOpcodes, R0); } - void addUnsignedImm(int imm, byte size) { - addImm(new Immediate(imm, false, size)); + Instruction addSigned(int imm) { + mIntImms.add(IntImmediate.newSigned(imm)); + return this; } - void addSignedImm(int imm) { - addImm(new Immediate(imm, true)); + Instruction addUnsigned(int imm) { + mIntImms.add(IntImmediate.newUnsigned(imm)); + return this; } - void addImm(Immediate imm) { - if (mImms.size() == mMaxSupportedImms) { - throw new IllegalArgumentException( - String.format("Opcode: %d only support at max: %d imms", mOpcode, - mMaxSupportedImms)); - } - mImms.add(imm); + + Instruction addTwosCompSigned(int imm) { + mIntImms.add(IntImmediate.newTwosComplementSigned(imm)); + return this; } - void setLabel(String label) throws IllegalInstructionException { + + 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); } @@ -196,18 +378,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; } /** @@ -218,23 +405,15 @@ return 0; } int size = 1; - byte maxImmSize = getMaxImmSize(); - // For the copy opcode, the last imm is the length field is always 1 byte - if (isCopyOpCode()) { - if (mMaxSupportedImms != mImms.size()) { - throw new IllegalStateException( - "mImm size: " + mImms.size() + " doesn't match the mMaxSupportedImms: " - + mMaxSupportedImms); - } - size += (mImms.size() - 1) * maxImmSize + mImms.get(mImms.size() - 1).mImmSize; - } else { - size += mImms.size() * maxImmSize; + int indeterminateSize = calculateRequiredIndeterminateSize(); + for (IntImmediate imm : mIntImms) { + size += imm.getEncodingSize(indeterminateSize); } if (mTargetLabel != null) { - size += maxImmSize; + size += indeterminateSize; } - if (mCompareBytes != null) { - size += mCompareBytes.length; + if (mBytesImm != null) { + size += mBytesImm.length; } return size; } @@ -248,20 +427,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 = getMaxImmSize(); + 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; } @@ -270,7 +463,7 @@ * Assemble first byte of generated instruction. */ private byte generateInstructionByte() { - byte sizeField = generateImmSizeField(); + int sizeField = generateImmSizeField(); return (byte)((mOpcode << 3) | (sizeField << 1) | mRegister); } @@ -283,7 +476,7 @@ * be sign extended and the truncation should simply throw away their signed * upper bits. */ - private int writeValue(int value, byte[] bytecode, int writingOffset, byte immSize) { + 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); } @@ -291,7 +484,7 @@ } /** - * 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) { @@ -299,33 +492,17 @@ } int writingOffset = offset; bytecode[writingOffset++] = generateInstructionByte(); - byte maxImmSize = getMaxImmSize(); + int indeterminateSize = calculateRequiredIndeterminateSize(); if (mTargetLabel != null) { writingOffset = writeValue(calculateTargetLabelOffset(), bytecode, writingOffset, - maxImmSize); + indeterminateSize); } - // For the copy opcode, the last imm is the length field is always 1 byte - if (isCopyOpCode()) { - if (mMaxSupportedImms != mImms.size()) { - throw new IllegalStateException( - "mImm size: " + mImms.size() + " doesn't match the mMaxSupportedImms: " - + mMaxSupportedImms); - } - int i; - for (i = 0; i < mImms.size() - 1; ++i) { - writingOffset = writeValue(mImms.get(i).mValue, bytecode, writingOffset, - maxImmSize); - } - writingOffset = writeValue(mImms.get(i).mValue, bytecode, writingOffset, - mImms.get(i).mImmSize); - } else { - for (Immediate imm : mImms) { - writingOffset = writeValue(imm.mValue, bytecode, writingOffset, maxImmSize); - } + for (IntImmediate imm : mIntImms) { + writingOffset = imm.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) + @@ -333,32 +510,16 @@ } } - private boolean isCopyOpCode() { - if (mOpcode == Opcodes.MEMCOPY.value) { - return true; - } - if (mOpcode == Opcodes.EXT.value) { - int realOpcode = mImms.get(0).mValue; - if (realOpcode == ExtendedOpcodes.EPKTCOPY.value - || realOpcode == ExtendedOpcodes.EDATACOPY.value) { - return true; - } - } - return false; - } - /** - * Calculate the size of either the immediate fields or the target label field, if either is - * present. Most instructions have either immediates 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 fields, because there is only one length field in the instruction - * byte, hence why this function simply takes the maximum of those 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 getMaxImmSize() { - byte maxSize = mTargetLabelSize; - for (int i = 0; i < mImms.size(); ++i) { - maxSize = (byte) Math.max(maxSize, mImms.get(i).mImmSize); + private int calculateRequiredIndeterminateSize() { + int maxSize = mTargetLabelSize; + for (IntImmediate imm : mIntImms) { + maxSize = Math.max(maxSize, imm.calculateIndeterminateSize()); } return maxSize; } @@ -436,7 +597,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>(); @@ -469,11 +633,12 @@ } } - private void addInstruction(Instruction instruction) { + private ApfGenerator append(Instruction instruction) { if (mGenerated) { throw new IllegalStateException("Program already generated"); } mInstructions.add(instruction); + return this; } /** @@ -492,53 +657,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.addUnsignedImm(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.addUnsignedImm(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.addUnsignedImm(offset); - addInstruction(instruction); - return this; + public ApfGenerator addLoad32(Register r, int ofs) { + return append(new Instruction(Opcodes.LDW, r).addUnsigned(ofs)); } /** @@ -546,11 +696,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.addUnsignedImm(offset); - addInstruction(instruction); - return this; + public ApfGenerator addLoad8Indexed(Register r, int ofs) { + return append(new Instruction(Opcodes.LDBX, r).addUnsigned(ofs)); } /** @@ -558,11 +705,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.addUnsignedImm(offset); - addInstruction(instruction); - return this; + public ApfGenerator addLoad16Indexed(Register r, int ofs) { + return append(new Instruction(Opcodes.LDHX, r).addUnsigned(ofs)); } /** @@ -570,109 +714,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.addUnsignedImm(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.addUnsignedImm(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.addUnsignedImm(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.addUnsignedImm(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.addUnsignedImm(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.addUnsignedImm(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.addSignedImm(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.addSignedImm(-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)); } /** @@ -680,9 +796,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)); } /** @@ -690,9 +804,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)); } /** @@ -700,111 +812,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.addSignedImm(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.addUnsignedImm(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.addUnsignedImm(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.addUnsignedImm(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.addUnsignedImm(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.addUnsignedImm(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)); } /** @@ -812,327 +890,329 @@ * 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. + * packet at an offset specified by {@code register} don't match {@code bytes} */ - public ApfGenerator addJumpIfBytesNotEqual(Register register, byte[] bytes, String target) - throws IllegalInstructionException { - if (register == Register.R1) { - throw new IllegalInstructionException("JNEBS fails with R1"); - } - Instruction instruction = new Instruction(Opcodes.JNEBS, register); - instruction.addUnsignedImm(bytes.length); - instruction.setTargetLabel(target); - instruction.setCompareBytes(bytes); - addInstruction(instruction); - return this; + 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 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.addUnsignedImm(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.addUnsignedImm(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.addUnsignedImm(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.addUnsignedImm(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.addUnsignedImm(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.addUnsignedImm(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 register the register value contains the buffer size. + * @param size the buffer length to be allocated. */ - public ApfGenerator addAlloc(Register register) throws IllegalInstructionException { - requireApfVersion(5); - Instruction instruction = new Instruction(Opcodes.EXT, register); - instruction.addUnsignedImm(ExtendedOpcodes.ALLOC.value); - addInstruction(instruction); - return this; + 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 end of the program to call the apf_transmit_buffer() function. - * - * @param register the register value contains the packet type. + * Add an instruction to the beginning of the program to reserve the data region. + * @param data the actual data byte */ - public ApfGenerator addTrans(Register register) throws IllegalInstructionException { - requireApfVersion(5); - Instruction instruction = new Instruction(Opcodes.EXT, register); - instruction.addUnsignedImm(ExtendedOpcodes.TRANS.value); - addInstruction(instruction); - return this; + 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 write 1, 2 or 4 bytes value to output buffer. - * - * @param value the value to write - * @param size the size of the value - * @return the ApfGenerator object - * @throws IllegalInstructionException throws when size is not 1, 2 or 4 + * Add an instruction to the end of the program to transmit the allocated buffer. */ - public ApfGenerator addWrite(int value, byte size) throws IllegalInstructionException { - requireApfVersion(5); - if (!(size == 1 || size == 2 || size == 4)) { - throw new IllegalInstructionException("length field must be 1, 2 or 4"); - } - if (size < calculateImmSize(value, false)) { - throw new IllegalInstructionException( - String.format("the value %d is unfit into size: %d", value, size)); - } - Instruction instruction = new Instruction(Opcodes.WRITE); - instruction.addUnsignedImm(value, size); - addInstruction(instruction); - return this; + 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 write 1, 2 or 4 bytes value from register - * to output buffer. - * - * @param register the register contains the value to be written - * @param size the size of the value - * @return the ApfGenerator object - * @throws IllegalInstructionException throws when size is not 1, 2 or 4 + * Add an instruction to the end of the program to discard the allocated buffer. */ - public ApfGenerator addWrite(Register register, byte size) - throws IllegalInstructionException { - requireApfVersion(5); - if (!(size == 1 || size == 2 || size == 4)) { - throw new IllegalInstructionException( - "length field must be 1, 2 or 4"); - } - Instruction instruction = new Instruction(Opcodes.EXT, register); - if (size == 1) { - instruction.addUnsignedImm(ExtendedOpcodes.EWRITE1.value); - } else if (size == 2) { - instruction.addUnsignedImm(ExtendedOpcodes.EWRITE2.value); - } else { - instruction.addUnsignedImm(ExtendedOpcodes.EWRITE4.value); - } - addInstruction(instruction); - return this; + 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 copy data from APF data region to output + * 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. - * - * @param srcOffset the offset inside the APF data region for where to start copy - * @param length the length of bytes needed to be copied, only <= 255 bytes can be copied at - * one time. - * @return the ApfGenerator object - * @throws IllegalInstructionException throws when imm size is incorrectly set. */ - public ApfGenerator addDataCopy(int srcOffset, int length) - throws IllegalInstructionException { - return addMemCopy(srcOffset, length, Register.R1); + 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 copy data from input packet to output buffer. - * - * @param srcOffset the offset inside the input packet for where to start copy - * @param length the length of bytes needed to be copied, only <= 255 bytes can be copied at - * one time. - * @return the ApfGenerator object - * @throws IllegalInstructionException throws when imm size is incorrectly set. - */ - public ApfGenerator addPacketCopy(int srcOffset, int length) - throws IllegalInstructionException { - return addMemCopy(srcOffset, length, Register.R0); - } - - private ApfGenerator addMemCopy(int srcOffset, int length, Register register) - throws IllegalInstructionException { - requireApfVersion(5); - checkCopyLength(length); - checkCopyOffset(srcOffset); - Instruction instruction = new Instruction(Opcodes.MEMCOPY, - register, 2 /* maxSupportedImms */); - // if the offset == 0, it should still be encoded with 1 byte size. - if (srcOffset == 0) { - instruction.addUnsignedImm(srcOffset, (byte) 1 /* size */); - } else { - instruction.addUnsignedImm(srcOffset); - } - instruction.addUnsignedImm(length, (byte) 1 /* size */); - addInstruction(instruction); - return this; - } - - /** - * Add an instruction to the end of the program to copy data from APF data region to output + * Add an instruction to the end of the program to write 2 byte value from register to output * buffer. - * - * @param register the register that stored the base offset value. - * @param relativeOffset the offset inside the APF data region for where to start copy - * @param length the length of bytes needed to be copied, only <= 255 bytes can be copied at - * one time. - * @return the ApfGenerator object - * @throws IllegalInstructionException throws when imm size is incorrectly set. */ - public ApfGenerator addDataCopy(Register register, int relativeOffset, int length) - throws IllegalInstructionException { - return addMemcopy(register, relativeOffset, length, ExtendedOpcodes.EDATACOPY.value); + 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 copy data from input packet to output buffer. + * 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 register the register that stored the base offset value. - * @param relativeOffset the offset inside the input packet for where to start copy - * @param length the length of bytes needed to be copied, only <= 255 bytes can be copied at + * @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 - * @throws IllegalInstructionException throws when imm size is incorrectly set. */ - public ApfGenerator addPacketCopy(Register register, int relativeOffset, int length) + public ApfGenerator addDataCopy(int src, int len) throws IllegalInstructionException { - return addMemcopy(register, relativeOffset, length, ExtendedOpcodes.EPKTCOPY.value); + requireApfVersion(MIN_APF_VERSION_IN_DEV); + return append(new Instruction(Opcodes.PKTDATACOPY, R1).addUnsigned(src).addU8(len)); } - private ApfGenerator addMemcopy(Register register, int relativeOffset, int length, int opcode) + /** + * 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(5); - checkCopyLength(length); - checkCopyOffset(relativeOffset); - Instruction instruction = new Instruction(Opcodes.EXT, register, 3 /* maxSupportedImms */); - instruction.addUnsignedImm(opcode); - // if the offset == 0, it should still be encoded with 1 byte size. - if (relativeOffset == 0) { - instruction.addUnsignedImm(relativeOffset, (byte) 1 /* size */); - } else { - instruction.addUnsignedImm(relativeOffset); - } - instruction.addUnsignedImm(length, (byte) 1 /* size */); - addInstruction(instruction); - return this; + requireApfVersion(MIN_APF_VERSION_IN_DEV); + return append(new Instruction(Opcodes.PKTDATACOPY, R0).addUnsigned(src).addU8(len)); } - private void checkCopyLength(int length) { - if (length < 0 || length > 255) { - throw new IllegalArgumentException( - "copy length must between 0 to 255, length: " + length); - } + /** + * 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)); } - private void checkCopyOffset(int offset) { - if (offset < 0) { - throw new IllegalArgumentException( - "offset must be non less than zero, offset: " + offset); + /** + * 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)); + } + + 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.addSignedImm(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.addSignedImm(offset); - addInstruction(instruction); - return this; + requireApfVersion(APF_VERSION_4); + return append(new Instruction(Opcodes.STDW, src).addSigned(ofs)); } /** @@ -1151,7 +1231,7 @@ /** * Calculate the size of the imm. */ - private static byte calculateImmSize(int imm, boolean signed) { + private static int calculateImmSize(int imm, boolean signed) { if (imm == 0) { return 0; }
diff --git a/src/android/net/apf/DnsUtils.java b/src/android/net/apf/DnsUtils.java index 0300d34..5bd2515 100644 --- a/src/android/net/apf/DnsUtils.java +++ b/src/android/net/apf/DnsUtils.java
@@ -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 index 63e2bbc..6b93d89 100644 --- a/src/android/net/apf/LegacyApfFilter.java +++ b/src/android/net/apf/LegacyApfFilter.java
@@ -42,6 +42,7 @@ 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; @@ -50,7 +51,7 @@ 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; @@ -67,6 +68,9 @@ 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; @@ -85,6 +89,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} @@ -117,62 +122,6 @@ } /** - * 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; - } - } - - /** * When APFv4 is supported, loads R1 with the offset of the specified counter. */ private void maybeSetupCounter(ApfGenerator gen, Counter c) { @@ -191,7 +140,7 @@ public class ReceiveThread extends Thread { private final byte[] mPacket = new byte[1514]; private final FileDescriptor mSocket; - private final long mStart = SystemClock.elapsedRealtime(); + private final long mStart = mClock.elapsedRealtime(); private int mReceivedRas = 0; private int mMatchingRas = 0; @@ -254,7 +203,7 @@ } private void logStats() { - final long nowMs = SystemClock.elapsedRealtime(); + final long nowMs = mClock.elapsedRealtime(); synchronized (this) { final ApfStats stats = new ApfStats.Builder() .setReceivedRas(mReceivedRas) @@ -356,7 +305,6 @@ ETH_HEADER_LEN + UDP_HEADER_LEN + DNS_HEADER_LEN; - private final ApfCapabilities mApfCapabilities; private final IpClientCallbacksWrapper mIpClientCallback; private final InterfaceParams mInterfaceParams; @@ -375,9 +323,33 @@ 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 @@ -400,10 +372,21 @@ @GuardedBy("this") private int mIPv4PrefixLength; + private final ApfFilter.Dependencies mDependencies; + @VisibleForTesting public LegacyApfFilter(Context context, ApfFilter.ApfConfiguration config, InterfaceParams ifParams, IpClientCallbacksWrapper ipClientCallback, - IpConnectivityLog log) { + 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; @@ -411,6 +394,13 @@ 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"; @@ -437,6 +427,7 @@ public synchronized void setDataSnapshot(byte[] data) { mDataSnapshot = data; + mApfCounterTracker.updateCountersFromData(data); } private void log(String s) { @@ -492,7 +483,9 @@ // 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 @@ -513,7 +506,7 @@ // Returns seconds since device boot. @VisibleForTesting protected long currentTimeSeconds() { - return SystemClock.elapsedRealtime() / DateUtils.SECOND_IN_MILLIS; + return mClock.elapsedRealtime() / DateUtils.SECOND_IN_MILLIS; } public static class InvalidRaException extends Exception { @@ -598,6 +591,14 @@ // 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 @@ -770,6 +771,34 @@ 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. @@ -817,9 +846,9 @@ // 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, 0, mRouterLifetime); + builder.updateRouterLifetime(mRouterLifetime); // Add remaining fields (reachable time and retransmission timer) to match section. addMatchUntil(ICMP6_RA_OPTION_OFFSET); @@ -839,6 +868,8 @@ 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()); @@ -855,11 +886,14 @@ 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); @@ -948,7 +982,7 @@ // 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); @@ -1031,7 +1065,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 @@ -1046,11 +1080,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); @@ -1146,7 +1180,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 @@ -1168,7 +1202,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); @@ -1241,6 +1275,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; @@ -1276,7 +1316,7 @@ // Pass 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); + gen.addJumpIfBytesAtR0NotEqual(ARP_IPV4_HEADER, mCountAndPassLabel); // Pass if unknown ARP opcode. gen.addLoad16(Register.R0, ARP_OPCODE_OFFSET); @@ -1292,7 +1332,7 @@ // Pass if unicast 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. gen.defineLabel(checkTargetIPv4); @@ -1306,7 +1346,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); @@ -1354,7 +1394,7 @@ 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); @@ -1388,7 +1428,7 @@ // 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 { @@ -1515,8 +1555,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); @@ -1573,7 +1612,7 @@ // 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, + gen.addJumpIfBytesAtR0NotEqual(ETH_MULTICAST_MDNS_V4_MAC_ADDRESS, skipMdnsv4Filter); // Checks it's IPv4. @@ -1591,8 +1630,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); @@ -1623,7 +1661,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 @@ -1669,7 +1707,7 @@ * </ul> */ @GuardedBy("this") - private ApfGenerator emitPrologueLocked() throws IllegalInstructionException { + protected ApfGenerator emitPrologueLocked() throws IllegalInstructionException { // This is guaranteed to succeed because of the check in maybeCreate. ApfGenerator gen = new ApfGenerator(mApfCapabilities.apfVersionSupported); @@ -1706,7 +1744,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); } @@ -1737,7 +1775,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); @@ -1808,6 +1846,7 @@ // 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; } @@ -1817,6 +1856,7 @@ // 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; } @@ -1832,13 +1872,17 @@ 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); + 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); @@ -1891,6 +1935,20 @@ } } + // 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. @@ -1927,6 +1985,9 @@ } } 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; @@ -1936,10 +1997,23 @@ 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); @@ -1953,7 +2027,8 @@ * filtering using APF programs. */ public static LegacyApfFilter maybeCreate(Context context, ApfFilter.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; @@ -1972,10 +2047,42 @@ return null; } - return new LegacyApfFilter(context, config, ifParams, ipClientCallback, new IpConnectivityLog()); + 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. @@ -2100,23 +2207,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")); @@ -2201,11 +2291,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); @@ -2260,4 +2356,10 @@ } 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 4353746..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; @@ -294,7 +295,14 @@ @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); @@ -306,6 +314,10 @@ 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(); } @@ -635,6 +647,9 @@ private byte[] getOptionsToSkip() { final ByteArrayOutputStream optionsToSkip = new ByteArrayOutputStream(2); if (!isCapportApiEnabled()) optionsToSkip.write(DHCP_CAPTIVE_PORTAL); + if (!mConfiguration.isWifiManagedProfile) { + optionsToSkip.write(DHCP_DOMAIN_SEARCHLIST); + } return optionsToSkip.toByteArray(); } @@ -992,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; } }
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 770baac..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; @@ -1292,6 +1310,14 @@ 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); } @@ -1359,7 +1385,6 @@ newPacket.mBroadcastAddress = bcAddr; newPacket.mClientId = clientId; newPacket.mDnsServers = dnsServers; - newPacket.mDomainName = domainName; newPacket.mGateways = gateways; newPacket.mHostName = hostName; newPacket.mLeaseTime = leaseTime; @@ -1382,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; } @@ -1451,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; } @@ -1512,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); @@ -1526,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; } @@ -1546,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 */); } /** @@ -1557,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); @@ -1571,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; } @@ -1592,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/dhcp6/Dhcp6Client.java b/src/android/net/dhcp6/Dhcp6Client.java index a107b9c..8d53048 100644 --- a/src/android/net/dhcp6/Dhcp6Client.java +++ b/src/android/net/dhcp6/Dhcp6Client.java
@@ -20,9 +20,7 @@ import static android.net.dhcp6.Dhcp6Packet.PrefixDelegation; import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; import static android.system.OsConstants.AF_INET6; -import static android.system.OsConstants.IFA_F_NODAD; 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; @@ -31,14 +29,8 @@ 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.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH; -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.networkstack.util.NetworkStackUtils.macAddressToEui64; 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; @@ -58,16 +50,14 @@ 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.nio.ByteBuffer; -import java.util.Arrays; import java.util.Collections; +import java.util.List; import java.util.Random; import java.util.function.IntSupplier; @@ -95,8 +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; - public static final int DHCP6_PD_PREFIX_MSG_EXCHANGE_TERMINATED = 4; // Notification from DHCPv6 state machine before quitting public static final int CMD_ON_QUIT = PUBLIC_BASE + 4; @@ -385,8 +373,9 @@ } private void scheduleLeaseTimers() { - // TODO: validate t1, t2, valid and preferred lifetimes before the timers are scheduled to - // prevent packet storms due to low timeouts. + // 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(); @@ -435,8 +424,8 @@ 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() { @@ -556,10 +545,15 @@ return sendSolicitPacket(transId, elapsedTimeMs, pd.build()); } - // TODO: support multiple prefixes. @Override protected void receivePacket(Dhcp6Packet packet) { 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) { Log.d(TAG, "Get prefix delegation option from Advertise: " + pd); mAdvertise = pd; @@ -600,6 +594,11 @@ protected void receivePacket(Dhcp6Packet packet) { if (!(packet instanceof Dhcp6ReplyPacket)) return; final PrefixDelegation pd = packet.mPrefixDelegation; + 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); @@ -620,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: @@ -638,33 +637,6 @@ } } - // 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. - private boolean 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.e(TAG, la + " is not a global preferred IPv6 address"); - return false; - } - if (!NetlinkUtils.sendRtmNewAddressRequest(mIface.index, address, - (short) RFC7421_PREFIX_LENGTH, - flags, (byte) RT_SCOPE_UNIVERSE /* scope */, - ipo.preferred, ipo.valid)) { - Log.e(TAG, "Failed to set IPv6 address " + address.getHostAddress() - + "%" + mIface.index); - return false; - } - return true; - } - /** * Client has already obtained the lease(e.g. IA_PD option) from server and stays in Bound * state until T1 expires, and then transition to Renew state to extend the lease duration. @@ -674,26 +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. - for (IaPrefixOption ipo : mReply.getValidIaPrefixes()) { - // TODO: The prefix with preferred/valid lifetime of 0 is valid, but client - // should stop using the prefix immediately. Actually kernel doesn't accept - // the address with valid lifetime of 0 and returns EINVAL when it sees that. - // We should send RTM_DELADDR netlink message to kernel to delete these addresses - // from the interface if any. - // Configure IPv6 addresses based on 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 IpPrefix prefix = ipo.getIpPrefix(); - final Inet6Address address = createInet6AddressFromEui64(prefix, - macAddressToEui64(mIface.macAddr)); - if (!addInterfaceAddress(address, ipo)) continue; - } - 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 @@ -709,6 +664,27 @@ } } + + /** + * 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 */); @@ -723,17 +699,16 @@ protected void receivePacket(Dhcp6Packet packet) { if (!(packet instanceof Dhcp6ReplyPacket)) return; final PrefixDelegation pd = packet.mPrefixDelegation; - final IaPrefixOption request = mReply.ipos.get(0); - final IaPrefixOption response = pd.ipos.get(0); - if (!(Arrays.equals(request.prefix, response.prefix) - && request.prefixLen == response.prefixLen)) { - Log.i(TAG, "Renewal prefix " + HexDump.toHexString(response.prefix) - + " does not match current prefix " - + HexDump.toHexString(request.prefix)); - notifyPrefixDelegation(DHCP6_PD_PREFIX_CHANGED, null); - transitionTo(mSolicitState); + // 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 @@ -772,7 +747,9 @@ @Override protected boolean sendPacket(int transId, long elapsedTimeMs) { - return sendRenewPacket(transId, elapsedTimeMs, mReply.build()); + final List<IaPrefixOption> toBeRenewed = mReply.getRenewableIaPrefixes(); + if (toBeRenewed.isEmpty()) return false; + return sendRenewPacket(transId, elapsedTimeMs, mReply.build(toBeRenewed)); } } @@ -788,7 +765,9 @@ @Override protected boolean sendPacket(int transId, long elapsedTimeMs) { - return sendRebindPacket(transId, elapsedTimeMs, mReply.build()); + final List<IaPrefixOption> toBeRebound = mReply.getRenewableIaPrefixes(); + if (toBeRebound.isEmpty()) return false; + return sendRebindPacket(transId, elapsedTimeMs, mReply.build(toBeRebound)); } }
diff --git a/src/android/net/dhcp6/Dhcp6Packet.java b/src/android/net/dhcp6/Dhcp6Packet.java index 7a977e5..53dd274 100644 --- a/src/android/net/dhcp6/Dhcp6Packet.java +++ b/src/android/net/dhcp6/Dhcp6Packet.java
@@ -32,10 +32,8 @@ 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.Collections; import java.util.List; import java.util.Objects; import java.util.OptionalInt; @@ -96,16 +94,16 @@ * 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 @@ -210,14 +208,22 @@ public final int t2; @NonNull public final List<IaPrefixOption> ipos; + public final short statusCode; + @VisibleForTesting public PrefixDelegation(int iaid, int t1, int t2, - @NonNull final List<IaPrefixOption> ipos) { + @NonNull final List<IaPrefixOption> ipos, short statusCode) { Objects.requireNonNull(ipos); this.iaid = iaid; this.t1 = t1; this.t2 = t2; 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 */); } /** @@ -252,6 +258,7 @@ 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(); @@ -263,12 +270,18 @@ Log.d(TAG, "IA Prefix Option: " + ipo); ipos.add(ipo); break; - // TODO: support DHCP6_STATUS_CODE option + 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); + return new PrefixDelegation(iaid, t1, t2, ipos, statusCode); } catch (BufferUnderflowException e) { throw new ParseException(e.getMessage()); } @@ -278,14 +291,31 @@ * 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) * ipos.size()); + + 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 : ipos) { + 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; } @@ -308,29 +338,46 @@ @Override public String toString() { - return "Prefix Delegation: iaid " + iaid + ", t1 " + t1 + ", t2 " + t2 - + ", IA prefix options: " + ipos; + 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. - * TODO: exclude 0 preferred lifetime. */ public long getMinimalPreferredLifetime() { - final IaPrefixOption ipo = Collections.min(ipos, - (IaPrefixOption lhs, IaPrefixOption rhs) -> Long.compare(lhs.preferred, - rhs.preferred)); - return ipo.preferred; + 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. - * TODO: exclude 0 valid lifetime. */ public long getMinimalValidLifetime() { - final IaPrefixOption ipo = Collections.min(ipos, - (IaPrefixOption lhs, IaPrefixOption rhs) -> Long.compare(lhs.valid, rhs.valid)); - return ipo.valid; + 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; } } @@ -343,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++) { @@ -351,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 @@ -403,7 +442,6 @@ byte[] serverDuid = null; byte[] clientDuid = null; short statusCode = STATUS_SUCCESS; - String statusMsg = null; boolean rapidCommit = false; int solMaxRt = 0; PrefixDelegation pd = null; @@ -464,7 +502,12 @@ 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; @@ -518,9 +561,10 @@ 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) @@ -556,10 +600,8 @@ Log.e(TAG, "Unexpected transaction ID " + mTransId + ", expected " + transId); return false; } - if (mPrefixDelegation == null) { - Log.e(TAG, "DHCPv6 message without IA_PD option, ignoring"); - return false; - } + // 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;
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java index 9747c2c..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; @@ -32,25 +31,35 @@ 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_GARP_NA_ROAMING_VERSION; import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION; import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_IGNORE_LOW_RA_LIFETIME_VERSION; -import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_MULTICAST_NS_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; @@ -93,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; @@ -117,8 +128,10 @@ 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; @@ -148,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; @@ -374,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; } /** @@ -516,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; @@ -532,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; @@ -547,6 +564,12 @@ @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 @@ -662,6 +685,10 @@ 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; @@ -669,11 +696,15 @@ // 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; @@ -691,6 +722,7 @@ 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; @@ -699,7 +731,6 @@ private Integer mDadTransmits = null; private int mMaxDtimMultiplier = DTIM_MULTIPLIER_RESET; private ApfCapabilities mCurrentApfCapabilities; - private PrefixDelegation mPrefixDelegation; private WakeupMessage mIpv6AutoconfTimeoutAlarm = null; /** @@ -828,11 +859,13 @@ */ public AndroidPacketFilter maybeCreateApfFilter(Context context, ApfFilter.ApfConfiguration config, InterfaceParams ifParams, - IpClientCallbacksWrapper cb, boolean useNewApfFilter) { + IpClientCallbacksWrapper cb, NetworkQuirkMetrics networkQuirkMetrics, + boolean useNewApfFilter) { if (useNewApfFilter) { - return ApfFilter.maybeCreate(context, config, ifParams, cb); + return ApfFilter.maybeCreate(context, config, ifParams, cb, networkQuirkMetrics); } else { - return LegacyApfFilter.maybeCreate(context, config, ifParams, cb); + return LegacyApfFilter.maybeCreate(context, config, ifParams, cb, + networkQuirkMetrics); } } @@ -844,6 +877,14 @@ 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, @@ -861,6 +902,8 @@ mTag = getName(); + mDevicePolicyManager = (DevicePolicyManager) + context.getSystemService(Context.DEVICE_POLICY_SERVICE); mContext = context; mInterfaceName = ifName; mDependencies = deps; @@ -890,7 +933,12 @@ 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. @@ -1089,17 +1137,13 @@ } private boolean isGratuitousNaEnabled() { - return mDependencies.isFeatureEnabled(mContext, IPCLIENT_GRATUITOUS_NA_VERSION); + return mDependencies.isFeatureNotChickenedOut(mContext, IPCLIENT_GRATUITOUS_NA_VERSION); } private boolean isGratuitousArpNaRoamingEnabled() { return mDependencies.isFeatureEnabled(mContext, IPCLIENT_GARP_NA_ROAMING_VERSION); } - private boolean isMulticastNsEnabled() { - return mDependencies.isFeatureNotChickenedOut(mContext, IPCLIENT_MULTICAST_NS_VERSION); - } - @VisibleForTesting static MacAddress getInitialBssid(final Layer2Information layer2Info, final ScanResultInfo scanResultInfo, boolean isAtLeastS) { @@ -1150,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; @@ -1420,7 +1465,6 @@ mDhcpResults = null; mTcpBufferSizes = ""; mHttpProxy = null; - mPrefixDelegation = null; mLinkProperties = new LinkProperties(); mLinkProperties.setInterfaceName(mInterfaceName); @@ -1701,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 @@ -1712,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); @@ -1732,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) { - for (IaPrefixOption ipo : mPrefixDelegation.ipos) { - try { - final IpPrefix destination = - new IpPrefix(Inet6Address.getByAddress(ipo.prefix), 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); - newLp.addRoute(route); - } catch (UnknownHostException e) { - Log.wtf(mTag, "Invalid delegated prefix " + HexDump.toHexString(ipo.prefix)); - } + // [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); } } @@ -1949,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(); @@ -1990,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. @@ -2366,8 +2452,9 @@ apfConfig.minRdnssLifetimeSec = mMinRdnssLifetimeSec; apfConfig.acceptRaMinLft = mAcceptRaMinLft; apfConfig.shouldHandleLightDoze = mApfShouldHandleLightDoze; + apfConfig.minMetricsSessionDurationMs = mApfCounterPollingIntervalMs; return mDependencies.maybeCreateApfFilter(mContext, apfConfig, mInterfaceParams, - mCallback, mUseNewApfFilter); + mCallback, mNetworkQuirkMetrics, mUseNewApfFilter); } private boolean handleUpdateApfCapabilities(@NonNull final ApfCapabilities apfCapabilities) { @@ -2395,6 +2482,7 @@ mHasDisabledAcceptRaDefrtrOnProvLoss = false; mGratuitousNaTargetAddresses.clear(); mMulticastNsSourceAddresses.clear(); + mDelegatedPrefixes.clear(); resetLinkProperties(); if (mStartTimeMillis > 0) { @@ -2570,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 { @@ -2773,6 +2945,9 @@ if (mApfFilter == null) { mCallback.setFallbackMulticastFilter(mMulticastFiltering); } + if (mEnableApfPollingCounters) { + sendMessageDelayed(CMD_UPDATE_APF_DATA_SNAPSHOT, mApfCounterPollingIntervalMs); + } mPacketTracker = createPacketTracker(); if (mPacketTracker != null) mPacketTracker.start(mConfiguration.mDisplayName); @@ -2830,6 +3005,8 @@ } resetLinkProperties(); + + removeMessages(CMD_UPDATE_APF_DATA_SNAPSHOT); } private void enqueueJumpToStoppingState(final DisconnectCode code) { @@ -2863,30 +3040,71 @@ } } - private void clearIpv6PrefixDelegationAddresses() { - if (mPrefixDelegation == null) { - Log.wtf(mTag, "PrefixDelegation shouldn't be null when DHCPv6 PD fails."); - return; - } - final IaPrefixOption ipo = mPrefixDelegation.ipos.get(0); - final IpPrefix prefix; - try { - prefix = new IpPrefix(Inet6Address.getByAddress(ipo.prefix), RFC7421_PREFIX_LENGTH); - } catch (UnknownHostException e) { - Log.wtf(TAG, "Invalid delegated prefix " + HexDump.toHexString(ipo.prefix)); - return; - } - - // Delete the global IPv6 address based on delegated prefix from interface. + private void deleteIpv6PrefixDelegationAddresses(final IpPrefix prefix) { for (LinkAddress la : mLinkProperties.getLinkAddresses()) { final InetAddress address = la.getAddress(); if (prefix.contains(address)) { - NetlinkUtils.sendRtmDelAddressRequest(mInterfaceParams.index, - (Inet6Address) address, (short) la.getPrefixLength()); + 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); + } + } + + 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); + } + } + @Override public boolean processMessage(Message msg) { switch (msg.what) { @@ -3081,15 +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: - case Dhcp6Client.DHCP6_PD_PREFIX_MSG_EXCHANGE_TERMINATED: - clearIpv6PrefixDelegationAddresses(); - mPrefixDelegation = null; + final List<IaPrefixOption> toBeRemoved = (List<IaPrefixOption>) msg.obj; + removeExpiredDelegatedAddresses(toBeRemoved); handleLinkPropertiesUpdate(SEND_CALLBACKS); break; @@ -3121,6 +3338,11 @@ } break; + case CMD_UPDATE_APF_DATA_SNAPSHOT: + mCallback.startReadPacketFilter(); + sendMessageDelayed(CMD_UPDATE_APF_DATA_SNAPSHOT, mApfCounterPollingIntervalMs); + break; + default: return NOT_HANDLED; }
diff --git a/src/android/net/ip/IpReachabilityMonitor.java b/src/android/net/ip/IpReachabilityMonitor.java index 40a4bb6..e252a68 100644 --- a/src/android/net/ip/IpReachabilityMonitor.java +++ b/src/android/net/ip/IpReachabilityMonitor.java
@@ -23,7 +23,6 @@ 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; @@ -236,7 +235,6 @@ private int mInterSolicitIntervalMs; @NonNull private final Callback mCallback; - private final boolean mMulticastResolicitEnabled; private final boolean mIgnoreIncompleteIpv6DnsServerEnabled; private final boolean mIgnoreIncompleteIpv6DefaultRouterEnabled; @@ -260,8 +258,6 @@ mUsingMultinetworkPolicyTracker = usingMultinetworkPolicyTracker; mCm = context.getSystemService(ConnectivityManager.class); mDependencies = dependencies; - mMulticastResolicitEnabled = dependencies.isFeatureNotChickenedOut(context, - IP_REACHABILITY_MCAST_RESOLICIT_VERSION); mIgnoreIncompleteIpv6DnsServerEnabled = dependencies.isFeatureNotChickenedOut(context, IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION); mIgnoreIncompleteIpv6DefaultRouterEnabled = dependencies.isFeatureEnabled(context, @@ -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/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 bd5e62b..06419f9 100644 --- a/src/com/android/networkstack/metrics/stats.proto +++ b/src/com/android/networkstack/metrics/stats.proto
@@ -204,17 +204,17 @@ // The number of parsing error for RAs (Router Advertisements). optional int32 number_of_parsing_error_ras = 3; - // The lowest router lifetime in seconds. + // 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. - optional int32 lowest_pio_valid_lifetime_seconds = 5; + // 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. - optional int32 lowest_rio_route_lifetime_seconds = 6; + // 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. - optional int32 lowest_rdnss_lifetime_seconds = 7; + // The lowest lifetime of RDNSS (Recursive DNS Server Option) in seconds, excluding 0. + optional int64 lowest_rdnss_lifetime_seconds = 7; } /** @@ -225,7 +225,7 @@ optional .android.stats.connectivity.CounterName counter_name = 1; // The value of APF counter. - optional int32 counter_value = 2; + optional int64 counter_value = 2; } @@ -249,8 +249,8 @@ // The values of all APF counters. optional ApfCounterList apf_counter_list = 3; - // The duration of ip client in milliseconds. - optional int32 ip_client_session_duration_ms = 4; + // 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;
diff --git a/src/com/android/networkstack/util/NetworkStackUtils.java b/src/com/android/networkstack/util/NetworkStackUtils.java index 2dbf86e..829d0c6 100755 --- a/src/com/android/networkstack/util/NetworkStackUtils.java +++ b/src/com/android/networkstack/util/NetworkStackUtils.java
@@ -197,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. */ @@ -219,13 +212,6 @@ "ipclient_accept_ipv6_link_local_dns_version"; /** - * 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. */ @@ -250,6 +236,10 @@ */ 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. */ @@ -278,7 +268,7 @@ public static final String SKIP_TCP_POLL_IN_LIGHT_DOZE = "skip_tcp_poll_in_light_doze_mode"; /** - * Kill switch flag to disable the feature of re-evaluate when network resumes. + * Experiment flag to enable the feature of re-evaluate when network resumes. */ public static final String REEVALUATE_WHEN_RESUME = "reevaluate_when_resume";
diff --git a/src/com/android/server/connectivity/NetworkMonitor.java b/src/com/android/server/connectivity/NetworkMonitor.java index 8c10138..9303f95 100755 --- a/src/com/android/server/connectivity/NetworkMonitor.java +++ b/src/com/android/server/connectivity/NetworkMonitor.java
@@ -630,8 +630,8 @@ mPrivateIpNoInternetEnabled = getIsPrivateIpNoInternetEnabled(); mMetricsEnabled = deps.isFeatureNotChickenedOut(context, NetworkStackUtils.VALIDATION_METRICS_VERSION); - mReevaluateWhenResumeEnabled = deps.isFeatureNotChickenedOut(context, - NetworkStackUtils.REEVALUATE_WHEN_RESUME); + mReevaluateWhenResumeEnabled = deps.isFeatureEnabled( + context, NetworkStackUtils.REEVALUATE_WHEN_RESUME); mUseHttps = getUseHttpsValidation(); mCaptivePortalUserAgent = getCaptivePortalUserAgent(); mCaptivePortalFallbackSpecs =
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 d239379..2f1f7d1 100644 --- a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java +++ b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
@@ -27,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; @@ -42,7 +41,9 @@ 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; @@ -121,8 +122,11 @@ 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.DhcpResultsParcelable; @@ -190,6 +194,7 @@ 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; @@ -226,7 +231,6 @@ import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter; import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; import com.android.testutils.HandlerUtils; -import com.android.testutils.SkipPresubmit; import com.android.testutils.TapPacketReader; import com.android.testutils.TestableNetworkAgent; import com.android.testutils.TestableNetworkCallback; @@ -295,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(); @@ -351,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; @@ -768,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); @@ -789,8 +803,11 @@ 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))) @@ -810,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); @@ -829,6 +851,10 @@ // 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 { @@ -1105,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) { @@ -1297,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) { @@ -1304,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 { @@ -3308,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, @@ -3378,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()); @@ -3427,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, @@ -4096,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 @@ -4134,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, @@ -4144,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(); @@ -4159,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 @@ -4198,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 */); @@ -4251,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 */); @@ -4623,9 +4629,6 @@ .withoutIPv4() .build(); - setFeatureEnabled(NetworkStackUtils.IPCLIENT_MULTICAST_NS_VERSION, - true /* isUnsolicitedNsEnabled */); - assertTrue(isFeatureEnabled(NetworkStackUtils.IPCLIENT_MULTICAST_NS_VERSION)); startIpClientProvisioning(config); doIpv6OnlyProvisioning(); @@ -5105,9 +5108,8 @@ } @SignatureRequiredTest(reason = "Need to mock the DHCP6 renew/rebind alarms") - @SkipPresubmit(reason = "Out of SLO flakiness") @Test - public void testDhcp6Pd_prefixMismatchOnRenew() throws Exception { + public void testDhcp6Pd_prefixMismatchOnRenew_newPrefix() throws Exception { prepareDhcp6PdRenewTest(); final InOrder inOrder = inOrder(mAlarm); @@ -5120,17 +5122,396 @@ Dhcp6Packet packet = getNextDhcp6Packet(); assertTrue(packet instanceof Dhcp6RenewPacket); - // Reply with a different prefix with requested one, per RFC8415#section-18.2.10.1 + // 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 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 */, Collections.singletonList(ipo)); + 4500 /* t2 */, Arrays.asList(ipo)); final ByteBuffer iapd = pd.build(); mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac, (Inet6Address) mClientIpAddress, false /* rapidCommit */)); - verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningFailure(any()); + 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 @@ -5190,4 +5571,51 @@ 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/unit/Android.bp b/tests/unit/Android.bp index 464e4a1..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,18 +82,24 @@ min_sdk_version: "30", defaults: ["NetworkStackTestsDefaults"], static_libs: ["NetworkStackApiStableLib"], - lint: { test: true }, + 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", @@ -95,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/apf_jni.cpp b/tests/unit/jni/apf_jni.cpp index 84c5c1a..8e14b3a 100644 --- a/tests/unit/jni/apf_jni.cpp +++ b/tests/unit/jni/apf_jni.cpp
@@ -40,7 +40,7 @@ return accept_packet(program, program_len, ram_len, packet, packet_len, filter_age); } else { - return apf_run(program, program_len, ram_len, packet, packet_len, + return apf_run(nullptr, program, program_len, ram_len, packet, packet_len, filter_age << 14); } }
diff --git a/tests/unit/src/android/net/apf/ApfTest.java b/tests/unit/src/android/net/apf/ApfTest.java index 6a54e1c..4e1187b 100644 --- a/tests/unit/src/android/net/apf/ApfTest.java +++ b/tests/unit/src/android/net/apf/ApfTest.java
@@ -16,6 +16,7 @@ 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.net.apf.ApfJniUtils.compareBpfApf; @@ -44,6 +45,10 @@ import static org.junit.Assert.fail; 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 android.content.BroadcastReceiver; @@ -56,14 +61,20 @@ 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.apf.ApfTestUtils.MockIpClientCallback; import android.net.apf.ApfTestUtils.TestApfFilter; +import android.net.apf.ApfTestUtils.TestLegacyApfFilter; +import android.net.metrics.IpConnectivityLog; import android.os.Build; import android.os.PowerManager; +import android.stats.connectivity.NetworkQuirkEvent; import android.system.ErrnoException; import android.text.TextUtils; +import android.text.format.DateUtils; +import android.util.ArrayMap; import android.util.Log; import android.util.Pair; @@ -75,6 +86,9 @@ import com.android.net.module.util.Inet4AddressUtils; import com.android.net.module.util.NetworkStackConstants; import com.android.net.module.util.PacketBuilder; +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; @@ -106,6 +120,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Random; /** @@ -133,10 +148,17 @@ @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); doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class); + doReturn(mApfSessionInfoMetrics).when(mDependencies).getApfSessionInfoMetrics(); + doReturn(mIpClientRaInfoMetrics).when(mDependencies).getIpClientRaInfoMetrics(); } private static final String TAG = "ApfTest"; @@ -151,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); @@ -170,6 +193,7 @@ config.ethTypeBlackList = new int[0]; config.minRdnssLifetimeSec = MIN_RDNSS_LIFETIME_SEC; config.minRdnssLifetimeSec = 67; + config.minMetricsSessionDurationMs = MIN_METRICS_SESSION_DURATIONS_MS; return config; } @@ -608,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]); @@ -620,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); } @@ -724,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, @@ -758,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) @@ -780,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); @@ -809,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 @@ -836,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. @@ -907,10 +931,11 @@ config.apfCapabilities = MOCK_APF_PCAP_CAPABILITIES; config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; - TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback); + TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + mNetworkQuirkMetrics); apfFilter.setLinkProperties(lp); byte[] program = ipClientCallback.assertProgramUpdateAndGet(); - byte[] data = new byte[ApfFilter.Counter.totalSize()]; + byte[] data = new byte[Counter.totalSize()]; final boolean result; result = dropsAllPackets(mApfVersion, program, data, pcapFilename); @@ -1083,7 +1108,8 @@ ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; - TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback); + TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + mNetworkQuirkMetrics); apfFilter.setLinkProperties(lp); byte[] program = ipClientCallback.assertProgramUpdateAndGet(); @@ -1135,7 +1161,8 @@ public void testApfFilterIPv6() throws Exception { MockIpClientCallback ipClientCallback = new MockIpClientCallback(); ApfConfiguration config = getDefaultConfig(); - TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback); + TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + mNetworkQuirkMetrics); byte[] program = ipClientCallback.assertProgramUpdateAndGet(); // Verify empty IPv6 packet is passed @@ -1390,7 +1417,8 @@ lp.addLinkAddress(link); ApfConfiguration config = getDefaultConfig(); - TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback); + TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + mNetworkQuirkMetrics); apfFilter.setLinkProperties(lp); // Construct IPv4 mDNS packet @@ -1627,7 +1655,8 @@ ApfConfiguration config = getDefaultConfig(); config.ieee802_3Filter = DROP_802_3_FRAMES; - TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback); + TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + mNetworkQuirkMetrics); apfFilter.setLinkProperties(lp); byte[] program = ipClientCallback.assertProgramUpdateAndGet(); @@ -1688,7 +1717,7 @@ apfFilter.shutdown(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; - apfFilter = new TestApfFilter(mContext, config, ipClientCallback); + apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mNetworkQuirkMetrics); apfFilter.setLinkProperties(lp); program = ipClientCallback.assertProgramUpdateAndGet(); assertDrop(program, mcastv4packet.array()); @@ -1721,7 +1750,7 @@ final ApfConfiguration configuration = getDefaultConfig(); configuration.shouldHandleLightDoze = false; final ApfFilter apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, - configuration, mDependencies); + configuration, mNetworkQuirkMetrics, mDependencies); final ArgumentCaptor<BroadcastReceiver> receiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class); verify(mDependencies).addDeviceIdleReceiver(receiverCaptor.capture(), anyBoolean()); @@ -1736,7 +1765,7 @@ final ApfConfiguration configuration = getDefaultConfig(); configuration.shouldHandleLightDoze = true; final ApfFilter apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, - configuration, mDependencies); + configuration, mNetworkQuirkMetrics, mDependencies); final ArgumentCaptor<BroadcastReceiver> receiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class); verify(mDependencies).addDeviceIdleReceiver(receiverCaptor.capture(), anyBoolean()); @@ -1791,7 +1820,7 @@ MockIpClientCallback ipClientCallback = new MockIpClientCallback(); ApfConfiguration config = getDefaultConfig(); ApfFilter apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config, - mDependencies); + mNetworkQuirkMetrics, mDependencies); byte[] program = ipClientCallback.assertProgramUpdateAndGet(); // Verify empty packet of 100 zero bytes is passed @@ -1812,7 +1841,7 @@ apfFilter.shutdown(); config.ieee802_3Filter = DROP_802_3_FRAMES; apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config, - mDependencies); + mNetworkQuirkMetrics, mDependencies); program = ipClientCallback.assertProgramUpdateAndGet(); // Verify that IEEE802.3 frame is dropped @@ -1840,7 +1869,7 @@ MockIpClientCallback ipClientCallback = new MockIpClientCallback(); ApfConfiguration config = getDefaultConfig(); ApfFilter apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config, - mDependencies); + mNetworkQuirkMetrics, mDependencies); byte[] program = ipClientCallback.assertProgramUpdateAndGet(); // Verify empty packet of 100 zero bytes is passed @@ -1861,7 +1890,7 @@ apfFilter.shutdown(); config.ethTypeBlackList = ipv4BlackList; apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config, - mDependencies); + mNetworkQuirkMetrics, mDependencies); program = ipClientCallback.assertProgramUpdateAndGet(); // Verify that IPv4 frame will be dropped @@ -1877,7 +1906,7 @@ apfFilter.shutdown(); config.ethTypeBlackList = ipv4Ipv6BlackList; apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config, - mDependencies); + mNetworkQuirkMetrics, mDependencies); program = ipClientCallback.assertProgramUpdateAndGet(); // Verify that IPv4 frame will be dropped @@ -1924,7 +1953,8 @@ ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; - TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback); + TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + mNetworkQuirkMetrics); // Verify initially ARP request filter is off, and GARP filter is on. verifyArpFilter(ipClientCallback.assertProgramUpdateAndGet(), PASS); @@ -1984,7 +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); + final TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, + mNetworkQuirkMetrics); byte[] program; final int srcPort = 12345; final int dstPort = 54321; @@ -2177,7 +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); + final TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, + mNetworkQuirkMetrics); byte[] program; final int srcPort = 1024; final int dstPort = 4500; @@ -2426,7 +2458,7 @@ public void testRaToString() throws Exception { MockIpClientCallback cb = new MockIpClientCallback(); ApfConfiguration config = getDefaultConfig(); - TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb); + TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mNetworkQuirkMetrics); byte[] packet = buildLargeRa(); ApfFilter.Ra ra = apfFilter.new Ra(packet, packet.length); @@ -2496,7 +2528,8 @@ ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; - TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback); + TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + mNetworkQuirkMetrics); byte[] program = ipClientCallback.assertProgramUpdateAndGet(); final int ROUTER_LIFETIME = 1000; @@ -2587,7 +2620,8 @@ final ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; - final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback); + 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; @@ -2628,7 +2662,8 @@ final ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; - final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback); + final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + mNetworkQuirkMetrics); byte[] program = ipClientCallback.assertProgramUpdateAndGet(); final int routerLifetime = 1000; @@ -2697,7 +2732,7 @@ ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; - TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb); + 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); @@ -2719,7 +2754,7 @@ ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; - TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb); + 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); @@ -2736,7 +2771,7 @@ public void testMatchedRaUpdatesLifetime() throws Exception { final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); final TestApfFilter apfFilter = new TestApfFilter(mContext, getDefaultConfig(), - ipClientCallback); + ipClientCallback, mNetworkQuirkMetrics); // Create an RA and build an APF program byte[] ra = new RaPacketBuilder(1800 /* router lifetime */).build(); @@ -2764,7 +2799,8 @@ // configure accept_ra_min_lft final ApfConfiguration config = getDefaultConfig(); config.acceptRaMinLft = 180; - final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback); + 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 */) @@ -2793,7 +2829,8 @@ // configure accept_ra_min_lft final ApfConfiguration config = getDefaultConfig(); config.acceptRaMinLft = 180; - final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback); + 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 */) @@ -2829,7 +2866,8 @@ // configure accept_ra_min_lft final ApfConfiguration config = getDefaultConfig(); config.acceptRaMinLft = 180; - final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback); + 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(); @@ -2859,7 +2897,8 @@ // configure accept_ra_min_lft final ApfConfiguration config = getDefaultConfig(); config.acceptRaMinLft = 180; - final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback); + 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(); @@ -2896,7 +2935,8 @@ // configure accept_ra_min_lft final ApfConfiguration config = getDefaultConfig(); config.acceptRaMinLft = 180; - final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback); + 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(); @@ -2929,7 +2969,8 @@ // configure accept_ra_min_lft final ApfConfiguration config = getDefaultConfig(); config.acceptRaMinLft = 180; - final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback); + 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(); @@ -2968,7 +3009,8 @@ // configure accept_ra_min_lft final ApfConfiguration config = getDefaultConfig(); config.acceptRaMinLft = 180; - final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback); + 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(); @@ -3035,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 index 69c4079..abbdd6b 100644 --- a/tests/unit/src/android/net/apf/ApfTestUtils.java +++ b/tests/unit/src/android/net/apf/ApfTestUtils.java
@@ -28,18 +28,22 @@ 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; @@ -216,16 +220,24 @@ 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 void installPacketFilter(byte[] filter) { + public boolean installPacketFilter(byte[] filter) { mLastApfProgram = filter; mGotApfProgram.open(); + return mInstallPacketFilterReturn; } /** @@ -254,23 +266,52 @@ /** * The test apf filter class. */ - public static class TestApfFilter extends ApfFilter { + 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) throws Exception { - this(context, config, ipClientCallback, new Dependencies(context)); + 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, Dependencies dependencies) { - super(context, config, InterfaceParams.getByName("lo"), ipClientCallback, dependencies); + 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; } /** @@ -278,12 +319,13 @@ */ public static ApfFilter createTestApfFilter(Context context, MockIpClientCallback ipClientCallback, ApfConfiguration config, - ApfFilter.Dependencies dependencies) throws Exception { + 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, - dependencies); + networkQuirkMetrics, dependencies); apfFilter.setLinkProperties(lp); return apfFilter; } @@ -330,7 +372,7 @@ } @Override - public void shutdown() { + public synchronized void shutdown() { super.shutdown(); if (mReceiveThread != null) { mReceiveThread.halt(); @@ -338,5 +380,110 @@ } 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 index 8cfa316..1977a6c 100644 --- a/tests/unit/src/android/net/apf/ApfV5Test.kt +++ b/tests/unit/src/android/net/apf/ApfV5Test.kt
@@ -15,9 +15,14 @@ */ 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 @@ -29,82 +34,221 @@ 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) } + } + + @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) } + } + + @Test fun testApfInstructionsEncoding() { - var gen = ApfGenerator(MIN_APF_VERSION) - gen.addAlloc(ApfGenerator.Register.R0) + var gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION) + gen.addPass() var program = gen.generate() - assertContentEquals(byteArrayOf(encodeInstruction(21, 1, 0), 36), program) - assertContentEquals(arrayOf(" 0: alloc r0"), ApfJniUtils.disassembleApf(program)) + // encoding PASS opcode: opcode=0, imm_len=0, R=0 + assertContentEquals( + byteArrayOf(encodeInstruction(opcode = 0, immLength = 0, register = 0)), program) - gen = ApfGenerator(MIN_APF_VERSION) - gen.addTrans(ApfGenerator.Register.R1) + gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + gen.addDrop() program = gen.generate() - assertContentEquals(byteArrayOf(encodeInstruction(21, 1, 1), 37), program) - assertContentEquals(arrayOf(" 0: trans r1"), ApfJniUtils.disassembleApf(program)) + // encoding DROP opcode: opcode=0, imm_len=0, R=1 + assertContentEquals( + byteArrayOf(encodeInstruction(opcode = 0, immLength = 0, register = 1)), program) - gen = ApfGenerator(MIN_APF_VERSION) - gen.addWrite(0x01, 1) - gen.addWrite(0x0102, 2) - gen.addWrite(0x01020304, 4) + 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 - ), program) + 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"), ApfJniUtils.disassembleApf(program)) + " 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(MIN_APF_VERSION) - gen.addWrite(ApfGenerator.Register.R0, 1) - gen.addWrite(ApfGenerator.Register.R0, 2) - gen.addWrite(ApfGenerator.Register.R0, 4) + 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, 0), 40, + encodeInstruction(21, 1, 1), 38, + encodeInstruction(21, 1, 1), 39, + encodeInstruction(21, 1, 1), 40 ), program) - assertContentEquals(arrayOf( - " 0: write r0, 1", - " 2: write r0, 2", - " 4: write r0, 4"), ApfJniUtils.disassembleApf(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(MIN_APF_VERSION) + 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) - assertContentEquals(arrayOf( - " 0: dcopy 1, 5", - " 3: pcopy 1000, 255"), ApfJniUtils.disassembleApf(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(MIN_APF_VERSION) - gen.addDataCopy(ApfGenerator.Register.R1, 0, 5) - gen.addPacketCopy(ApfGenerator.Register.R0, 1000, 255) + 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), 42, 0, 5, - encodeInstruction(21, 2, 0), - 0, 41, 0x03.toByte(), 0xe8.toByte(), 0xff.toByte() + encodeInstruction(21, 1, 1), 41, + encodeInstruction(21, 1, 0), 41, 5, + encodeInstruction(21, 1, 1), 42, + encodeInstruction(21, 1, 0), 42, 5, ), program) - assertContentEquals(arrayOf( - " 0: dcopy [r1+0], 5", - " 4: pcopy [r0+1000], 255"), ApfJniUtils.disassembleApf(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)) } 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() } - - companion object { - private const val MIN_APF_VERSION = 5 - } }
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/DhcpResultsParcelableUtilTest.java b/tests/unit/src/android/net/dhcp/DhcpResultsParcelableUtilTest.java index 2810301..2d0916f 100644 --- a/tests/unit/src/android/net/dhcp/DhcpResultsParcelableUtilTest.java +++ b/tests/unit/src/android/net/dhcp/DhcpResultsParcelableUtilTest.java
@@ -40,6 +40,7 @@ import org.junit.runner.RunWith; import java.net.Inet4Address; +import java.util.Arrays; /** * Tests for {@link IpConfigurationParcelableUtil}. @@ -63,8 +64,9 @@ 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 @@ -126,10 +128,16 @@ assertEquals(mDhcpResults, unparceled); } - private static void setFieldsLostWhileParceling(@NonNull DhcpResults unparceledResults) { + 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, - // parceledResults.dmnsrchList.addAll(mDhcpResults.dmnSrchList); + 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; } /**
diff --git a/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt b/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt index 00e480b..32cf464 100644 --- a/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt +++ b/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt
@@ -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 @@ -175,4 +190,198 @@ 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 4e40fa2..1849776 100644 --- a/tests/unit/src/android/net/ip/IpClientTest.java +++ b/tests/unit/src/android/net/ip/IpClientTest.java
@@ -707,7 +707,7 @@ final ArgumentCaptor<ApfConfiguration> configCaptor = ArgumentCaptor.forClass( ApfConfiguration.class); verify(mDependencies, timeout(TEST_TIMEOUT_MS)).maybeCreateApfFilter( - any(), configCaptor.capture(), any(), any(), anyBoolean()); + any(), configCaptor.capture(), any(), any(), any(), anyBoolean()); return configCaptor.getValue(); } @@ -776,7 +776,7 @@ final ArgumentCaptor<ApfConfiguration> configCaptor = ArgumentCaptor.forClass( ApfConfiguration.class); verify(mDependencies, timeout(TEST_TIMEOUT_MS)).maybeCreateApfFilter( - any(), configCaptor.capture(), any(), any(), anyBoolean()); + any(), configCaptor.capture(), any(), any(), any(), anyBoolean()); final ApfConfiguration actual = configCaptor.getValue(); assertNotNull(actual); assertEquals(4, actual.apfCapabilities.apfVersionSupported); @@ -809,7 +809,7 @@ 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); } @@ -824,7 +824,7 @@ 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); }
diff --git a/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt b/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt index 6471f3a..4d57df5 100644 --- a/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt +++ b/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt
@@ -56,7 +56,6 @@ 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.networkstack.util.NetworkStackUtils.IP_REACHABILITY_MCAST_RESOLICIT_VERSION import com.android.testutils.makeNewNeighMessage import com.android.testutils.waitForIdle import java.io.FileDescriptor @@ -259,8 +258,6 @@ }.`when`(dependencies).makeIpNeighborMonitor(any(), any(), any()) doReturn(mIpReachabilityMonitorMetrics) .`when`(dependencies).getIpReachabilityMonitorMetrics() - doReturn(true).`when`(dependencies).isFeatureNotChickenedOut(any(), - eq(IP_REACHABILITY_MCAST_RESOLICIT_VERSION)) val monitorFuture = CompletableFuture<IpReachabilityMonitor>() // IpReachabilityMonitor needs to be started from the handler thread
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/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/server/connectivity/NetworkMonitorTest.java b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java index 5be2573..77e3a12 100644 --- a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java +++ b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
@@ -2302,8 +2302,7 @@ @Test public void testReevaluationInterval_networkResume() throws Exception { // Setup nothing and expect validation to fail. - doReturn(true).when(mDependencies) - .isFeatureNotChickenedOut(any(), eq(REEVALUATE_WHEN_RESUME)); + doReturn(true).when(mDependencies).isFeatureEnabled(any(), eq(REEVALUATE_WHEN_RESUME)); final NetworkMonitor nm = runFailedNetworkTest(); verifyNetworkTested(VALIDATION_RESULT_INVALID, 0 /* probesSucceeded */, 1 /* interactions */);