Merge "Add serial urls probe logic for NetworkMonitor" into main
diff --git a/Android.bp b/Android.bp
index 734797a..5898d04 100644
--- a/Android.bp
+++ b/Android.bp
@@ -35,7 +35,7 @@
java_defaults {
name: "NetworkStackReleaseTargetSdk",
min_sdk_version: "30",
- target_sdk_version: "35",
+ target_sdk_version: "36",
}
java_defaults {
@@ -64,7 +64,7 @@
],
apex_available: [
"com.android.tethering",
- "//apex_available:platform", // For InProcessNetworkStack
+ "//apex_available:platform",
],
min_sdk_version: "30",
}
@@ -266,8 +266,7 @@
],
}
-// Common defaults for android libraries containing network stack code, used to compile variants of
-// the network stack in the system process and in the network_stack process
+// Common defaults for android libraries containing network stack code
java_defaults {
name: "NetworkStackAndroidLibraryDefaults",
srcs: [
@@ -439,29 +438,6 @@
},
}
-// Non-updatable network stack running in the system server process for devices not using the module
-android_app {
- name: "InProcessNetworkStack",
- defaults: [
- "NetworkStackAppDefaults",
- "NetworkStackReleaseApiLevel",
- "ConnectivityNextEnableDefaults",
- ],
- static_libs: ["NetworkStackApiCurrentLib"],
- certificate: "platform",
- manifest: "AndroidManifest_InProcess.xml",
- // InProcessNetworkStack is a replacement for NetworkStack
- overrides: [
- "NetworkStack",
- "NetworkStackNext",
- ],
- // The InProcessNetworkStack goes together with the PlatformCaptivePortalLogin, which replaces
- // the default CaptivePortalLogin.
- required: [
- "PlatformCaptivePortalLogin",
- ],
-}
-
// Pre-merge the AndroidManifest for NetworkStackNext, so that its manifest can be merged on top
android_library {
name: "NetworkStackNextManifestBase",
diff --git a/AndroidManifest_InProcess.xml b/AndroidManifest_InProcess.xml
deleted file mode 100644
index 40d74a5..0000000
--- a/AndroidManifest_InProcess.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
- * 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.
- */
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.networkstack.inprocess"
- android:sharedUserId="android.uid.system"
- android:process="system"
- coreApp="true">
- <!--- Defines the MAINLINE_NETWORK_STACK permission used by the networkstack process. -->
- <permission android:name="android.permission.MAINLINE_NETWORK_STACK"
- android:protectionLevel="signature"/>
- <application>
- <service android:name="com.android.server.NetworkStackService"
- android:process="system"
- android:exported="true"
- android:permission="android.permission.MAINLINE_NETWORK_STACK">
- <intent-filter>
- <action android:name="android.net.INetworkStackConnector.InProcess"/>
- </intent-filter>
- </service>
- <service android:name="com.android.networkstack.ipmemorystore.RegularMaintenanceJobService"
- android:process="system"
- android:permission="android.permission.BIND_JOB_SERVICE" >
- </service>
- </application>
-</manifest>
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index ee06302..27538b6 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,12 +1,10 @@
[Builtin Hooks]
bpfmt = true
clang_format = true
-ktfmt = true
[Builtin Hooks Options]
clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp,hpp
-ktfmt = --kotlinlang-style
[Hook Scripts]
checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
-ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py --no-verify-format -f ${PREUPLOAD_FILES}
+ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py --disabled-rules comment-wrapping -f ${PREUPLOAD_FILES}
diff --git a/common/networkstackclient/Android.bp b/common/networkstackclient/Android.bp
index 983f1b7..7131409 100644
--- a/common/networkstackclient/Android.bp
+++ b/common/networkstackclient/Android.bp
@@ -35,7 +35,7 @@
java: {
apex_available: [
"//apex_available:platform",
- "com.android.btservices",
+ "com.android.bt",
"com.android.wifi",
"com.android.tethering",
],
@@ -152,7 +152,7 @@
java: {
apex_available: [
"//apex_available:platform",
- "com.android.btservices",
+ "com.android.bt",
"com.android.wifi",
"com.android.tethering",
],
@@ -221,6 +221,10 @@
version: "22",
imports: ["ipmemorystore-aidl-interfaces-V11"],
},
+ {
+ version: "23",
+ imports: ["ipmemorystore-aidl-interfaces-V11"],
+ },
],
frozen: true,
@@ -232,12 +236,12 @@
min_sdk_version: "30",
static_libs: [
"ipmemorystore-aidl-interfaces-V11-java",
- "networkstack-aidl-interfaces-V22-java",
+ "networkstack-aidl-interfaces-V23-java",
],
visibility: ["//packages/modules/NetworkStack:__subpackages__"],
apex_available: [
"//apex_available:platform",
- "com.android.btservices",
+ "com.android.bt",
"com.android.tethering",
"com.android.wifi",
],
@@ -280,7 +284,7 @@
],
apex_available: [
"//apex_available:platform",
- "com.android.btservices",
+ "com.android.bt",
"com.android.tethering",
"com.android.wifi",
],
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/.hash b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/.hash
new file mode 100644
index 0000000..59c0bfa
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/.hash
@@ -0,0 +1 @@
+9dd581b4741329188b6e58107600f38a3eaa9be1
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/DataStallReportParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/DataStallReportParcelable.aidl
new file mode 100644
index 0000000..771deda
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/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/23/android/net/DhcpResultsParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/DhcpResultsParcelable.aidl
new file mode 100644
index 0000000..31f2194
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/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/23/android/net/INetworkMonitor.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/INetworkMonitor.aidl
new file mode 100644
index 0000000..fb13c0c
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/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/23/android/net/INetworkMonitorCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/INetworkMonitorCallbacks.aidl
new file mode 100644
index 0000000..2a4fb1d
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/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 {
+ void onNetworkMonitorCreated(in android.net.INetworkMonitor networkMonitor) = 0;
+ void notifyNetworkTested(int testResult, @nullable String redirectUrl) = 1;
+ void notifyPrivateDnsConfigResolved(in android.net.PrivateDnsConfigParcel config) = 2;
+ void showProvisioningNotification(String action, String packageName) = 3;
+ void hideProvisioningNotification() = 4;
+ void notifyProbeStatusChanged(int probesCompleted, int probesSucceeded) = 5;
+ void notifyNetworkTestedWithExtras(in android.net.NetworkTestResultParcelable result) = 6;
+ void notifyDataStallSuspected(in android.net.DataStallReportParcelable report) = 7;
+ void notifyCaptivePortalDataChanged(in android.net.CaptivePortalData data) = 8;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/INetworkStackConnector.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/INetworkStackConnector.aidl
new file mode 100644
index 0000000..8120ffc
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/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/23/android/net/INetworkStackStatusCallback.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/INetworkStackStatusCallback.aidl
new file mode 100644
index 0000000..0b6b778
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/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/23/android/net/InformationElementParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/InformationElementParcelable.aidl
new file mode 100644
index 0000000..6103774
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/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/23/android/net/InitialConfigurationParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/InitialConfigurationParcelable.aidl
new file mode 100644
index 0000000..6a597e6
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/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/23/android/net/Layer2InformationParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/Layer2InformationParcelable.aidl
new file mode 100644
index 0000000..83796ee
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/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/23/android/net/Layer2PacketParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/Layer2PacketParcelable.aidl
new file mode 100644
index 0000000..4b3fff5
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/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/23/android/net/NattKeepalivePacketDataParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/NattKeepalivePacketDataParcelable.aidl
new file mode 100644
index 0000000..18cf954
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/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/23/android/net/NetworkTestResultParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/NetworkTestResultParcelable.aidl
new file mode 100644
index 0000000..4d6d5a2
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/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/23/android/net/PrivateDnsConfigParcel.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/PrivateDnsConfigParcel.aidl
new file mode 100644
index 0000000..b624ee4
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/PrivateDnsConfigParcel.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 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 */;
+ boolean ddrEnabled = false;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/ProvisioningConfigurationParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/ProvisioningConfigurationParcelable.aidl
new file mode 100644
index 0000000..0ce91f0
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/ProvisioningConfigurationParcelable.aidl
@@ -0,0 +1,65 @@
+/*
+**
+** 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;
+ int hostnameSetting;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/ScanResultInfoParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/ScanResultInfoParcelable.aidl
new file mode 100644
index 0000000..94fc27f
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/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/23/android/net/TcpKeepalivePacketDataParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/TcpKeepalivePacketDataParcelable.aidl
new file mode 100644
index 0000000..0e1c21c
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/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/23/android/net/dhcp/DhcpLeaseParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/dhcp/DhcpLeaseParcelable.aidl
new file mode 100644
index 0000000..3cd8860
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/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/23/android/net/dhcp/DhcpServingParamsParcel.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/dhcp/DhcpServingParamsParcel.aidl
new file mode 100644
index 0000000..7997936
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/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/23/android/net/dhcp/IDhcpEventCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/dhcp/IDhcpEventCallbacks.aidl
new file mode 100644
index 0000000..9312f47
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/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/23/android/net/dhcp/IDhcpServer.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/dhcp/IDhcpServer.aidl
new file mode 100644
index 0000000..1109f35
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/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/23/android/net/dhcp/IDhcpServerCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/dhcp/IDhcpServerCallbacks.aidl
new file mode 100644
index 0000000..ab8577c
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/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/23/android/net/ip/IIpClient.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/ip/IIpClient.aidl
new file mode 100644
index 0000000..87de4a6
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/ip/IIpClient.aidl
@@ -0,0 +1,62 @@
+/**
+ * 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;
+ const int HOSTNAME_SETTING_UNSET = 0x00;
+ const int HOSTNAME_SETTING_SEND = 0x01;
+ const int HOSTNAME_SETTING_DO_NOT_SEND = 0x02;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/ip/IIpClientCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/ip/IIpClientCallbacks.aidl
new file mode 100644
index 0000000..9d36419
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/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/23/android/net/networkstack/aidl/NetworkMonitorParameters.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/networkstack/aidl/NetworkMonitorParameters.aidl
new file mode 100644
index 0000000..2ab9db0
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/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/23/android/net/networkstack/aidl/dhcp/DhcpOption.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/networkstack/aidl/dhcp/DhcpOption.aidl
new file mode 100644
index 0000000..eea3e0d
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/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/23/android/net/networkstack/aidl/ip/ReachabilityLossInfoParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/networkstack/aidl/ip/ReachabilityLossInfoParcelable.aidl
new file mode 100644
index 0000000..bb88434
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/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/23/android/net/networkstack/aidl/ip/ReachabilityLossReason.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/android/net/networkstack/aidl/ip/ReachabilityLossReason.aidl
new file mode 100644
index 0000000..f9bb3c4
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/23/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/INetworkMonitorCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/INetworkMonitorCallbacks.aidl
index 36eda8e..2a4fb1d 100644
--- a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/INetworkMonitorCallbacks.aidl
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/INetworkMonitorCallbacks.aidl
@@ -34,13 +34,13 @@
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;
+ void onNetworkMonitorCreated(in android.net.INetworkMonitor networkMonitor) = 0;
+ void notifyNetworkTested(int testResult, @nullable String redirectUrl) = 1;
+ void notifyPrivateDnsConfigResolved(in android.net.PrivateDnsConfigParcel config) = 2;
+ void showProvisioningNotification(String action, String packageName) = 3;
+ void hideProvisioningNotification() = 4;
+ void notifyProbeStatusChanged(int probesCompleted, int probesSucceeded) = 5;
+ void notifyNetworkTestedWithExtras(in android.net.NetworkTestResultParcelable result) = 6;
+ void notifyDataStallSuspected(in android.net.DataStallReportParcelable report) = 7;
+ void notifyCaptivePortalDataChanged(in android.net.CaptivePortalData data) = 8;
}
diff --git a/common/networkstackclient/src/android/net/INetworkMonitorCallbacks.aidl b/common/networkstackclient/src/android/net/INetworkMonitorCallbacks.aidl
index b5fd280..280620e 100644
--- a/common/networkstackclient/src/android/net/INetworkMonitorCallbacks.aidl
+++ b/common/networkstackclient/src/android/net/INetworkMonitorCallbacks.aidl
@@ -24,7 +24,7 @@
import android.os.PersistableBundle;
/** @hide */
-oneway interface INetworkMonitorCallbacks {
+interface INetworkMonitorCallbacks {
void onNetworkMonitorCreated(in INetworkMonitor networkMonitor) = 0;
// Deprecated. Use notifyNetworkTestedWithExtras() instead.
@@ -36,4 +36,4 @@
void notifyNetworkTestedWithExtras(in NetworkTestResultParcelable result) = 6;
void notifyDataStallSuspected(in DataStallReportParcelable report) = 7;
void notifyCaptivePortalDataChanged(in CaptivePortalData data) = 8;
-}
\ No newline at end of file
+}
diff --git a/common/networkstackclient/src/android/net/networkstack/NetworkStackClientBase.java b/common/networkstackclient/src/android/net/networkstack/NetworkStackClientBase.java
index c2f7ddd..1093426 100644
--- a/common/networkstackclient/src/android/net/networkstack/NetworkStackClientBase.java
+++ b/common/networkstackclient/src/android/net/networkstack/NetworkStackClientBase.java
@@ -81,7 +81,8 @@
*
* <p>The INetworkMonitor will be returned asynchronously through the provided callbacks.
*/
- public void makeNetworkMonitor(Network network, String name, INetworkMonitorCallbacks cb) {
+ public void makeNetworkMonitor(Network network, @Nullable String name,
+ INetworkMonitorCallbacks cb) {
requestConnector(connector -> {
try {
connector.makeNetworkMonitor(network, name, cb);
diff --git a/jni/network_stack_utils_jni.cpp b/jni/network_stack_utils_jni.cpp
index 6f47d7e..b82f797 100644
--- a/jni/network_stack_utils_jni.cpp
+++ b/jni/network_stack_utils_jni.cpp
@@ -24,6 +24,7 @@
#include <net/if.h>
#include <netinet/ether.h>
#include <netinet/icmp6.h>
+#include <netinet/igmp.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <netinet/udp.h>
@@ -123,6 +124,115 @@
}
}
+// fd is a "socket(AF_PACKET, SOCK_RAW, ETH_P_ALL)"
+static void network_stack_units_attachEgressIgmpReportFilter(
+ JNIEnv *env, jclass clazz, jobject javaFd) {
+ static sock_filter filter_code[] = {
+ // Check if skb->pkt_type is PACKET_OUTGOING
+ BPF_LOAD_SKB_PKTTYPE,
+ BPF2_REJECT_IF_NOT_EQUAL(PACKET_OUTGOING),
+
+ // Check if skb->protocol is ETH_P_IP
+ BPF_LOAD_SKB_PROTOCOL,
+ BPF2_REJECT_IF_NOT_EQUAL(ETH_P_IP),
+
+ // Check the protocol is IGMP.
+ BPF_LOAD_IPV4_U8(protocol),
+ BPF2_REJECT_IF_NOT_EQUAL(IPPROTO_IGMP),
+
+ // Check this is not a fragment.
+ BPF_LOAD_IPV4_BE16(frag_off),
+ BPF2_REJECT_IF_ANY_MASKED_BITS_SET(IP_MF | IP_OFFMASK),
+
+ // Get the IP header length.
+ BPF_LOADX_NET_RELATIVE_IPV4_HLEN,
+
+ // Check if IGMPv2/IGMPv3 join/leave message.
+ BPF_LOAD_NETX_RELATIVE_IGMP_TYPE,
+ BPF2_ACCEPT_IF_EQUAL(IGMPV2_HOST_MEMBERSHIP_REPORT),
+ BPF2_ACCEPT_IF_EQUAL(IGMP_HOST_LEAVE_MESSAGE),
+ BPF2_ACCEPT_IF_EQUAL(IGMPV3_HOST_MEMBERSHIP_REPORT),
+ BPF_REJECT,
+ };
+ static const sock_fprog filter = {
+ sizeof(filter_code) / sizeof(filter_code[0]),
+ filter_code,
+ };
+
+ int fd = netjniutils::GetNativeFileDescriptor(env, javaFd);
+ if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
+ jniThrowErrnoException(env, "setsockopt(SO_ATTACH_FILTER)", errno);
+ }
+}
+
+// fd is a "socket(AF_PACKET, SOCK_RAW, ETH_P_ALL)"
+static void network_stack_units_attachEgressMulticastReportFilter(
+ JNIEnv *env, jclass clazz, jobject javaFd) {
+ static sock_filter filter_code[] = {
+ // Check if skb->pkt_type is PACKET_OUTGOING
+ BPF_LOAD_SKB_PKTTYPE,
+ BPF2_REJECT_IF_NOT_EQUAL(PACKET_OUTGOING),
+
+ // If IPv4: (otherwise jump to the 'IPv6 ...' below)
+ // Check if skb->protocol is ETH_P_IP
+ BPF_LOAD_SKB_PROTOCOL,
+ // Jump over instructions after this and before IPv6 handling section
+ BPF_JUMP_IF_NOT_EQUAL(ETH_P_IP, 15),
+
+ // Check the protocol is IGMP.
+ BPF_LOAD_IPV4_U8(protocol),
+ BPF2_REJECT_IF_NOT_EQUAL(IPPROTO_IGMP),
+
+ // Check this is not a fragment.
+ BPF_LOAD_IPV4_BE16(frag_off),
+ BPF2_REJECT_IF_ANY_MASKED_BITS_SET(IP_MF | IP_OFFMASK),
+
+ // Get the IP header length.
+ BPF_LOADX_NET_RELATIVE_IPV4_HLEN,
+
+ // Check if IGMPv2/IGMPv3 join/leave message.
+ BPF_LOAD_NETX_RELATIVE_IGMP_TYPE,
+ BPF2_ACCEPT_IF_EQUAL(IGMPV2_HOST_MEMBERSHIP_REPORT),
+ BPF2_ACCEPT_IF_EQUAL(IGMP_HOST_LEAVE_MESSAGE),
+ BPF2_ACCEPT_IF_EQUAL(IGMPV3_HOST_MEMBERSHIP_REPORT),
+ BPF_REJECT,
+
+ // IPv6 ...
+ // Check if skb->protocol is ETH_P_IPV6
+ BPF2_REJECT_IF_NOT_EQUAL(ETH_P_IPV6),
+
+ BPF_LOADX_CONSTANT_IPV6_HLEN,
+
+ // Check IPv6 Next Header is HOPOPTS
+ BPF_LOAD_IPV6_U8(nexthdr),
+ BPF2_REJECT_IF_NOT_EQUAL(IPPROTO_HOPOPTS),
+
+ // Check if HOPOPTS is ICMPv6
+ BPF_LOAD_NETX_RELATIVE_V6EXTHDR_NEXTHDR,
+ BPF2_REJECT_IF_NOT_EQUAL(IPPROTO_ICMPV6),
+
+ // Skip the IPv6 extension header
+ BPF3_LOAD_NETX_RELATIVE_V6EXTHDR_LEN,
+ BPF2_ADD_A_TO_X,
+
+ // Check if MLDv1/MLDv2 report message
+ BPF_LOAD_NETX_RELATIVE_MLD_TYPE,
+ BPF2_ACCEPT_IF_EQUAL(MLD_LISTENER_REPORT),
+ BPF2_ACCEPT_IF_EQUAL(MLD_LISTENER_DONE),
+ BPF2_ACCEPT_IF_EQUAL(MLDV2_LISTENER_REPORT),
+ BPF_REJECT
+ };
+ static const sock_fprog filter = {
+ sizeof(filter_code) / sizeof(filter_code[0]),
+ filter_code,
+ };
+
+ int fd = netjniutils::GetNativeFileDescriptor(env, javaFd);
+ if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
+ jniThrowErrnoException(env, "setsockopt(SO_ATTACH_FILTER)", errno);
+ }
+}
+
// fd is a "socket(AF_PACKET, SOCK_RAW, ETH_P_IPV6)"
// which guarantees packets already have skb->protocol == htons(ETH_P_IPV6)
static void network_stack_utils_attachRaFilter(JNIEnv *env, jclass clazz, jobject javaFd) {
@@ -229,6 +339,8 @@
{ "addArpEntry", "([B[BLjava/lang/String;Ljava/io/FileDescriptor;)V", (void*) network_stack_utils_addArpEntry },
{ "attachDhcpFilter", "(Ljava/io/FileDescriptor;)V", (void*) network_stack_utils_attachDhcpFilter },
{ "attachRaFilter", "(Ljava/io/FileDescriptor;)V", (void*) network_stack_utils_attachRaFilter },
+ { "attachEgressIgmpReportFilter", "(Ljava/io/FileDescriptor;)V", (void*) network_stack_units_attachEgressIgmpReportFilter },
+ { "attachEgressMulticastReportFilter", "(Ljava/io/FileDescriptor;)V", (void*) network_stack_units_attachEgressMulticastReportFilter },
{ "attachControlPacketFilter", "(Ljava/io/FileDescriptor;)V", (void*) network_stack_utils_attachControlPacketFilter },
};
diff --git a/proguard.flags b/proguard.flags
index 5a96d5a..96af0c5 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -1,3 +1,6 @@
+# Keep JNI registered methods
+-keepclasseswithmembers,includedescriptorclasses class * { native <methods>; }
+
-keepclassmembers class com.android.networkstack.android.net.ip.IpClient$IpClientCommands {
static final int CMD_*;
static final int EVENT_*;
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 2267cd4..5e22718 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -21,6 +21,6 @@
<string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"מידע על מקום הרשת"</string>
<string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"התראות המוצגות כדי לציין שלרשת יש דף מידע על מקום"</string>
<string name="connected" msgid="4563643884927480998">"המכשיר מחובר"</string>
- <string name="tap_for_info" msgid="6849746325626883711">"מחוברת / יש להקיש כדי להציג את האתר"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"מחוברת / יש ללחוץ כדי להציג את האתר"</string>
<string name="application_label" msgid="1322847171305285454">"ניהול רשתות"</string>
</resources>
diff --git a/src/android/net/apf/AndroidPacketFilter.java b/src/android/net/apf/AndroidPacketFilter.java
deleted file mode 100644
index c88587b..0000000
--- a/src/android/net/apf/AndroidPacketFilter.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.net.apf;
-
-import android.annotation.Nullable;
-import android.net.LinkProperties;
-import android.net.NattKeepalivePacketDataParcelable;
-import android.net.TcpKeepalivePacketDataParcelable;
-
-import com.android.internal.util.IndentingPrintWriter;
-
-/**
- * The interface for AndroidPacketFilter
- */
-public interface AndroidPacketFilter {
- /**
- * Update the LinkProperties that will be used by APF.
- */
- void setLinkProperties(LinkProperties lp);
-
- /**
- * Shutdown the APF.
- */
- void shutdown();
-
- /**
- * Switch for the multicast filter.
- * @param isEnabled if the multicast filter should be enabled or not.
- */
- void setMulticastFilter(boolean isEnabled);
-
- /**
- * Set the APF data snapshot and return the latest counter snapshot as a String.
- */
- String setDataSnapshot(byte[] data);
-
- /**
- * Add TCP keepalive ack packet filter.
- * This will add a filter to drop acks to the keepalive packet passed as an argument.
- *
- * @param slot The index used to access the filter.
- * @param sentKeepalivePacket The attributes of the sent keepalive packet.
- */
- void addTcpKeepalivePacketFilter(int slot,
- TcpKeepalivePacketDataParcelable sentKeepalivePacket);
-
- /**
- * Add NAT-T keepalive packet filter.
- * This will add a filter to drop NAT-T keepalive packet which is passed as an argument.
- *
- * @param slot The index used to access the filter.
- * @param sentKeepalivePacket The attributes of the sent keepalive packet.
- */
- void addNattKeepalivePacketFilter(int slot,
- NattKeepalivePacketDataParcelable sentKeepalivePacket);
-
- /**
- * Remove keepalive packet filter.
- *
- * @param slot The index used to access the filter.
- */
- void removeKeepalivePacketFilter(int slot);
-
- /**
- * Dump the status of APF.
- */
- void dump(IndentingPrintWriter pw);
-
- /**
- * Indicates whether the ApfFilter is currently running / paused for test and debugging
- * purposes.
- */
- boolean isRunning();
-
- /**
- * Indicates whether the clat interface is added or removed.
- */
- default void updateClatInterfaceState(boolean add) {}
-
- /** Pause ApfFilter updates for testing purposes. */
- void pause();
-
- /** Resume ApfFilter updates for testing purposes. */
- void resume();
-
- /** Return hex string of current APF snapshot for testing purposes. */
- @Nullable String getDataSnapshotHexString();
-
- /**
- * Determines whether the APF interpreter advertises support for the data buffer access
- * opcodes LDDW (LoaD Data Word) and STDW (STore Data Word).
- */
- default boolean hasDataAccess(int apfVersionSupported) {
- return apfVersionSupported > 2;
- }
-
- /**
- * Whether the ApfFilter supports generating ND offload code.
- */
- default boolean supportNdOffload() {
- return false;
- }
-
- /**
- * Return if the ApfFilter should enable mDNS offload.
- */
- default boolean shouldEnableMdnsOffload() {
- return false;
- }
-}
diff --git a/src/android/net/apf/ApfConstants.java b/src/android/net/apf/ApfConstants.java
index a23e970..9a5af85 100644
--- a/src/android/net/apf/ApfConstants.java
+++ b/src/android/net/apf/ApfConstants.java
@@ -15,6 +15,23 @@
*/
package android.net.apf;
+import static android.system.OsConstants.IPPROTO_ICMPV6;
+
+import static com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_IGMP_TYPE_V1_REPORT;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_IGMP_TYPE_V2_JOIN_REPORT;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_IGMP_TYPE_V2_LEAVE_REPORT;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_IGMP_TYPE_V3_REPORT;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_OPTION_LEN_ROUTER_ALERT;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_OPTION_TYPE_ROUTER_ALERT;
+
+import android.net.InetAddresses;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Set;
+
/**
* The class which declares constants used in ApfFilter and unit tests.
*/
@@ -38,6 +55,51 @@
public static final int IPV4_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 16;
public static final int IPV4_ANY_HOST_ADDRESS = 0;
public static final int IPV4_BROADCAST_ADDRESS = -1; // 255.255.255.255
+ // The IPv4 all hosts destination 224.0.0.1
+ public static final byte[] IPV4_ALL_HOSTS_ADDRESS =
+ InetAddresses.parseNumericAddress("224.0.0.1").getAddress();
+ // The IPv4 all multicast routers destination 224.0.0.22
+ public static final byte[] IPV4_ALL_IGMPV3_MULTICAST_ROUTERS_ADDRESS =
+ InetAddresses.parseNumericAddress("224.0.0.22").getAddress();
+ public static long IPV4_ALL_HOSTS_ADDRESS_IN_LONG = 0xe0000001L; // 224.0.0.1
+ public static final int IPV4_IGMP_TYPE_QUERY = 0x11;
+ public static final Set<Long> IGMP_TYPE_REPORTS = Set.of(
+ (long) IPV4_IGMP_TYPE_V1_REPORT,
+ (long) IPV4_IGMP_TYPE_V2_JOIN_REPORT,
+ (long) IPV4_IGMP_TYPE_V2_LEAVE_REPORT,
+ (long) IPV4_IGMP_TYPE_V3_REPORT);
+ public static final byte[] IPV4_ROUTER_ALERT_OPTION = {
+ (byte) IPV4_OPTION_TYPE_ROUTER_ALERT, // option type
+ (byte) IPV4_OPTION_LEN_ROUTER_ALERT, // option length
+ 0, 0 // option value
+ };
+ public static final int IPV4_ROUTER_ALERT_OPTION_LEN = 4;
+ public static final int IGMP_CHECKSUM_WITH_ROUTER_ALERT_OFFSET =
+ ETHER_HEADER_LEN + IPV4_HEADER_MIN_LEN + IPV4_ROUTER_ALERT_OPTION_LEN + 2;
+ public static final byte[] IGMPV2_REPORT_FROM_IPV4_OPTION_TO_IGMP_CHECKSUM = {
+ // option type
+ (byte) IPV4_OPTION_TYPE_ROUTER_ALERT,
+ // option length
+ (byte) IPV4_OPTION_LEN_ROUTER_ALERT,
+ // option value
+ 0, 0,
+ // IGMP type
+ // Indicating an IGMPv2 Membership Report (Join Group)
+ (byte) IPV4_IGMP_TYPE_V2_JOIN_REPORT,
+ // max response time
+ // Typically used in IGMP queries,but is not significant in IGMPv2 reports.
+ 0,
+ // checksum, calculate later
+ 0, 0
+ };
+
+ // IGMPv3 group record types
+ // From include/uapi/linux/igmp.h
+ public static final int IGMPV3_MODE_IS_EXCLUDE = 2;
+
+ // MLDv2 group record types
+ // From include/uapi/linux/icmpv6.h
+ public static final int MLD2_MODE_IS_EXCLUDE = 2;
// Traffic class and Flow label are not byte aligned. Luckily we
// don't care about either value so we'll consider bytes 1-3 of the
@@ -59,10 +121,69 @@
// The IPv6 solicited nodes multicast address prefix ff02::1:ffXX:X/104
public static final byte[] IPV6_SOLICITED_NODES_PREFIX =
{ (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, (byte) 0xff};
+ public static final byte[] IPV6_MLD_V2_ALL_ROUTERS_MULTICAST_ADDRESS =
+ { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0x16 };
+ /**
+ * IPv6 Router Alert Option constants.
+ *
+ * See also:
+ * - https://tools.ietf.org/html/rfc2711
+ */
+ public static final int IPV6_HBH_ROUTER_ALERT_OPTION_TYPE = 5;
+ public static final int IPV6_HBH_ROUTER_ALERT_OPTION_LEN = 2;
+
+ public static final int IPV6_HBH_PADN_OPTION_TYPE = 1;
+
+ /**
+ * IPv6 MLD constants.
+ *
+ * See also:
+ * - https://tools.ietf.org/html/rfc2710
+ * - https://tools.ietf.org/html/rfc3810
+ */
+ public static final int IPV6_MLD_MESSAGE_MIN_SIZE = 8;
+ public static final int IPV6_MLD_MIN_SIZE = 24; // including icmp header
+ public static final int IPV6_MLD_TYPE_QUERY = 130;
+ public static final int IPV6_MLD_TYPE_V1_REPORT = 131;
+ public static final int IPV6_MLD_TYPE_V1_DONE = 132;
+ public static final int IPV6_MLD_TYPE_V2_REPORT = 143;
+ public static final int IPV6_MLD_V1_MESSAGE_SIZE = 24;
+ public static final int IPV6_MLD_V2_MULTICAST_ADDRESS_RECORD_SIZE = 20;
+ // kernel reference: net/ipv6/mcast.c#igmp6_send()
+ public static final byte[] IPV6_MLD_HOPOPTS = {
+ (byte) IPPROTO_ICMPV6, // next header type
+ 0, // next header length
+ (byte) IPV6_HBH_ROUTER_ALERT_OPTION_TYPE, // Router Alert option type
+ (byte) IPV6_HBH_ROUTER_ALERT_OPTION_LEN, // Router Alert option length
+ 0, 0, // Router Alert option value
+ (byte) IPV6_HBH_PADN_OPTION_TYPE, (byte) 0x00 // PadN type and length
+ };
+
+ public static final Set<Long> IPV6_MLD_TYPE_REPORTS = Set.of(
+ (long) IPV6_MLD_TYPE_V1_REPORT,
+ (long) IPV6_MLD_TYPE_V1_DONE,
+ (long) IPV6_MLD_TYPE_V2_REPORT
+ );
+ public static final int IPV6_EXT_HEADER_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN;
+ public static final int IPV6_MLD_CHECKSUM_OFFSET =
+ ETHER_HEADER_LEN + IPV6_HEADER_LEN + IPV6_MLD_HOPOPTS.length + 2;
+ public static final int IPV6_MLD_TYPE_OFFSET =
+ IPV6_EXT_HEADER_OFFSET + IPV6_MLD_HOPOPTS.length;
+ public static final int IPV6_MLD_MULTICAST_ADDR_OFFSET =
+ IPV6_EXT_HEADER_OFFSET + IPV6_MLD_HOPOPTS.length + 8;
+
+ public static final int ICMP4_TYPE_NO_OPTIONS_OFFSET = ETH_HEADER_LEN + IPV4_HEADER_MIN_LEN;
+ public static final int ICMP4_CHECKSUM_NO_OPTIONS_OFFSET =
+ ETH_HEADER_LEN + IPV4_HEADER_MIN_LEN + 2;
+ public static final int ICMP4_CONTENT_NO_OPTIONS_OFFSET =
+ ETH_HEADER_LEN + IPV4_HEADER_MIN_LEN + 4;
+
+ public static final int ICMP6_ECHO_REQUEST_HEADER_LEN = 8;
public static final int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN;
public static final int ICMP6_CODE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN + 1;
public static final int ICMP6_CHECKSUM_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN + 2;
+ public static final int ICMP6_CONTENT_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN + 4;
public static final int ICMP6_NS_TARGET_IP_OFFSET = ICMP6_TYPE_OFFSET + 8;
public static final int ICMP6_NS_OPTION_TYPE_OFFSET = ICMP6_NS_TARGET_IP_OFFSET + 16;
// From RFC4861:
@@ -106,6 +227,8 @@
// NOTE: this must be added to the IPv4 header length in MemorySlot.IPV4_HEADER_SIZE
public static final int TCP_UDP_SOURCE_PORT_OFFSET = ETH_HEADER_LEN;
public static final int TCP_UDP_DESTINATION_PORT_OFFSET = ETH_HEADER_LEN + 2;
+ public static final int IGMP_MAX_RESP_TIME_OFFSET = ETHER_HEADER_LEN + 1;
+ public static final int IGMP_MULTICAST_ADDRESS_OFFSET = ETH_HEADER_LEN + 4;
public static final int UDP_HEADER_LEN = 8;
public static final int TCP_HEADER_SIZE_OFFSET = 12;
@@ -113,6 +236,24 @@
public static final int DHCP_SERVER_PORT = 67;
public static final int DHCP_CLIENT_PORT = 68;
+ public static final int DNS_HEADER_LEN = 12;
+ public static final int IPV4_UDP_DESTINATION_PORT_NO_OPTIONS_OFFSET =
+ ETH_HEADER_LEN + IPV4_HEADER_MIN_LEN + 2;
+ public static final int IPV4_UDP_DESTINATION_CHECKSUM_NO_OPTIONS_OFFSET =
+ ETH_HEADER_LEN + IPV4_HEADER_MIN_LEN + 6;
+ public static final int IPV4_UDP_PAYLOAD_NO_OPTIONS_OFFSET =
+ ETH_HEADER_LEN + IPV4_HEADER_MIN_LEN + UDP_HEADER_LEN;
+ public static final int IPV4_DNS_QDCOUNT_NO_OPTIONS_OFFSET =
+ ETH_HEADER_LEN + IPV4_HEADER_MIN_LEN + UDP_HEADER_LEN + 4;
+ public static final int IPV6_UDP_DESTINATION_PORT_OFFSET =
+ ETH_HEADER_LEN + IPV6_HEADER_LEN + 2;
+ public static final int IPV6_UDP_DESTINATION_CHECKSUM_OFFSET =
+ ETH_HEADER_LEN + IPV6_HEADER_LEN + 6;
+ public static final int IPv6_UDP_PAYLOAD_OFFSET =
+ ETH_HEADER_LEN + IPV6_HEADER_LEN + UDP_HEADER_LEN;
+ public static final int IPV6_DNS_QDCOUNT_OFFSET =
+ ETH_HEADER_LEN + IPV6_HEADER_LEN + UDP_HEADER_LEN + 4;
+
public static final int ARP_HEADER_OFFSET = ETH_HEADER_LEN;
public static final byte[] ARP_IPV4_HEADER = {
0, 1, // Hardware type: Ethernet (1)
@@ -139,18 +280,23 @@
{(byte) 0x01, (byte) 0x00, (byte) 0x5e, (byte) 0x00, (byte) 0x00, (byte) 0xfb};
public static final byte[] ETH_MULTICAST_MDNS_V6_MAC_ADDRESS =
{(byte) 0x33, (byte) 0x33, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xfb};
+ public static final byte[] ETH_MULTICAST_IGMP_V3_ALL_MULTICAST_ROUTERS_ADDRESS =
+ { (byte) 0x01, 0, (byte) 0x5e, 0, 0, (byte) 0x16};
+ public static final byte[] ETH_MULTICAST_MLD_V2_ALL_MULTICAST_ROUTERS_ADDRESS =
+ { (byte) 0x33, (byte) 0x33, 0, 0, 0, (byte) 0x16};
public static final int MDNS_PORT = 5353;
+ public static final byte[] MDNS_PORT_IN_BYTES = ByteBuffer.allocate(2).order(
+ ByteOrder.BIG_ENDIAN).putShort((short) MDNS_PORT).array();
+ public static final long MDNS_IPV4_ADDR_IN_LONG = 0xE00000FBL; // 224.0.0.251
+ public static final byte[] MDNS_IPV4_ADDR = InetAddresses.parseNumericAddress(
+ "224.0.0.251").getAddress();
+ public static final byte[] MDNS_IPV6_ADDR = InetAddresses.parseNumericAddress(
+ "FF02::FB").getAddress();
public static final int ECHO_PORT = 7;
- public static final int DNS_HEADER_LEN = 12;
- public static final int DNS_QDCOUNT_OFFSET = 4;
// NOTE: this must be added to the IPv4 header length in MemorySlot.IPV4_HEADER_SIZE, or the
// IPv6 header length.
public static final int DHCP_CLIENT_MAC_OFFSET = ETH_HEADER_LEN + UDP_HEADER_LEN + 28;
- public static final int MDNS_QDCOUNT_OFFSET =
- ETH_HEADER_LEN + UDP_HEADER_LEN + DNS_QDCOUNT_OFFSET;
- public static final int MDNS_QNAME_OFFSET =
- ETH_HEADER_LEN + UDP_HEADER_LEN + DNS_HEADER_LEN;
/**
* Fixed byte sequence representing the following part of the ARP reply header:
diff --git a/src/android/net/apf/ApfCounterTracker.java b/src/android/net/apf/ApfCounterTracker.java
index 9700b5b..6b15bee 100644
--- a/src/android/net/apf/ApfCounterTracker.java
+++ b/src/android/net/apf/ApfCounterTracker.java
@@ -26,7 +26,7 @@
import java.util.Map;
/**
- * Common counter class for {@code ApfFilter} and {@code LegacyApfFilter}.
+ * Counter class for {@code ApfFilter}.
*
* @hide
*/
@@ -45,45 +45,44 @@
PASSED_ALLOCATE_FAILURE, // hardcoded in APFv6 interpreter
PASSED_TRANSMIT_FAILURE, // hardcoded in APFv6 interpreter
CORRUPT_DNS_PACKET, // hardcoded in APFv6 interpreter
+ EXCEPTIONS, // hardcoded in APFv6.1 interpreter
FILTER_AGE_SECONDS,
FILTER_AGE_16384THS,
APF_VERSION,
APF_PROGRAM_ID,
- // TODO: removing PASSED_ARP after remove LegacyApfFilter.java
// The counter sequence should keep the same as ApfSessionInfoMetrics.java
- PASSED_ARP, // see also MIN_PASS_COUNTER below.
- PASSED_ARP_BROADCAST_REPLY,
- // TODO: removing PASSED_ARP_NON_IPV4 after remove LegacyApfFilter.java
- PASSED_ARP_NON_IPV4,
+ PASSED_ARP_BROADCAST_REPLY, // see also MIN_PASS_COUNTER below.
PASSED_ARP_REQUEST,
PASSED_ARP_UNICAST_REPLY,
- PASSED_ARP_UNKNOWN,
PASSED_DHCP,
PASSED_ETHER_OUR_SRC_MAC,
PASSED_IPV4,
PASSED_IPV4_FROM_DHCPV4_SERVER,
PASSED_IPV4_UNICAST,
+ PASSED_IPV6_HOPOPTS,
PASSED_IPV6_ICMP,
PASSED_IPV6_NON_ICMP,
- PASSED_IPV6_NS_DAD,
- PASSED_IPV6_NS_NO_ADDRESS,
- PASSED_IPV6_NS_NO_SLLA_OPTION,
- PASSED_IPV6_NS_TENTATIVE,
PASSED_IPV6_UNICAST_NON_ICMP,
PASSED_NON_IP_UNICAST,
- PASSED_MDNS,
- PASSED_MLD, // see also MAX_PASS_COUNTER below
+ PASSED_MDNS, // see also MAX_PASS_COUNTER below
DROPPED_ETH_BROADCAST, // see also MIN_DROP_COUNTER below
+ DROPPED_ETHER_OUR_SRC_MAC,
DROPPED_RA,
DROPPED_IPV4_L2_BROADCAST,
DROPPED_IPV4_BROADCAST_ADDR,
DROPPED_IPV4_BROADCAST_NET,
+ DROPPED_IPV4_ICMP_INVALID,
DROPPED_IPV4_MULTICAST,
DROPPED_IPV4_NON_DHCP4,
+ DROPPED_IPV4_PING_REQUEST_REPLIED,
+ DROPPED_IPV6_ICMP6_ECHO_REQUEST_INVALID,
+ DROPPED_IPV6_ICMP6_ECHO_REQUEST_REPLIED,
DROPPED_IPV6_ROUTER_SOLICITATION,
+ DROPPED_IPV6_MLD_INVALID,
+ DROPPED_IPV6_MLD_REPORT,
+ DROPPED_IPV6_MLD_V1_GENERAL_QUERY_REPLIED,
+ DROPPED_IPV6_MLD_V2_GENERAL_QUERY_REPLIED,
DROPPED_IPV6_MULTICAST_NA,
- DROPPED_IPV6_MULTICAST,
- DROPPED_IPV6_MULTICAST_PING,
DROPPED_IPV6_NON_ICMP_MULTICAST,
DROPPED_IPV6_NS_INVALID,
DROPPED_IPV6_NS_OTHER_HOST,
@@ -91,17 +90,21 @@
DROPPED_802_3_FRAME,
DROPPED_ETHERTYPE_NOT_ALLOWED,
DROPPED_IPV4_KEEPALIVE_ACK,
- DROPPED_IPV6_KEEPALIVE_ACK,
DROPPED_IPV4_NATT_KEEPALIVE,
DROPPED_MDNS,
+ DROPPED_MDNS_REPLIED,
+ DROPPED_NON_UNICAST_TDLS,
DROPPED_IPV4_TCP_PORT7_UNICAST,
DROPPED_ARP_NON_IPV4,
DROPPED_ARP_OTHER_HOST,
DROPPED_ARP_REPLY_SPA_NO_HOST,
- DROPPED_ARP_REQUEST_ANYHOST,
DROPPED_ARP_REQUEST_REPLIED,
DROPPED_ARP_UNKNOWN,
DROPPED_ARP_V6_ONLY,
+ DROPPED_IGMP_V2_GENERAL_QUERY_REPLIED,
+ DROPPED_IGMP_V3_GENERAL_QUERY_REPLIED,
+ DROPPED_IGMP_INVALID,
+ DROPPED_IGMP_REPORT,
DROPPED_GARP_REPLY; // see also MAX_DROP_COUNTER below
/**
@@ -109,7 +112,7 @@
* a given counter.
*/
public int offset() {
- return -this.ordinal() * 4; // Currently, all counters are 32bit long.
+ return -this.value() * 4; // Currently, all counters are 32bit long.
}
/**
@@ -139,12 +142,38 @@
}
return RESERVED_OOB;
}
+
+ private void checkCounterRange(Counter lowerBound, Counter upperBound) {
+ if (value() < lowerBound.value() || value() > upperBound.value()) {
+ throw new IllegalArgumentException(
+ String.format("Counter %s, is not in range [%s, %s]", this,
+ lowerBound, upperBound));
+ }
+ }
+
+ /**
+ * Return the label such that if we jump to it, the counter will be increased by 1 and
+ * the packet will be passed.
+ */
+ public short getJumpPassLabel() {
+ checkCounterRange(MIN_PASS_COUNTER, MAX_PASS_COUNTER);
+ return (short) (2 * this.value());
+ }
+
+ /**
+ * Return the label such that if we jump to it, the counter will be increased by 1 and
+ * the packet will be dropped.
+ */
+ public short getJumpDropLabel() {
+ checkCounterRange(MIN_DROP_COUNTER, MAX_DROP_COUNTER);
+ return (short) (2 * this.value() + 1);
+ }
}
public static final Counter MIN_DROP_COUNTER = Counter.DROPPED_ETH_BROADCAST;
public static final Counter MAX_DROP_COUNTER = Counter.DROPPED_GARP_REPLY;
- public static final Counter MIN_PASS_COUNTER = Counter.PASSED_ARP;
- public static final Counter MAX_PASS_COUNTER = Counter.PASSED_MLD;
+ public static final Counter MIN_PASS_COUNTER = Counter.PASSED_ARP_BROADCAST_REPLY;
+ public static final Counter MAX_PASS_COUNTER = Counter.PASSED_MDNS;
private static final String TAG = ApfCounterTracker.class.getSimpleName();
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java
index 08a370d..249a1e5 100644
--- a/src/android/net/apf/ApfFilter.java
+++ b/src/android/net/apf/ApfFilter.java
@@ -27,21 +27,29 @@
import static android.net.apf.ApfConstants.DHCP_CLIENT_MAC_OFFSET;
import static android.net.apf.ApfConstants.DHCP_CLIENT_PORT;
import static android.net.apf.ApfConstants.DHCP_SERVER_PORT;
+import static android.net.apf.ApfConstants.DNS_HEADER_LEN;
import static android.net.apf.ApfConstants.ECHO_PORT;
import static android.net.apf.ApfConstants.ETH_DEST_ADDR_OFFSET;
import static android.net.apf.ApfConstants.ETH_ETHERTYPE_OFFSET;
import static android.net.apf.ApfConstants.ETH_HEADER_LEN;
+import static android.net.apf.ApfConstants.ETH_MULTICAST_IGMP_V3_ALL_MULTICAST_ROUTERS_ADDRESS;
import static android.net.apf.ApfConstants.ETH_MULTICAST_MDNS_V4_MAC_ADDRESS;
import static android.net.apf.ApfConstants.ETH_MULTICAST_MDNS_V6_MAC_ADDRESS;
+import static android.net.apf.ApfConstants.ETH_MULTICAST_MLD_V2_ALL_MULTICAST_ROUTERS_ADDRESS;
import static android.net.apf.ApfConstants.ETH_TYPE_MAX;
import static android.net.apf.ApfConstants.ETH_TYPE_MIN;
import static android.net.apf.ApfConstants.FIXED_ARP_REPLY_HEADER;
+import static android.net.apf.ApfConstants.ICMP4_CHECKSUM_NO_OPTIONS_OFFSET;
+import static android.net.apf.ApfConstants.ICMP4_CONTENT_NO_OPTIONS_OFFSET;
+import static android.net.apf.ApfConstants.ICMP4_TYPE_NO_OPTIONS_OFFSET;
import static android.net.apf.ApfConstants.ICMP6_4_BYTE_LIFETIME_LEN;
import static android.net.apf.ApfConstants.ICMP6_4_BYTE_LIFETIME_OFFSET;
import static android.net.apf.ApfConstants.ICMP6_CAPTIVE_PORTAL_OPTION_TYPE;
import static android.net.apf.ApfConstants.ICMP6_CHECKSUM_OFFSET;
import static android.net.apf.ApfConstants.ICMP6_CODE_OFFSET;
+import static android.net.apf.ApfConstants.ICMP6_CONTENT_OFFSET;
import static android.net.apf.ApfConstants.ICMP6_DNSSL_OPTION_TYPE;
+import static android.net.apf.ApfConstants.ICMP6_ECHO_REQUEST_HEADER_LEN;
import static android.net.apf.ApfConstants.ICMP6_MTU_OPTION_TYPE;
import static android.net.apf.ApfConstants.ICMP6_NS_OPTION_TYPE_OFFSET;
import static android.net.apf.ApfConstants.ICMP6_NS_TARGET_IP_OFFSET;
@@ -60,53 +68,144 @@
import static android.net.apf.ApfConstants.ICMP6_ROUTE_INFO_OPTION_TYPE;
import static android.net.apf.ApfConstants.ICMP6_SOURCE_LL_ADDRESS_OPTION_TYPE;
import static android.net.apf.ApfConstants.ICMP6_TYPE_OFFSET;
+import static android.net.apf.ApfConstants.IGMPV2_REPORT_FROM_IPV4_OPTION_TO_IGMP_CHECKSUM;
+import static android.net.apf.ApfConstants.IGMPV3_MODE_IS_EXCLUDE;
+import static android.net.apf.ApfConstants.IGMP_CHECKSUM_WITH_ROUTER_ALERT_OFFSET;
+import static android.net.apf.ApfConstants.IGMP_MAX_RESP_TIME_OFFSET;
+import static android.net.apf.ApfConstants.IGMP_MULTICAST_ADDRESS_OFFSET;
+import static android.net.apf.ApfConstants.IGMP_TYPE_REPORTS;
import static android.net.apf.ApfConstants.IPPROTO_HOPOPTS;
+import static android.net.apf.ApfConstants.IPV4_ALL_HOSTS_ADDRESS_IN_LONG;
+import static android.net.apf.ApfConstants.IPV4_ALL_IGMPV3_MULTICAST_ROUTERS_ADDRESS;
import static android.net.apf.ApfConstants.IPV4_ANY_HOST_ADDRESS;
import static android.net.apf.ApfConstants.IPV4_BROADCAST_ADDRESS;
import static android.net.apf.ApfConstants.IPV4_DEST_ADDR_OFFSET;
+import static android.net.apf.ApfConstants.IPV4_DNS_QDCOUNT_NO_OPTIONS_OFFSET;
import static android.net.apf.ApfConstants.IPV4_FRAGMENT_MORE_FRAGS_MASK;
import static android.net.apf.ApfConstants.IPV4_FRAGMENT_OFFSET_MASK;
import static android.net.apf.ApfConstants.IPV4_FRAGMENT_OFFSET_OFFSET;
+import static android.net.apf.ApfConstants.IPV4_IGMP_TYPE_QUERY;
import static android.net.apf.ApfConstants.IPV4_PROTOCOL_OFFSET;
+import static android.net.apf.ApfConstants.IPV4_SRC_ADDR_OFFSET;
+import static android.net.apf.ApfConstants.IPV4_ROUTER_ALERT_OPTION;
+import static android.net.apf.ApfConstants.IPV4_ROUTER_ALERT_OPTION_LEN;
import static android.net.apf.ApfConstants.IPV4_TOTAL_LENGTH_OFFSET;
+import static android.net.apf.ApfConstants.IPV4_UDP_DESTINATION_CHECKSUM_NO_OPTIONS_OFFSET;
+import static android.net.apf.ApfConstants.IPV4_UDP_DESTINATION_PORT_NO_OPTIONS_OFFSET;
+import static android.net.apf.ApfConstants.IPV4_UDP_PAYLOAD_NO_OPTIONS_OFFSET;
import static android.net.apf.ApfConstants.IPV6_ALL_NODES_ADDRESS;
import static android.net.apf.ApfConstants.IPV6_DEST_ADDR_OFFSET;
+import static android.net.apf.ApfConstants.IPV6_DNS_QDCOUNT_OFFSET;
+import static android.net.apf.ApfConstants.IPV6_EXT_HEADER_OFFSET;
import static android.net.apf.ApfConstants.IPV6_FLOW_LABEL_LEN;
import static android.net.apf.ApfConstants.IPV6_FLOW_LABEL_OFFSET;
import static android.net.apf.ApfConstants.IPV6_HEADER_LEN;
import static android.net.apf.ApfConstants.IPV6_HOP_LIMIT_OFFSET;
+import static android.net.apf.ApfConstants.IPV6_MLD_CHECKSUM_OFFSET;
+import static android.net.apf.ApfConstants.IPV6_MLD_HOPOPTS;
+import static android.net.apf.ApfConstants.IPV6_MLD_MESSAGE_MIN_SIZE;
+import static android.net.apf.ApfConstants.IPV6_MLD_MIN_SIZE;
+import static android.net.apf.ApfConstants.IPV6_MLD_MULTICAST_ADDR_OFFSET;
+import static android.net.apf.ApfConstants.IPV6_MLD_TYPE_OFFSET;
+import static android.net.apf.ApfConstants.IPV6_MLD_TYPE_QUERY;
+import static android.net.apf.ApfConstants.IPV6_MLD_TYPE_REPORTS;
+import static android.net.apf.ApfConstants.IPV6_MLD_TYPE_V1_REPORT;
+import static android.net.apf.ApfConstants.IPV6_MLD_TYPE_V2_REPORT;
+import static android.net.apf.ApfConstants.IPV6_MLD_V1_MESSAGE_SIZE;
+import static android.net.apf.ApfConstants.IPV6_MLD_V2_ALL_ROUTERS_MULTICAST_ADDRESS;
+import static android.net.apf.ApfConstants.IPV6_MLD_V2_MULTICAST_ADDRESS_RECORD_SIZE;
import static android.net.apf.ApfConstants.IPV6_NEXT_HEADER_OFFSET;
import static android.net.apf.ApfConstants.IPV6_PAYLOAD_LEN_OFFSET;
import static android.net.apf.ApfConstants.IPV6_SOLICITED_NODES_PREFIX;
import static android.net.apf.ApfConstants.IPV6_SRC_ADDR_OFFSET;
+import static android.net.apf.ApfConstants.IPV6_UDP_DESTINATION_CHECKSUM_OFFSET;
+import static android.net.apf.ApfConstants.IPV6_UDP_DESTINATION_PORT_OFFSET;
import static android.net.apf.ApfConstants.IPV6_UNSPECIFIED_ADDRESS;
-import static android.net.apf.ApfConstants.MDNS_PORT;
+import static android.net.apf.ApfConstants.MLD2_MODE_IS_EXCLUDE;
import static android.net.apf.ApfConstants.TCP_HEADER_SIZE_OFFSET;
import static android.net.apf.ApfConstants.TCP_UDP_DESTINATION_PORT_OFFSET;
import static android.net.apf.ApfConstants.TCP_UDP_SOURCE_PORT_OFFSET;
+import static android.net.apf.ApfCounterTracker.Counter.APF_PROGRAM_ID;
+import static android.net.apf.ApfCounterTracker.Counter.APF_VERSION;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_802_3_FRAME;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_NON_IPV4;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_OTHER_HOST;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_REPLY_SPA_NO_HOST;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_REQUEST_REPLIED;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_UNKNOWN;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_V6_ONLY;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ETHERTYPE_NOT_ALLOWED;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ETHER_OUR_SRC_MAC;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ETH_BROADCAST;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_GARP_REPLY;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_BROADCAST_ADDR;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_BROADCAST_NET;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IGMP_INVALID;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IGMP_REPORT;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_ICMP_INVALID;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_KEEPALIVE_ACK;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_L2_BROADCAST;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_MULTICAST;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_NATT_KEEPALIVE;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_NON_DHCP4;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_PING_REQUEST_REPLIED;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_TCP_PORT7_UNICAST;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_ICMP6_ECHO_REQUEST_INVALID;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_ICMP6_ECHO_REQUEST_REPLIED;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MLD_INVALID;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MLD_REPORT;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MLD_V1_GENERAL_QUERY_REPLIED;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MLD_V2_GENERAL_QUERY_REPLIED;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MULTICAST_NA;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NON_ICMP_MULTICAST;
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_INVALID;
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_OTHER_HOST;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_REPLIED_NON_DAD;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_ROUTER_SOLICITATION;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_MDNS;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_NON_UNICAST_TDLS;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_RA;
import static android.net.apf.ApfCounterTracker.Counter.FILTER_AGE_16384THS;
import static android.net.apf.ApfCounterTracker.Counter.FILTER_AGE_SECONDS;
+import static android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_BROADCAST_REPLY;
+import static android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_REQUEST;
+import static android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_UNICAST_REPLY;
+import static android.net.apf.ApfCounterTracker.Counter.PASSED_DHCP;
+import static android.net.apf.ApfConstants.IPv6_UDP_PAYLOAD_OFFSET;
+import static android.net.apf.ApfConstants.MDNS_IPV4_ADDR;
+import static android.net.apf.ApfConstants.MDNS_IPV4_ADDR_IN_LONG;
+import static android.net.apf.ApfConstants.MDNS_IPV6_ADDR;
+import static android.net.apf.ApfConstants.MDNS_PORT;
+import static android.net.apf.ApfConstants.UDP_HEADER_LEN;
+import static android.net.apf.ApfConstants.MDNS_PORT_IN_BYTES;
import static android.net.apf.ApfCounterTracker.Counter.PASSED_ETHER_OUR_SRC_MAC;
-import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_DAD;
-import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_NO_SLLA_OPTION;
-import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_TENTATIVE;
-import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_NO_ADDRESS;
+import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV4;
+import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV4_FROM_DHCPV4_SERVER;
+import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV4_UNICAST;
+import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_HOPOPTS;
+import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_ICMP;
+import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NON_ICMP;
+import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_UNICAST_NON_ICMP;
+import static android.net.apf.ApfCounterTracker.Counter.PASSED_MDNS;
+import static android.net.apf.ApfCounterTracker.Counter.PASSED_NON_IP_UNICAST;
+import static android.net.apf.ApfCounterTracker.Counter.TOTAL_PACKETS;
import static android.net.apf.ApfCounterTracker.getCounterValue;
import static android.net.apf.BaseApfGenerator.MemorySlot;
import static android.net.apf.BaseApfGenerator.Register.R0;
import static android.net.apf.BaseApfGenerator.Register.R1;
-import static android.net.nsd.OffloadEngine.OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK;
-import static android.net.nsd.OffloadEngine.OFFLOAD_TYPE_REPLY;
import static android.net.util.SocketUtils.makePacketSocketAddress;
import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
import static android.os.PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED;
import static android.system.OsConstants.AF_PACKET;
+import static android.system.OsConstants.ETH_P_ALL;
import static android.system.OsConstants.ETH_P_ARP;
import static android.system.OsConstants.ETH_P_IP;
import static android.system.OsConstants.ETH_P_IPV6;
+import static android.system.OsConstants.ICMP6_ECHO_REPLY;
+import static android.system.OsConstants.ICMP_ECHO;
+import static android.system.OsConstants.ICMP_ECHOREPLY;
import static android.system.OsConstants.IFA_F_TENTATIVE;
+import static android.system.OsConstants.IPPROTO_ICMP;
import static android.system.OsConstants.IPPROTO_ICMPV6;
import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.IPPROTO_UDP;
@@ -114,10 +213,13 @@
import static android.system.OsConstants.SOCK_NONBLOCK;
import static android.system.OsConstants.SOCK_RAW;
+import static com.android.net.module.util.CollectionUtils.concatArrays;
import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN;
import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_DST_ADDR_OFFSET;
import static com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN;
import static com.android.net.module.util.NetworkStackConstants.ETHER_SRC_ADDR_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.ICMP_HEADER_LEN;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NA_HEADER_LEN;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA;
@@ -127,13 +229,22 @@
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_SOLICITATION;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ALL_HOST_MULTICAST;
import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_LEN;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_FLAG_DF;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_IGMP_GROUP_RECORD_SIZE;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_IGMP_MIN_SIZE;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_IGMP_TYPE_V3_REPORT;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_PROTOCOL_IGMP;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_NODES_MULTICAST;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ANY;
import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_LEN;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_NODE_LOCAL_ALL_NODES_MULTICAST;
import android.annotation.ChecksSdkIntAtLeast;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.RequiresApi;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -145,11 +256,8 @@
import android.net.TcpKeepalivePacketDataParcelable;
import android.net.apf.ApfCounterTracker.Counter;
import android.net.apf.BaseApfGenerator.IllegalInstructionException;
-import android.net.ip.IpClient.IpClientCallbacksWrapper;
+import android.net.ip.MulticastReportMonitor;
import android.net.nsd.NsdManager;
-import android.net.nsd.OffloadEngine;
-import android.net.nsd.OffloadServiceInfo;
-import android.os.Build;
import android.os.Handler;
import android.os.PowerManager;
import android.os.SystemClock;
@@ -202,7 +310,23 @@
*
* @hide
*/
-public class ApfFilter implements AndroidPacketFilter {
+public class ApfFilter {
+
+ /**
+ * Defines the communication API between the ApfFilter and the APF interpreter
+ * residing within the Wi-Fi/Ethernet firmware.
+ */
+ public interface IApfController {
+ /**
+ * Install the APF program to firmware.
+ */
+ boolean installPacketFilter(@NonNull byte[] filter, @NonNull String filterConfig);
+
+ /**
+ * Read the APF RAM from firmware.
+ */
+ void readPacketFilterRam(@NonNull String event);
+ }
// Helper class for specifying functional filter parameters.
public static class ApfConfiguration {
@@ -216,9 +340,13 @@
public int acceptRaMinLft;
public long minMetricsSessionDurationMs;
public boolean hasClatInterface;
- public boolean shouldHandleArpOffload;
- public boolean shouldHandleNdOffload;
- public boolean shouldHandleMdnsOffload;
+ public boolean handleArpOffload;
+ public boolean handleNdOffload;
+ public boolean handleMdnsOffload;
+ public boolean handleIgmpOffload;
+ public boolean handleMldOffload;
+ public boolean handleIpv4PingOffload;
+ public boolean handleIpv6PingOffload;
}
@@ -243,13 +371,11 @@
}
private static final String TAG = "ApfFilter";
- private static final boolean DBG = true;
- private static final boolean VDBG = false;
private final int mApfRamSize;
private final int mMaximumApfProgramSize;
private final int mInstallableProgramSizeClamp;
- private final IpClientCallbacksWrapper mIpClientCallback;
+ private final IApfController mApfController;
private final InterfaceParams mInterfaceParams;
private final TokenBucket mTokenBucket;
@@ -283,17 +409,27 @@
// 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 mShouldHandleArpOffload;
- private final boolean mShouldHandleNdOffload;
- private final boolean mShouldHandleMdnsOffload;
+ private final boolean mHandleArpOffload;
+ private final boolean mHandleNdOffload;
+ private final boolean mHandleMdnsOffload;
+ private final boolean mHandleIgmpOffload;
+ private final boolean mHandleMldOffload;
+ private final boolean mHandleIpv4PingOffload;
+ private final boolean mHandleIpv6PingOffload;
private final NetworkQuirkMetrics mNetworkQuirkMetrics;
private final IpClientRaInfoMetrics mIpClientRaInfoMetrics;
private final ApfSessionInfoMetrics mApfSessionInfoMetrics;
private final NsdManager mNsdManager;
- @VisibleForTesting
- final List<OffloadServiceInfo> mOffloadServiceInfos = new ArrayList<>();
- private OffloadEngine mOffloadEngine;
+ private final MulticastReportMonitor mMulticastReportMonitor;
+ private final ApfMdnsOffloadEngine mApfMdnsOffloadEngine;
+ private final List<MdnsOffloadRule> mOffloadRules = new ArrayList<>();
+ // The number of mDNS rules requiring APF to transmit a reply and drop the query packet. A
+ // value of -1 means all mDNS query packets should be passed; no mDNS query packets will trigger
+ // the transmit and reply logic.
+ private int mNumOfMdnsRuleToOffload = -1;
+
+ private int mOverEstimatedProgramSize = 0;
private static boolean isDeviceIdleModeChangedAction(Intent intent) {
return ACTION_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction());
@@ -350,6 +486,25 @@
// Our tentative IPv6 addresses
private Set<Inet6Address> mIPv6TentativeAddresses = new ArraySet<>();
+ // Our link-local IPv6 address
+ private Inet6Address mIPv6LinkLocalAddress;
+
+ // Our joined IPv4 multicast addresses
+ @VisibleForTesting
+ final Set<Inet4Address> mIPv4MulticastAddresses = new ArraySet<>();
+
+ // Our joined IPv4 multicast address exclude all all host multicast (224.0.0.1)
+ @VisibleForTesting
+ final Set<Inet4Address> mIPv4McastAddrsExcludeAllHost = new ArraySet<>();
+
+ // Our joined IPv6 multicast addresses
+ @VisibleForTesting
+ final Set<Inet6Address> mIPv6MulticastAddresses = new ArraySet<>();
+
+ // Our joined IPv6 multicast address exclude ff02::1, ff01::1
+ @VisibleForTesting
+ final Set<Inet6Address> mIPv6McastAddrsExcludeAllHost = new ArraySet<>();
+
// Whether CLAT is enabled.
private boolean mHasClat;
@@ -360,44 +515,10 @@
private final Dependencies mDependencies;
- @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
- private void registerOffloadEngine() {
- if (mOffloadEngine != null) {
- Log.wtf(TAG,
- "registerOffloadEngine called twice without calling unregisterOffloadEngine");
- return;
- }
- mOffloadEngine = new OffloadEngine() {
- @Override
- public void onOffloadServiceUpdated(@NonNull OffloadServiceInfo info) {
- mOffloadServiceInfos.removeIf(i -> i.getKey().equals(info.getKey()));
- mOffloadServiceInfos.add(info);
- }
-
- @Override
- public void onOffloadServiceRemoved(@NonNull OffloadServiceInfo info) {
- mOffloadServiceInfos.removeIf(i -> i.getKey().equals(info.getKey()));
- }
- };
- mNsdManager.registerOffloadEngine(mInterfaceParams.name,
- OFFLOAD_TYPE_REPLY,
- OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK,
- mHandler::post, mOffloadEngine);
- }
-
- @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
- private void unregisterOffloadEngine() {
- if (mOffloadEngine != null) {
- mNsdManager.unregisterOffloadEngine(mOffloadEngine);
- mOffloadServiceInfos.clear();
- mOffloadEngine = null;
- }
- }
-
public ApfFilter(Handler handler, Context context, ApfConfiguration config,
- InterfaceParams ifParams, IpClientCallbacksWrapper ipClientCallback,
+ InterfaceParams ifParams, IApfController apfController,
NetworkQuirkMetrics networkQuirkMetrics) {
- this(handler, context, config, ifParams, ipClientCallback, networkQuirkMetrics,
+ this(handler, context, config, ifParams, apfController, networkQuirkMetrics,
new Dependencies(context));
}
@@ -406,16 +527,13 @@
// in an SSID. This is limited to APFv3 devices because this large write triggers
// a crash on some older devices (b/78905546).
if (hasDataAccess(mApfVersionSupported)) {
- byte[] zeroes = new byte[mApfRamSize];
- if (!mIpClientCallback.installPacketFilter(zeroes)) {
- sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE);
- }
+ installPacketFilter(new byte[mApfRamSize], getApfConfigMessage() + " (cleanup)");
}
}
@VisibleForTesting
public ApfFilter(Handler handler, Context context, ApfConfiguration config,
- InterfaceParams ifParams, IpClientCallbacksWrapper ipClientCallback,
+ InterfaceParams ifParams, IApfController apfController,
NetworkQuirkMetrics networkQuirkMetrics, Dependencies dependencies) {
mHandler = handler;
mApfVersionSupported = config.apfVersionSupported;
@@ -430,16 +548,20 @@
if (maximumApfProgramSize > mInstallableProgramSizeClamp) {
maximumApfProgramSize = mInstallableProgramSizeClamp;
}
- mMaximumApfProgramSize = maximumApfProgramSize;
- mIpClientCallback = ipClientCallback;
+ mMaximumApfProgramSize = Math.max(0, maximumApfProgramSize);
+ mApfController = apfController;
mInterfaceParams = ifParams;
mMulticastFilter = config.multicastFilter;
mDrop802_3Frames = config.ieee802_3Filter;
mMinRdnssLifetimeSec = config.minRdnssLifetimeSec;
mAcceptRaMinLft = config.acceptRaMinLft;
- mShouldHandleArpOffload = config.shouldHandleArpOffload;
- mShouldHandleNdOffload = config.shouldHandleNdOffload;
- mShouldHandleMdnsOffload = config.shouldHandleMdnsOffload;
+ mHandleArpOffload = config.handleArpOffload;
+ mHandleNdOffload = config.handleNdOffload;
+ mHandleMdnsOffload = config.handleMdnsOffload;
+ mHandleIgmpOffload = config.handleIgmpOffload;
+ mHandleMldOffload = config.handleMldOffload;
+ mHandleIpv4PingOffload = config.handleIpv4PingOffload;
+ mHandleIpv6PingOffload = config.handleIpv6PingOffload;
mDependencies = dependencies;
mNetworkQuirkMetrics = networkQuirkMetrics;
mIpClientRaInfoMetrics = dependencies.getIpClientRaInfoMetrics();
@@ -474,13 +596,38 @@
Log.wtf(TAG, "Failed to start RaPacketReader");
}
+ mMulticastReportMonitor = createMulticastReportMonitor();
+ if (mMulticastReportMonitor != null) {
+ mMulticastReportMonitor.start();
+ }
+
// Listen for doze-mode transition changes to enable/disable the IPv6 multicast filter.
mDependencies.addDeviceIdleReceiver(mDeviceIdleReceiver);
mNsdManager = context.getSystemService(NsdManager.class);
- if (shouldEnableMdnsOffload()) {
- registerOffloadEngine();
+ if (enableOffloadEngineRegistration()) {
+ mApfMdnsOffloadEngine = new ApfMdnsOffloadEngine(mInterfaceParams.name, mHandler,
+ mNsdManager,
+ allRules -> {
+ mOffloadRules.clear();
+ mOffloadRules.addAll(allRules);
+ installNewProgram();
+ });
+ mApfMdnsOffloadEngine.registerOffloadEngine();
+ } else {
+ mApfMdnsOffloadEngine = null;
}
+
+ mIPv4MulticastAddresses.addAll(
+ mDependencies.getIPv4MulticastAddresses(mInterfaceParams.name));
+ mIPv4McastAddrsExcludeAllHost.addAll(mIPv4MulticastAddresses);
+ mIPv4McastAddrsExcludeAllHost.remove((IPV4_ADDR_ALL_HOST_MULTICAST));
+
+ mIPv6MulticastAddresses.addAll(
+ mDependencies.getIPv6MulticastAddresses(mInterfaceParams.name));
+ mIPv6McastAddrsExcludeAllHost.addAll(mIPv6MulticastAddresses);
+ mIPv6McastAddrsExcludeAllHost.remove(IPV6_ADDR_ALL_NODES_MULTICAST);
+ mIPv6McastAddrsExcludeAllHost.remove(IPV6_ADDR_NODE_LOCAL_ALL_NODES_MULTICAST);
}
/**
@@ -512,6 +659,42 @@
}
/**
+ * Create a socket to read egress IGMPv2/v3 reports.
+ */
+ @Nullable
+ public FileDescriptor createEgressIgmpReportsReaderSocket(int ifIndex) {
+ FileDescriptor socket;
+ try {
+ socket = Os.socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, 0);
+ NetworkStackUtils.attachEgressIgmpReportFilter(socket);
+ Os.bind(socket, makePacketSocketAddress(ETH_P_ALL, ifIndex));
+ } catch (SocketException | ErrnoException e) {
+ Log.wtf(TAG, "Error starting filter", e);
+ return null;
+ }
+
+ return socket;
+ }
+
+ /**
+ * Create a socket to read egress IGMPv2/v3, MLDv1/v2 reports.
+ */
+ @Nullable
+ public FileDescriptor createEgressMulticastReportsReaderSocket(int ifIndex) {
+ FileDescriptor socket;
+ try {
+ socket = Os.socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, 0);
+ NetworkStackUtils.attachEgressMulticastReportFilter(socket);
+ Os.bind(socket, makePacketSocketAddress(ETH_P_ALL, ifIndex));
+ } catch (SocketException | ErrnoException e) {
+ Log.wtf(TAG, "Error starting filter", e);
+ return null;
+ }
+
+ return socket;
+ }
+
+ /**
* Get elapsedRealtime.
*/
public long elapsedRealtime() {
@@ -552,7 +735,7 @@
* This method is designed to be overridden in test classes to collect created ApfFilter
* instances.
*/
- public void onApfFilterCreated(@NonNull AndroidPacketFilter apfFilter) {
+ public void onApfFilterCreated(@NonNull ApfFilter apfFilter) {
}
/**
@@ -603,9 +786,41 @@
public int getNdTrafficClass(@NonNull String ifname) {
return ProcfsParsingUtils.getNdTrafficClass(ifname);
}
+
+ /**
+ * Returns the default TTL value for IPv4 packets from '/proc/sys/net/ipv4/ip_default_ttl'.
+ */
+ public int getIpv4DefaultTtl() {
+ return ProcfsParsingUtils.getIpv4DefaultTtl();
+ }
+
+ /**
+ * Returns the default HopLimit value for IPv6 packets.
+ */
+ public int getIpv6DefaultHopLimit(@NonNull String ifname) {
+ return ProcfsParsingUtils.getIpv6DefaultHopLimit(ifname);
+ }
+
+ /**
+ * Loads the existing IPv4 multicast addresses from the file
+ * `/proc/net/igmp`.
+ */
+ public List<Inet4Address> getIPv4MulticastAddresses(@NonNull String ifname) {
+ return ProcfsParsingUtils.getIPv4MulticastAddresses(ifname);
+ }
+
+ /**
+ * Loads the existing IPv6 multicast addresses from the file `/proc/net/igmp6`.
+ */
+ public List<Inet6Address> getIPv6MulticastAddresses(@NonNull String ifname) {
+ return ProcfsParsingUtils.getIpv6MulticastAddresses(ifname);
+ }
}
- @Override
+ public IApfController getApfController() {
+ return mApfController;
+ }
+
public String setDataSnapshot(byte[] data) {
mDataSnapshot = data;
if (mIsRunning) {
@@ -614,6 +829,26 @@
return mApfCounterTracker.getCounters().toString();
}
+ private MulticastReportMonitor createMulticastReportMonitor() {
+ FileDescriptor socketFd = null;
+
+ // Check if MLD report monitor is enabled first, it includes the IGMP report monitor.
+ if (enableMldReportsMonitor()) {
+ socketFd =
+ mDependencies.createEgressMulticastReportsReaderSocket(mInterfaceParams.index);
+ } else if (enableIgmpReportsMonitor()) {
+ socketFd =
+ mDependencies.createEgressIgmpReportsReaderSocket(mInterfaceParams.index);
+ }
+
+ return socketFd != null ? new MulticastReportMonitor(
+ mHandler,
+ mInterfaceParams,
+ this::updateMulticastAddrs,
+ socketFd
+ ) : null;
+ }
+
private void log(String s) {
Log.d(TAG, "(" + mInterfaceParams.name + "): " + s);
}
@@ -698,6 +933,7 @@
this.min = min;
}
+ @Override
public String toString() {
if (type == Type.LIFETIME) {
return String.format("%s: (%d, %d) %d %d", type, start, length, lifetime, min);
@@ -719,11 +955,11 @@
// 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;
+ private final long mMinPioValidLifetime;
// Minimum route lifetime of RIOs in packet, Long.MAX_VALUE means not seen.
- private long mMinRioRouteLifetime = Long.MAX_VALUE;
+ private final long mMinRioRouteLifetime;
// Minimum lifetime of RDNSSs in packet, Long.MAX_VALUE means not seen.
- private long mMinRdnssLifetime = Long.MAX_VALUE;
+ private final long mMinRdnssLifetime;
// The time in seconds in which some of the information contained in this RA expires.
private final int mExpirationTime;
// When the packet was last captured, in seconds since Unix Epoch
@@ -809,6 +1045,7 @@
sb.append("/").append(prefixLen).append(" ");
}
+ @Override
public String toString() {
try {
StringBuffer sb = new StringBuffer();
@@ -897,9 +1134,15 @@
* Adds packet sections for an RA option with a 4-byte lifetime 4 bytes into the option
* @param optionLength the length of the option in bytes
* @param min the minimum acceptable lifetime
+ * @param isRdnss true iff this is an RDNSS option
*/
- private long add4ByteLifetimeOption(int optionLength, int min) {
- addMatchSection(ICMP6_4_BYTE_LIFETIME_OFFSET);
+ private long add4ByteLifetimeOption(int optionLength, int min, boolean isRdnss) {
+ if (isRdnss) {
+ addMatchSection(ICMP6_4_BYTE_LIFETIME_OFFSET - 2);
+ addIgnoreSection(2); // reserved, but observed non-zero
+ } else {
+ addMatchSection(ICMP6_4_BYTE_LIFETIME_OFFSET);
+ }
final long lifetime = getUint32(mPacket, mPacket.position());
addLifetimeSection(ICMP6_4_BYTE_LIFETIME_LEN, lifetime, min);
addMatchSection(optionLength - ICMP6_4_BYTE_LIFETIME_OFFSET
@@ -907,34 +1150,6 @@
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
@@ -980,6 +1195,10 @@
// Add remaining fields (reachable time and retransmission timer) to match section.
addMatchUntil(ICMP6_RA_OPTION_OFFSET);
+ long minPioValidLifetime = Long.MAX_VALUE;
+ long minRioRouteLifetime = Long.MAX_VALUE;
+ long minRdnssLifetime = Long.MAX_VALUE;
+
while (mPacket.hasRemaining()) {
final int position = mPacket.position();
final int optionType = getUint8(mPacket, position);
@@ -999,8 +1218,8 @@
lifetime = getUint32(mPacket, mPacket.position());
addLifetimeSection(ICMP6_PREFIX_OPTION_VALID_LIFETIME_LEN,
lifetime, mAcceptRaMinLft);
- mMinPioValidLifetime = getMinForPositiveValue(
- mMinPioValidLifetime, lifetime);
+ minPioValidLifetime = getMinForPositiveValue(
+ minPioValidLifetime, lifetime);
if (lifetime == 0) mNumZeroLifetimeRas++;
// Parse preferred lifetime
@@ -1017,15 +1236,15 @@
// are processed with the same specialized add4ByteLifetimeOption:
case ICMP6_RDNSS_OPTION_TYPE:
mRdnssOptionOffsets.add(position);
- lifetime = add4ByteLifetimeOption(optionLength, mMinRdnssLifetimeSec);
- mMinRdnssLifetime = getMinForPositiveValue(mMinRdnssLifetime, lifetime);
+ lifetime = add4ByteLifetimeOption(optionLength, mMinRdnssLifetimeSec, true);
+ minRdnssLifetime = getMinForPositiveValue(minRdnssLifetime, lifetime);
if (lifetime == 0) mNumZeroLifetimeRas++;
break;
case ICMP6_ROUTE_INFO_OPTION_TYPE:
mRioOptionOffsets.add(position);
- lifetime = add4ByteLifetimeOption(optionLength, mAcceptRaMinLft);
- mMinRioRouteLifetime = getMinForPositiveValue(
- mMinRioRouteLifetime, lifetime);
+ lifetime = add4ByteLifetimeOption(optionLength, mAcceptRaMinLft, false);
+ minRioRouteLifetime = getMinForPositiveValue(
+ minRioRouteLifetime, lifetime);
if (lifetime == 0) mNumZeroLifetimeRas++;
break;
case ICMP6_SOURCE_LL_ADDRESS_OPTION_TYPE:
@@ -1046,6 +1265,10 @@
break;
}
}
+
+ mMinPioValidLifetime = minPioValidLifetime;
+ mMinRioRouteLifetime = minRioRouteLifetime;
+ mMinRdnssLifetime = minRdnssLifetime;
mExpirationTime = getExpirationTime();
}
@@ -1189,11 +1412,17 @@
return Math.min(65535, filterLifetime);
}
+ int getRaProgramLengthOverEstimate(int timeSeconds) throws IllegalInstructionException {
+ final ApfV4GeneratorBase<?> gen = createApfGenerator();
+ generateFilter(gen, timeSeconds);
+ return gen.programLengthOverEstimate() - gen.getBaseProgramSize();
+ }
+
// Append a filter for this RA to {@code gen}. Jump to DROP_LABEL if it should be dropped.
// Jump to the next filter if packet doesn't match this RA.
void generateFilter(ApfV4GeneratorBase<?> gen, int timeSeconds)
throws IllegalInstructionException {
- String nextFilterLabel = gen.getUniqueLabel();
+ short nextFilterLabel = gen.getUniqueLabel();
// Skip if packet is not the right size
gen.addLoadFromMemory(R0, MemorySlot.PACKET_SIZE);
gen.addJumpIfR0NotEquals(mPacket.capacity(), nextFilterLabel);
@@ -1211,8 +1440,8 @@
} else {
switch (section.length) {
// length asserted to be either 2 or 4 on PacketSection construction
- case 2: gen.addLoad16(R0, section.start); break;
- case 4: gen.addLoad32(R0, section.start); break;
+ case 2: gen.addLoad16intoR0(section.start); break;
+ case 4: gen.addLoad32intoR0(section.start); break;
}
// WARNING: keep this in sync with matches()!
@@ -1258,7 +1487,7 @@
gen.addJumpIfR0Equals(0, nextFilterLabel);
gen.addJumpIfR0GreaterThan(section.lifetime, nextFilterLabel);
} else {
- final String continueLabel = gen.getUniqueLabel();
+ final short continueLabel = gen.getUniqueLabel();
// Case 4a) otherwise
//
// if lft == 0 -> PASS
@@ -1276,7 +1505,7 @@
}
}
}
- gen.addCountAndDrop(Counter.DROPPED_RA);
+ gen.addCountAndDrop(DROPPED_RA);
gen.defineLabel(nextFilterLabel);
}
}
@@ -1321,7 +1550,7 @@
NattKeepaliveResponse(final NattKeepalivePacketDataParcelable sentKeepalivePacket) {
mPacket = new NattKeepaliveResponseData(sentKeepalivePacket);
- mSrcDstAddr = concatArrays(mPacket.srcAddress, mPacket.dstAddress);
+ mSrcDstAddr = CollectionUtils.concatArrays(mPacket.srcAddress, mPacket.dstAddress);
mPortFingerprint = generatePortFingerprint(mPacket.srcPort, mPacket.dstPort);
}
@@ -1335,7 +1564,7 @@
@Override
void generateFilter(ApfV4GeneratorBase<?> gen) throws IllegalInstructionException {
- final String nextFilterLabel = gen.getUniqueLabel();
+ final short nextFilterLabel = gen.getUniqueLabel();
gen.addLoadImmediate(R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET);
gen.addJumpIfBytesAtR0NotEqual(mSrcDstAddr, nextFilterLabel);
@@ -1345,7 +1574,7 @@
gen.addLoadFromMemory(R0, MemorySlot.IPV4_HEADER_SIZE);
gen.addAdd(UDP_HEADER_LEN);
gen.addSwap();
- gen.addLoad16(R0, IPV4_TOTAL_LENGTH_OFFSET);
+ gen.addLoad16intoR0(IPV4_TOTAL_LENGTH_OFFSET);
gen.addNeg(R1);
gen.addAddR1ToR0();
gen.addJumpIfR0NotEquals(1, nextFilterLabel);
@@ -1359,10 +1588,11 @@
gen.addAdd(UDP_HEADER_LEN);
gen.addJumpIfBytesAtR0NotEqual(mPayload, nextFilterLabel);
- gen.addCountAndDrop(Counter.DROPPED_IPV4_NATT_KEEPALIVE);
+ gen.addCountAndDrop(DROPPED_IPV4_NATT_KEEPALIVE);
gen.defineLabel(nextFilterLabel);
}
+ @Override
public String toString() {
try {
return String.format("%s -> %s",
@@ -1418,6 +1648,7 @@
return fp.array();
}
+ @Override
public String toString() {
try {
return String.format("%s -> %s , seq=%d, ack=%d",
@@ -1445,12 +1676,13 @@
this(new TcpKeepaliveAckData(sentKeepalivePacket));
}
TcpKeepaliveAckV4(final TcpKeepaliveAckData packet) {
- super(packet, concatArrays(packet.srcAddress, packet.dstAddress) /* srcDstAddr */);
+ super(packet, CollectionUtils.concatArrays(packet.srcAddress,
+ packet.dstAddress) /* srcDstAddr */);
}
@Override
void generateFilter(ApfV4GeneratorBase<?> gen) throws IllegalInstructionException {
- final String nextFilterLabel = gen.getUniqueLabel();
+ final short nextFilterLabel = gen.getUniqueLabel();
gen.addLoadImmediate(R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET);
gen.addJumpIfBytesAtR0NotEqual(mSrcDstAddr, nextFilterLabel);
@@ -1460,15 +1692,18 @@
// Load the IP header size into R1
gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE);
// Load the TCP header size into R0 (it's indexed by R1)
- gen.addLoad8Indexed(R0, ETH_HEADER_LEN + TCP_HEADER_SIZE_OFFSET);
- // Size offset is in the top nibble, but it must be multiplied by 4, and the two
- // top bits of the low nibble are guaranteed to be zeroes. Right-shift R0 by 2.
+ gen.addLoad8R1IndexedIntoR0(ETH_HEADER_LEN + TCP_HEADER_SIZE_OFFSET);
+ // Size offset is in the top nibble, bottom nibble is reserved,
+ // but not necessarily zero. Thus we need to >> 4 then << 2,
+ // achieve this by >> 2 and masking with 0b00111100.
gen.addRightShift(2);
+ gen.addAnd(0x3C);
// R0 += R1 -> R0 contains TCP + IP headers length
gen.addAddR1ToR0();
// Load IPv4 total length
- gen.addLoad16(R1, IPV4_TOTAL_LENGTH_OFFSET);
- gen.addNeg(R0);
+ gen.addSwap();
+ gen.addLoad16intoR0(IPV4_TOTAL_LENGTH_OFFSET);
+ gen.addNeg(R1);
gen.addAddR1ToR0();
gen.addJumpIfR0NotEquals(0, nextFilterLabel);
// Add IPv4 header length
@@ -1477,7 +1712,7 @@
gen.addAddR1ToR0();
gen.addJumpIfBytesAtR0NotEqual(mPortSeqAckFingerprint, nextFilterLabel);
- gen.addCountAndDrop(Counter.DROPPED_IPV4_KEEPALIVE_ACK);
+ gen.addCountAndDrop(DROPPED_IPV4_KEEPALIVE_ACK);
gen.defineLabel(nextFilterLabel);
}
}
@@ -1487,7 +1722,8 @@
this(new TcpKeepaliveAckData(sentKeepalivePacket));
}
TcpKeepaliveAckV6(final TcpKeepaliveAckData packet) {
- super(packet, concatArrays(packet.srcAddress, packet.dstAddress) /* srcDstAddr */);
+ super(packet, CollectionUtils.concatArrays(packet.srcAddress,
+ packet.dstAddress) /* srcDstAddr */);
}
@Override
@@ -1500,9 +1736,8 @@
private static final int MAX_RAS = 10;
private final ArrayList<Ra> mRas = new ArrayList<>();
+ private int mNumFilteredRas = 0;
private final SparseArray<KeepalivePacket> mKeepalivePackets = new SparseArray<>();
- // TODO: change the mMdnsAllowList to proper type for APFv6 based mDNS offload
- private final List<String[]> mMdnsAllowList = new ArrayList<>();
// We don't want to filter an RA for it's whole lifetime as it'll be expired by the time we ever
// see a refresh. Using half the lifetime might be a good idea except for the fact that
@@ -1534,13 +1769,6 @@
// The maximum number of distinct RAs
private int mMaxDistinctRas = 0;
- private ApfV6Generator tryToConvertToApfV6Generator(ApfV4GeneratorBase<?> gen) {
- if (gen instanceof ApfV6Generator) {
- return (ApfV6Generator) gen;
- }
- return null;
- }
-
/**
* Generate filter code to process ARP packets. Execution of this code ends in either the
* DROP_LABEL or PASS_LABEL and does not fall off the end.
@@ -1579,45 +1807,44 @@
// For IPv6 only network, drop all ARP packet.
if (mHasClat) {
- gen.addCountAndDrop(Counter.DROPPED_ARP_V6_ONLY);
+ gen.addCountAndDrop(DROPPED_ARP_V6_ONLY);
return;
}
// Drop if not ARP IPv4.
- gen.addLoadImmediate(R0, ARP_HEADER_OFFSET);
- gen.addCountAndDropIfBytesAtR0NotEqual(ARP_IPV4_HEADER, Counter.DROPPED_ARP_NON_IPV4);
+ gen.addCountAndDropIfBytesAtOffsetNotEqual(ARP_HEADER_OFFSET, ARP_IPV4_HEADER,
+ DROPPED_ARP_NON_IPV4);
- final String checkArpRequest = gen.getUniqueLabel();
+ final short checkArpRequest = gen.getUniqueLabel();
- gen.addLoad16(R0, ARP_OPCODE_OFFSET);
+ gen.addLoad16intoR0(ARP_OPCODE_OFFSET);
gen.addJumpIfR0Equals(ARP_OPCODE_REQUEST, checkArpRequest); // Skip to arp request check.
// Drop if unknown ARP opcode.
- gen.addCountAndDropIfR0NotEquals(ARP_OPCODE_REPLY, Counter.DROPPED_ARP_UNKNOWN);
+ gen.addCountAndDropIfR0NotEquals(ARP_OPCODE_REPLY, DROPPED_ARP_UNKNOWN);
/*---------- Handle ARP Replies. ----------*/
// Drop if ARP reply source IP is 0.0.0.0
- gen.addLoad32(R0, ARP_SOURCE_IP_ADDRESS_OFFSET);
- gen.addCountAndDropIfR0Equals(IPV4_ANY_HOST_ADDRESS, Counter.DROPPED_ARP_REPLY_SPA_NO_HOST);
+ gen.addLoad32intoR0(ARP_SOURCE_IP_ADDRESS_OFFSET);
+ gen.addCountAndDropIfR0Equals(IPV4_ANY_HOST_ADDRESS, DROPPED_ARP_REPLY_SPA_NO_HOST);
// Pass if non-broadcast reply.
// This also accepts multicast arp, but we assume those don't exist.
- gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET);
- gen.addCountAndPassIfBytesAtR0NotEqual(ETHER_BROADCAST, Counter.PASSED_ARP_UNICAST_REPLY);
+ gen.addCountAndPassIfBytesAtOffsetNotEqual(ETH_DEST_ADDR_OFFSET, ETHER_BROADCAST,
+ PASSED_ARP_UNICAST_REPLY);
// It is a broadcast reply.
if (mIPv4Address == null) {
// When there is no IPv4 address, drop GARP replies (b/29404209).
- gen.addLoad32(R0, ARP_TARGET_IP_ADDRESS_OFFSET);
- gen.addCountAndDropIfR0Equals(IPV4_ANY_HOST_ADDRESS, Counter.DROPPED_GARP_REPLY);
+ gen.addLoad32intoR0(ARP_TARGET_IP_ADDRESS_OFFSET);
+ gen.addCountAndDropIfR0Equals(IPV4_ANY_HOST_ADDRESS, DROPPED_GARP_REPLY);
} else {
// When there is an IPv4 address, drop broadcast replies with a different target IPv4
// address.
- gen.addLoad32(R0, ARP_TARGET_IP_ADDRESS_OFFSET);
- gen.addCountAndDropIfR0NotEquals(bytesToBEInt(mIPv4Address),
- Counter.DROPPED_ARP_OTHER_HOST);
+ gen.addLoad32intoR0(ARP_TARGET_IP_ADDRESS_OFFSET);
+ gen.addCountAndDropIfR0NotEquals(bytesToBEInt(mIPv4Address), DROPPED_ARP_OTHER_HOST);
}
- gen.addCountAndPass(Counter.PASSED_ARP_BROADCAST_REPLY);
+ gen.addCountAndPass(PASSED_ARP_BROADCAST_REPLY);
/*---------- Handle ARP Requests. ----------*/
@@ -1625,12 +1852,11 @@
if (mIPv4Address != null) {
// When there is an IPv4 address, drop unicast/broadcast requests with a different
// target IPv4 address.
- gen.addLoad32(R0, ARP_TARGET_IP_ADDRESS_OFFSET);
- gen.addCountAndDropIfR0NotEquals(bytesToBEInt(mIPv4Address),
- Counter.DROPPED_ARP_OTHER_HOST);
+ gen.addLoad32intoR0(ARP_TARGET_IP_ADDRESS_OFFSET);
+ gen.addCountAndDropIfR0NotEquals(bytesToBEInt(mIPv4Address), DROPPED_ARP_OTHER_HOST);
- ApfV6Generator v6Gen = tryToConvertToApfV6Generator(gen);
- if (v6Gen != null && mShouldHandleArpOffload) {
+ if (enableArpOffload()) {
+ ApfV6GeneratorBase<?> v6Gen = (ApfV6GeneratorBase<?>) gen;
// Ethernet requires that all packets be at least 60 bytes long
v6Gen.addAllocate(60)
.addPacketCopy(ETHER_SRC_ADDR_OFFSET, ETHER_ADDR_LEN)
@@ -1644,12 +1870,156 @@
.addAdd(18)
.addStoreToMemory(MemorySlot.TX_BUFFER_OUTPUT_POINTER, R0)
.addTransmitWithoutChecksum()
- .addCountAndDrop(Counter.DROPPED_ARP_REQUEST_REPLIED);
+ .addCountAndDrop(DROPPED_ARP_REQUEST_REPLIED);
}
}
// If we're not clat, and we don't have an ipv4 address, allow all ARP request to avoid
// racing against DHCP.
- gen.addCountAndPass(Counter.PASSED_ARP_REQUEST);
+ gen.addCountAndPass(PASSED_ARP_REQUEST);
+ }
+
+ /**
+ * Generate filter code to reply and drop unicast ICMPv4 echo request.
+ * <p>
+ * 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.
+ */
+ private void generateUnicastIpv4PingOffload(ApfV6GeneratorBase<?> gen)
+ throws IllegalInstructionException {
+
+ final short skipIpv4PingFilter = gen.getUniqueLabel();
+ // Check 1) it's not a fragment. 2) it's ICMP.
+ // If condition not match then skip the ping filter logic
+ gen.addJumpIfNotUnfragmentedIPv4Protocol(IPPROTO_ICMP, skipIpv4PingFilter);
+
+ // Only offload unicast Ipv4 ping request for now.
+ // While we could potentially support offloading multicast and broadcast ping requests in
+ // the future, such packets will likely be dropped by multicast filters.
+ // Since the device may have packet forwarding enabled, APF needs to pass any received
+ // unicast IPv4 ping not destined for the device's IP address to the kernel.
+ gen.addJumpIfBytesAtOffsetNotEqual(
+ ETH_DEST_ADDR_OFFSET, mHardwareAddress, skipIpv4PingFilter)
+ .addLoadImmediate(R0, IPV4_DEST_ADDR_OFFSET)
+ .addJumpIfBytesAtR0NotEqual(mIPv4Address, skipIpv4PingFilter);
+
+ // Ignore ping packets with IPv4 options (header size != 20) as they are rare.
+ // Pass them to the kernel to save bytecode space.
+ gen.addLoadFromMemory(R0, MemorySlot.IPV4_HEADER_SIZE)
+ .addJumpIfR0NotEquals(IPV4_HEADER_MIN_LEN, skipIpv4PingFilter);
+
+ // We need to check if the packet is sufficiently large to be a valid ICMP packet.
+ gen.addLoadFromMemory(R0, MemorySlot.PACKET_SIZE)
+ .addCountAndDropIfR0LessThan(
+ ETHER_HEADER_LEN + IPV4_HEADER_MIN_LEN + ICMP_HEADER_LEN,
+ DROPPED_IPV4_ICMP_INVALID);
+
+ // If it is not a ICMP echo request, then skip.
+ gen.addLoad8intoR0(ICMP4_TYPE_NO_OPTIONS_OFFSET)
+ .addJumpIfR0NotEquals(ICMP_ECHO, skipIpv4PingFilter);
+
+ final int defaultTtl = mDependencies.getIpv4DefaultTtl();
+ // Construct the ICMP echo reply packet.
+ gen.addLoadFromMemory(R0, MemorySlot.PACKET_SIZE)
+ .addAllocateR0()
+ .addPacketCopy(ETHER_SRC_ADDR_OFFSET, ETHER_ADDR_LEN) // Dst MAC address
+ .addDataCopy(mHardwareAddress) // Src MAC address
+ // Reuse the following fields from the input packet:
+ // 2 bytes: EtherType
+ // 4 bytes: version, IHL, TOS, total length
+ // 4 bytes: identification, flags, fragment offset
+ .addPacketCopy(ETH_ETHERTYPE_OFFSET, 10)
+ // Ttl: default ttl, Protocol: IPPROTO_ICMP, checksum: 0
+ .addWrite32((defaultTtl << 24) | (IPPROTO_ICMP << 16))
+ .addWrite32(mIPv4Address) // Src ip
+ .addPacketCopy(IPV4_SRC_ADDR_OFFSET, IPV4_ADDR_LEN) // Dst ip
+ .addWrite32((ICMP_ECHOREPLY << 24)) // Type: echo reply, code: 0, checksum: 0
+ // Copy identifier, sequence number and ping payload
+ .addSub(ICMP4_CONTENT_NO_OPTIONS_OFFSET)
+ .addLoadImmediate(R1, ICMP4_CONTENT_NO_OPTIONS_OFFSET)
+ .addSwap() // Swaps R0 and R1, so they're the offset and length.
+ .addPacketCopyFromR0LenR1()
+ .addTransmitL4(
+ ETHER_HEADER_LEN, // ip_ofs
+ ICMP4_CHECKSUM_NO_OPTIONS_OFFSET, // csum_ofs
+ ICMP4_TYPE_NO_OPTIONS_OFFSET, // csum_start
+ 0, // partial_sum
+ false // udp
+ )
+ .addCountAndDrop(DROPPED_IPV4_PING_REQUEST_REPLIED);
+
+ gen.defineLabel(skipIpv4PingFilter);
+ }
+
+ /**
+ * Generates filter code to handle IPv4 mDNS packets.
+ * <p>
+ * On entry, this filter knows it is processing an IPv4 packet. It will then process all IPv4
+ * mDNS packets, either passing or dropping them. IPv4 non-mDNS packets are skipped.
+ *
+ * @param gen the APF generator to generate the filter code
+ * @param labelCheckMdnsQueryPayload the label to jump to for checking the mDNS query payload
+ */
+ private void generateIPv4MdnsFilter(ApfV6GeneratorBase<?> gen,
+ short labelCheckMdnsQueryPayload)
+ throws IllegalInstructionException {
+ final short skipMdnsFilter = gen.getUniqueLabel();
+
+ // If the packet is too short to be a valid IPv4 mDNS packet, the filter is skipped.
+ // For APF performance reasons, we check udp destination port before confirming it is
+ // non-fragmented IPv4 udp packet. We proceed only if the destination port is 5353 (mDNS).
+ // Otherwise, skip filtering.
+ gen.addLoadFromMemory(R0, MemorySlot.PACKET_SIZE)
+ .addJumpIfR0LessThan(
+ ETH_HEADER_LEN + IPV4_HEADER_MIN_LEN + UDP_HEADER_LEN + DNS_HEADER_LEN,
+ skipMdnsFilter)
+ .addLoad16intoR0(IPV4_UDP_DESTINATION_PORT_NO_OPTIONS_OFFSET)
+ .addJumpIfR0NotEquals(MDNS_PORT, skipMdnsFilter);
+
+ // If the destination MAC address is not 01:00:5e:00:00:fb (the mDNS multicast MAC
+ // address for IPv4 mDNS packet) or the device's MAC address, skip filtering.
+ // We need to check both the mDNS multicast MAC address and the device's MAC address
+ // because multicast to unicast conversion might have occurred.
+ gen.addJumpIfBytesAtOffsetEqualsNoneOf(
+ ETH_DEST_ADDR_OFFSET,
+ List.of(mHardwareAddress, ETH_MULTICAST_MDNS_V4_MAC_ADDRESS),
+ skipMdnsFilter
+ );
+
+ // Ignore packets with IPv4 options (header size not equal to 20) as they are rare.
+ gen.addLoadFromMemory(R0, MemorySlot.IPV4_HEADER_SIZE)
+ .addJumpIfR0NotEquals(IPV4_HEADER_MIN_LEN, skipMdnsFilter);
+
+ // Skip filtering if the packet is not a non-fragmented IPv4 UDP packet.
+ gen.addJumpIfNotUnfragmentedIPv4Protocol(IPPROTO_UDP, skipMdnsFilter);
+
+ // Skip filtering if the IPv4 destination address is not 224.0.0.251 (the mDNS multicast
+ // address).
+ // Some devices can use unicast queries for mDNS to improve performance and reliability.
+ // These packets are not currently offloaded and will be passed by APF and handled
+ // by NsdService.
+ gen.addLoad32intoR0(IPV4_DEST_ADDR_OFFSET)
+ .addJumpIfR0NotEquals(MDNS_IPV4_ADDR_IN_LONG, skipMdnsFilter);
+
+ // We now know that the packet is an mDNS packet,
+ // i.e., a non-fragmented IPv4 UDP packet destined for port 5353 with the expected
+ // destination MAC and IP addresses.
+
+ // If the packet contains questions, check the query payload. Otherwise, check the
+ // reply payload.
+ gen.addLoad16intoR0(IPV4_DNS_QDCOUNT_NO_OPTIONS_OFFSET)
+ // Set the UDP payload offset in R1 before potentially jumping to the payload
+ // check logic.
+ .addLoadImmediate(R1, IPV4_UDP_PAYLOAD_NO_OPTIONS_OFFSET)
+ .addJumpIfR0NotEquals(0, labelCheckMdnsQueryPayload);
+
+ // TODO: check the reply payload.
+ if (mMulticastFilter) {
+ gen.addCountAndDrop(DROPPED_MDNS);
+ } else {
+ gen.addCountAndPass(PASSED_MDNS);
+ }
+
+ gen.defineLabel(skipMdnsFilter);
}
/**
@@ -1657,8 +2027,11 @@
* DROP_LABEL or PASS_LABEL and does not fall off the end.
* Preconditions:
* - Packet being filtered is IPv4
+ *
+ * @param gen the APF generator to generate the filter code
+ * @param labelCheckMdnsQueryPayload the label to jump to for checking the mDNS query payload
*/
- private void generateIPv4Filter(ApfV4GeneratorBase<?> gen)
+ private void generateIPv4Filter(ApfV4GeneratorBase<?> gen, short labelCheckMdnsQueryPayload)
throws IllegalInstructionException {
// Here's a basic summary of what the IPv4 filter program does:
//
@@ -1669,6 +2042,47 @@
// pass
// else
// drop
+ //
+ // (APFv6+ specific logic)
+ // if it's mDNS:
+ // if it's a query:
+ // if mNumOfMdnsRuleToOffload == -1:
+ // pass
+ // if the query matches one of the offload rules from idx [0, mNumOfMdnsRuleToOffload):
+ // transmit mDNS reply and drop
+ // else if query matches one of the rest of the offload rules:
+ // pass
+ // else if filtering multicast (i.e. multicast lock not held):
+ // drop
+ // else
+ // pass
+ // else:
+ // if filtering multicast (i.e. multicast lock not held):
+ // drop
+ // else
+ // pass
+ //
+ // (APFv6+ specific logic)
+ // if it's IGMP:
+ // if payload length is invalid (less than 8 or equal to 9, 10, 11):
+ // drop
+ // if the packet is an IGMP report:
+ // drop
+ // if the packet is not an IGMP query:
+ // drop
+ // if the group_addr is not 0.0.0.0, then it is group specific query:
+ // pass
+ // ===== handle IGMPv1/v2/v3 general query =====
+ // if the IPv4 dst addr is not 224.0.0.1:
+ // drop
+ // if the packet length >= 12, then it is IGMPv3:
+ // transmit IGMPv3 report and drop
+ // else if the packet length == 8, then it is either IGMPv1 or IGMPv2:
+ // if the max_res_code == 0, then it is IGMPv1:
+ // pass
+ // else it is IGMPv2:
+ // transmit IGMPv2 reports (one report per group) and drop
+ //
// if filtering multicast (i.e. multicast lock not held):
// if it's DHCP destined to our MAC:
// pass
@@ -1678,14 +2092,19 @@
// drop
// if it's IPv4 broadcast:
// drop
+ //
// if keepalive ack
// drop
+ //
+ // (APFv6+ specific logic) if it's unicast IPv4 ICMP echo request to our host:
+ // transmit echo reply and drop
+ //
// pass
if (mHasClat) {
// Check 1) it's not a fragment. 2) it's UDP.
// Load 16 bit frag flags/offset field, 8 bit ttl, 8 bit protocol
- gen.addLoad32(R0, IPV4_FRAGMENT_OFFSET_OFFSET);
+ gen.addLoad32intoR0(IPV4_FRAGMENT_OFFSET_OFFSET);
// Mask out the reserved and don't fragment bits, plus the TTL field.
// Because:
// IPV4_FRAGMENT_OFFSET_MASK = 0x1fff
@@ -1694,52 +2113,58 @@
// We want the more flag bit and offset to be 0 (ie. not a fragment),
// so after this masking we end up with just the ip protocol (hopefully UDP).
gen.addAnd((IPV4_FRAGMENT_MORE_FRAGS_MASK | IPV4_FRAGMENT_OFFSET_MASK) << 16 | 0xFF);
- gen.addCountAndDropIfR0NotEquals(IPPROTO_UDP, Counter.DROPPED_IPV4_NON_DHCP4);
+ gen.addCountAndDropIfR0NotEquals(IPPROTO_UDP, DROPPED_IPV4_NON_DHCP4);
// Check it's addressed to DHCP client port.
gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE);
- gen.addLoad32Indexed(R0, TCP_UDP_SOURCE_PORT_OFFSET);
+ gen.addLoad32R1IndexedIntoR0(TCP_UDP_SOURCE_PORT_OFFSET);
gen.addCountAndDropIfR0NotEquals(DHCP_SERVER_PORT << 16 | DHCP_CLIENT_PORT,
- Counter.DROPPED_IPV4_NON_DHCP4);
- gen.addCountAndPass(Counter.PASSED_IPV4_FROM_DHCPV4_SERVER);
+ DROPPED_IPV4_NON_DHCP4);
+ gen.addCountAndPass(PASSED_IPV4_FROM_DHCPV4_SERVER);
return;
}
+ if (enableMdns4Offload()) {
+ generateIPv4MdnsFilter((ApfV6GeneratorBase<?>) gen, labelCheckMdnsQueryPayload);
+ }
+
+ if (enableIgmpOffload()) {
+ generateIgmpFilter((ApfV6GeneratorBase<?>) gen);
+ }
+
if (mMulticastFilter) {
- final String skipDhcpv4Filter = gen.getUniqueLabel();
+ final short skipDhcpv4Filter = gen.getUniqueLabel();
// Pass DHCP addressed to us.
// Check 1) it's not a fragment. 2) it's UDP.
- // Load 16 bit frag flags/offset field, 8 bit ttl, 8 bit protocol
- gen.addLoad32(R0, IPV4_FRAGMENT_OFFSET_OFFSET);
- // see above for explanation of this constant
- gen.addAnd((IPV4_FRAGMENT_MORE_FRAGS_MASK | IPV4_FRAGMENT_OFFSET_MASK) << 16 | 0xFF);
- gen.addJumpIfR0NotEquals(IPPROTO_UDP, skipDhcpv4Filter);
+ gen.addJumpIfNotUnfragmentedIPv4Protocol(IPPROTO_UDP, skipDhcpv4Filter);
// Check it's addressed to DHCP client port.
gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE);
- gen.addLoad16Indexed(R0, TCP_UDP_DESTINATION_PORT_OFFSET);
+ gen.addLoad16R1IndexedIntoR0(TCP_UDP_DESTINATION_PORT_OFFSET);
gen.addJumpIfR0NotEquals(DHCP_CLIENT_PORT, skipDhcpv4Filter);
// Check it's DHCP to our MAC address.
gen.addLoadImmediate(R0, DHCP_CLIENT_MAC_OFFSET);
// NOTE: Relies on R1 containing IPv4 header offset.
gen.addAddR1ToR0();
gen.addJumpIfBytesAtR0NotEqual(mHardwareAddress, skipDhcpv4Filter);
- gen.addCountAndPass(Counter.PASSED_DHCP);
+ gen.addCountAndPass(PASSED_DHCP);
// Drop all multicasts/broadcasts.
gen.defineLabel(skipDhcpv4Filter);
// If IPv4 destination address is in multicast range, drop.
- gen.addLoad8(R0, IPV4_DEST_ADDR_OFFSET);
- gen.addAnd(0xf0);
- gen.addCountAndDropIfR0Equals(0xe0, Counter.DROPPED_IPV4_MULTICAST);
+ gen.addLoad8intoR0(IPV4_DEST_ADDR_OFFSET);
+ // we just loaded a byte, so top 24 bits are zero, thus and'ing
+ // with either one of 0xF0 and 0xFFFFFFF0 accomplishes the same thing,
+ // we thus choose the one which encodes shorter
+ gen.addAnd((gen instanceof ApfV4Generator) ? 0xF0 : 0xFFFFFFF0);
+ gen.addCountAndDropIfR0Equals(0xe0, DROPPED_IPV4_MULTICAST);
// If IPv4 broadcast packet, drop regardless of L2 (b/30231088).
- gen.addLoad32(R0, IPV4_DEST_ADDR_OFFSET);
- gen.addCountAndDropIfR0Equals(IPV4_BROADCAST_ADDRESS,
- Counter.DROPPED_IPV4_BROADCAST_ADDR);
+ gen.addLoad32intoR0(IPV4_DEST_ADDR_OFFSET);
+ gen.addCountAndDropIfR0Equals(IPV4_BROADCAST_ADDRESS, DROPPED_IPV4_BROADCAST_ADDR);
if (mIPv4Address != null && mIPv4PrefixLength < 31) {
int broadcastAddr = ipv4BroadcastAddress(mIPv4Address, mIPv4PrefixLength);
- gen.addCountAndDropIfR0Equals(broadcastAddr, Counter.DROPPED_IPV4_BROADCAST_NET);
+ gen.addCountAndDropIfR0Equals(broadcastAddr, DROPPED_IPV4_BROADCAST_NET);
}
}
@@ -1752,21 +2177,25 @@
// If TCP unicast on port 7, drop
generateV4TcpPort7Filter(gen);
+ if (enableIpv4PingOffload()) {
+ generateUnicastIpv4PingOffload((ApfV6GeneratorBase<?>) 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?
- gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET);
- gen.addCountAndPassIfBytesAtR0NotEqual(ETHER_BROADCAST, Counter.PASSED_IPV4_UNICAST);
- gen.addCountAndDrop(Counter.DROPPED_IPV4_L2_BROADCAST);
+ gen.addCountAndPassIfBytesAtOffsetNotEqual(ETH_DEST_ADDR_OFFSET, ETHER_BROADCAST,
+ PASSED_IPV4_UNICAST);
+ gen.addCountAndDrop(DROPPED_IPV4_L2_BROADCAST);
}
// Otherwise, pass
- gen.addCountAndPass(Counter.PASSED_IPV4);
+ gen.addCountAndPass(PASSED_IPV4);
}
private void generateKeepaliveFilters(ApfV4GeneratorBase<?> gen, Class<?> filterType, int proto,
- int offset, String label) throws IllegalInstructionException {
+ int offset, short label) throws IllegalInstructionException {
final boolean haveKeepaliveResponses = CollectionUtils.any(mKeepalivePackets,
filterType::isInstance);
@@ -1774,7 +2203,7 @@
if (!haveKeepaliveResponses) return;
// If not the right proto, skip keepalive filters
- gen.addLoad8(R0, offset);
+ gen.addLoad8intoR0(offset);
gen.addJumpIfR0NotEquals(proto, label);
// Drop Keepalive responses
@@ -1883,7 +2312,7 @@
);
}
- private void generateNsFilter(ApfV6Generator v6Gen)
+ private void generateNsFilter(ApfV6GeneratorBase<?> v6Gen)
throws IllegalInstructionException {
final List<byte[]> allIPv6Addrs = getIpv6Addresses(
true /* includeNonTentative */,
@@ -1892,7 +2321,7 @@
if (allIPv6Addrs.isEmpty()) {
// If there is no IPv6 link local address, allow all NS packets to avoid racing
// against RS.
- v6Gen.addCountAndPass(PASSED_IPV6_NS_NO_ADDRESS);
+ v6Gen.addCountAndPass(PASSED_IPV6_ICMP);
return;
}
@@ -1900,32 +2329,33 @@
// used by processes other than clatd. This is because APF cannot reliably detect signal
// on when IPV6_{JOIN,LEAVE}_ANYCAST is triggered.
final List<byte[]> allMACs = getKnownMacAddresses();
- v6Gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET)
- .addCountAndDropIfBytesAtR0EqualsNoneOf(allMACs, DROPPED_IPV6_NS_OTHER_HOST);
+ v6Gen.addCountAndDropIfBytesAtOffsetEqualsNoneOf(ETH_DEST_ADDR_OFFSET, allMACs,
+ DROPPED_IPV6_NS_OTHER_HOST);
// Dst IPv6 address check:
final List<byte[]> allSuffixes = getSolicitedNodeMcastAddressSuffix(allIPv6Addrs);
- final String notIpV6SolicitedNodeMcast = v6Gen.getUniqueLabel();
- final String endOfIpV6DstCheck = v6Gen.getUniqueLabel();
- v6Gen.addLoadImmediate(R0, IPV6_DEST_ADDR_OFFSET)
- .addJumpIfBytesAtR0NotEqual(IPV6_SOLICITED_NODES_PREFIX, notIpV6SolicitedNodeMcast)
- .addAdd(13)
+ final short notIpV6SolicitedNodeMcast = v6Gen.getUniqueLabel();
+ final short endOfIpV6DstCheck = v6Gen.getUniqueLabel();
+ v6Gen.addJumpIfBytesAtOffsetNotEqual(
+ IPV6_DEST_ADDR_OFFSET, IPV6_SOLICITED_NODES_PREFIX, notIpV6SolicitedNodeMcast)
+ .addLoadImmediate(R0, IPV6_DEST_ADDR_OFFSET + 13)
.addCountAndDropIfBytesAtR0EqualsNoneOf(allSuffixes, DROPPED_IPV6_NS_OTHER_HOST)
.addJump(endOfIpV6DstCheck)
.defineLabel(notIpV6SolicitedNodeMcast)
- .addCountAndDropIfBytesAtR0EqualsNoneOf(allIPv6Addrs, DROPPED_IPV6_NS_OTHER_HOST)
+ .addCountAndDropIfBytesAtOffsetEqualsNoneOf(
+ IPV6_DEST_ADDR_OFFSET, allIPv6Addrs, DROPPED_IPV6_NS_OTHER_HOST)
.defineLabel(endOfIpV6DstCheck);
// Hop limit not 255, NS requires hop limit to be 255 -> drop
- v6Gen.addLoad8(R0, IPV6_HOP_LIMIT_OFFSET)
+ v6Gen.addLoad8intoR0(IPV6_HOP_LIMIT_OFFSET)
.addCountAndDropIfR0NotEquals(255, DROPPED_IPV6_NS_INVALID);
// payload length < 24 (8 bytes ICMP6 header + 16 bytes target address) -> drop
- v6Gen.addLoad16(R0, IPV6_PAYLOAD_LEN_OFFSET)
+ v6Gen.addLoad16intoR0(IPV6_PAYLOAD_LEN_OFFSET)
.addCountAndDropIfR0LessThan(24, DROPPED_IPV6_NS_INVALID);
// ICMPv6 code not 0 -> drop
- v6Gen.addLoad8(R0, ICMP6_CODE_OFFSET)
+ v6Gen.addLoad8intoR0(ICMP6_CODE_OFFSET)
.addCountAndDropIfR0NotEquals(0, DROPPED_IPV6_NS_INVALID);
// target address (ICMPv6 NS payload)
@@ -1936,10 +2366,10 @@
true, /* includeTentative */
false /* includeAnycast */
);
- v6Gen.addLoadImmediate(R0, ICMP6_NS_TARGET_IP_OFFSET);
+
if (!tentativeIPv6Addrs.isEmpty()) {
- v6Gen.addCountAndPassIfBytesAtR0EqualsAnyOf(
- tentativeIPv6Addrs, PASSED_IPV6_NS_TENTATIVE);
+ v6Gen.addCountAndPassIfBytesAtOffsetEqualsAnyOf(
+ ICMP6_NS_TARGET_IP_OFFSET, tentativeIPv6Addrs, PASSED_IPV6_ICMP);
}
final List<byte[]> nonTentativeIpv6Addrs = getIpv6Addresses(
@@ -1951,19 +2381,19 @@
v6Gen.addCountAndDrop(DROPPED_IPV6_NS_OTHER_HOST);
return;
}
- v6Gen.addCountAndDropIfBytesAtR0EqualsNoneOf(
- nonTentativeIpv6Addrs, DROPPED_IPV6_NS_OTHER_HOST);
+ v6Gen.addCountAndDropIfBytesAtOffsetEqualsNoneOf(
+ ICMP6_NS_TARGET_IP_OFFSET, nonTentativeIpv6Addrs, DROPPED_IPV6_NS_OTHER_HOST);
// if source ip is unspecified (::), it's DAD request -> pass
- v6Gen.addLoadImmediate(R0, IPV6_SRC_ADDR_OFFSET)
- .addCountAndPassIfBytesAtR0Equal(IPV6_UNSPECIFIED_ADDRESS, PASSED_IPV6_NS_DAD);
+ v6Gen.addCountAndPassIfBytesAtOffsetEqual(
+ IPV6_SRC_ADDR_OFFSET, IPV6_UNSPECIFIED_ADDRESS, PASSED_IPV6_ICMP);
// Only offload NUD/Address resolution packets that have SLLA as the their first option.
// For option-less NUD packets or NUD/Address resolution packets where
// the first option is not SLLA, pass them to the kernel for handling.
// if payload len < 32 -> pass
- v6Gen.addLoad16(R0, IPV6_PAYLOAD_LEN_OFFSET)
- .addCountAndPassIfR0LessThan(32, PASSED_IPV6_NS_NO_SLLA_OPTION);
+ v6Gen.addLoad16intoR0(IPV6_PAYLOAD_LEN_OFFSET)
+ .addCountAndPassIfR0LessThan(32, PASSED_IPV6_ICMP);
// if the first option is not SLLA -> pass
// 0 1 2 3
@@ -1971,20 +2401,159 @@
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Type | Length |Link-Layer Addr |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- v6Gen.addLoad8(R0, ICMP6_NS_OPTION_TYPE_OFFSET)
- .addCountAndPassIfR0NotEquals(ICMPV6_ND_OPTION_SLLA,
- PASSED_IPV6_NS_NO_SLLA_OPTION);
+ v6Gen.addLoad8intoR0(ICMP6_NS_OPTION_TYPE_OFFSET)
+ .addCountAndPassIfR0NotEquals(ICMPV6_ND_OPTION_SLLA, PASSED_IPV6_ICMP);
// Src IPv6 address check:
// if multicast address (FF::/8) or loopback address (00::/8) -> drop
- v6Gen.addLoad8(R0, IPV6_SRC_ADDR_OFFSET)
+ v6Gen.addLoad8intoR0(IPV6_SRC_ADDR_OFFSET)
.addCountAndDropIfR0IsOneOf(Set.of(0L, 0xffL), DROPPED_IPV6_NS_INVALID);
// if multicast MAC in SLLA option -> drop
- v6Gen.addLoad8(R0, ICMP6_NS_OPTION_TYPE_OFFSET + 2)
+ v6Gen.addLoad8intoR0(ICMP6_NS_OPTION_TYPE_OFFSET + 2)
.addCountAndDropIfR0AnyBitsSet(1, DROPPED_IPV6_NS_INVALID);
generateNonDadNaTransmit(v6Gen);
- v6Gen.addCountAndDrop(Counter.DROPPED_IPV6_NS_REPLIED_NON_DAD);
+ v6Gen.addCountAndDrop(DROPPED_IPV6_NS_REPLIED_NON_DAD);
+ }
+
+ /**
+ * Generates filter code to handle IPv6 mDNS packets.
+ * <p>
+ * On entry, this filter knows it is processing an IPv6 packet. It will then process all IPv6
+ * mDNS packets, either passing or dropping them. IPv6 non-mDNS packets are skipped.
+ *
+ * @param gen the APF generator to generate the filter code
+ * @param labelCheckMdnsQueryPayload the label to jump to for checking the mDNS query payload
+ */
+ private void generateIPv6MdnsFilter(ApfV6GeneratorBase<?> gen,
+ short labelCheckMdnsQueryPayload) throws IllegalInstructionException {
+ final short skipMdnsFilter = gen.getUniqueLabel();
+
+ // If the packet is too short to be a valid IPv6 mDNS packet, the filter is skipped.
+ // For APF performance reasons, we check udp destination port before confirming it is IPv6
+ // udp packet. We proceed only if the destination port is 5353 (mDNS). Otherwise, skip
+ // filtering.
+ gen.addLoadFromMemory(R0, MemorySlot.PACKET_SIZE)
+ .addJumpIfR0LessThan(
+ ETH_HEADER_LEN + IPV6_HEADER_LEN + UDP_HEADER_LEN + DNS_HEADER_LEN,
+ skipMdnsFilter)
+ .addLoad16intoR0(IPV6_UDP_DESTINATION_PORT_OFFSET)
+ .addJumpIfR0NotEquals(MDNS_PORT, skipMdnsFilter);
+
+ // If the destination MAC address is not 33:33:00:00:00:fb (the mDNS multicast MAC
+ // address for IPv6 mDNS packet) or the device's MAC address, skip filtering.
+ // We need to check both the mDNS multicast MAC address and the device's MAC address
+ // because multicast to unicast conversion might have occurred.
+ gen.addJumpIfBytesAtOffsetEqualsNoneOf(
+ ETH_DEST_ADDR_OFFSET,
+ List.of(mHardwareAddress, ETH_MULTICAST_MDNS_V6_MAC_ADDRESS),
+ skipMdnsFilter
+ );
+
+ // Skip filtering if the packet is not an IPv6 UDP packet.
+ gen.addLoad8intoR0(IPV6_NEXT_HEADER_OFFSET)
+ .addJumpIfR0NotEquals(IPPROTO_UDP, skipMdnsFilter);
+
+ // Skip filtering if the IPv6 destination address is not ff02::fb (the mDNS multicast
+ // IPv6 address).
+ // Some devices can use unicast queries for mDNS to improve performance and reliability.
+ // These packets are not currently offloaded and will be passed by APF and handled
+ // by NsdService.
+ gen.addJumpIfBytesAtOffsetNotEqual(IPV6_DEST_ADDR_OFFSET, MDNS_IPV6_ADDR, skipMdnsFilter);
+
+ // We now know that the packet is an mDNS packet,
+ // i.e., an IPv6 UDP packet destined for port 5353 with the expected destination MAC and IP
+ // addresses.
+
+ // If the packet contains questions, check the query payload. Otherwise, check the
+ // reply payload.
+ gen.addLoad16intoR0(IPV6_DNS_QDCOUNT_OFFSET)
+ // Set the UDP payload offset in R1 before potentially jumping to the payload
+ // check logic.
+ .addLoadImmediate(R1, IPv6_UDP_PAYLOAD_OFFSET)
+ .addJumpIfR0NotEquals(0, labelCheckMdnsQueryPayload);
+
+ // TODO: check the reply payload.
+ if (mMulticastFilter) {
+ gen.addCountAndDrop(DROPPED_MDNS);
+ } else {
+ gen.addCountAndPass(PASSED_MDNS);
+ }
+
+ gen.defineLabel(skipMdnsFilter);
+ }
+
+ /**
+ * Generate filter code to reply and drop unicast ICMPv6 echo request.
+ * <p>
+ * On entry, we know it is IPv6 packet, but don't know anything else.
+ * R0 contains the u8 IPv6 next header.
+ * R1 contains nothing useful in it, and can be clobbered.
+ */
+ private void generateUnicastIpv6PingOffload(ApfV6GeneratorBase<?> gen)
+ throws IllegalInstructionException {
+
+ final short skipPing6Offload = gen.getUniqueLabel();
+ gen.addJumpIfR0NotEquals(IPPROTO_ICMPV6, skipPing6Offload);
+
+ gen.addLoad8intoR0(ICMP6_TYPE_OFFSET)
+ .addJumpIfR0NotEquals(ICMPV6_ECHO_REQUEST_TYPE, skipPing6Offload);
+
+ // Only offload unicast ping6.
+ // While we could potentially support offloading multicast and broadcast ping6 requests in
+ // the future, such packets will likely be dropped by the multicast filter.
+ // Since the device may have packet forwarding enabled, APF needs to pass any received
+ // unicast ping6 not destined for the device's IP address to the kernel.
+ final List<byte[]> nonTentativeIPv6Addrs = getIpv6Addresses(
+ true /* includeNonTentative */,
+ false /* includeTentative */,
+ false /* includeAnycast */);
+ gen.addJumpIfBytesAtOffsetNotEqual(
+ ETHER_DST_ADDR_OFFSET, mHardwareAddress, skipPing6Offload)
+ .addJumpIfBytesAtOffsetEqualsNoneOf(
+ IPV6_DEST_ADDR_OFFSET, nonTentativeIPv6Addrs, skipPing6Offload);
+
+ // We need to check if the packet is sufficiently large to be a valid ICMPv6 echo packet.
+ gen.addLoadFromMemory(R0, MemorySlot.PACKET_SIZE)
+ .addCountAndDropIfR0LessThan(
+ ETHER_HEADER_LEN + IPV6_HEADER_LEN + ICMP6_ECHO_REQUEST_HEADER_LEN,
+ DROPPED_IPV6_ICMP6_ECHO_REQUEST_INVALID);
+
+ int hopLimit = mDependencies.getIpv6DefaultHopLimit(mInterfaceParams.name);
+ // Construct the ICMPv6 echo reply packet.
+ gen.addLoadFromMemory(R0, MemorySlot.PACKET_SIZE)
+ .addAllocateR0()
+ // Eth header
+ .addPacketCopy(ETHER_SRC_ADDR_OFFSET, ETHER_ADDR_LEN) // Dst MAC address
+ .addDataCopy(mHardwareAddress) // Src MAC address
+ // Reuse the following fields from input packet
+ // 2 byte: ethertype
+ // 4 bytes: version, traffic class, flowlabel
+ // 2 bytes: payload length
+ // 1 byte: next header
+ .addPacketCopy(ETH_ETHERTYPE_OFFSET, 9)
+ .addWriteU8(hopLimit)
+ .addPacketCopy(IPV6_DEST_ADDR_OFFSET, IPV6_ADDR_LEN) // Src ip
+ .addPacketCopy(IPV6_SRC_ADDR_OFFSET, IPV6_ADDR_LEN) // Dst ip
+ .addWriteU16((ICMP6_ECHO_REPLY << 8) | 0) // Type: echo reply, code: 0
+ // Checksum: initialized to the IPv6 payload length as a partial checksum. The final
+ // checksum will be calculated by the interpreter.
+ .addPacketCopy(IPV6_PAYLOAD_LEN_OFFSET, 2)
+ // Copy identifier, sequence number and ping payload
+ .addSub(ICMP6_CONTENT_OFFSET)
+ .addLoadImmediate(R1, ICMP6_CONTENT_OFFSET)
+ .addSwap() // Swaps R0 and R1, so they're the offset and length.
+ .addPacketCopyFromR0LenR1()
+ .addTransmitL4(
+ ETHER_HEADER_LEN, // ip_ofs
+ ICMP6_CHECKSUM_OFFSET, // csum_ofs
+ IPV6_SRC_ADDR_OFFSET, // csum_start
+ IPPROTO_ICMPV6, // partial_sum
+ false // udp
+ )
+ .addCountAndDrop(DROPPED_IPV6_ICMP6_ECHO_REQUEST_REPLIED);
+
+ gen.defineLabel(skipPing6Offload);
}
/**
@@ -1992,19 +2561,66 @@
* DROP_LABEL or PASS_LABEL, or falls off the end for ICMPv6 packets.
* Preconditions:
* - Packet being filtered is IPv6
+ *
+ * @param gen the APF generator to generate the filter code
+ * @param labelCheckMdnsQueryPayload the label to jump to for checking the mDNS query payload
*/
- private void generateIPv6Filter(ApfV4GeneratorBase<?> gen)
+ private void generateIPv6Filter(ApfV4GeneratorBase<?> gen, short labelCheckMdnsQueryPayload)
throws IllegalInstructionException {
// Here's a basic summary of what the IPv6 filter program does:
//
- // if there is a hop-by-hop option present (e.g. MLD query)
- // pass
+ // if there is a HOPOPTS option present (e.g. MLD query)
+ // (APFv6+ specific logic)
+ // if MLD offload is enabled:
+ // if it is an MLDv1 report/done or MLDv2 report:
+ // drop
+ // if the payload length is invalid (25, 26, 27):
+ // drop
+ // if the IPv6 src addr is not link-local address:
+ // drop
+ // if the IPv6 hop limit is not 1:
+ // drop
+ // if it is an multicast address specific query (the MLD multicast address is not "::"):
+ // pass
+ // if the IPv6 dst addr is not ff02::1:
+ // drop
+ // if it is an MLDv2 general query (payload length is not 24):
+ // transmit MLDv2 report and drop
+ // else it is an MLDv1 general query:
+ // transmit MLDv1 reports (one report per multicast group) and drop
+ // else
+ // pass (on APFv2+)
+ //
+ // (APFv6+ specific logic)
+ // if it's mDNS:
+ // if it's a query:
+ // if mNumOfMdnsRuleToOffload == -1:
+ // pass
+ // if the query matches one of the offload rules from idx [0, mNumOfMdnsRuleToOffload):
+ // transmit mDNS reply and drop
+ // else if query matches one of the rest of the offload rules:
+ // pass
+ // else if filtering multicast (i.e. multicast lock not held):
+ // drop
+ // else
+ // pass
+ // else:
+ // if filtering multicast (i.e. multicast lock not held):
+ // drop
+ // else
+ // pass
+ //
+ // (APFv6+ specific logic) if it's unicast ICMPv6 echo request to our host:
+ // transmit echo reply and drop
+ //
// if we're dropping multicast
// if it's not ICMPv6 or it's ICMPv6 but we're in doze mode:
// if it's multicast:
// drop
// pass
- // (APFv6+ specific logic) if it's ICMPv6 NS:
+ //
+ // (APFv6+ specific logic)
+ // if it's ICMPv6 NS:
// if there are no IPv6 addresses (including link local address) on the interface:
// pass
// if MAC dst is none of known {unicast, multicast, broadcast} MAC addresses
@@ -2033,23 +2649,39 @@
// if multicast MAC in SLLA option:
// drop
// transmit NA and drop
+ //
// if it's ICMPv6 RS to any:
// drop
+ //
// if it's ICMPv6 NA to anything in ff02::/120
// drop
+ //
// if keepalive ack
// drop
- gen.addLoad8(R0, IPV6_NEXT_HEADER_OFFSET);
+ gen.addLoad8intoR0(IPV6_NEXT_HEADER_OFFSET);
- // MLD packets set the router-alert hop-by-hop option.
- // TODO: be smarter about not blindly passing every packet with HBH options.
- gen.addCountAndPassIfR0Equals(IPPROTO_HOPOPTS, Counter.PASSED_MLD);
+ if (enableMldOffload()) {
+ generateMldFilter((ApfV6GeneratorBase<?>) gen);
+ gen.addLoad8intoR0(IPV6_NEXT_HEADER_OFFSET);
+ } else {
+ gen.addCountAndPassIfR0Equals(IPPROTO_HOPOPTS, PASSED_IPV6_HOPOPTS);
+ }
+
+ if (enableMdns6Offload()) {
+ generateIPv6MdnsFilter((ApfV6GeneratorBase<?>) gen, labelCheckMdnsQueryPayload);
+ gen.addLoad8intoR0(IPV6_NEXT_HEADER_OFFSET);
+ }
+
+ if (enableIpv6PingOffload()) {
+ generateUnicastIpv6PingOffload((ApfV6GeneratorBase<?>) gen);
+ gen.addLoad8intoR0(IPV6_NEXT_HEADER_OFFSET);
+ }
// Drop multicast if the multicast filter is enabled.
if (mMulticastFilter) {
- final String skipIPv6MulticastFilterLabel = gen.getUniqueLabel();
- final String dropAllIPv6MulticastsLabel = gen.getUniqueLabel();
+ final short skipIPv6MulticastFilterLabel = gen.getUniqueLabel();
+ final short dropAllIPv6MulticastsLabel = gen.getUniqueLabel();
// While in doze mode, drop ICMPv6 multicast pings, let the others pass.
// While awake, let all ICMPv6 multicasts through.
@@ -2059,7 +2691,7 @@
// ICMPv6 but not ECHO? -> Skip the multicast filter.
// (ICMPv6 ECHO requests will go through the multicast filter below).
- gen.addLoad8(R0, ICMP6_TYPE_OFFSET);
+ gen.addLoad8intoR0(ICMP6_TYPE_OFFSET);
gen.addJumpIfR0NotEquals(ICMPV6_ECHO_REQUEST_TYPE, skipIPv6MulticastFilterLabel);
} else {
gen.addJumpIfR0Equals(IPPROTO_ICMPV6, skipIPv6MulticastFilterLabel);
@@ -2067,121 +2699,603 @@
// Drop all other packets sent to ff00::/8 (multicast prefix).
gen.defineLabel(dropAllIPv6MulticastsLabel);
- gen.addLoad8(R0, IPV6_DEST_ADDR_OFFSET);
- gen.addCountAndDropIfR0Equals(0xff, Counter.DROPPED_IPV6_NON_ICMP_MULTICAST);
+ gen.addLoad8intoR0(IPV6_DEST_ADDR_OFFSET);
+ gen.addCountAndDropIfR0Equals(0xff, DROPPED_IPV6_NON_ICMP_MULTICAST);
// If any keepalive filter matches, drop
generateV6KeepaliveFilters(gen);
// Not multicast. Pass.
- gen.addCountAndPass(Counter.PASSED_IPV6_UNICAST_NON_ICMP);
+ gen.addCountAndPass(PASSED_IPV6_UNICAST_NON_ICMP);
gen.defineLabel(skipIPv6MulticastFilterLabel);
} else {
generateV6KeepaliveFilters(gen);
// If not ICMPv6, pass.
- gen.addCountAndPassIfR0NotEquals(IPPROTO_ICMPV6, Counter.PASSED_IPV6_NON_ICMP);
+ gen.addCountAndPassIfR0NotEquals(IPPROTO_ICMPV6, PASSED_IPV6_NON_ICMP);
}
// If we got this far, the packet is ICMPv6. Drop some specific types.
// Not ICMPv6 NS -> skip.
- gen.addLoad8(R0, ICMP6_TYPE_OFFSET); // warning: also used further below.
- final ApfV6Generator v6Gen = tryToConvertToApfV6Generator(gen);
- if (v6Gen != null && mShouldHandleNdOffload) {
- final String skipNsPacketFilter = v6Gen.getUniqueLabel();
- v6Gen.addJumpIfR0NotEquals(ICMPV6_NEIGHBOR_SOLICITATION, skipNsPacketFilter);
- generateNsFilter(v6Gen);
+ gen.addLoad8intoR0(ICMP6_TYPE_OFFSET); // warning: also used further below.
+ if (enableNdOffload()) {
+ final short skipNsPacketFilter = gen.getUniqueLabel();
+ gen.addJumpIfR0NotEquals(ICMPV6_NEIGHBOR_SOLICITATION, skipNsPacketFilter);
+ generateNsFilter((ApfV6GeneratorBase<?>) gen);
// End of NS filter. generateNsFilter() method is terminal, so NS packet will be
// either dropped or passed inside generateNsFilter().
- v6Gen.defineLabel(skipNsPacketFilter);
+ gen.defineLabel(skipNsPacketFilter);
}
// Add unsolicited multicast neighbor announcements filter
- String skipUnsolicitedMulticastNALabel = gen.getUniqueLabel();
+ short skipUnsolicitedMulticastNALabel = gen.getUniqueLabel();
// Drop all router solicitations (b/32833400)
- gen.addCountAndDropIfR0Equals(ICMPV6_ROUTER_SOLICITATION,
- Counter.DROPPED_IPV6_ROUTER_SOLICITATION);
+ gen.addCountAndDropIfR0Equals(ICMPV6_ROUTER_SOLICITATION, DROPPED_IPV6_ROUTER_SOLICITATION);
// If not neighbor announcements, skip filter.
gen.addJumpIfR0NotEquals(ICMPV6_NEIGHBOR_ADVERTISEMENT, skipUnsolicitedMulticastNALabel);
// Drop all multicast NA to ff02::/120.
// This is a way to cover ff02::1 and ff02::2 with a single JNEBS.
// TODO: Drop only if they don't contain the address of on-link neighbours.
final byte[] unsolicitedNaDropPrefix = Arrays.copyOf(IPV6_ALL_NODES_ADDRESS, 15);
- gen.addLoadImmediate(R0, IPV6_DEST_ADDR_OFFSET);
- gen.addJumpIfBytesAtR0NotEqual(unsolicitedNaDropPrefix, skipUnsolicitedMulticastNALabel);
+ gen.addJumpIfBytesAtOffsetNotEqual(
+ IPV6_DEST_ADDR_OFFSET, unsolicitedNaDropPrefix, skipUnsolicitedMulticastNALabel);
- gen.addCountAndDrop(Counter.DROPPED_IPV6_MULTICAST_NA);
+ gen.addCountAndDrop(DROPPED_IPV6_MULTICAST_NA);
gen.defineLabel(skipUnsolicitedMulticastNALabel);
}
/**
- * Generate filter code to process mDNS packets. Execution of this code ends in * DROP_LABEL
- * or PASS_LABEL if the packet is mDNS packets. Otherwise, skip this check.
+ * Creates the portion of an IGMP packet from the Ethernet source MAC address to the IPv4
+ * Type of Service field.
*/
- private void generateMdnsFilter(ApfV4GeneratorBase<?> gen)
- throws IllegalInstructionException {
- final String skipMdnsv4Filter = gen.getUniqueLabel();
- final String skipMdnsFilter = gen.getUniqueLabel();
- final String checkMdnsUdpPort = gen.getUniqueLabel();
+ private byte[] createIgmpPktFromEthSrcToIPv4Tos() {
+ return CollectionUtils.concatArrays(
+ mHardwareAddress,
+ new byte[] {
+ // etherType: IPv4
+ (byte) 0x08, 0x00,
+ // version, IHL
+ (byte) 0x46,
+ // Tos: 0xC0 (ref: net/ipv4/igmp.c#igmp_send_report())
+ (byte) 0xc0}
+ );
+ }
- // Only turn on the filter if multicast filter is on and the qname allowlist is non-empty.
- if (!mMulticastFilter || mMdnsAllowList.isEmpty()) {
- return;
+ /**
+ * Creates the portion of an IGMP packet from the IPv4 Identification field to the IPv4
+ * Source Address.
+ */
+ private byte[] createIgmpPktFromIPv4IdToSrc() {
+ final byte[] ipIdToSrc = new byte[] {
+ // identification
+ 0, 0,
+ // fragment flag
+ (byte) (IPV4_FLAG_DF >> 8), 0,
+ // TTL
+ (byte) 1,
+ // protocol
+ (byte) IPV4_PROTOCOL_IGMP,
+ // router alert option is { 0x94, 0x04, 0x00, 0x00 }, so we precalculate IPv4
+ // checksum as 0x9404 + 0x0000 = 0x9404
+ (byte) 0x94, (byte) 0x04
+ };
+ return CollectionUtils.concatArrays(
+ ipIdToSrc,
+ mIPv4Address
+ );
+ }
+
+ /**
+ * Creates IGMPv3 Membership Report packet payload (rfc3376#section-7.3.2).
+ */
+ private byte[] createIgmpV3ReportPayload() {
+ final int groupNum = mIPv4McastAddrsExcludeAllHost.size();
+ final byte[] igmpHeader = new byte[] {
+ // IGMP type
+ (byte) IPV4_IGMP_TYPE_V3_REPORT,
+ // reserved
+ 0,
+ // checksum, calculate later
+ 0, 0,
+ // reserved
+ 0, 0,
+ // num group records
+ (byte) ((groupNum >> 8) & 0xff), (byte) (groupNum & 0xff)
+ };
+ final byte[] groupRecordHeader = new byte[] {
+ // record type
+ (byte) IGMPV3_MODE_IS_EXCLUDE,
+ // aux data len,
+ 0,
+ // num src
+ 0, 0
+ };
+ final byte[] payload =
+ new byte[igmpHeader.length + groupNum * (groupRecordHeader.length + IPV4_ADDR_LEN)];
+ int offset = 0;
+
+ System.arraycopy(igmpHeader, 0, payload, offset, igmpHeader.length);
+ offset += igmpHeader.length;
+ for (Inet4Address mcastAddr: mIPv4McastAddrsExcludeAllHost) {
+ System.arraycopy(groupRecordHeader, 0, payload, offset, groupRecordHeader.length);
+ offset += groupRecordHeader.length;
+ System.arraycopy(mcastAddr.getAddress(), 0, payload, offset, IPV4_ADDR_LEN);
+ offset += IPV4_ADDR_LEN;
}
- // Here's a basic summary of what the mDNS filter program does:
- //
- // A packet is considered as a multicast mDNS packet if it matches all the following
- // conditions
- // 1. its destination MAC address matches 01:00:5E:00:00:FB or 33:33:00:00:00:FB, for
- // v4 and v6 respectively.
- // 2. it is an IPv4/IPv6 packet
- // 3. it is a UDP packet with port 5353
+ return payload;
+ }
- // Check it's L2 mDNS multicast address.
- gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET);
- gen.addJumpIfBytesAtR0NotEqual(ETH_MULTICAST_MDNS_V4_MAC_ADDRESS, skipMdnsv4Filter);
+ /**
+ * Generate transmit code to send IGMPv3 report in response to general query packets.
+ */
+ private void generateIgmpV3ReportTransmit(ApfV6GeneratorBase<?> gen,
+ byte[] igmpPktFromEthSrcToIpTos, byte[] igmpPktFromIpIdToSrc)
+ throws IllegalInstructionException {
+ // We place template packet chunks in the data region first to reduce the number of
+ // instructions needed for creating multiple IGMPv2 reports.
+ // The following packet chunks can be used for creating both IGMPv2 and IGMPv3 reports:
+ // - from Ethernet source to IPv4 Tos: 10 bytes
+ // - from IPv4 identification to source address: 12 bytes
+ final int igmpV2Ipv4TotalLen =
+ IPV4_HEADER_MIN_LEN + IPV4_ROUTER_ALERT_OPTION_LEN + IPV4_IGMP_MIN_SIZE;
+ final byte[] igmpV3ReportPayload = createIgmpV3ReportPayload();
+ final byte[] igmpReportTemplate = CollectionUtils.concatArrays(
+ ETH_MULTICAST_IGMP_V3_ALL_MULTICAST_ROUTERS_ADDRESS,
+ igmpPktFromEthSrcToIpTos,
+ new byte[] {
+ (byte) ((igmpV2Ipv4TotalLen >> 8) & 0xff),
+ (byte) (igmpV2Ipv4TotalLen & 0xff),
+ },
+ igmpPktFromIpIdToSrc,
+ IPV4_ALL_IGMPV3_MULTICAST_ROUTERS_ADDRESS,
+ IPV4_ROUTER_ALERT_OPTION,
+ igmpV3ReportPayload
+ );
+ gen.maybeUpdateDataRegion(igmpReportTemplate);
- // Checks it's IPv4.
- gen.addLoad16(R0, ETH_ETHERTYPE_OFFSET);
- gen.addJumpIfR0NotEquals(ETH_P_IP, skipMdnsFilter);
+ final int ipv4TotalLen = IPV4_HEADER_MIN_LEN
+ + IPV4_ROUTER_ALERT_OPTION_LEN
+ + IPV4_IGMP_MIN_SIZE
+ + (mIPv4McastAddrsExcludeAllHost.size() * IPV4_IGMP_GROUP_RECORD_SIZE);
+ final byte[] igmpV3FromEthDstToIpTos = CollectionUtils.concatArrays(
+ ETH_MULTICAST_IGMP_V3_ALL_MULTICAST_ROUTERS_ADDRESS,
+ igmpPktFromEthSrcToIpTos
+ );
+ final byte[] igmpV3PktFromIpIdToEnd = CollectionUtils.concatArrays(
+ igmpPktFromIpIdToSrc,
+ IPV4_ALL_IGMPV3_MULTICAST_ROUTERS_ADDRESS,
+ IPV4_ROUTER_ALERT_OPTION,
+ igmpV3ReportPayload
+ );
+ gen.addAllocate(ETHER_HEADER_LEN + ipv4TotalLen)
+ .addDataCopy(igmpV3FromEthDstToIpTos)
+ .addWriteU16(ipv4TotalLen)
+ .addDataCopy(igmpV3PktFromIpIdToEnd)
+ .addTransmitL4(
+ // ip_ofs
+ ETHER_HEADER_LEN,
+ // csum_ofs
+ IGMP_CHECKSUM_WITH_ROUTER_ALERT_OFFSET,
+ // csum_start
+ ETHER_HEADER_LEN + IPV4_HEADER_MIN_LEN + IPV4_ROUTER_ALERT_OPTION_LEN,
+ // partial_sum
+ 0,
+ // udp
+ false
+ )
+ .addCountAndDrop(Counter.DROPPED_IGMP_V3_GENERAL_QUERY_REPLIED);
+ }
- // Check it's not a fragment.
- gen.addLoad16(R0, IPV4_FRAGMENT_OFFSET_OFFSET);
- gen.addJumpIfR0AnyBitsSet(IPV4_FRAGMENT_MORE_FRAGS_MASK | IPV4_FRAGMENT_OFFSET_MASK,
- skipMdnsFilter);
+ /**
+ * Generate transmit code to send IGMPv2 report in response to general query packets.
+ */
+ private void generateIgmpV2ReportTransmit(ApfV6GeneratorBase<?> gen,
+ byte[] igmpPktFromEthSrcToIpTos, byte[] igmpPktFromIpIdToSrc)
+ throws IllegalInstructionException {
+ final int ipv4TotalLen =
+ IPV4_HEADER_MIN_LEN + IPV4_ROUTER_ALERT_OPTION_LEN + IPV4_IGMP_MIN_SIZE;
+ final byte[] igmpV2PktFromEthSrcToIpSrc = CollectionUtils.concatArrays(
+ igmpPktFromEthSrcToIpTos,
+ new byte[] {
+ (byte) ((ipv4TotalLen >> 8) & 0xff), (byte) (ipv4TotalLen & 0xff),
+ },
+ igmpPktFromIpIdToSrc
+ );
+ for (Inet4Address mcastAddr: mIPv4McastAddrsExcludeAllHost) {
+ final MacAddress mcastEther =
+ NetworkStackUtils.ipv4MulticastToEthernetMulticast(mcastAddr);
+ gen.addAllocate(ETHER_HEADER_LEN + ipv4TotalLen)
+ .addDataCopy(mcastEther.toByteArray())
+ .addDataCopy(igmpV2PktFromEthSrcToIpSrc)
+ .addDataCopy(mcastAddr.getAddress())
+ .addDataCopy(IGMPV2_REPORT_FROM_IPV4_OPTION_TO_IGMP_CHECKSUM)
+ .addDataCopy(mcastAddr.getAddress())
+ .addTransmitL4(
+ // ip_ofs
+ ETHER_HEADER_LEN,
+ // csum_ofs
+ IGMP_CHECKSUM_WITH_ROUTER_ALERT_OFFSET,
+ // csum_start
+ ETHER_HEADER_LEN + IPV4_HEADER_MIN_LEN + IPV4_ROUTER_ALERT_OPTION_LEN,
+ // partial_sum
+ 0,
+ // udp
+ false
+ );
+ }
- // Checks it's UDP.
- gen.addLoad8(R0, IPV4_PROTOCOL_OFFSET);
- gen.addJumpIfR0NotEquals(IPPROTO_UDP, skipMdnsFilter);
+ gen.addCountAndDrop(Counter.DROPPED_IGMP_V2_GENERAL_QUERY_REPLIED);
+ }
- // Set R1 to IPv4 header.
- gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE);
- gen.addJump(checkMdnsUdpPort);
+ /**
+ * Generates filter code to handle IGMP packets.
+ * <p>
+ * On entry, this filter know it is processing an IPv4 packet. It will then process all IGMP
+ * packets, either passing or dropping them. Non-IGMP packets are skipped.
+ */
+ private void generateIgmpFilter(ApfV6GeneratorBase<?> v6Gen)
+ throws IllegalInstructionException {
+ final short skipIgmpFilter = v6Gen.getUniqueLabel();
+ final short checkIgmpV1orV2 = v6Gen.getUniqueLabel();
- gen.defineLabel(skipMdnsv4Filter);
+ // Check 1) it's not a fragment. 2) it's IGMP.
+ v6Gen.addJumpIfNotUnfragmentedIPv4Protocol(IPV4_PROTOCOL_IGMP, skipIgmpFilter);
- // Checks it's L2 mDNS multicast address.
- // Relies on R0 containing the ethernet destination mac address offset.
- gen.addJumpIfBytesAtR0NotEqual(ETH_MULTICAST_MDNS_V6_MAC_ADDRESS, skipMdnsFilter);
+ // Calculate the IPv4 payload length: (total length - IPv4 header length).
+ // Memory slot 0 is occupied temporarily to store the length.
+ v6Gen.addLoad16intoR0(IPV4_TOTAL_LENGTH_OFFSET)
+ .addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE)
+ .addNeg(R1)
+ .addAddR1ToR0()
+ .addStoreToMemory(MemorySlot.SLOT_0, R0);
- // Checks it's IPv6.
- gen.addLoad16(R0, ETH_ETHERTYPE_OFFSET);
- gen.addJumpIfR0NotEquals(ETH_P_IPV6, skipMdnsFilter);
+ // If payload length is less than 8 or equal to 9, 10, 11, it's invalid IGMP packet: drop.
+ v6Gen.addCountAndDropIfR0LessThan(IPV4_IGMP_MIN_SIZE, DROPPED_IGMP_INVALID)
+ .addCountAndDropIfR0IsOneOf(Set.of(9L, 10L, 11L), DROPPED_IGMP_INVALID);
- // Checks it's UDP.
- gen.addLoad8(R0, IPV6_NEXT_HEADER_OFFSET);
- gen.addJumpIfR0NotEquals(IPPROTO_UDP, skipMdnsFilter);
+ // If it's an IGMPv1/IGMPv2/IGMPv3 report: drop.
+ // A host normally cancels its own pending report if it observes
+ // an identical report from another host on the network (host suppression).
+ // While dropping reports here technically disrupts this host's suppression behavior,
+ // it is acceptable since other devices on the network will perform the suppression.
+ // If the IGMP type is not one of the reports, it's either a query(type=0x11) or an
+ // invalid packet.
+ v6Gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE)
+ .addLoad8R1IndexedIntoR0(ETHER_HEADER_LEN)
+ .addCountAndDropIfR0IsOneOf(IGMP_TYPE_REPORTS, DROPPED_IGMP_REPORT)
+ .addCountAndDropIfR0NotEquals(IPV4_IGMP_TYPE_QUERY, DROPPED_IGMP_INVALID);
- // Set R1 to IPv6 header.
- gen.addLoadImmediate(R1, IPV6_HEADER_LEN);
+ // If group address is not 0.0.0.0, it's an IGMPv2/v3 group specific query: pass.
+ // rfc3376#section-6.1 mentions group specific queries are sent when a router receives a
+ // State-Change record indicating a system is leaving a group. Therefore, since the
+ // router only sends group-specific queries after receiving a leave message, it is not
+ // sent out periodically.
+ // Increased APF bytecode size for offloading these queries may not yield significant
+ // power benefits. In this case, letting the kernel handle group-specific queries is
+ // acceptable.
+ v6Gen.addLoad32R1IndexedIntoR0(IGMP_MULTICAST_ADDRESS_OFFSET)
+ .addCountAndPassIfR0NotEquals(0 /* 0.0.0.0 */, PASSED_IPV4);
- // Checks it's mDNS UDP port
- gen.defineLabel(checkMdnsUdpPort);
- gen.addLoad16Indexed(R0, TCP_UDP_DESTINATION_PORT_OFFSET);
- gen.addJumpIfR0NotEquals(MDNS_PORT, skipMdnsFilter);
+ // If we reach here, we know it is an IGMPv1/IGMPv2/IGMPv3 general query.
- // TODO: implement APFv6 mDNS offload
+ // The general query IPv4 destination address must be 224.0.0.1.
+ v6Gen.addLoad32intoR0(IPV4_DEST_ADDR_OFFSET)
+ .addCountAndDropIfR0NotEquals(IPV4_ALL_HOSTS_ADDRESS_IN_LONG,
+ DROPPED_IGMP_INVALID);
- // end of mDNS filter
- gen.defineLabel(skipMdnsFilter);
+ // Check payload length, since invalid length already checked,
+ // it should be 8 (IGMPv1 or IGMPv2) or >=12 (IGMPv3)
+ v6Gen.addLoadFromMemory(R0, MemorySlot.SLOT_0)
+ .addJumpIfR0Equals(IPV4_IGMP_MIN_SIZE, checkIgmpV1orV2);
+
+ // ===== IGMPv3 general query =====
+ // To optimize for bytecode size, the IGMPv3 report is constructed first.
+ // Its packet structure is then reused as a template when creating the IGMPv2 report.
+ final byte[] igmpPktFromEthSrcToIpTos = createIgmpPktFromEthSrcToIPv4Tos();
+ final byte[] igmpPktFromIpIdToSrc = createIgmpPktFromIPv4IdToSrc();
+ generateIgmpV3ReportTransmit(v6Gen, igmpPktFromEthSrcToIpTos, igmpPktFromIpIdToSrc);
+
+ // ===== IGMPv1 or IGMPv2 general query =====
+ v6Gen.defineLabel(checkIgmpV1orV2);
+ // Based on rfc3376#section-7.1 If max resp time is 0, it's IGMPv1: pass.
+ // We don't expect many networks are still using IGMPv1, pass it to the kernel to save
+ // bytecode size.
+ // (Note: R1 is still IPV4_HEADER_SIZE)
+ v6Gen.addLoad8R1IndexedIntoR0(IGMP_MAX_RESP_TIME_OFFSET)
+ .addCountAndPassIfR0Equals(0, PASSED_IPV4); // IGMPv1
+
+ // Drop and transmit IGMPv2 reports
+ generateIgmpV2ReportTransmit(v6Gen, igmpPktFromEthSrcToIpTos, igmpPktFromIpIdToSrc);
+
+ v6Gen.defineLabel(skipIgmpFilter);
+ }
+
+ /**
+ * Creates MLDv1 Listener Report packet message (rfc2710#section-3).
+ */
+ private byte[] createMldV1ReportMessage(final Inet6Address mcastAddr) {
+ final byte[] mldv1Header = new byte[] {
+ // MLD type
+ (byte) IPV6_MLD_TYPE_V1_REPORT,
+ // code
+ 0,
+ // hop-by-hop option is { 0x3a, 0x00, 0x05, 0x02, 0x00, 0x00, 0x01, 0x00 }
+ // so we precalculate MLD checksum as follows:
+ // 0xffff - (0x3a00 + 0x0502 + 0x0000 + 0x0100) = 0xbffd
+ (byte) 0xbf, (byte) 0xfd,
+ // max response delay
+ 0, 0,
+ // reserved
+ 0, 0
+ };
+
+ return CollectionUtils.concatArrays(mldv1Header, mcastAddr.getAddress());
+ }
+
+ /**
+ * Creates MLDv2 Listener Report packet payload (rfc3810#section-5.2).
+ */
+ private byte[] createMldV2ReportPayload() {
+ final int mcastAddrsNum = mIPv6McastAddrsExcludeAllHost.size();
+ final byte[] mldHeader = new byte[] {
+ // MLD type
+ (byte) IPV6_MLD_TYPE_V2_REPORT,
+ // code
+ 0,
+ // hop-by-hop option is { 0x3a, 0x00, 0x05, 0x02, 0x00, 0x00, 0x01, 0x00 }
+ // so we precalculate MLD checksum as follows:
+ // 0xffff - (0x3a00 + 0x0502 + 0x0000 + 0x0100) = 0xbffd
+ (byte) 0xbf, (byte) 0xfd,
+ // reserved
+ 0, 0,
+ // num of multicast address records
+ (byte) ((mcastAddrsNum >> 8) & 0xff), (byte) (mcastAddrsNum & 0xff)
+ };
+
+ final byte[] mcastRecordHeader = new byte[] {
+ // record type
+ (byte) MLD2_MODE_IS_EXCLUDE,
+ // aux data len,
+ 0,
+ // num src
+ 0, 0
+ };
+
+ final byte[] payload =
+ new byte[
+ mldHeader.length + mcastAddrsNum * IPV6_MLD_V2_MULTICAST_ADDRESS_RECORD_SIZE
+ ];
+ int offset = 0;
+
+ System.arraycopy(mldHeader, 0, payload, offset, mldHeader.length);
+ offset += mldHeader.length;
+ for (Inet6Address mcastAddr: mIPv6McastAddrsExcludeAllHost) {
+ System.arraycopy(mcastRecordHeader, 0, payload, offset, mcastRecordHeader.length);
+ offset += mcastRecordHeader.length;
+ System.arraycopy(mcastAddr.getAddress(), 0, payload, offset, IPV6_ADDR_LEN);
+ offset += IPV6_ADDR_LEN;
+ }
+
+ return payload;
+ }
+
+ /**
+ * Creates the portion of an MLD packet from the Ethernet source MAC address to the IPv6
+ * VTF field.
+ */
+ private byte[] createMldPktFromEthSrcToIPv6Vtf() {
+ return CollectionUtils.concatArrays(
+ mHardwareAddress,
+ new byte[] {
+ // etherType: IPv6
+ (byte) 0x86, (byte) 0xdd,
+ // version, traffic class, flow label
+ // 0x60000000 (ref: net/ipv6/mcast.c#ip6_mc_hdr())
+ (byte) 0x60, 0, 0, 0}
+ );
+ }
+
+ /**
+ * Creates the portion of an MLD packet from the IPv6 Next Header to the IPv6 Source Address.
+ */
+ private byte[] createMldPktFromIPv6NextHdrToSrc() {
+ final byte[] ipv6FromNextHdrToHoplimit = new byte[] {
+ // Next header: HOPOPTS
+ 0,
+ // Hop limit
+ (byte) 1
+ };
+ return CollectionUtils.concatArrays(
+ ipv6FromNextHdrToHoplimit,
+ mIPv6LinkLocalAddress.getAddress()
+ );
+ }
+
+ /**
+ * Generate transmit code to send MLDv1 report in response to general query packets.
+ */
+ private void generateMldV1ReportTransmit(ApfV6GeneratorBase<?> gen,
+ byte[] mldPktFromEthSrcToIpv6Vtf, byte[] mldPktFromIpv6NextHdrToSrc)
+ throws IllegalInstructionException {
+ final int packetSize =
+ ETHER_HEADER_LEN
+ + IPV6_HEADER_LEN
+ + IPV6_MLD_HOPOPTS.length
+ + IPV6_MLD_V1_MESSAGE_SIZE;
+ final int mldV1Ipv6PayloadLength = IPV6_MLD_HOPOPTS.length + IPV6_MLD_V1_MESSAGE_SIZE;
+ final byte[] mldV1PktFromEthSrcToIpv6Src = CollectionUtils.concatArrays(
+ mldPktFromEthSrcToIpv6Vtf,
+ new byte[] {
+ (byte) ((mldV1Ipv6PayloadLength >> 8) & 0xff),
+ (byte) (mldV1Ipv6PayloadLength & 0xff),
+ },
+ mldPktFromIpv6NextHdrToSrc
+ );
+ for (Inet6Address mcastAddr: mIPv6McastAddrsExcludeAllHost) {
+ final MacAddress mcastEther =
+ NetworkStackUtils.ipv6MulticastToEthernetMulticast(mcastAddr);
+ gen.addAllocate(packetSize)
+ .addDataCopy(mcastEther.toByteArray())
+ .addDataCopy(mldV1PktFromEthSrcToIpv6Src)
+ .addDataCopy(mcastAddr.getAddress())
+ .addDataCopy(IPV6_MLD_HOPOPTS)
+ .addDataCopy(createMldV1ReportMessage(mcastAddr))
+ .addTransmitL4(
+ // ip_ofs
+ ETHER_HEADER_LEN,
+ // csum_ofs
+ IPV6_MLD_CHECKSUM_OFFSET,
+ // csum_start
+ IPV6_SRC_ADDR_OFFSET,
+ // partial_sum
+ IPPROTO_ICMPV6 + IPV6_MLD_V1_MESSAGE_SIZE,
+ // udp
+ false
+ );
+ }
+
+ gen.addCountAndDrop(DROPPED_IPV6_MLD_V1_GENERAL_QUERY_REPLIED);
+ }
+
+ /**
+ * Generate transmit code to send MLDv2 report in response to general query packets.
+ */
+ private void generateMldV2ReportTransmit(ApfV6GeneratorBase<?> gen,
+ byte[] mldPktFromEthSrcToIpv6Vtf, byte[] mldPktFromIpv6NextHdrToSrc)
+ throws IllegalInstructionException {
+ final int mldV1Ipv6PayloadLength = IPV6_MLD_HOPOPTS.length + IPV6_MLD_V1_MESSAGE_SIZE;
+ final byte[] encodedMldV1Ipv6PayloadLength = {
+ (byte) ((mldV1Ipv6PayloadLength >> 8) & 0xff), (byte) (mldV1Ipv6PayloadLength & 0xff),
+ };
+ // We place template packet chunks in the data region first to reduce the number of
+ // instructions needed for creating multiple MLDv1 reports.
+ // The following packet chunks can be used for creating both MLDv1 and MLDv2 reports:
+ // - from Ethernet source to IPv6 VTF: 12 bytes
+ // - from IPv6 next header to source address: 18 bytes
+ final byte[] mldV2ReportPayload = createMldV2ReportPayload();
+ final byte[] template = CollectionUtils.concatArrays(
+ ETH_MULTICAST_MLD_V2_ALL_MULTICAST_ROUTERS_ADDRESS,
+ mldPktFromEthSrcToIpv6Vtf,
+ encodedMldV1Ipv6PayloadLength,
+ mldPktFromIpv6NextHdrToSrc,
+ IPV6_MLD_V2_ALL_ROUTERS_MULTICAST_ADDRESS,
+ IPV6_MLD_HOPOPTS,
+ mldV2ReportPayload
+ );
+ gen.maybeUpdateDataRegion(template);
+
+ final byte[] mldV2PktFromEthDstToIpv6Vtf = CollectionUtils.concatArrays(
+ ETH_MULTICAST_MLD_V2_ALL_MULTICAST_ROUTERS_ADDRESS,
+ mldPktFromEthSrcToIpv6Vtf
+ );
+ final byte[] mldV2PktFromIpv6NextHdrToEnd = CollectionUtils.concatArrays(
+ mldPktFromIpv6NextHdrToSrc,
+ IPV6_MLD_V2_ALL_ROUTERS_MULTICAST_ADDRESS,
+ IPV6_MLD_HOPOPTS,
+ mldV2ReportPayload
+ );
+ final int mcastAddrsNum = mIPv6McastAddrsExcludeAllHost.size();
+ final int ipv6PayloadLength = IPV6_MLD_HOPOPTS.length
+ + IPV6_MLD_MESSAGE_MIN_SIZE
+ + (mcastAddrsNum * IPV6_MLD_V2_MULTICAST_ADDRESS_RECORD_SIZE);
+ gen.addAllocate(ETHER_HEADER_LEN + IPV6_HEADER_LEN + ipv6PayloadLength)
+ .addDataCopy(mldV2PktFromEthDstToIpv6Vtf)
+ .addWriteU16(ipv6PayloadLength)
+ .addDataCopy(mldV2PktFromIpv6NextHdrToEnd)
+ .addTransmitL4(
+ // ip_ofs
+ ETHER_HEADER_LEN,
+ // csum_ofs
+ IPV6_MLD_CHECKSUM_OFFSET,
+ // csum_start
+ IPV6_SRC_ADDR_OFFSET,
+ // partial_sum
+ IPPROTO_ICMPV6 + (ipv6PayloadLength - IPV6_MLD_HOPOPTS.length),
+ // udp
+ false
+ ).addCountAndDrop(DROPPED_IPV6_MLD_V2_GENERAL_QUERY_REPLIED);
+ }
+
+ /**
+ * Generates filter code to handle MLD packets.
+ * <p>
+ * On entry, this filter knows it is processing an IPv6 packet. It will then process all MLD
+ * packets, either passing or dropping them. Non-MLD packets are skipped.
+ * R0 contains the u8 IPv6 next header.
+ */
+ private void generateMldFilter(ApfV6GeneratorBase<?> gen)
+ throws IllegalInstructionException {
+ final short skipMldFilter = gen.getUniqueLabel();
+ final short checkMldv1 = gen.getUniqueLabel();
+
+ // If next header is not hop-by-hop, then skip
+ gen.addJumpIfR0NotEquals(IPPROTO_HOPOPTS, skipMldFilter);
+
+ final int mldPacketMinSize =
+ ETHER_HEADER_LEN + IPV6_HEADER_LEN + IPV6_MLD_HOPOPTS.length + IPV6_MLD_MIN_SIZE;
+ // If packet is too small to be MLD packet, then skip
+ gen.addLoadFromMemory(R0, MemorySlot.PACKET_SIZE)
+ .addJumpIfR0LessThan(mldPacketMinSize, skipMldFilter)
+ .addSub(ETHER_HEADER_LEN + IPV6_HEADER_LEN + IPV6_MLD_HOPOPTS.length)
+ // Memory slot 0 is occupied temporarily to store the MLD payload length.
+ .addStoreToMemory(MemorySlot.SLOT_0, R0);
+
+ // If the hop-by-hop option is not the one used by MLD, then skip
+ gen.addLoadImmediate(R0, IPV6_EXT_HEADER_OFFSET)
+ .addJumpIfBytesAtR0NotEqual(IPV6_MLD_HOPOPTS, skipMldFilter);
+
+ // If the packet is an MLDv1 report or done, or an MLDv2 report, then drop it.
+ // Else if the packet is not an MLD query packet, then skip.
+ gen.addLoad8intoR0(IPV6_MLD_TYPE_OFFSET)
+ .addCountAndDropIfR0IsOneOf(IPV6_MLD_TYPE_REPORTS, DROPPED_IPV6_MLD_REPORT)
+ .addJumpIfR0NotEquals(IPV6_MLD_TYPE_QUERY, skipMldFilter);
+
+ // If we reach here, we know it is an MLDv1/MLDv2 query.
+
+ // If the payload length is 25, 26, or 27, the MLD packet is invalid and should be dropped.
+ gen.addLoadFromMemory(R0, MemorySlot.SLOT_0)
+ .addCountAndDropIfR0IsOneOf(Set.of(25L, 26L, 27L), DROPPED_IPV6_MLD_INVALID);
+
+ // rfc3810#section-5 and rfc2710#section-3 describe that all MLD messages are sent with a
+ // link-local IPv6 source address, an IPv6 Hop Limit of 1, and an IPv6 Router Alert
+ // option [RTR-ALERT] in a Hop-by-Hop Options header.
+ // rfc3810#section-5.2.13 describes that an MLDv2 Report MUST be sent with a valid
+ // IPv6 link-local source address, or the unspecified address (::), if the sending interface
+ // has not yet acquired a valid link-local address.
+ // Its OK to not check :: here since we also drop MLD reports.
+ // If the source address is a not a link-local address, then drop.
+ gen.addLoad16intoR0(IPV6_SRC_ADDR_OFFSET)
+ .addCountAndDropIfR0NotEquals(0xfe80, DROPPED_IPV6_MLD_INVALID);
+
+ // If hop limit is not 1, then drop.
+ gen.addLoad8intoR0(IPV6_HOP_LIMIT_OFFSET)
+ .addCountAndDropIfR0NotEquals(1, DROPPED_IPV6_MLD_INVALID);
+
+ // If the multicast address is not "::", it is an MLD2 multicast-address-specific query,
+ // then pass.
+ gen.addCountAndPassIfBytesAtOffsetNotEqual(
+ IPV6_MLD_MULTICAST_ADDR_OFFSET, IPV6_ADDR_ANY.getAddress(), PASSED_IPV6_ICMP);
+
+ // If we reach here, we know it is an MLDv1/MLDv2 general query.
+
+ // The general query IPv6 destination address must be ff02::1.
+ gen.addCountAndDropIfBytesAtOffsetNotEqual(
+ IPV6_DEST_ADDR_OFFSET, IPV6_ALL_NODES_ADDRESS, DROPPED_IPV6_MLD_INVALID);
+
+ // If the MLD payload length is 24, it is an MLDv1 packet, otherwise, it is an MLDv2 packet.
+ gen.addLoadFromMemory(R0, MemorySlot.SLOT_0)
+ .addJumpIfR0Equals(IPV6_MLD_MIN_SIZE, checkMldv1);
+
+ // ===== MLDv2 general query =====
+ // To optimize for bytecode size, the MLDv2 report is constructed first.
+ // Its packet structure is then reused as a template when creating the IGMPv1 report.
+ final byte[] mldPktFromEthSrcToIPv6Vtf = createMldPktFromEthSrcToIPv6Vtf();
+ final byte[] mldPktFromIPv6NextHdrToSrc = createMldPktFromIPv6NextHdrToSrc();
+ generateMldV2ReportTransmit(gen, mldPktFromEthSrcToIPv6Vtf, mldPktFromIPv6NextHdrToSrc);
+
+ gen.defineLabel(checkMldv1);
+ // ===== MLDv1 general query =====
+ generateMldV1ReportTransmit(gen, mldPktFromEthSrcToIPv6Vtf, mldPktFromIPv6NextHdrToSrc);
+
+ gen.defineLabel(skipMldFilter);
}
/**
@@ -2192,23 +3306,23 @@
*/
private void generateV4TcpPort7Filter(ApfV4GeneratorBase<?> gen)
throws IllegalInstructionException {
- final String skipPort7V4Filter = gen.getUniqueLabel();
+ final short skipPort7V4Filter = gen.getUniqueLabel();
// Check it's TCP.
- gen.addLoad8(R0, IPV4_PROTOCOL_OFFSET);
+ gen.addLoad8intoR0(IPV4_PROTOCOL_OFFSET);
gen.addJumpIfR0NotEquals(IPPROTO_TCP, skipPort7V4Filter);
// Check it's not a fragment or is the initial fragment.
- gen.addLoad16(R0, IPV4_FRAGMENT_OFFSET_OFFSET);
+ gen.addLoad16intoR0(IPV4_FRAGMENT_OFFSET_OFFSET);
gen.addJumpIfR0AnyBitsSet(IPV4_FRAGMENT_OFFSET_MASK, skipPort7V4Filter);
// Check it's destination port 7.
gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE);
- gen.addLoad16Indexed(R0, TCP_UDP_DESTINATION_PORT_OFFSET);
+ gen.addLoad16R1IndexedIntoR0(TCP_UDP_DESTINATION_PORT_OFFSET);
gen.addJumpIfR0NotEquals(ECHO_PORT, skipPort7V4Filter);
// Drop it.
- gen.addCountAndDrop(Counter.DROPPED_IPV4_TCP_PORT7_UNICAST);
+ gen.addCountAndDrop(DROPPED_IPV4_TCP_PORT7_UNICAST);
// Skip label.
gen.defineLabel(skipPort7V4Filter);
@@ -2220,6 +3334,188 @@
gen.getUniqueLabel());
}
+ private byte[] createMdns4PktFromEthDstToIPv4Tos(boolean enabled) {
+ if (!enabled) {
+ return null;
+ }
+ return concatArrays(
+ ETH_MULTICAST_MDNS_V4_MAC_ADDRESS,
+ mHardwareAddress,
+ new byte[]{
+ 0x08, 0x00, // ethertype: IPv4
+ 0x45, 0x00, // version, IHL, DSCP, ECN,
+ });
+ }
+
+ private byte[] createMdns6PktFromEthDstToIPv6FlowLabel(boolean enabled) {
+ if (!enabled) {
+ return null;
+ }
+ return concatArrays(
+ ETH_MULTICAST_MDNS_V6_MAC_ADDRESS,
+ mHardwareAddress,
+ new byte[]{
+ (byte) 0x86, (byte) 0xdd, // ethertype: IPv6
+ 0x60, 0x00, 0x00, 0x00, // version, traffic class, flow label
+ });
+ }
+
+
+ private byte[] createMdns4PktFromIPv4IdToUdpDport(boolean enabled) {
+ if (!enabled) {
+ return null;
+ }
+ return concatArrays(
+ new byte[]{
+ 0x00, 0x00, // identification
+ (byte) (IPV4_FLAG_DF >> 8), 0, // flags, fragment offset
+ (byte) 0xff, // set TTL to 255 per rfc6762#section-11
+ (byte) IPPROTO_UDP,
+ 0x00, 0x00, // checksum, it's a placeholder that will be filled in later.
+ },
+ mIPv4Address,
+ MDNS_IPV4_ADDR,
+ MDNS_PORT_IN_BYTES, // source port
+ MDNS_PORT_IN_BYTES); // destination port
+ }
+
+ private byte[] createMdns6PktFromIPv6NextHdrToUdpDport(boolean enabled) {
+ if (!enabled) {
+ return null;
+ }
+ return concatArrays(
+ new byte[]{
+ (byte) IPPROTO_UDP,
+ (byte) 0xff, // set hop limit to 255 per rfc6762#section-11
+ },
+ mIPv6LinkLocalAddress.getAddress(),
+ MDNS_IPV6_ADDR,
+ MDNS_PORT_IN_BYTES, // source port
+ MDNS_PORT_IN_BYTES); // destination port
+ }
+
+ /**
+ * Generates filter code to process an mDNS payload against offload rules.
+ * The generated filter code is guaranteed to process all IPv4 and IPv6 mDNS packets,
+ * ensuring each packet is either passed or dropped.
+ * <p>
+ * The only way to enter the mDNS offload payload check logic is by jumping to the
+ * labelCheckMdnsQueryPayload label.
+ * On entry, the packet is known to be an IPv4/IPv6 mDNS query packet, and register R1
+ * is set to the offset of the beginning of the UDP payload (the DNS header).
+ *
+ * @param gen the APF generator to generate the filter code
+ * @param labelCheckMdnsQueryPayload the label to jump to for checking the mDNS query payload
+ */
+ private void generateMdnsQueryOffload(ApfV6GeneratorBase<?> gen,
+ short labelCheckMdnsQueryPayload, int numOfMdnsRuleToOffload)
+ throws IllegalInstructionException {
+ // The mDNS payload check logic is terminal; the program will always result in either
+ // PASS or DROP.
+ gen.defineLabel(labelCheckMdnsQueryPayload);
+
+ if (numOfMdnsRuleToOffload == -1) {
+ gen.addCountAndPass(PASSED_MDNS);
+ return;
+ }
+
+ // Set R0 to the offset of the beginning of the UDP payload (the DNS header)
+ gen.addSwap();
+
+ final boolean enableMdns4 = enableMdns4Offload();
+ final boolean enableMdns6 = enableMdns6Offload();
+ final byte[] mdns4EthDstToTos = createMdns4PktFromEthDstToIPv4Tos(enableMdns4);
+ final byte[] mdns4IdToUdpDport = createMdns4PktFromIPv4IdToUdpDport(enableMdns4);
+ final byte[] mdns6EthDstToFlowLabel = createMdns6PktFromEthDstToIPv6FlowLabel(enableMdns6);
+ final byte[] mdns6NextHdrToUdpDport = createMdns6PktFromIPv6NextHdrToUdpDport(enableMdns6);
+
+ for (int i = 0; i < mOffloadRules.size(); i++) {
+ final MdnsOffloadRule rule = mOffloadRules.get(i);
+ final short ruleNotMatch = gen.getUniqueLabel();
+ final short ruleMatch = gen.getUniqueLabel();
+ final short offloadIPv6Mdns = gen.getUniqueLabel();
+
+ for (MdnsOffloadRule.Matcher matcher : rule.mMatchers) {
+ try {
+ gen.addJumpIfPktAtR0ContainDnsQ(matcher.mQnames, matcher.mQtypes, ruleMatch);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Failed to generate mDNS offload filter for rule: " + rule, e);
+ }
+ }
+
+ gen.addJump(ruleNotMatch);
+
+ gen.defineLabel(ruleMatch);
+
+ // If there is no offload payload, pass the packet to let NsdService handle it.
+ // If there isn't enough space to offload all rules, packets should be processed
+ // by iterating through the rules, starting with the lowest priority.
+ if (rule.mOffloadPayload == null || i >= numOfMdnsRuleToOffload) {
+ gen.addCountAndPass(PASSED_MDNS);
+ } else {
+ if (enableMdns4 && enableMdns6) {
+ gen.addLoad16intoR0(ETH_ETHERTYPE_OFFSET)
+ .addJumpIfR0NotEquals(ETH_P_IP, offloadIPv6Mdns);
+ }
+
+ if (enableMdns4) {
+ final int udpLength = UDP_HEADER_LEN + rule.mOffloadPayload.length;
+ final int ipv4TotalLength = IPV4_HEADER_MIN_LEN + udpLength;
+ final int pktLength = ETH_HEADER_LEN + ipv4TotalLength;
+
+ gen.addAllocate(pktLength)
+ .addDataCopy(mdns4EthDstToTos)
+ .addWriteU16(ipv4TotalLength)
+ .addDataCopy(mdns4IdToUdpDport)
+ .addWrite32(udpLength << 16) // udp length and checksum
+ .addDataCopy(rule.mOffloadPayload)
+ .addTransmitL4(
+ ETH_HEADER_LEN, // ip_ofs
+ IPV4_UDP_DESTINATION_CHECKSUM_NO_OPTIONS_OFFSET, // csum_ofs
+ IPV4_SRC_ADDR_OFFSET, // csum_start
+ IPPROTO_UDP + udpLength, // partial_sum
+ true // udp
+ ).addCountAndDrop(Counter.DROPPED_MDNS_REPLIED);
+ }
+
+ if (enableMdns4 && enableMdns6) {
+ gen.defineLabel(offloadIPv6Mdns);
+ }
+
+ if (enableMdns6) {
+ final int udpLength = UDP_HEADER_LEN + rule.mOffloadPayload.length;
+ final int pktLength = ETH_HEADER_LEN + IPV6_HEADER_LEN + udpLength;
+ gen.addAllocate(pktLength)
+ .addDataCopy(mdns6EthDstToFlowLabel)
+ .addWriteU16(udpLength) // payload length
+ .addDataCopy(mdns6NextHdrToUdpDport)
+ .addWrite32(udpLength << 16) // udp length and checksum
+ .addDataCopy(rule.mOffloadPayload)
+ .addTransmitL4(
+ ETH_HEADER_LEN, // ip_ofs
+ IPV6_UDP_DESTINATION_CHECKSUM_OFFSET, // csum_ofs
+ IPV6_SRC_ADDR_OFFSET, // csum_start
+ IPPROTO_UDP + udpLength, // partial_sum
+ true // udp
+ ).addCountAndDrop(Counter.DROPPED_MDNS_REPLIED);
+ }
+ }
+
+ gen.defineLabel(ruleNotMatch);
+ }
+
+ // If no offload rules match, we should still respect the multicast filter. During the
+ // transition period, not all apps will use NsdManager for mDNS advertising. If an app
+ // decides to perform mDNS advertising itself, it must acquire a multicast lock, and no
+ // offload rules will be registered for that app. In this case, the APF should pass the
+ // mDNS packet and allow the app to handle the query.
+ if (mMulticastFilter) {
+ gen.addCountAndDrop(DROPPED_MDNS);
+ } else {
+ gen.addCountAndPass(PASSED_MDNS);
+ }
+ }
+
/**
* Begin generating an APF program to:
* <ul>
@@ -2239,46 +3535,47 @@
* <li>Let execution continue off the end of the program for IPv6 ICMPv6 packets. This allows
* insertion of RA filters here, or if there aren't any, just passes the packets.
* </ul>
+ * @param gen the APF generator to generate the filter code
+ * @param labelCheckMdnsQueryPayload the label to jump to for checking the mDNS query payload
*/
- private ApfV4GeneratorBase<?> emitPrologue() throws IllegalInstructionException {
- // This is guaranteed to succeed because of the check in maybeCreate.
- ApfV4GeneratorBase<?> gen;
- if (shouldUseApfV6Generator()) {
- gen = new ApfV6Generator(mApfVersionSupported, mApfRamSize,
- mInstallableProgramSizeClamp);
- } else {
- gen = new ApfV4Generator(mApfVersionSupported, mApfRamSize,
- mInstallableProgramSizeClamp);
- }
-
+ private void emitPrologue(@NonNull ApfV4GeneratorBase<?> gen, short labelCheckMdnsQueryPayload)
+ throws IllegalInstructionException {
if (hasDataAccess(mApfVersionSupported)) {
if (gen instanceof ApfV4Generator) {
// Increment TOTAL_PACKETS.
// Only needed in APFv4.
// In APFv6, the interpreter will increase the counter on packet receive.
- gen.addIncrementCounter(Counter.TOTAL_PACKETS);
+ gen.addIncrementCounter(TOTAL_PACKETS);
}
gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_SECONDS);
- gen.addStoreCounter(Counter.FILTER_AGE_SECONDS, R0);
+ gen.addStoreCounter(FILTER_AGE_SECONDS, R0);
// requires a new enough APFv5+ interpreter, otherwise will be 0
gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_16384THS);
- gen.addStoreCounter(Counter.FILTER_AGE_16384THS, R0);
+ gen.addStoreCounter(FILTER_AGE_16384THS, R0);
// requires a new enough APFv5+ interpreter, otherwise will be 0
gen.addLoadFromMemory(R0, MemorySlot.APF_VERSION);
- gen.addStoreCounter(Counter.APF_VERSION, R0);
+ gen.addStoreCounter(APF_VERSION, R0);
// store this program's sequential id, for later comparison
gen.addLoadImmediate(R0, mNumProgramUpdates);
- gen.addStoreCounter(Counter.APF_PROGRAM_ID, R0);
+ gen.addStoreCounter(APF_PROGRAM_ID, R0);
}
// Here's a basic summary of what the initial program does:
//
// if it is a loopback (src mac is nic's primary mac) packet
- // pass
+ // if 25Q2+:
+ // drop
+ // else
+ // pass
+ // if it's a TDLS packet:
+ // it is unicast:
+ // pass
+ // else
+ // drop
// if it's a 802.3 Frame (ethtype < 0x0600):
// drop or pass based on configurations
// if it has a ether-type that belongs to the black list
@@ -2293,18 +3590,33 @@
// pass
// insert IPv6 filter to drop, pass, or fall off the end for ICMPv6 packets
- gen.addLoadImmediate(R0, ETHER_SRC_ADDR_OFFSET);
- gen.addCountAndPassIfBytesAtR0Equal(mHardwareAddress, PASSED_ETHER_OUR_SRC_MAC);
+ if (NetworkStackUtils.isAtLeast25Q2()) {
+ gen.addCountAndDropIfBytesAtOffsetEqual(ETHER_SRC_ADDR_OFFSET, mHardwareAddress,
+ DROPPED_ETHER_OUR_SRC_MAC);
+ } else {
+ // TODO: we don't have test coverage for this line
+ gen.addCountAndPassIfBytesAtOffsetEqual(ETHER_SRC_ADDR_OFFSET, mHardwareAddress,
+ PASSED_ETHER_OUR_SRC_MAC);
+ }
- gen.addLoad16(R0, ETH_ETHERTYPE_OFFSET);
+ gen.addLoad16intoR0(ETH_ETHERTYPE_OFFSET);
if (SdkLevel.isAtLeastV()) {
+ // Pass unicast TDLS packet but drop non-unicast TDLS packet.
+ short skipTDLScheck = gen.getUniqueLabel();
+ gen.addJumpIfR0NotEquals(0x890DL, skipTDLScheck)
+ .addCountAndDropIfBytesAtOffsetNotEqual(
+ ETH_DEST_ADDR_OFFSET, mHardwareAddress, DROPPED_NON_UNICAST_TDLS)
+ .addCountAndPass(PASSED_NON_IP_UNICAST)
+ .defineLabel(skipTDLScheck);
+
// IPv4, ARP, IPv6, EAPOL, WAPI
- gen.addCountAndDropIfR0IsNoneOf(Set.of(0x0800L, 0x0806L, 0x86DDL, 0x888EL, 0x88B4L),
- Counter.DROPPED_ETHERTYPE_NOT_ALLOWED);
+ gen.addCountAndDropIfR0IsNoneOf(
+ Set.of(0x0800L, 0x0806L, 0x86DDL, 0x888EL, 0x88B4L),
+ DROPPED_ETHERTYPE_NOT_ALLOWED);
} else {
if (mDrop802_3Frames) {
// drop 802.3 frames (ethtype < 0x0600)
- gen.addCountAndDropIfR0LessThan(ETH_TYPE_MIN, Counter.DROPPED_802_3_FRAME);
+ gen.addCountAndDropIfR0LessThan(ETH_TYPE_MIN, DROPPED_802_3_FRAME);
}
// Handle ether-type black list
if (mEthTypeBlackList.length > 0) {
@@ -2312,58 +3624,188 @@
for (int p : mEthTypeBlackList) {
deniedEtherTypes.add((long) p);
}
- gen.addCountAndDropIfR0IsOneOf(deniedEtherTypes,
- Counter.DROPPED_ETHERTYPE_NOT_ALLOWED);
+ gen.addCountAndDropIfR0IsOneOf(deniedEtherTypes, DROPPED_ETHERTYPE_NOT_ALLOWED);
}
}
// Add ARP filters:
- String skipArpFiltersLabel = gen.getUniqueLabel();
+ short skipArpFiltersLabel = gen.getUniqueLabel();
gen.addJumpIfR0NotEquals(ETH_P_ARP, skipArpFiltersLabel);
generateArpFilter(gen);
gen.defineLabel(skipArpFiltersLabel);
- // Add mDNS filter:
- generateMdnsFilter(gen);
- gen.addLoad16(R0, ETH_ETHERTYPE_OFFSET);
+ gen.addLoad16intoR0(ETH_ETHERTYPE_OFFSET);
// Add IPv4 filters:
- String skipIPv4FiltersLabel = gen.getUniqueLabel();
+ short skipIPv4FiltersLabel = gen.getUniqueLabel();
gen.addJumpIfR0NotEquals(ETH_P_IP, skipIPv4FiltersLabel);
- generateIPv4Filter(gen);
+ generateIPv4Filter(gen, labelCheckMdnsQueryPayload);
gen.defineLabel(skipIPv4FiltersLabel);
// Check for IPv6:
// NOTE: Relies on R0 containing ethertype. This is safe because if we got here, we did
// not execute the IPv4 filter, since this filter do not fall through, but either drop or
// pass.
- String ipv6FilterLabel = gen.getUniqueLabel();
+ short ipv6FilterLabel = gen.getUniqueLabel();
gen.addJumpIfR0Equals(ETH_P_IPV6, ipv6FilterLabel);
// Drop non-IP non-ARP broadcasts, pass the rest
- gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET);
- gen.addCountAndPassIfBytesAtR0NotEqual(ETHER_BROADCAST, Counter.PASSED_NON_IP_UNICAST);
- gen.addCountAndDrop(Counter.DROPPED_ETH_BROADCAST);
+ gen.addCountAndPassIfBytesAtOffsetNotEqual(ETH_DEST_ADDR_OFFSET, ETHER_BROADCAST,
+ PASSED_NON_IP_UNICAST);
+ gen.addCountAndDrop(DROPPED_ETH_BROADCAST);
// Add IPv6 filters:
gen.defineLabel(ipv6FilterLabel);
- generateIPv6Filter(gen);
- return gen;
+ generateIPv6Filter(gen, labelCheckMdnsQueryPayload);
}
- /**
- * Append packet counting epilogue to the APF program.
- * <p>
- * Currently, the epilogue consists of two trampolines which count passed and dropped packets
- * before jumping to the actual PASS and DROP labels.
- */
- private void emitEpilogue(ApfV4GeneratorBase<?> gen) throws IllegalInstructionException {
- // Execution will reach here if none of the filters match, which will pass the packet to
- // the application processor.
- gen.addCountAndPass(Counter.PASSED_IPV6_ICMP);
+ private String getApfConfigMessage() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("{ ");
+ sb.append("mcast: ");
+ sb.append(mMulticastFilter ? "DROP" : "ALLOW");
+ sb.append(", ");
+ sb.append("doze: ");
+ sb.append(mInDozeMode ? "TRUE" : "FALSE");
+ sb.append(", ");
+ sb.append("offloads: ");
+ sb.append("[ ");
+ if (enableArpOffload()) {
+ sb.append("ARP, ");
+ }
+ if (enableNdOffload()) {
+ sb.append("ND, ");
+ }
+ if (enableIgmpOffload()) {
+ sb.append("IGMP, ");
+ }
+ if (enableMldOffload()) {
+ sb.append("MLD, ");
+ }
+ if (enableIpv4PingOffload()) {
+ sb.append("Ping4, ");
+ }
+ if (enableIpv6PingOffload()) {
+ sb.append("Ping6, ");
+ }
+ if (enableMdns4Offload()) {
+ sb.append("Mdns4, ");
+ }
+ if (enableMdns6Offload()) {
+ sb.append("Mdns6, ");
+ }
+ sb.append("] ");
+ sb.append("total RAs: ");
+ sb.append(mRas.size());
+ sb.append(" filtered RAs: ");
+ sb.append(mNumFilteredRas);
+ sb.append(" mDNSs: ");
+ sb.append(mOffloadRules.size());
+ sb.append(" }");
+ return sb.toString();
+ }
- // TODO: merge the addCountTrampoline() into generate() method
- gen.addCountTrampoline();
+ private void installPacketFilter(byte[] program, String logInfo) {
+ if (!mApfController.installPacketFilter(program, logInfo)) {
+ sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE);
+ }
+ }
+
+ private ApfV4GeneratorBase<?> createApfGenerator() throws IllegalInstructionException {
+ if (useApfV61Generator()) {
+ return new ApfV61Generator(mApfVersionSupported, mApfRamSize,
+ mInstallableProgramSizeClamp);
+ } else if (useApfV6Generator()) {
+ return new ApfV6Generator(mApfVersionSupported, mApfRamSize,
+ mInstallableProgramSizeClamp);
+ } else {
+ return new ApfV4Generator(mApfVersionSupported, mApfRamSize,
+ mInstallableProgramSizeClamp);
+ }
+ }
+
+ @VisibleForTesting
+ public int getOverEstimatedProgramSize() {
+ return mOverEstimatedProgramSize;
+ }
+
+ private int calcMdnsOffloadProgramSizeOverEstimate(int numOfMdnsRuleToOffload)
+ throws IllegalInstructionException {
+ ApfV6GeneratorBase<?> gen = (ApfV6GeneratorBase<?>) createApfGenerator();
+ // We need to preload data for size estimation because the preloaded data contains mDNS
+ // data chunks. If we don't preload, generateMdnsQueryOffload() will add data to the data
+ // region, resulting in an incorrect estimated size.
+ if (gen instanceof ApfV61GeneratorBase<?>) {
+ preloadData((ApfV61GeneratorBase<?>) gen);
+ }
+ final int programLengthOverEstimateBefore = gen.programLengthOverEstimate();
+ short tmpLabelCheckMdnsQueryPayload = gen.getUniqueLabel();
+ generateMdnsQueryOffload(gen, tmpLabelCheckMdnsQueryPayload,
+ numOfMdnsRuleToOffload);
+ return gen.programLengthOverEstimate() - programLengthOverEstimateBefore;
+ }
+
+ void preloadData(ApfV61GeneratorBase<?> gen) throws IllegalInstructionException {
+ final List<byte[]> preloadedMacAddress = getKnownMacAddresses();
+ final List<byte[]> preloadedIPv6Address = getIpv6Addresses(true /* includeNonTentative */,
+ true /* includeTentative */, true /* includeAnycast */);
+ preloadedIPv6Address.add(IPV6_ADDR_ALL_NODES_MULTICAST.getAddress());
+ preloadedIPv6Address.add(IPV6_ADDR_ANY.getAddress());
+ byte[] mdns6NextHdrToUdpDport = new byte[0];
+ byte[] mdns6EthDstToFlowLabel = new byte[0];
+ byte[] mdns4EthDstToTos = new byte[0];
+ if (enableMdns6Offload()) {
+ mdns6NextHdrToUdpDport = createMdns6PktFromIPv6NextHdrToUdpDport(true);
+ preloadedIPv6Address.removeIf(
+ addr -> Arrays.equals(addr, mIPv6LinkLocalAddress.getAddress()));
+ mdns6EthDstToFlowLabel = createMdns6PktFromEthDstToIPv6FlowLabel(true);
+ preloadedMacAddress.removeIf(
+ addr -> Arrays.equals(addr, mHardwareAddress) || Arrays.equals(addr,
+ ETH_MULTICAST_MDNS_V6_MAC_ADDRESS));
+ }
+
+ if (enableMdns4Offload()) {
+ mdns4EthDstToTos = createMdns4PktFromEthDstToIPv4Tos(true);
+ preloadedMacAddress.removeIf(
+ addr -> Arrays.equals(addr, mHardwareAddress) || Arrays.equals(addr,
+ ETH_MULTICAST_MDNS_V4_MAC_ADDRESS));
+ }
+
+ int preloadDataSize = mdns6NextHdrToUdpDport.length + mdns6EthDstToFlowLabel.length
+ + mdns4EthDstToTos.length + preloadedIPv6Address.size() * 16
+ + preloadedMacAddress.size() * 6;
+
+ if (enableArpOffload()) {
+ preloadDataSize += FIXED_ARP_REPLY_HEADER.length;
+ }
+
+ final byte[] preloadData = new byte[preloadDataSize];
+ int offset = 0;
+ System.arraycopy(mdns6NextHdrToUdpDport, 0, preloadData, offset,
+ mdns6NextHdrToUdpDport.length);
+ offset += mdns6NextHdrToUdpDport.length;
+ System.arraycopy(mdns6EthDstToFlowLabel, 0, preloadData, offset,
+ mdns6EthDstToFlowLabel.length);
+ offset += mdns6EthDstToFlowLabel.length;
+ System.arraycopy(mdns4EthDstToTos, 0, preloadData, offset, mdns4EthDstToTos.length);
+ offset += mdns4EthDstToTos.length;
+ for (byte[] addr : preloadedMacAddress) {
+ System.arraycopy(addr, 0, preloadData, offset, 6);
+ offset += 6;
+ }
+ for (byte[] addr : preloadedIPv6Address) {
+ System.arraycopy(addr, 0, preloadData, offset, 16);
+ offset += 16;
+ }
+ if (enableArpOffload()) {
+ System.arraycopy(FIXED_ARP_REPLY_HEADER, 0, preloadData, offset,
+ FIXED_ARP_REPLY_HEADER.length);
+ offset += FIXED_ARP_REPLY_HEADER.length;
+ }
+
+ if (preloadDataSize > 0) {
+ gen.addPreloadData(preloadData);
+ }
}
/**
@@ -2375,67 +3817,136 @@
final byte[] program;
int programMinLft = Integer.MAX_VALUE;
+ // Ensure the entire APF program uses the same time base.
+ final int timeSeconds = secondsSinceBoot();
+ // Every return from this function calls installPacketFilter().
+ mLastTimeInstalledProgram = timeSeconds;
+
+ // Increase the counter before we generate the program.
+ // This keeps the APF_PROGRAM_ID counter in sync with the program.
+ mNumProgramUpdates++;
+
try {
- // Ensure the entire APF program uses the same time base.
- final int timeSeconds = secondsSinceBoot();
- mLastTimeInstalledProgram = timeSeconds;
- // Step 1: Determine how many RA filters we can fit in the program.
- ApfV4GeneratorBase<?> gen = emitPrologue();
+ // Step 1: Determine how many RA filters/mDNS offloads we can fit in the program.
+ ApfV4GeneratorBase<?> gen = createApfGenerator();
+ if (gen instanceof ApfV61GeneratorBase<?>) {
+ preloadData((ApfV61GeneratorBase<?>) gen);
+ }
+ short labelCheckMdnsQueryPayload = gen.getUniqueLabel();
- // The epilogue normally goes after the RA filters, but add it early to include its
- // length when estimating the total.
- emitEpilogue(gen);
+ emitPrologue(gen, labelCheckMdnsQueryPayload);
- // Can't fit the program even without any RA filters?
- if (gen.programLengthOverEstimate() > mMaximumApfProgramSize) {
+ int programLengthOverEstimate = gen.programLengthOverEstimate();
+
+ // The default packet handling normally goes after the RA filters, but add it early to
+ // include its length when estimating the total.
+ programLengthOverEstimate += gen.getDefaultPacketHandlingSizeOverEstimate();
+
+ // Can't fit the program even without any RA filters/Mdns offloads?
+ if (programLengthOverEstimate > mMaximumApfProgramSize) {
Log.e(TAG, "Program exceeds maximum size " + mMaximumApfProgramSize);
sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_OVER_SIZE_FAILURE);
+ installPacketFilter(new byte[mMaximumApfProgramSize],
+ getApfConfigMessage() + " (clear memory, reason: program too large)");
return;
}
+ // We attempt to fit the mDNS offload rules into the program before fitting RAs. The
+ // strategy is as follows:
+ // 1. If sufficient memory is available, offload all rules.
+ // 2. If memory is insufficient, switch low-priority rules to passthrough mode.
+ // 3. If memory remains insufficient after all rules are switched to passthrough
+ // mode, fail open to pass all mDNS packets.
+ //
+ // We prioritize mDNS offload over RA filters because:
+ // 1. We plan to move away from the multicast lock API, making mDNS offload critical
+ // for the application's proper function. Without it, app developers would likely
+ // still use the multicast lock, which would wake the device for almost all
+ // multicast traffic, leading to serious power problems.
+ // 2. For devices like TVs, reliable mDNS offload is key to meeting EU power regulation
+ // requirement. These devices are usually on home networks with very chatty mDNS
+ // traffic.
+ if (enableMdns4Offload() || enableMdns6Offload()) {
+ final int remainSize = mMaximumApfProgramSize - programLengthOverEstimate;
+ mNumOfMdnsRuleToOffload = mOffloadRules.size();
+ int mDnsProgramLengthOverEstimate = 0;
+ for (; mNumOfMdnsRuleToOffload >= -1; --mNumOfMdnsRuleToOffload) {
+ mDnsProgramLengthOverEstimate = calcMdnsOffloadProgramSizeOverEstimate(
+ mNumOfMdnsRuleToOffload);
+ if (mDnsProgramLengthOverEstimate <= remainSize) {
+ break;
+ }
+ }
+
+ // When the size of offload rules is non-zero, at bare minimum, the
+ // program should be pass the mDNS packets. Otherwise, the application use case will
+ // be broken after we migrate away from multicast lock.
+ // If the remaining size is insufficient for mDNS fail-open, we should fail-open
+ // for the entire program.
+ if (mNumOfMdnsRuleToOffload < -1) {
+ Log.e(TAG, "Program exceeds maximum size (unable to fail-open for mDNS) "
+ + mMaximumApfProgramSize);
+ sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_OVER_SIZE_FAILURE);
+ installPacketFilter(new byte[mMaximumApfProgramSize], getApfConfigMessage()
+ + " (clear memory, reason: unable to fail-open for mDNS)");
+ return;
+ }
+
+ programLengthOverEstimate += mDnsProgramLengthOverEstimate;
+ } else {
+ mNumOfMdnsRuleToOffload = -1;
+ }
+
+
for (Ra ra : mRas) {
// skip filter if it has expired.
if (ra.getRemainingFilterLft(timeSeconds) <= 0) continue;
- ra.generateFilter(gen, timeSeconds);
+ programLengthOverEstimate += ra.getRaProgramLengthOverEstimate(timeSeconds);
// Stop if we get too big.
- if (gen.programLengthOverEstimate() > mMaximumApfProgramSize) {
- if (VDBG) Log.d(TAG, "Past maximum program size, skipping RAs");
- sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_OVER_SIZE_FAILURE);
+ if (programLengthOverEstimate > mMaximumApfProgramSize) {
+ Log.i(TAG, "Past maximum program size, skipping RAs");
break;
}
+ ra.generateFilter(gen, timeSeconds);
+ programMinLft = Math.min(programMinLft, ra.getRemainingFilterLft(timeSeconds));
rasToFilter.add(ra);
}
- // Increase the counter before we generate the program.
- // This keeps the APF_PROGRAM_ID counter in sync with the program.
- mNumProgramUpdates++;
-
- // Step 2: Actually generate the program
- gen = emitPrologue();
- for (Ra ra : rasToFilter) {
- ra.generateFilter(gen, timeSeconds);
- programMinLft = Math.min(programMinLft, ra.getRemainingFilterLft(timeSeconds));
+ gen.addDefaultPacketHandling();
+ if (enableMdns4Offload() || enableMdns6Offload()) {
+ generateMdnsQueryOffload((ApfV6GeneratorBase<?>) gen, labelCheckMdnsQueryPayload,
+ mNumOfMdnsRuleToOffload);
}
- emitEpilogue(gen);
+
+ mNumFilteredRas = rasToFilter.size();
+ mOverEstimatedProgramSize = gen.programLengthOverEstimate();
program = gen.generate();
} catch (IllegalInstructionException | IllegalStateException | IllegalArgumentException e) {
Log.wtf(TAG, "Failed to generate APF program.", e);
sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_GENERATE_FILTER_EXCEPTION);
+ installPacketFilter(new byte[mMaximumApfProgramSize],
+ getApfConfigMessage() + String.format(" (clear memory, reason: %s)",
+ e.getMessage()));
return;
}
if (mIsRunning) {
- if (!mIpClientCallback.installPacketFilter(program)) {
- sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE);
+ if (program.length > mMaximumApfProgramSize) {
+ Log.wtf(TAG, String.format(
+ "Size estimation logic is wrong: final program size: %d exceeds maximum "
+ + "size: %d. ",
+ program.length, mMaximumApfProgramSize));
+ sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_OVER_SIZE_FAILURE);
+ installPacketFilter(new byte[mMaximumApfProgramSize],
+ getApfConfigMessage() + " (clear memory, reason: wrong size estimation)");
+ return;
}
+ installPacketFilter(program, getApfConfigMessage());
}
mLastInstalledProgramMinLifetime = programMinLft;
mLastInstalledProgram = program;
mMaxProgramSize = Math.max(mMaxProgramSize, program.length);
- if (VDBG) {
- hexDump("Installing filter: ", program, program.length);
- }
}
private void hexDump(String msg, byte[] packet, int length) {
@@ -2462,8 +3973,6 @@
*/
@VisibleForTesting
public void processRa(byte[] packet, int length) {
- if (VDBG) hexDump("Read packet = ", packet, length);
-
final Ra ra;
try {
ra = new Ra(packet, length);
@@ -2475,13 +3984,13 @@
// Update info for Metrics
mLowestRouterLifetimeSeconds = getMinForPositiveValue(
- mLowestRouterLifetimeSeconds, ra.routerLifetime());
+ mLowestRouterLifetimeSeconds, ra.mRouterLifetime);
mLowestPioValidLifetimeSeconds = getMinForPositiveValue(
- mLowestPioValidLifetimeSeconds, ra.minPioValidLifetime());
+ mLowestPioValidLifetimeSeconds, ra.mMinPioValidLifetime);
mLowestRioRouteLifetimeSeconds = getMinForPositiveValue(
- mLowestRioRouteLifetimeSeconds, ra.minRioRouteLifetime());
+ mLowestRioRouteLifetimeSeconds, ra.mMinRioRouteLifetime);
mLowestRdnssLifetimeSeconds = getMinForPositiveValue(
- mLowestRdnssLifetimeSeconds, ra.minRdnssLifetime());
+ mLowestRdnssLifetimeSeconds, ra.mMinRdnssLifetime);
// 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
@@ -2538,7 +4047,7 @@
* filtering using APF programs.
*/
public static ApfFilter maybeCreate(Handler handler, Context context, ApfConfiguration config,
- InterfaceParams ifParams, IpClientCallbacksWrapper ipClientCallback,
+ InterfaceParams ifParams, IApfController apfController,
NetworkQuirkMetrics networkQuirkMetrics) {
if (context == null || config == null || ifParams == null) return null;
if (!ApfV4Generator.supportsVersion(config.apfVersionSupported)) {
@@ -2549,7 +4058,7 @@
return null;
}
- return new ApfFilter(handler, context, config, ifParams, ipClientCallback,
+ return new ApfFilter(handler, context, config, ifParams, apfController,
networkQuirkMetrics);
}
@@ -2590,8 +4099,12 @@
mRas.clear();
mDependencies.removeBroadcastReceiver(mDeviceIdleReceiver);
mIsApfShutdown = true;
- if (shouldEnableMdnsOffload()) {
- unregisterOffloadEngine();
+ if (SdkLevel.isAtLeastV() && mApfMdnsOffloadEngine != null) {
+ mApfMdnsOffloadEngine.unregisterOffloadEngine();
+ }
+
+ if (mMulticastReportMonitor != null) {
+ mMulticastReportMonitor.stop();
}
}
@@ -2667,11 +4180,11 @@
mIPv4PrefixLength = prefix;
mIPv6TentativeAddresses = ipv6Addresses.first;
mIPv6NonTentativeAddresses = ipv6Addresses.second;
+ mIPv6LinkLocalAddress = NetworkStackUtils.selectPreferredIPv6LinkLocalAddress(lp);
installNewProgram();
}
- @Override
public void updateClatInterfaceState(boolean add) {
if (mHasClat == add) {
return;
@@ -2680,22 +4193,124 @@
installNewProgram();
}
- @Override
- public boolean supportNdOffload() {
- return shouldUseApfV6Generator() && mShouldHandleNdOffload;
+ private boolean updateIPv6MulticastAddrs() {
+ final Set<Inet6Address> mcastAddrs =
+ new ArraySet<>(mDependencies.getIPv6MulticastAddresses(mInterfaceParams.name));
+
+ if (!mIPv6MulticastAddresses.equals(mcastAddrs)) {
+ mIPv6MulticastAddresses.clear();
+ mIPv6MulticastAddresses.addAll(mcastAddrs);
+
+ mIPv6McastAddrsExcludeAllHost.clear();
+ mIPv6McastAddrsExcludeAllHost.addAll(mIPv6MulticastAddresses);
+ mIPv6McastAddrsExcludeAllHost.remove(IPV6_ADDR_ALL_NODES_MULTICAST);
+ mIPv6McastAddrsExcludeAllHost.remove(IPV6_ADDR_NODE_LOCAL_ALL_NODES_MULTICAST);
+ return true;
+ }
+ return false;
}
- @ChecksSdkIntAtLeast(api = 35 /* Build.VERSION_CODES.VanillaIceCream */, codename =
- "VanillaIceCream")
- @Override
- public boolean shouldEnableMdnsOffload() {
- return shouldUseApfV6Generator() && mShouldHandleMdnsOffload;
+ private boolean updateIPv4MulticastAddrs() {
+ final Set<Inet4Address> mcastAddrs =
+ new ArraySet<>(mDependencies.getIPv4MulticastAddresses(mInterfaceParams.name));
+
+ if (!mIPv4MulticastAddresses.equals(mcastAddrs)) {
+ mIPv4MulticastAddresses.clear();
+ mIPv4MulticastAddresses.addAll(mcastAddrs);
+
+ mIPv4McastAddrsExcludeAllHost.clear();
+ mIPv4McastAddrsExcludeAllHost.addAll(mcastAddrs);
+ mIPv4McastAddrsExcludeAllHost.remove(IPV4_ADDR_ALL_HOST_MULTICAST);
+ return true;
+ }
+ return false;
}
- private boolean shouldUseApfV6Generator() {
+ /**
+ * Updates IPv4/IPv6 multicast addresses.
+ */
+ public void updateMulticastAddrs() {
+ boolean ipv6MulticastUpdated = updateIPv6MulticastAddrs();
+ boolean ipv4MulticastUpdated = updateIPv4MulticastAddrs();
+ if (ipv6MulticastUpdated || ipv4MulticastUpdated) {
+ installNewProgram();
+ }
+ }
+
+ @ChecksSdkIntAtLeast(api = 35 /* Build.VERSION_CODES.VanillaIceCream */)
+ private boolean enableArpOffload() {
+ return mHandleArpOffload && useApfV6Generator() && mIPv4Address != null;
+ }
+
+ @ChecksSdkIntAtLeast(api = 35 /* Build.VERSION_CODES.VanillaIceCream */)
+ public boolean enableNdOffload() {
+ return mHandleNdOffload && useApfV6Generator();
+ }
+
+ @ChecksSdkIntAtLeast(api = 35 /* Build.VERSION_CODES.VanillaIceCream */)
+ private boolean enableOffloadEngineRegistration() {
+ return mHandleMdnsOffload && useApfV6Generator();
+ }
+
+ @ChecksSdkIntAtLeast(api = 35 /* Build.VERSION_CODES.VanillaIceCream */)
+ private boolean enableIgmpReportsMonitor() {
+ return mHandleIgmpOffload && useApfV6Generator();
+ }
+
+ @ChecksSdkIntAtLeast(api = 35 /* Build.VERSION_CODES.VanillaIceCream */)
+ private boolean enableMdns4Offload() {
+ return enableOffloadEngineRegistration() && mIPv4Address != null
+ && !mOffloadRules.isEmpty();
+ }
+
+ @ChecksSdkIntAtLeast(api = 35 /* Build.VERSION_CODES.VanillaIceCream */)
+ private boolean enableMdns6Offload() {
+ return enableOffloadEngineRegistration() && mIPv6LinkLocalAddress != null
+ && !mOffloadRules.isEmpty();
+ }
+
+ @ChecksSdkIntAtLeast(api = 35 /* Build.VERSION_CODES.VanillaIceCream */)
+ private boolean enableIgmpOffload() {
+ // Since the all-hosts multicast address (224.0.0.1) is always present for IPv4
+ // multicast, and IGMP packets are not needed for this address, IGMP offloading is only
+ // necessary if there are additional joined multicast addresses
+ // (mIPv4MulticastAddresses.size() > 1).
+ return enableIgmpReportsMonitor() && mIPv4MulticastAddresses.size() > 1
+ && mIPv4Address != null;
+ }
+
+ @ChecksSdkIntAtLeast(api = 35 /* Build.VERSION_CODES.VanillaIceCream */)
+ private boolean enableIpv4PingOffload() {
+ return mHandleIpv4PingOffload && useApfV6Generator() && mIPv4Address != null;
+ }
+
+ @ChecksSdkIntAtLeast(api = 35 /* Build.VERSION_CODES.VanillaIceCream */)
+ private boolean enableIpv6PingOffload() {
+ return mHandleIpv6PingOffload && useApfV6Generator()
+ && !mIPv6NonTentativeAddresses.isEmpty();
+ }
+
+ @ChecksSdkIntAtLeast(api = 35 /* Build.VERSION_CODES.VanillaIceCream */)
+ private boolean enableMldReportsMonitor() {
+ return mHandleMldOffload && useApfV6Generator();
+ }
+
+ @ChecksSdkIntAtLeast(api = 35 /* Build.VERSION_CODES.VanillaIceCream */)
+ private boolean enableMldOffload() {
+ return enableMldReportsMonitor() && mIPv6LinkLocalAddress != null
+ && !mIPv6McastAddrsExcludeAllHost.isEmpty();
+ }
+
+ @ChecksSdkIntAtLeast(api = 35 /* Build.VERSION_CODES.VanillaIceCream */)
+ private boolean useApfV6Generator() {
return SdkLevel.isAtLeastV() && ApfV6Generator.supportsVersion(mApfVersionSupported);
}
+ @ChecksSdkIntAtLeast(api = 35 /* Build.VERSION_CODES.VanillaIceCream */)
+ private boolean useApfV61Generator() {
+ return SdkLevel.isAtLeastV() && ApfV61Generator.supportsVersion(mApfVersionSupported);
+ }
+
/**
* Add TCP keepalive ack packet filter.
* This will add a filter to drop acks to the keepalive packet passed as an argument.
@@ -2750,53 +4365,81 @@
installNewProgram();
}
+ /**
+ * Determines whether the APF interpreter advertises support for the data buffer access
+ * opcodes LDDW (LoaD Data Word) and STDW (STore Data Word).
+ */
+ public boolean hasDataAccess(int apfVersionSupported) {
+ return apfVersionSupported > 2;
+ }
+
public void dump(IndentingPrintWriter pw) {
- // TODO: use HandlerUtils.runWithScissors() to dump APF on the handler thread.
pw.println(String.format(
"Capabilities: { apfVersionSupported: %d, maximumApfProgramSize: %d }",
mApfVersionSupported, mApfRamSize));
pw.println("InstallableProgramSizeClamp: " + mInstallableProgramSizeClamp);
pw.println("Filter update status: " + (mIsRunning ? "RUNNING" : "PAUSED"));
- pw.println("Multicast: " + (mMulticastFilter ? "DROP" : "ALLOW"));
+ pw.println("ApfConfig: " + getApfConfigMessage());
pw.println("Minimum RDNSS lifetime: " + mMinRdnssLifetimeSec);
pw.println("Interface MAC address: " + MacAddress.fromBytes(mHardwareAddress));
- pw.println("Multicast MAC addresses: ");
+ pw.println("Multicast MAC addresses:");
pw.increaseIndent();
for (byte[] addr : mDependencies.getEtherMulticastAddresses(mInterfaceParams.name)) {
pw.println(MacAddress.fromBytes(addr));
}
pw.decreaseIndent();
+ if (SdkLevel.isAtLeastV()) {
+ pw.print("Hardcoded not denylisted Ethertypes:");
+ pw.println(
+ " 0800(IPv4) 0806(ARP) 86DD(IPv6) 888E(EAPOL) 88B4(WAPI) 890D(TDLS unicast)");
+ } else {
+ pw.print("Denylisted Ethertypes:");
+ for (int p : mEthTypeBlackList) {
+ pw.print(String.format(" %04x", p));
+ }
+ }
try {
pw.println("IPv4 address: " + InetAddress.getByAddress(mIPv4Address).getHostAddress());
- pw.println("IPv6 non-tentative addresses: ");
- pw.increaseIndent();
- for (Inet6Address addr : mIPv6NonTentativeAddresses) {
- pw.println(addr.getHostAddress());
- }
- pw.decreaseIndent();
- pw.println("IPv6 tentative addresses: ");
- pw.increaseIndent();
- for (Inet6Address addr : mIPv6TentativeAddresses) {
- pw.println(addr.getHostAddress());
- }
- pw.decreaseIndent();
- pw.println("IPv6 anycast addresses:");
- pw.increaseIndent();
- final List<Inet6Address> anycastAddrs =
- ProcfsParsingUtils.getAnycast6Addresses(mInterfaceParams.name);
- for (Inet6Address addr : anycastAddrs) {
- pw.println(addr.getHostAddress());
- }
- pw.decreaseIndent();
- pw.println("IPv6 multicast addresses:");
- pw.increaseIndent();
- final List<Inet6Address> multicastAddrs =
- ProcfsParsingUtils.getIpv6MulticastAddresses(mInterfaceParams.name);
- for (Inet6Address addr : multicastAddrs) {
- pw.println(addr.getHostAddress());
- }
- pw.decreaseIndent();
- } catch (UnknownHostException|NullPointerException e) {}
+ } catch (UnknownHostException | NullPointerException e) {
+ pw.println("IPv4 address: None");
+ }
+
+ pw.println("IPv4 multicast addresses:");
+ pw.increaseIndent();
+ final List<Inet4Address> ipv4McastAddrs =
+ ProcfsParsingUtils.getIPv4MulticastAddresses(mInterfaceParams.name);
+ for (Inet4Address addr: ipv4McastAddrs) {
+ pw.println(addr.getHostAddress());
+ }
+ pw.decreaseIndent();
+ pw.println("IPv6 non-tentative addresses:");
+ pw.increaseIndent();
+ for (Inet6Address addr : mIPv6NonTentativeAddresses) {
+ pw.println(addr.getHostAddress());
+ }
+ pw.decreaseIndent();
+ pw.println("IPv6 tentative addresses:");
+ pw.increaseIndent();
+ for (Inet6Address addr : mIPv6TentativeAddresses) {
+ pw.println(addr.getHostAddress());
+ }
+ pw.decreaseIndent();
+ pw.println("IPv6 anycast addresses:");
+ pw.increaseIndent();
+ final List<Inet6Address> anycastAddrs =
+ ProcfsParsingUtils.getAnycast6Addresses(mInterfaceParams.name);
+ for (Inet6Address addr : anycastAddrs) {
+ pw.println(addr.getHostAddress());
+ }
+ pw.decreaseIndent();
+ pw.println("IPv6 multicast addresses:");
+ pw.increaseIndent();
+ final List<Inet6Address> multicastAddrs =
+ ProcfsParsingUtils.getIpv6MulticastAddresses(mInterfaceParams.name);
+ for (Inet6Address addr : multicastAddrs) {
+ pw.println(addr.getHostAddress());
+ }
+ pw.decreaseIndent();
if (mLastTimeInstalledProgram == 0) {
pw.println("No program installed.");
@@ -2808,29 +4451,42 @@
"Last program length %d, installed %ds ago, lifetime %ds",
mLastInstalledProgram.length, filterAgeSeconds,
mLastInstalledProgramMinLifetime));
- if (SdkLevel.isAtLeastV()) {
- pw.print("Hardcoded Allowlisted Ethertypes:");
- pw.println(" 0800(IPv4) 0806(ARP) 86DD(IPv6) 888E(EAPOL) 88B4(WAPI)");
+ pw.println();
+ pw.println("Mdns filters:");
+ pw.increaseIndent();
+ if (mNumOfMdnsRuleToOffload == -1) {
+ pw.println("pass all mDNS packet");
} else {
- pw.print("Denylisted Ethertypes:");
- for (int p : mEthTypeBlackList) {
- pw.print(String.format(" %04x", p));
+ for (int i = 0; i < mOffloadRules.size(); ++i) {
+ final MdnsOffloadRule rule = mOffloadRules.get(i);
+ if (i >= mNumOfMdnsRuleToOffload) {
+ pw.println(String.format("passthrough service: %s", rule.mFullServiceName));
+ } else {
+ pw.println(String.format("offload service: %s, payloadSize: %d",
+ rule.mFullServiceName,
+ rule.mOffloadPayload == null ? 0 : rule.mOffloadPayload.length));
+ }
}
}
+ pw.decreaseIndent();
pw.println();
pw.println("RA filters:");
pw.increaseIndent();
- for (Ra ra: mRas) {
+ for (int i = 0; i < mRas.size(); ++i) {
+ if (i < mNumFilteredRas) {
+ pw.println("Filtered: ");
+ } else {
+ pw.println("Ignored: ");
+ }
+ final Ra ra = mRas.get(i);
pw.println(ra);
pw.increaseIndent();
pw.println(String.format(
"Last seen %ds ago", secondsSinceBoot() - ra.mLastSeen));
- if (DBG) {
- pw.println("Last match:");
- pw.increaseIndent();
- pw.println(ra.getLastMatchingPacket());
- pw.decreaseIndent();
- }
+ pw.println("Last match:");
+ pw.increaseIndent();
+ pw.println(ra.getLastMatchingPacket());
+ pw.decreaseIndent();
pw.decreaseIndent();
}
pw.decreaseIndent();
@@ -2861,14 +4517,12 @@
}
pw.decreaseIndent();
- if (DBG) {
- pw.println("Last program:");
- pw.increaseIndent();
- pw.println(HexDump.toHexString(mLastInstalledProgram, false /* lowercase */));
- pw.decreaseIndent();
- }
+ pw.println("Last program:");
+ pw.increaseIndent();
+ pw.println(HexDump.toHexString(mLastInstalledProgram, false /* lowercase */));
+ pw.decreaseIndent();
- pw.println("APF packet counters: ");
+ pw.println("APF packet counters:");
pw.increaseIndent();
if (!hasDataAccess(mApfVersionSupported)) {
pw.println("APF counters not supported");
@@ -2878,9 +4532,9 @@
try {
Counter[] counters = Counter.class.getEnumConstants();
long counterFilterAgeSeconds =
- getCounterValue(mDataSnapshot, Counter.FILTER_AGE_SECONDS);
+ getCounterValue(mDataSnapshot, FILTER_AGE_SECONDS);
long counterApfProgramId =
- getCounterValue(mDataSnapshot, Counter.APF_PROGRAM_ID);
+ getCounterValue(mDataSnapshot, APF_PROGRAM_ID);
for (Counter c : Arrays.asList(counters).subList(1, counters.length)) {
long value = getCounterValue(mDataSnapshot, c);
@@ -2945,10 +4599,6 @@
} catch (ArrayIndexOutOfBoundsException e) {
pw.println("Uh-oh: " + e);
}
- if (VDBG) {
- pw.println("Raw data dump: ");
- pw.println(HexDump.dumpHexString(mDataSnapshot));
- }
}
pw.decreaseIndent();
}
@@ -3010,20 +4660,6 @@
+ (uint8(bytes[3]));
}
- private static byte[] concatArrays(final byte[]... arr) {
- int size = 0;
- for (byte[] a : arr) {
- size += a.length;
- }
- final byte[] result = new byte[size];
- int offset = 0;
- for (byte[] a : arr) {
- System.arraycopy(a, 0, result, offset, a.length);
- offset += a.length;
- }
- return result;
- }
-
private void sendNetworkQuirkMetrics(final NetworkQuirkEvent event) {
if (mNetworkQuirkMetrics == null) return;
mNetworkQuirkMetrics.setEvent(event);
diff --git a/src/android/net/apf/ApfMdnsOffloadEngine.java b/src/android/net/apf/ApfMdnsOffloadEngine.java
new file mode 100644
index 0000000..3aee08f
--- /dev/null
+++ b/src/android/net/apf/ApfMdnsOffloadEngine.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2025 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.annotation.NonNull;
+import android.annotation.RequiresApi;
+import android.net.nsd.NsdManager;
+import android.net.nsd.OffloadEngine;
+import android.net.nsd.OffloadServiceInfo;
+import android.os.Build;
+import android.os.Handler;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * APF offload engine implementation for managing mDNS offloads.
+ */
+@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+public class ApfMdnsOffloadEngine implements OffloadEngine {
+
+ private static final String TAG = ApfMdnsOffloadEngine.class.getSimpleName();
+
+ /**
+ * Callback interface for receiving notifications about offload rule updates.
+ */
+ public interface Callback {
+ /**
+ * Called when the offload rules are updated.
+ * <p>
+ * This method is called on the handler thread.
+ *
+ * @param allRules The updated list of MDNS offload rules.
+ */
+ void onOffloadRulesUpdated(@NonNull List<MdnsOffloadRule> allRules);
+ }
+
+ @NonNull
+ private final List<OffloadServiceInfo> mOffloadServiceInfos = new ArrayList<>();
+ @NonNull
+ private final String mInterfaceName;
+ @NonNull
+ private final Handler mHandler;
+ @NonNull
+ private final NsdManager mNsdManager;
+ @NonNull
+ private final Callback mCallback;
+
+ /**
+ * Constructor for ApfOffloadEngine.
+ */
+ public ApfMdnsOffloadEngine(@NonNull String interfaceName, @NonNull Handler handler,
+ @NonNull NsdManager nsdManager, @NonNull Callback callback) {
+ mInterfaceName = interfaceName;
+ mHandler = handler;
+ mNsdManager = nsdManager;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onOffloadServiceUpdated(@NonNull OffloadServiceInfo info) {
+ handleOffloadServiceUpdated(info, false /* isRemoved */);
+ }
+
+ @Override
+ public void onOffloadServiceRemoved(@NonNull OffloadServiceInfo info) {
+ handleOffloadServiceUpdated(info, true /* isRemoved */);
+ }
+
+ private void handleOffloadServiceUpdated(@NonNull OffloadServiceInfo info, boolean isRemoved) {
+ if (isRemoved) {
+ mOffloadServiceInfos.removeIf(i -> i.getKey().equals(info.getKey()));
+ } else {
+ mOffloadServiceInfos.removeIf(i -> i.getKey().equals(info.getKey()));
+ mOffloadServiceInfos.add(info);
+ }
+ try {
+ List<MdnsOffloadRule> offloadRules = ApfMdnsUtils.extractOffloadReplyRule(
+ mOffloadServiceInfos);
+ mCallback.onOffloadRulesUpdated(offloadRules);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to extract offload reply rule", e);
+ }
+ }
+
+ /**
+ * Registers the offload engine with the NsdManager.
+ */
+ public void registerOffloadEngine() {
+ mNsdManager.registerOffloadEngine(mInterfaceName, OFFLOAD_TYPE_REPLY,
+ OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK, mHandler::post, this);
+ }
+
+ /**
+ * Unregisters the offload engine with the NsdManager.
+ */
+ public void unregisterOffloadEngine() {
+ mNsdManager.unregisterOffloadEngine(this);
+ mOffloadServiceInfos.clear();
+ }
+}
diff --git a/src/android/net/apf/ApfMdnsUtils.java b/src/android/net/apf/ApfMdnsUtils.java
index 7666864..4e2190e 100644
--- a/src/android/net/apf/ApfMdnsUtils.java
+++ b/src/android/net/apf/ApfMdnsUtils.java
@@ -27,6 +27,7 @@
import android.os.Build;
import android.util.ArraySet;
+import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.DnsUtils;
import java.io.ByteArrayOutputStream;
@@ -56,14 +57,6 @@
allMatchers.add(matcher);
}
- private static String[] prepend(String[] suffix, String... prefixes) {
- String[] result = new String[prefixes.length + suffix.length];
- System.arraycopy(prefixes, 0, result, 0, prefixes.length);
- System.arraycopy(suffix, 0, result, prefixes.length, suffix.length);
- return result;
- }
-
-
/**
* Extract the offload rules from the list of offloadServiceInfos. The rules are returned in
* priority order (most important first). If there are too many rules, APF could decide only
@@ -83,21 +76,18 @@
final List<MdnsOffloadRule> rules = new ArrayList<>();
final Set<MdnsOffloadRule.Matcher> allMatchers = new ArraySet<>();
for (OffloadServiceInfo info : sortedOffloadServiceInfos) {
- // Don't offload the records if the priority is not configured.
- int priority = info.getPriority();
- if (priority == Integer.MAX_VALUE) {
- continue;
- }
List<MdnsOffloadRule.Matcher> matcherGroup = new ArrayList<>();
final OffloadServiceInfo.Key key = info.getKey();
- final String[] serviceTypeLabels = key.getServiceType().split("\\.", 0);
- final String[] fullQualifiedName = prepend(serviceTypeLabels, key.getServiceName());
+ final String[] serviceTypeLabels = CollectionUtils.appendArray(String.class,
+ key.getServiceType().split("\\.", 0), "local");
+ final String[] fullQualifiedName = CollectionUtils.prependArray(String.class,
+ serviceTypeLabels, key.getServiceName());
final byte[] replyPayload = info.getOffloadPayload();
final byte[] encodedServiceType = encodeQname(serviceTypeLabels);
// If (QTYPE == PTR) and (QNAME == mServiceName + mServiceType), then reply.
MdnsOffloadRule.Matcher ptrMatcher = new MdnsOffloadRule.Matcher(
encodedServiceType,
- TYPE_PTR
+ new int[] { TYPE_PTR }
);
addMatcherIfNotExist(allMatchers, matcherGroup, ptrMatcher);
final List<String> subTypes = info.getSubtypes();
@@ -106,41 +96,43 @@
boolean tooManySubtypes = subTypes.size() > MAX_SUPPORTED_SUBTYPES;
if (tooManySubtypes) {
// If (QTYPE == PTR) and (QNAME == wildcard + _sub + mServiceType), then fail open.
- final String[] serviceTypeSuffix = prepend(serviceTypeLabels, "_sub");
+ final String[] serviceTypeSuffix = CollectionUtils.prependArray(String.class,
+ serviceTypeLabels, "_sub");
final ByteArrayOutputStream buf = new ByteArrayOutputStream();
// byte = 0xff is used as a wildcard.
buf.write(-1);
final byte[] encodedFullServiceType = encodeQname(buf, serviceTypeSuffix);
final MdnsOffloadRule.Matcher subtypePtrMatcher = new MdnsOffloadRule.Matcher(
- encodedFullServiceType, TYPE_PTR);
+ encodedFullServiceType, new int[] { TYPE_PTR });
addMatcherIfNotExist(allMatchers, matcherGroup, subtypePtrMatcher);
} else {
// If (QTYPE == PTR) and (QNAME == subType + _sub + mServiceType), then reply.
for (String subType : subTypes) {
- final String[] fullServiceType = prepend(serviceTypeLabels, subType, "_sub");
+ final String[] fullServiceType = CollectionUtils.prependArray(String.class,
+ serviceTypeLabels, subType, "_sub");
final byte[] encodedFullServiceType = encodeQname(fullServiceType);
// If (QTYPE == PTR) and (QNAME == subType + "_sub" + mServiceType), then reply.
final MdnsOffloadRule.Matcher subtypePtrMatcher = new MdnsOffloadRule.Matcher(
- encodedFullServiceType, TYPE_PTR);
+ encodedFullServiceType, new int[] { TYPE_PTR });
addMatcherIfNotExist(allMatchers, matcherGroup, subtypePtrMatcher);
}
}
final byte[] encodedFullQualifiedNameQname = encodeQname(fullQualifiedName);
// If (QTYPE == SRV) and (QNAME == mServiceName + mServiceType), then reply.
- addMatcherIfNotExist(allMatchers, matcherGroup,
- new MdnsOffloadRule.Matcher(encodedFullQualifiedNameQname, TYPE_SRV));
// If (QTYPE == TXT) and (QNAME == mServiceName + mServiceType), then reply.
addMatcherIfNotExist(allMatchers, matcherGroup,
- new MdnsOffloadRule.Matcher(encodedFullQualifiedNameQname, TYPE_TXT));
+ new MdnsOffloadRule.Matcher(encodedFullQualifiedNameQname,
+ new int[] { TYPE_SRV, TYPE_TXT }));
// If (QTYPE == A or AAAA) and (QNAME == mDeviceHostName), then reply.
final String[] hostNameLabels = info.getHostname().split("\\.", 0);
final byte[] encodedHostName = encodeQname(hostNameLabels);
addMatcherIfNotExist(allMatchers, matcherGroup,
- new MdnsOffloadRule.Matcher(encodedHostName, TYPE_A));
- addMatcherIfNotExist(allMatchers, matcherGroup,
- new MdnsOffloadRule.Matcher(encodedHostName, TYPE_AAAA));
+ new MdnsOffloadRule.Matcher(encodedHostName,
+ new int[] { TYPE_A, TYPE_AAAA }));
if (!matcherGroup.isEmpty()) {
- rules.add(new MdnsOffloadRule(matcherGroup, tooManySubtypes ? null : replyPayload));
+ rules.add(new MdnsOffloadRule(
+ key.getServiceName() + "." + key.getServiceType(),
+ matcherGroup, tooManySubtypes ? null : replyPayload));
}
}
return rules;
diff --git a/src/android/net/apf/ApfV4Generator.java b/src/android/net/apf/ApfV4Generator.java
index f9918b2..fe081df 100644
--- a/src/android/net/apf/ApfV4Generator.java
+++ b/src/android/net/apf/ApfV4Generator.java
@@ -33,20 +33,8 @@
*/
public final class ApfV4Generator extends ApfV4GeneratorBase<ApfV4Generator> {
- /**
- * Jump to this label to terminate the program, increment the counter and indicate the packet
- * should be passed to the AP.
- */
- private static final String COUNT_AND_PASS_LABEL = "__COUNT_AND_PASS__";
-
- /**
- * Jump to this label to terminate the program, increment counter, and indicate the packet
- * should be dropped.
- */
- private static final String COUNT_AND_DROP_LABEL = "__COUNT_AND_DROP__";
-
- public final String mCountAndDropLabel;
- public final String mCountAndPassLabel;
+ public final short mCountAndDropLabel;
+ public final short mCountAndPassLabel;
/**
* Returns true if we support the specified {@code version}, otherwise false.
@@ -65,8 +53,8 @@
throws IllegalInstructionException {
// make sure mVersion is not greater than 4 when using this class
super(version > 4 ? 4 : version, ramSize, clampSize, disableCounterRangeCheck);
- mCountAndDropLabel = version > 2 ? COUNT_AND_DROP_LABEL : DROP_LABEL;
- mCountAndPassLabel = version > 2 ? COUNT_AND_PASS_LABEL : PASS_LABEL;
+ mCountAndDropLabel = version > 2 ? getUniqueLabel() : DROP_LABEL;
+ mCountAndPassLabel = version > 2 ? getUniqueLabel() : PASS_LABEL;
}
/**
@@ -80,6 +68,11 @@
}
@Override
+ public int getBaseProgramSize() {
+ return 0;
+ }
+
+ @Override
void addR0ArithR1(Opcodes opcode) {
append(new Instruction(opcode, Rbit1)); // APFv2/4: R0 op= R1
}
@@ -236,7 +229,7 @@
if (values.isEmpty()) {
throw new IllegalArgumentException("values cannot be empty");
}
- String tgt = getUniqueLabel();
+ short tgt = getUniqueLabel();
for (Long v : values) {
addJumpIfR0Equals(v, tgt);
}
@@ -251,7 +244,7 @@
if (values.isEmpty()) {
throw new IllegalArgumentException("values cannot be empty");
}
- String tgt = getUniqueLabel();
+ short tgt = getUniqueLabel();
for (Long v : values) {
addJumpIfR0Equals(v, tgt);
}
@@ -265,10 +258,10 @@
throws IllegalInstructionException {
final List<byte[]> deduplicatedList = validateDeduplicateBytesList(bytesList);
maybeAddLoadCounterOffset(R1, cnt);
- String matchLabel = getUniqueLabel();
- String allNoMatchLabel = getUniqueLabel();
+ short matchLabel = getUniqueLabel();
+ short allNoMatchLabel = getUniqueLabel();
for (byte[] v : deduplicatedList) {
- String notMatchLabel = getUniqueLabel();
+ short notMatchLabel = getUniqueLabel();
addJumpIfBytesAtR0NotEqual(v, notMatchLabel);
addJump(matchLabel);
defineLabel(notMatchLabel);
@@ -356,6 +349,18 @@
return maybeAddLoadCounterOffset(register.other(), counter).addStoreData(register, 0);
}
+ @Override
+ public ApfV4Generator addAdd(long val) {
+ if (val == 0) return self(); // nop, as APFv6 would '+= R1'
+ return append(new Instruction(Opcodes.ADD).addTwosCompUnsigned(val));
+ }
+
+ @Override
+ public ApfV4Generator addAnd(long val) {
+ if (val == 0) return addLoadImmediate(R0, 0); // equivalent, as APFv6 would '+= R1'
+ return append(new Instruction(Opcodes.AND).addTwosCompUnsigned(val));
+ }
+
/**
* Append the count & (pass|drop) trampoline, which increments the counter at the data address
* pointed to by R1, then jumps to the (pass|drop) label. This saves a few bytes over inserting
@@ -367,18 +372,34 @@
@Override
public ApfV4Generator addCountTrampoline() throws IllegalInstructionException {
if (mVersion <= 2) return self();
- return defineLabel(COUNT_AND_PASS_LABEL)
+ return defineLabel(mCountAndPassLabel)
.addLoadData(R0, 0) // R0 = *(R1 + 0)
.addAdd(1) // R0++
.addStoreData(R0, 0) // *(R1 + 0) = R0
.addJump(PASS_LABEL)
- .defineLabel(COUNT_AND_DROP_LABEL)
+ .defineLabel(mCountAndDropLabel)
.addLoadData(R0, 0) // R0 = *(R1 + 0)
.addAdd(1) // R0++
.addStoreData(R0, 0) // *(R1 + 0) = R0
.addJump(DROP_LABEL);
}
+ @Override
+ public int getDefaultPacketHandlingSizeOverEstimate() {
+ // addCountAndPass(PASSED_IPV6_ICMP); -> 7 bytes
+ // defineLabel(mCountAndPassLabel)
+ // .addLoadData(R0, 0) -> 1 bytes
+ // .addAdd(1) -> 2 bytes
+ // .addStoreData(R0, 0) -> 1 bytes
+ // .addJump(PASS_LABEL) -> 5 bytes
+ // .defineLabel(mCountAndDropLabel)
+ // .addLoadData(R0, 0) -> 1 bytes
+ // .addAdd(1) -> 2 bytes
+ // .addStoreData(R0, 0) -> 1 bytes
+ // .addJump(DROP_LABEL); -> 5 bytes
+ return 25;
+ }
+
/**
* This function is no-op in APFv4
*/
diff --git a/src/android/net/apf/ApfV4GeneratorBase.java b/src/android/net/apf/ApfV4GeneratorBase.java
index a00aa2f..d750229 100644
--- a/src/android/net/apf/ApfV4GeneratorBase.java
+++ b/src/android/net/apf/ApfV4GeneratorBase.java
@@ -16,6 +16,10 @@
package android.net.apf;
+import static android.net.apf.ApfConstants.IPV4_FRAGMENT_MORE_FRAGS_MASK;
+import static android.net.apf.ApfConstants.IPV4_FRAGMENT_OFFSET_MASK;
+import static android.net.apf.ApfConstants.IPV4_FRAGMENT_OFFSET_OFFSET;
+import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_ICMP;
import static android.net.apf.BaseApfGenerator.Rbit.Rbit0;
import static android.net.apf.BaseApfGenerator.Register.R0;
import static android.net.apf.BaseApfGenerator.Register.R1;
@@ -85,14 +89,14 @@
* </pre>
* In this case "next_filter" may not have any generated code associated with it.
*/
- public final Type defineLabel(String name) throws IllegalInstructionException {
+ public final Type defineLabel(short name) throws IllegalInstructionException {
return append(new Instruction(Opcodes.LABEL).setLabel(name));
}
/**
* Add an unconditional jump instruction to the end of the program.
*/
- public final Type addJump(String target) {
+ public final Type addJump(short target) {
return append(new Instruction(Opcodes.JMP).setTargetLabel(target));
}
@@ -107,24 +111,24 @@
* 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 final Type addLoad8(Register r, int ofs) {
- return append(new Instruction(Opcodes.LDB, r).addPacketOffset(ofs));
+ public final Type addLoad8intoR0(int ofs) {
+ return append(new Instruction(Opcodes.LDB, R0).addPacketOffset(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 final Type addLoad16(Register r, int ofs) {
- return append(new Instruction(Opcodes.LDH, r).addPacketOffset(ofs));
+ public final Type addLoad16intoR0(int ofs) {
+ return append(new Instruction(Opcodes.LDH, R0).addPacketOffset(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 final Type addLoad32(Register r, int ofs) {
- return append(new Instruction(Opcodes.LDW, r).addPacketOffset(ofs));
+ public final Type addLoad32intoR0(int ofs) {
+ return append(new Instruction(Opcodes.LDW, R0).addPacketOffset(ofs));
}
/**
@@ -132,8 +136,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 final Type addLoad8Indexed(Register r, int ofs) {
- return append(new Instruction(Opcodes.LDBX, r).addTwosCompUnsigned(ofs));
+ public final Type addLoad8R1IndexedIntoR0(int ofs) {
+ return append(new Instruction(Opcodes.LDBX, R0).addTwosCompUnsigned(ofs));
}
/**
@@ -141,8 +145,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 final Type addLoad16Indexed(Register r, int ofs) {
- return append(new Instruction(Opcodes.LDHX, r).addTwosCompUnsigned(ofs));
+ public final Type addLoad16R1IndexedIntoR0(int ofs) {
+ return append(new Instruction(Opcodes.LDHX, R0).addTwosCompUnsigned(ofs));
}
/**
@@ -150,17 +154,14 @@
* {@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 final Type addLoad32Indexed(Register r, int ofs) {
- return append(new Instruction(Opcodes.LDWX, r).addTwosCompUnsigned(ofs));
+ public final Type addLoad32R1IndexedIntoR0(int ofs) {
+ return append(new Instruction(Opcodes.LDWX, R0).addTwosCompUnsigned(ofs));
}
/**
* Add an instruction to the end of the program to add {@code value} to register R0.
*/
- public final Type addAdd(long val) {
- if (val == 0) return self(); // nop, as APFv6 would '+= R1'
- return append(new Instruction(Opcodes.ADD).addTwosCompUnsigned(val));
- }
+ public abstract Type addAdd(long val);
/**
* Add an instruction to the end of the program to subtract {@code value} from register R0.
@@ -188,10 +189,7 @@
/**
* Add an instruction to the end of the program to logically and register R0 with {@code value}.
*/
- public final Type addAnd(long val) {
- if (val == 0) return addLoadImmediate(R0, 0); // equivalent, as APFv6 would '+= R1'
- return append(new Instruction(Opcodes.AND).addTwosCompUnsigned(val));
- }
+ public abstract Type addAnd(long val);
/**
* Add an instruction to the end of the program to logically or register R0 with {@code value}.
@@ -284,7 +282,7 @@
* Add an instruction to the end of the program to jump to {@code target} if register R0's
* value equals {@code value}.
*/
- public final Type addJumpIfR0Equals(long val, String tgt) {
+ public final Type addJumpIfR0Equals(long val, short tgt) {
return append(new Instruction(Opcodes.JEQ).addTwosCompUnsigned(val).setTargetLabel(tgt));
}
@@ -308,7 +306,7 @@
* 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 final Type addJumpIfR0NotEquals(long val, String tgt) {
+ public final Type addJumpIfR0NotEquals(long val, short tgt) {
return append(new Instruction(Opcodes.JNE).addTwosCompUnsigned(val).setTargetLabel(tgt));
}
@@ -332,7 +330,7 @@
* 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 final Type addJumpIfR0GreaterThan(long val, String tgt) {
+ public final Type addJumpIfR0GreaterThan(long val, short tgt) {
return append(new Instruction(Opcodes.JGT).addUnsigned(val).setTargetLabel(tgt));
}
@@ -356,7 +354,7 @@
* 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 final Type addJumpIfR0LessThan(long val, String tgt) {
+ public final Type addJumpIfR0LessThan(long val, short tgt) {
return append(new Instruction(Opcodes.JLT).addUnsigned(val).setTargetLabel(tgt));
}
@@ -380,7 +378,7 @@
* 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 final Type addJumpIfR0AnyBitsSet(long val, String tgt) {
+ public final Type addJumpIfR0AnyBitsSet(long val, short tgt) {
return append(new Instruction(Opcodes.JSET).addTwosCompUnsigned(val).setTargetLabel(tgt));
}
@@ -436,7 +434,7 @@
* 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 final Type addJumpIfR0EqualsR1(String tgt) {
+ public final Type addJumpIfR0EqualsR1(short tgt) {
return append(new Instruction(Opcodes.JEQ, R1).setTargetLabel(tgt));
}
@@ -444,7 +442,7 @@
* 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 final Type addJumpIfR0NotEqualsR1(String tgt) {
+ public final Type addJumpIfR0NotEqualsR1(short tgt) {
return append(new Instruction(Opcodes.JNE, R1).setTargetLabel(tgt));
}
@@ -452,7 +450,7 @@
* 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 final Type addJumpIfR0GreaterThanR1(String tgt) {
+ public final Type addJumpIfR0GreaterThanR1(short tgt) {
return append(new Instruction(Opcodes.JGT, R1).setTargetLabel(tgt));
}
@@ -460,7 +458,7 @@
* Add an instruction to the end of the program to jump to {@code target} if register R0's
* value is less than register R1's value.
*/
- public final Type addJumpIfR0LessThanR1(String target) {
+ public final Type addJumpIfR0LessThanR1(short target) {
return append(new Instruction(Opcodes.JLT, R1).setTargetLabel(target));
}
@@ -468,7 +466,7 @@
* 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 final Type addJumpIfR0AnyBitsSetR1(String tgt) {
+ public final Type addJumpIfR0AnyBitsSetR1(short tgt) {
return append(new Instruction(Opcodes.JSET, R1).setTargetLabel(tgt));
}
@@ -477,13 +475,24 @@
* packet at an offset specified by register0 don't match {@code bytes}.
* R=0 means check for not equal.
*/
- public final Type addJumpIfBytesAtR0NotEqual(@NonNull byte[] bytes, String tgt) {
+ public final Type addJumpIfBytesAtR0NotEqual(@NonNull byte[] bytes, short tgt) {
validateBytes(bytes);
return append(new Instruction(Opcodes.JBSMATCH).addUnsigned(
bytes.length).setTargetLabel(tgt).setBytesImm(bytes));
}
/**
+ * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the
+ * packet at an offset specified by {@code offset} don't match {@code bytes}.
+ * This method needs to be non-final because APFv4 and APFv6 share the same implementation,
+ * but in APFv6.1, this method will be overridden to use the JBSPTRMATCH instruction.
+ */
+ public Type addJumpIfBytesAtOffsetNotEqual(int offset, @NonNull byte[] bytes, short tgt)
+ throws IllegalInstructionException {
+ return addLoadImmediate(R0, offset).addJumpIfBytesAtR0NotEqual(bytes, tgt);
+ }
+
+ /**
* Add instructions to the end of the program to increase counter and drop packet if the
* bytes of the packet at an offset specified by register0 don't match {@code bytes}.
* WARNING: may modify R1
@@ -501,12 +510,56 @@
/**
* Add instructions to the end of the program to increase counter and drop packet if the
+ * bytes of the packet at an offset specified by {@code offset} don't match {@code bytes}.
+ * This method needs to be non-final because APFv4 and APFv6 share the same implementation,
+ * but in APFv6.1, this method will be overridden to use the JBSPTRMATCH instruction.
+ */
+ public Type addCountAndDropIfBytesAtOffsetNotEqual(int offset, byte[] bytes,
+ ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
+ return addLoadImmediate(R0, offset).addCountAndDropIfBytesAtR0NotEqual(bytes, cnt);
+ }
+
+ /**
+ * Add instructions to the end of the program to increase counter and pass packet if the
+ * bytes of the packet at an offset specified by {@code offset} don't match {@code bytes}.
+ * This method needs to be non-final because APFv4 and APFv6 share the same implementation,
+ * but in APFv6.1, this method will be overridden to use the JBSPTRMATCH instruction.
+ */
+ public Type addCountAndPassIfBytesAtOffsetNotEqual(int offset, byte[] bytes,
+ ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
+ return addLoadImmediate(R0, offset).addCountAndPassIfBytesAtR0NotEqual(bytes, cnt);
+ }
+
+ /**
+ * Add instructions to the end of the program to increase counter and drop packet if the
+ * bytes of the packet at an offset specified by {@code offset} does match {@code bytes}.
+ * This method needs to be non-final because APFv4 and APFv6 share the same implementation,
+ * but in APFv6.1, this method will be overridden to use the JBSPTRMATCH instruction.
+ */
+ public Type addCountAndDropIfBytesAtOffsetEqual(int offset, byte[] bytes,
+ ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
+ return addLoadImmediate(R0, offset).addCountAndDropIfBytesAtR0Equal(bytes, cnt);
+ }
+
+ /**
+ * Add instructions to the end of the program to increase counter and pass packet if the
+ * bytes of the packet at an offset specified by {@code offset} does match {@code bytes}.
+ * This method needs to be non-final because APFv4 and APFv6 share the same implementation,
+ * but in APFv6.1, this method will be overridden to use the JBSPTRMATCH instruction.
+ */
+ public Type addCountAndPassIfBytesAtOffsetEqual(int offset, byte[] bytes,
+ ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
+ return addLoadImmediate(R0, offset).addCountAndPassIfBytesAtR0Equal(bytes, cnt);
+ }
+
+ /**
+ * Add instructions to the end of the program to increase counter and drop packet if the
* bytes of the packet at an offset specified by register0 match {@code bytes}.
* WARNING: may modify R1
*/
public final Type addCountAndDropIfBytesAtR0Equal(byte[] bytes,
ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
- final String tgt = getUniqueLabel();
+ final short tgt = getUniqueLabel();
return addJumpIfBytesAtR0NotEqual(bytes, tgt).addCountAndDrop(cnt).defineLabel(tgt);
}
@@ -518,7 +571,7 @@
*/
public final Type addCountAndPassIfBytesAtR0Equal(byte[] bytes,
ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
- final String tgt = getUniqueLabel();
+ final short tgt = getUniqueLabel();
return addJumpIfBytesAtR0NotEqual(bytes, tgt).addCountAndPass(cnt).defineLabel(tgt);
}
@@ -573,6 +626,24 @@
}
/**
+ * Add instructions to the end of the program to check whether the packet is an unfragmented
+ * IPv4 packet with the specified protocol, and jump to the target if it is not.
+ * WARNING: this helper method will modify R0
+ */
+ public Type addJumpIfNotUnfragmentedIPv4Protocol(long protocol, short tgt) {
+ // Mask out all but the reserved and don't fragment bits, plus the TTL field.
+ // Because:
+ // IPV4_FRAGMENT_OFFSET_MASK = 0x1fff
+ // IPV4_FRAGMENT_MORE_FRAGS_MASK = 0x2000
+ // hence this constant ends up being 0x3FFF00FF.
+ // We want the more flag bit and offset to be 0 (ie. not a fragment),
+ // so after this masking we end up with just the ip protocol.
+ return addLoad32intoR0(IPV4_FRAGMENT_OFFSET_OFFSET)
+ .addAnd((IPV4_FRAGMENT_MORE_FRAGS_MASK | IPV4_FRAGMENT_OFFSET_MASK) << 16 | 0xFF)
+ .addJumpIfR0NotEquals(protocol, tgt);
+ }
+
+ /**
* Add an instruction to the end of the program to logically not {@code register}.
*/
public final Type addNot(Register r) {
@@ -664,5 +735,30 @@
* @throws IllegalInstructionException
*/
public abstract Type addCountTrampoline() throws IllegalInstructionException;
+
+ /**
+ * Returns the base program size when the interpreter is initialized.
+ */
+ public abstract int getBaseProgramSize();
+
+ /**
+ * Appends default packet handling and counting to the APF program.
+ * This method adds logic to:
+ * 1. Increment the {@code PASSED_IPV6_ICMP} counter and pass the packet.
+ * 3. Add trampoline logic for counter processing.
+ *
+ *
+ * @return The type resulting from the appended instructions.
+ * @throws IllegalInstructionException If an error occurs while adding instructions.
+ */
+ public final Type addDefaultPacketHandling() throws IllegalInstructionException {
+ addCountAndPass(PASSED_IPV6_ICMP);
+ return addCountTrampoline();
+ }
+
+ /**
+ * Returns an overestimate of the size of the default packet handling logic.
+ */
+ public abstract int getDefaultPacketHandlingSizeOverEstimate();
}
diff --git a/src/android/net/apf/ApfV61Generator.java b/src/android/net/apf/ApfV61Generator.java
new file mode 100644
index 0000000..465596a
--- /dev/null
+++ b/src/android/net/apf/ApfV61Generator.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2025 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;
+
+/**
+ * APFv6.1 assembler/generator. A tool for generating an APFv6.1 program.
+ *
+ * @hide
+ */
+public final class ApfV61Generator extends ApfV61GeneratorBase<ApfV61Generator> {
+ /**
+ * Returns true if we support the specified {@code version}, otherwise false.
+ */
+ public static boolean supportsVersion(int version) {
+ return version >= APF_VERSION_61;
+ }
+
+ /**
+ * Creates an ApfV61Generator instance.
+ */
+ public ApfV61Generator(int version, int ramSize, int clampSize)
+ throws IllegalInstructionException {
+ super(new byte[0], version, ramSize, clampSize);
+ }
+}
diff --git a/src/android/net/apf/ApfV61GeneratorBase.java b/src/android/net/apf/ApfV61GeneratorBase.java
new file mode 100644
index 0000000..c60de72
--- /dev/null
+++ b/src/android/net/apf/ApfV61GeneratorBase.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.apf;
+
+import static android.net.apf.BaseApfGenerator.Rbit.Rbit0;
+import static android.net.apf.BaseApfGenerator.Rbit.Rbit1;
+import static android.net.apf.BaseApfGenerator.Register.R0;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * The abstract class for APFv6.1 assembler/generator.
+ *
+ * @param <Type> the generator class
+ *
+ * @hide
+ */
+public abstract class ApfV61GeneratorBase<Type extends ApfV61GeneratorBase<Type>> extends
+ ApfV6GeneratorBase<Type> {
+
+ /**
+ * Creates an ApfV61GeneratorBase instance.
+ */
+ public ApfV61GeneratorBase(byte[] bytes, int version, int ramSize, int clampSize)
+ throws IllegalInstructionException {
+ super(bytes, version, ramSize, clampSize);
+ }
+
+ @Override
+ public final Type addCountAndDropIfR0Equals(long val, ApfCounterTracker.Counter cnt) {
+ return addJumpIfR0Equals(val, cnt.getJumpDropLabel());
+ }
+
+ @Override
+ public final Type addCountAndPassIfR0Equals(long val, ApfCounterTracker.Counter cnt) {
+ return addJumpIfR0Equals(val, cnt.getJumpPassLabel());
+ }
+
+ @Override
+ public final Type addCountAndDropIfR0NotEquals(long val, ApfCounterTracker.Counter cnt) {
+ return addJumpIfR0NotEquals(val, cnt.getJumpDropLabel());
+ }
+
+ @Override
+ public final Type addCountAndPassIfR0NotEquals(long val, ApfCounterTracker.Counter cnt) {
+ return addJumpIfR0NotEquals(val, cnt.getJumpPassLabel());
+ }
+
+ @Override
+ public Type addCountAndDropIfR0AnyBitsSet(long val, ApfCounterTracker.Counter cnt) {
+ return addJumpIfR0AnyBitsSet(val, cnt.getJumpDropLabel());
+ }
+
+ @Override
+ public Type addCountAndPassIfR0AnyBitsSet(long val, ApfCounterTracker.Counter cnt) {
+ return addJumpIfR0AnyBitsSet(val, cnt.getJumpPassLabel());
+ }
+
+ @Override
+ public final Type addCountAndDropIfR0LessThan(long val, ApfCounterTracker.Counter cnt) {
+ if (val <= 0) {
+ throw new IllegalArgumentException("val must > 0, current val: " + val);
+ }
+ return addJumpIfR0LessThan(val, cnt.getJumpDropLabel());
+ }
+
+ @Override
+ public final Type addCountAndPassIfR0LessThan(long val, ApfCounterTracker.Counter cnt) {
+ if (val <= 0) {
+ throw new IllegalArgumentException("val must > 0, current val: " + val);
+ }
+ return addJumpIfR0LessThan(val, cnt.getJumpPassLabel());
+ }
+
+ @Override
+ public Type addCountAndDropIfR0GreaterThan(long val, ApfCounterTracker.Counter cnt) {
+ if (val < 0 || val >= 4294967295L) {
+ throw new IllegalArgumentException("val must >= 0 and < 2^32-1, current val: " + val);
+ }
+ return addJumpIfR0GreaterThan(val, cnt.getJumpDropLabel());
+ }
+
+ @Override
+ public Type addCountAndPassIfR0GreaterThan(long val, ApfCounterTracker.Counter cnt) {
+ if (val < 0 || val >= 4294967295L) {
+ throw new IllegalArgumentException("val must >= 0 and < 2^32-1, current val: " + val);
+ }
+ return addJumpIfR0GreaterThan(val, cnt.getJumpPassLabel());
+ }
+
+ @Override
+ public final Type addCountAndDropIfBytesAtR0NotEqual(byte[] bytes,
+ ApfCounterTracker.Counter cnt) {
+ return addJumpIfBytesAtR0NotEqual(bytes, cnt.getJumpDropLabel());
+ }
+
+ @Override
+ public final Type addCountAndPassIfBytesAtR0NotEqual(byte[] bytes,
+ ApfCounterTracker.Counter cnt) {
+ return addJumpIfBytesAtR0NotEqual(bytes, cnt.getJumpPassLabel());
+ }
+
+ @Override
+ public Type addCountAndPassIfR0IsOneOf(@NonNull Set<Long> values,
+ ApfCounterTracker.Counter cnt) {
+ if (values.isEmpty()) {
+ throw new IllegalArgumentException("values cannot be empty");
+ }
+ if (values.size() == 1) {
+ return addCountAndPassIfR0Equals(values.iterator().next(), cnt);
+ }
+ return addJumpIfOneOf(R0, values, cnt.getJumpPassLabel());
+ }
+
+ @Override
+ public Type addCountAndDropIfR0IsOneOf(@NonNull Set<Long> values,
+ ApfCounterTracker.Counter cnt) {
+ if (values.isEmpty()) {
+ throw new IllegalArgumentException("values cannot be empty");
+ }
+ if (values.size() == 1) {
+ return addCountAndDropIfR0Equals(values.iterator().next(), cnt);
+ }
+ return addJumpIfOneOf(R0, values, cnt.getJumpDropLabel());
+ }
+
+ @Override
+ public Type addCountAndPassIfR0IsNoneOf(@NonNull Set<Long> values,
+ ApfCounterTracker.Counter cnt) {
+ if (values.isEmpty()) {
+ throw new IllegalArgumentException("values cannot be empty");
+ }
+ if (values.size() == 1) {
+ return addCountAndPassIfR0NotEquals(values.iterator().next(), cnt);
+ }
+ return addJumpIfNoneOf(R0, values, cnt.getJumpPassLabel());
+ }
+
+ @Override
+ public Type addCountAndDropIfBytesAtR0EqualsAnyOf(@NonNull List<byte[]> bytesList,
+ ApfCounterTracker.Counter cnt) {
+ return addJumpIfBytesAtR0EqualsAnyOf(bytesList, cnt.getJumpDropLabel());
+ }
+
+ @Override
+ public Type addCountAndPassIfBytesAtR0EqualsAnyOf(@NonNull List<byte[]> bytesList,
+ ApfCounterTracker.Counter cnt) {
+ return addJumpIfBytesAtR0EqualsAnyOf(bytesList, cnt.getJumpPassLabel());
+ }
+
+ @Override
+ public Type addCountAndDropIfBytesAtR0EqualsNoneOf(@NonNull List<byte[]> bytesList,
+ ApfCounterTracker.Counter cnt) {
+ return addJumpIfBytesAtR0EqualsNoneOf(bytesList, cnt.getJumpDropLabel());
+ }
+
+ @Override
+ public Type addCountAndPassIfBytesAtR0EqualsNoneOf(@NonNull List<byte[]> bytesList,
+ ApfCounterTracker.Counter cnt) {
+ return addJumpIfBytesAtR0EqualsNoneOf(bytesList, cnt.getJumpPassLabel());
+ }
+
+ @Override
+ public Type addCountAndDropIfR0IsNoneOf(@NonNull Set<Long> values,
+ ApfCounterTracker.Counter cnt) {
+ if (values.isEmpty()) {
+ throw new IllegalArgumentException("values cannot be empty");
+ }
+ if (values.size() == 1) {
+ return addCountAndDropIfR0NotEquals(values.iterator().next(), cnt);
+ }
+ return addJumpIfNoneOf(R0, values, cnt.getJumpDropLabel());
+ }
+
+ @Override
+ public final Type addJumpIfPktAtR0ContainDnsQ(byte[] qnames, int[] qtypes, short tgt) {
+ for (int i = 0; i < qtypes.length; i += 2) {
+ if (i == qtypes.length - 1) {
+ addJumpIfPktAtR0ContainDnsQ(qnames, qtypes[i], tgt);
+ } else {
+ addJumpIfPktAtR0ContainDnsQ2(qnames, qtypes[i], qtypes[i + 1], tgt);
+ }
+ }
+ return self();
+ }
+
+ @Override
+ public Type addAllocate(int size) {
+ final int imm = (size > 266) ? (size - 266 + 7) / 8 : 0;
+ return append(new Instruction(Opcodes.ALLOC_XMIT, Rbit1).addUnsigned(imm));
+ }
+
+ @Override
+ public Type addTransmitWithoutChecksum() {
+ return append(new Instruction(Opcodes.ALLOC_XMIT, Rbit0));
+ }
+
+ @Override
+ protected boolean handleOptimizedTransmit(int ipOfs, int csumOfs, int csumStart,
+ int partialCsum, boolean isUdp) {
+ if (ipOfs != 14) return false;
+ int v = -1;
+ if ( isUdp && csumStart == 26 && csumOfs == 40) v = 0; // ether/ipv4/udp
+ if (!isUdp && csumStart == 26 && csumOfs == 44) v = 1; // ether/ipv4/tcp
+ if (!isUdp && csumStart == 34 && csumOfs == 36) v = 2; // ether/ipv4/icmp
+ if (!isUdp && csumStart == 38 && csumOfs == 40) v = 3; // ether/ipv4/routeralert/icmp
+ if ( isUdp && csumStart == 22 && csumOfs == 60) v = 4; // ether/ipv6/udp
+ if (!isUdp && csumStart == 22 && csumOfs == 64) v = 5; // ether/ipv6/tcp
+ if (!isUdp && csumStart == 22 && csumOfs == 56) v = 6; // ether/ipv6/icmp
+ if (!isUdp && csumStart == 22 && csumOfs == 64) v = 7; // ether/ipv6/routeralert/icmp
+ if (v < 0) return false;
+ v |= partialCsum << 3;
+ append(new Instruction(Opcodes.ALLOC_XMIT, Rbit0).addUnsigned(v));
+ return true;
+ }
+
+ private List<byte[]> addJumpIfBytesAtOffsetEqualsHelper(int offset,
+ @NonNull List<byte[]> bytesList, short tgt, boolean jumpOnMatch)
+ throws IllegalInstructionException {
+ final List<byte[]> deduplicatedList =
+ bytesList.size() == 1 ? bytesList : validateDeduplicateBytesList(bytesList);
+ if (offset < 0 || offset > 255) {
+ return deduplicatedList;
+ }
+ final int count = deduplicatedList.size();
+ final int compareLength = deduplicatedList.get(0).length;
+ if (compareLength > 16) {
+ return deduplicatedList;
+ }
+ final List<byte[]> failbackList = new ArrayList<>();
+ final List<Integer> ptrs = new ArrayList<>();
+ for (int i = 0; i < count; ++i) {
+ final byte[] bytes = deduplicatedList.get(i);
+ int relativeOffset = mInstructions.get(0).findMatchInDataBytes(bytes, 0, bytes.length);
+ if (relativeOffset < 0 || relativeOffset % 2 == 1 || relativeOffset > 510) {
+ failbackList.add(bytes);
+ continue;
+ }
+ ptrs.add(relativeOffset / 2);
+ }
+ final Rbit rbit = jumpOnMatch ? Rbit1 : Rbit0;
+ int totalPtrs = ptrs.size();
+ for (int i = 0; i < totalPtrs; i += 16) {
+ final int currentCount = Math.min(totalPtrs - i, 16);
+ final Instruction instruction = new Instruction(Opcodes.JBSPTRMATCH, rbit)
+ .addU8(offset)
+ .addU8((currentCount - 1) * 16 + (compareLength - 1))
+ .setTargetLabel(tgt);
+ for (int j = 0; j < currentCount; j++) {
+ instruction.addU8(ptrs.get(i + j));
+ }
+ append(instruction);
+ }
+ return failbackList;
+ }
+
+ /**
+ * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the
+ * packet at an offset specified by {@code offset} match any of the elements in
+ * {@code bytesList}.
+ */
+ public Type addJumpIfBytesAtOffsetEqualsAnyOf(int offset, @NonNull List<byte[]> bytesList,
+ short tgt) throws IllegalInstructionException {
+ final List<byte[]> failbackList = addJumpIfBytesAtOffsetEqualsHelper(offset, bytesList, tgt,
+ true /* jumpOnMatch */);
+ if (failbackList.isEmpty()) {
+ return self();
+ }
+ return addLoadImmediate(R0, offset).addJumpIfBytesAtR0EqualsAnyOf(failbackList, tgt);
+ }
+
+ /**
+ * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the
+ * packet at an offset specified by {@code offset} match none of the elements in
+ * {@code bytesList}.
+ */
+ public Type addJumpIfBytesAtOffsetEqualsNoneOf(int offset, @NonNull List<byte[]> bytesList,
+ short tgt) throws IllegalInstructionException {
+ final List<byte[]> failbackList = addJumpIfBytesAtOffsetEqualsHelper(offset, bytesList, tgt,
+ false /* jumpOnMatch */);
+ if (failbackList.isEmpty()) {
+ return self();
+ }
+ return addLoadImmediate(R0, offset).addJumpIfBytesAtR0EqualsNoneOf(failbackList, tgt);
+ }
+
+ @Override
+ public Type addCountAndDropIfBytesAtOffsetEqualsAnyOf(int offset, List<byte[]> bytesList,
+ ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
+ return addJumpIfBytesAtOffsetEqualsAnyOf(offset, bytesList, cnt.getJumpDropLabel());
+ }
+
+ @Override
+ public Type addCountAndPassIfBytesAtOffsetEqualsAnyOf(int offset, List<byte[]> bytesList,
+ ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
+ return addJumpIfBytesAtOffsetEqualsAnyOf(offset, bytesList, cnt.getJumpPassLabel());
+ }
+
+ @Override
+ public Type addCountAndDropIfBytesAtOffsetEqualsNoneOf(int offset, List<byte[]> bytesList,
+ ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
+ return addJumpIfBytesAtOffsetEqualsNoneOf(offset, bytesList, cnt.getJumpDropLabel());
+ }
+
+ @Override
+ public Type addCountAndPassIfBytesAtOffsetEqualsNoneOf(int offset, List<byte[]> bytesList,
+ ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
+ return addJumpIfBytesAtOffsetEqualsNoneOf(offset, bytesList, cnt.getJumpPassLabel());
+ }
+
+ @Override
+ public Type addCountAndPassIfBytesAtOffsetNotEqual(int offset, byte[] bytes,
+ ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
+ return addJumpIfBytesAtOffsetEqualsNoneOf(offset, List.of(bytes), cnt.getJumpPassLabel());
+ }
+
+ @Override
+ public Type addCountAndDropIfBytesAtOffsetNotEqual(int offset, byte[] bytes,
+ ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
+ return addJumpIfBytesAtOffsetEqualsNoneOf(offset, List.of(bytes), cnt.getJumpDropLabel());
+ }
+
+ @Override
+ public Type addCountAndPassIfBytesAtOffsetEqual(int offset, byte[] bytes,
+ ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
+ return addJumpIfBytesAtOffsetEqualsAnyOf(offset, List.of(bytes), cnt.getJumpPassLabel());
+ }
+
+ @Override
+ public Type addCountAndDropIfBytesAtOffsetEqual(int offset, byte[] bytes,
+ ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
+ return addJumpIfBytesAtOffsetEqualsAnyOf(offset, List.of(bytes), cnt.getJumpDropLabel());
+ }
+
+ @Override
+ public Type addJumpIfBytesAtOffsetNotEqual(int offset, @NonNull byte[] bytes, short tgt)
+ throws IllegalInstructionException {
+ return addJumpIfBytesAtOffsetEqualsNoneOf(offset, List.of(bytes), tgt);
+ }
+
+ /**
+ * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP
+ * payload's DNS questions contain the QNAMEs specified in {@code qnames} and qtype
+ * equals {@code qtype1} or {@code qtype2}. Examines the payload starting at the offset in R0.
+ * Drops packets if packets are corrupted.
+ */
+ public final Type addJumpIfPktAtR0ContainDnsQ2(@android.annotation.NonNull byte[] qnames,
+ int qtype1, int qtype2, short tgt) {
+ validateNames(qnames);
+ return append(new Instruction(ExtendedOpcodes.JDNSQMATCH2, Rbit1).setTargetLabel(tgt)
+ .addU8(qtype1).addU8(qtype2).setBytesImm(qnames));
+ }
+
+ /**
+ * Preload the content of the data region.
+ */
+ public Type addPreloadData(@NonNull byte[] data) throws IllegalInstructionException {
+ mInstructions.get(0).maybeUpdateBytesImm(data, 0, data.length);
+ return self();
+ }
+}
diff --git a/src/android/net/apf/ApfV6Generator.java b/src/android/net/apf/ApfV6Generator.java
index f943bed..07bd191 100644
--- a/src/android/net/apf/ApfV6Generator.java
+++ b/src/android/net/apf/ApfV6Generator.java
@@ -15,9 +15,15 @@
*/
package android.net.apf;
+import static android.net.apf.BaseApfGenerator.Rbit.Rbit1;
+import static android.net.apf.BaseApfGenerator.Register.R0;
+
+import android.annotation.NonNull;
+
import com.android.internal.annotations.VisibleForTesting;
-import java.util.Objects;
+import java.util.List;
+import java.util.Set;
/**
* APFv6 assembler/generator. A tool for generating an APFv6 program.
@@ -40,12 +46,6 @@
this(new byte[0], version, ramSize, clampSize);
}
- @Override
- void updateExceptionBufferSize(int programSize) throws IllegalInstructionException {
- mInstructions.get(1).updateExceptionBufferSize(
- mRamSize - ApfCounterTracker.Counter.totalSize() - programSize);
- }
-
/**
* Creates an ApfV6Generator instance which emits instructions APFv6.
* Initializes the data region with {@code bytes}.
@@ -53,9 +53,261 @@
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public ApfV6Generator(byte[] bytes, int version, int ramSize, int clampSize)
throws IllegalInstructionException {
- super(version, ramSize, clampSize);
- Objects.requireNonNull(bytes);
- addData(bytes);
- addExceptionBuffer(0);
+ super(bytes, version, ramSize, clampSize);
+ }
+
+ @Override
+ public ApfV6Generator addCountAndDropIfR0Equals(long val, ApfCounterTracker.Counter cnt)
+ throws IllegalInstructionException {
+ final short tgt = getUniqueLabel();
+ return addJumpIfR0NotEquals(val, tgt).addCountAndDrop(cnt).defineLabel(tgt);
+ }
+
+ @Override
+ public ApfV6Generator addCountAndPassIfR0Equals(long val, ApfCounterTracker.Counter cnt)
+ throws IllegalInstructionException {
+ final short tgt = getUniqueLabel();
+ return addJumpIfR0NotEquals(val, tgt).addCountAndPass(cnt).defineLabel(tgt);
+ }
+
+ @Override
+ public ApfV6Generator addCountAndDropIfR0NotEquals(long val, ApfCounterTracker.Counter cnt)
+ throws IllegalInstructionException {
+ final short tgt = getUniqueLabel();
+ return addJumpIfR0Equals(val, tgt).addCountAndDrop(cnt).defineLabel(tgt);
+ }
+
+ @Override
+ public ApfV6Generator addCountAndPassIfR0NotEquals(long val, ApfCounterTracker.Counter cnt)
+ throws IllegalInstructionException {
+ final short tgt = getUniqueLabel();
+ return addJumpIfR0Equals(val, tgt).addCountAndPass(cnt).defineLabel(tgt);
+ }
+
+ @Override
+ public ApfV6Generator addCountAndDropIfR0AnyBitsSet(long val, ApfCounterTracker.Counter cnt)
+ throws IllegalInstructionException {
+ final short countAndDropLabel = getUniqueLabel();
+ final short skipLabel = getUniqueLabel();
+ return addJumpIfR0AnyBitsSet(val, countAndDropLabel)
+ .addJump(skipLabel)
+ .defineLabel(countAndDropLabel)
+ .addCountAndDrop(cnt)
+ .defineLabel(skipLabel);
+ }
+
+ @Override
+ public ApfV6Generator addCountAndPassIfR0AnyBitsSet(long val, ApfCounterTracker.Counter cnt)
+ throws IllegalInstructionException {
+ final short countAndPassLabel = getUniqueLabel();
+ final short skipLabel = getUniqueLabel();
+ return addJumpIfR0AnyBitsSet(val, countAndPassLabel)
+ .addJump(skipLabel)
+ .defineLabel(countAndPassLabel)
+ .addCountAndPass(cnt)
+ .defineLabel(skipLabel);
+ }
+
+ @Override
+ public ApfV6Generator addCountAndDropIfR0LessThan(long val, ApfCounterTracker.Counter cnt)
+ throws IllegalInstructionException {
+ if (val <= 0) {
+ throw new IllegalArgumentException("val must > 0, current val: " + val);
+ }
+ final short tgt = getUniqueLabel();
+ return addJumpIfR0GreaterThan(val - 1, tgt).addCountAndDrop(cnt).defineLabel(tgt);
+ }
+
+ @Override
+ public ApfV6Generator addCountAndPassIfR0LessThan(long val, ApfCounterTracker.Counter cnt)
+ throws IllegalInstructionException {
+ if (val <= 0) {
+ throw new IllegalArgumentException("val must > 0, current val: " + val);
+ }
+ final short tgt = getUniqueLabel();
+ return addJumpIfR0GreaterThan(val - 1, tgt).addCountAndPass(cnt).defineLabel(tgt);
+ }
+
+ @Override
+ public ApfV6Generator addCountAndDropIfR0GreaterThan(long val, ApfCounterTracker.Counter cnt)
+ throws IllegalInstructionException {
+ if (val < 0 || val >= 4294967295L) {
+ throw new IllegalArgumentException("val must >= 0 and < 2^32-1, current val: " + val);
+ }
+ final short tgt = getUniqueLabel();
+ return addJumpIfR0LessThan(val + 1, tgt).addCountAndDrop(cnt).defineLabel(tgt);
+ }
+
+ @Override
+ public ApfV6Generator addCountAndPassIfR0GreaterThan(long val, ApfCounterTracker.Counter cnt)
+ throws IllegalInstructionException {
+ if (val < 0 || val >= 4294967295L) {
+ throw new IllegalArgumentException("val must >= 0 and < 2^32-1, current val: " + val);
+ }
+ final short tgt = getUniqueLabel();
+ return addJumpIfR0LessThan(val + 1, tgt).addCountAndPass(cnt).defineLabel(tgt);
+ }
+
+ @Override
+ public ApfV6Generator addCountAndDropIfBytesAtR0NotEqual(byte[] bytes,
+ ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
+ final short tgt = getUniqueLabel();
+ return addJumpIfBytesAtR0Equal(bytes, tgt).addCountAndDrop(cnt).defineLabel(tgt);
+ }
+
+ @Override
+ public ApfV6Generator addCountAndPassIfBytesAtR0NotEqual(byte[] bytes,
+ ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
+ final short tgt = getUniqueLabel();
+ return addJumpIfBytesAtR0Equal(bytes, tgt).addCountAndPass(cnt).defineLabel(tgt);
+ }
+
+ @Override
+ public ApfV6Generator addCountAndPassIfR0IsOneOf(@NonNull Set<Long> values,
+ ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
+ if (values.isEmpty()) {
+ throw new IllegalArgumentException("values cannot be empty");
+ }
+ if (values.size() == 1) {
+ return addCountAndPassIfR0Equals(values.iterator().next(), cnt);
+ }
+ final short tgt = getUniqueLabel();
+ return addJumpIfNoneOf(R0, values, tgt).addCountAndPass(cnt).defineLabel(tgt);
+ }
+
+ @Override
+ public ApfV6Generator addCountAndDropIfR0IsOneOf(@NonNull Set<Long> values,
+ ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
+ if (values.isEmpty()) {
+ throw new IllegalArgumentException("values cannot be empty");
+ }
+ if (values.size() == 1) {
+ return addCountAndDropIfR0Equals(values.iterator().next(), cnt);
+ }
+ final short tgt = getUniqueLabel();
+ return addJumpIfNoneOf(R0, values, tgt).addCountAndDrop(cnt).defineLabel(tgt);
+ }
+
+ @Override
+ public ApfV6Generator addCountAndPassIfR0IsNoneOf(@NonNull Set<Long> values,
+ ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
+ if (values.isEmpty()) {
+ throw new IllegalArgumentException("values cannot be empty");
+ }
+ if (values.size() == 1) {
+ return addCountAndPassIfR0NotEquals(values.iterator().next(), cnt);
+ }
+ final short tgt = getUniqueLabel();
+ return addJumpIfOneOf(R0, values, tgt).addCountAndPass(cnt).defineLabel(tgt);
+ }
+
+ @Override
+ public ApfV6Generator addCountAndDropIfBytesAtR0EqualsAnyOf(@NonNull List<byte[]> bytesList,
+ ApfCounterTracker.Counter cnt)
+ throws IllegalInstructionException {
+ final short tgt = getUniqueLabel();
+ return addJumpIfBytesAtR0EqualsNoneOf(bytesList, tgt).addCountAndDrop(cnt).defineLabel(tgt);
+ }
+
+ @Override
+ public ApfV6Generator addCountAndPassIfBytesAtR0EqualsAnyOf(@NonNull List<byte[]> bytesList,
+ ApfCounterTracker.Counter cnt)
+ throws IllegalInstructionException {
+ final short tgt = getUniqueLabel();
+ return addJumpIfBytesAtR0EqualsNoneOf(bytesList, tgt).addCountAndPass(cnt).defineLabel(tgt);
+ }
+
+ @Override
+ public ApfV6Generator addCountAndDropIfBytesAtR0EqualsNoneOf(@NonNull List<byte[]> bytesList,
+ ApfCounterTracker.Counter cnt)
+ throws IllegalInstructionException {
+ final short tgt = getUniqueLabel();
+ return addJumpIfBytesAtR0EqualsAnyOf(bytesList, tgt).addCountAndDrop(cnt).defineLabel(tgt);
+ }
+
+ @Override
+ public ApfV6Generator addCountAndPassIfBytesAtR0EqualsNoneOf(@NonNull List<byte[]> bytesList,
+ ApfCounterTracker.Counter cnt)
+ throws IllegalInstructionException {
+ final short tgt = getUniqueLabel();
+ return addJumpIfBytesAtR0EqualsAnyOf(bytesList, tgt).addCountAndPass(cnt).defineLabel(tgt);
+ }
+
+ @Override
+ public ApfV6Generator addCountAndDropIfBytesAtOffsetEqualsAnyOf(int offset,
+ List<byte[]> bytesList, ApfCounterTracker.Counter cnt)
+ throws IllegalInstructionException {
+ return addLoadImmediate(R0, offset).addCountAndDropIfBytesAtR0EqualsAnyOf(bytesList, cnt);
+ }
+
+ @Override
+ public ApfV6Generator addCountAndPassIfBytesAtOffsetEqualsAnyOf(int offset,
+ List<byte[]> bytesList, ApfCounterTracker.Counter cnt)
+ throws IllegalInstructionException {
+ return addLoadImmediate(R0, offset).addCountAndPassIfBytesAtR0EqualsAnyOf(bytesList, cnt);
+ }
+
+ @Override
+ public ApfV6Generator addCountAndDropIfBytesAtOffsetEqualsNoneOf(int offset,
+ List<byte[]> bytesList, ApfCounterTracker.Counter cnt)
+ throws IllegalInstructionException {
+ return addLoadImmediate(R0, offset).addCountAndDropIfBytesAtR0EqualsNoneOf(bytesList, cnt);
+ }
+
+ @Override
+ public ApfV6Generator addCountAndPassIfBytesAtOffsetEqualsNoneOf(int offset,
+ List<byte[]> bytesList, ApfCounterTracker.Counter cnt)
+ throws IllegalInstructionException {
+ return addLoadImmediate(R0, offset).addCountAndPassIfBytesAtR0EqualsNoneOf(bytesList, cnt);
+ }
+
+ @Override
+ public ApfV6Generator addJumpIfBytesAtOffsetEqualsAnyOf(int offset, List<byte[]> bytesList,
+ short tgt) throws IllegalInstructionException {
+ return addLoadImmediate(R0, offset).addJumpIfBytesAtR0EqualsAnyOf(bytesList, tgt);
+ }
+
+ @Override
+ public ApfV6Generator addJumpIfBytesAtOffsetEqualsNoneOf(int offset, List<byte[]> bytesList,
+ short tgt) throws IllegalInstructionException {
+ return addLoadImmediate(R0, offset).addJumpIfBytesAtR0EqualsNoneOf(bytesList, tgt);
+ }
+
+ @Override
+ public ApfV6Generator addCountAndDropIfR0IsNoneOf(@NonNull Set<Long> values,
+ ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
+ if (values.isEmpty()) {
+ throw new IllegalArgumentException("values cannot be empty");
+ }
+ if (values.size() == 1) {
+ return addCountAndDropIfR0NotEquals(values.iterator().next(), cnt);
+ }
+ final short tgt = getUniqueLabel();
+ return addJumpIfOneOf(R0, values, tgt).addCountAndDrop(cnt).defineLabel(tgt);
+ }
+
+ @Override
+ public ApfV6Generator addJumpIfPktAtR0ContainDnsQ(byte[] qnames, int[] qtypes, short tgt) {
+ for (int qtype : qtypes) {
+ addJumpIfPktAtR0ContainDnsQ(qnames, qtype, tgt);
+ }
+ return self();
+ }
+
+ @Override
+ public ApfV6Generator addAllocate(int size) {
+ // Rbit1 means the extra be16 immediate is present
+ return append(new Instruction(ExtendedOpcodes.ALLOCATE, Rbit1).addU16(size));
+ }
+
+ @Override
+ public ApfV6Generator addTransmitWithoutChecksum() {
+ return addTransmit(-1 /* ipOfs */);
+ }
+
+ @Override
+ protected boolean handleOptimizedTransmit(int ipOfs, int csumOfs, int csumStart,
+ int partialCsum, boolean isUdp) {
+ return false;
}
}
diff --git a/src/android/net/apf/ApfV6GeneratorBase.java b/src/android/net/apf/ApfV6GeneratorBase.java
index 17629d1..90f0a28 100644
--- a/src/android/net/apf/ApfV6GeneratorBase.java
+++ b/src/android/net/apf/ApfV6GeneratorBase.java
@@ -46,9 +46,25 @@
* the requested version is unsupported.
*
*/
- public ApfV6GeneratorBase(int version, int ramSize, int clampSize)
+ public ApfV6GeneratorBase(byte[] bytes, int version, int ramSize, int clampSize)
throws IllegalInstructionException {
super(version, ramSize, clampSize, false);
+ Objects.requireNonNull(bytes);
+ addData(bytes);
+ addExceptionBuffer(0);
+ }
+
+ @Override
+ public final int getBaseProgramSize() {
+ // When the APFv6+ generator is initialized, it always adds a 3-byte data jump
+ // instruction and a 4-byte exception instruction to the front of the program.
+ return 7;
+ }
+
+ @Override
+ void updateExceptionBufferSize(int programSize) throws IllegalInstructionException {
+ mInstructions.get(1).updateExceptionBufferSize(
+ mRamSize - ApfCounterTracker.Counter.totalSize() - programSize);
}
/**
@@ -98,10 +114,7 @@
*
* @param size the buffer length to be allocated.
*/
- public final Type addAllocate(int size) {
- // Rbit1 means the extra be16 immediate is present
- return append(new Instruction(ExtendedOpcodes.ALLOCATE, Rbit1).addU16(size));
- }
+ public abstract Type addAllocate(int size);
/**
* Add an instruction to the beginning of the program to reserve the empty data region.
@@ -137,9 +150,7 @@
* Add an instruction to the end of the program to transmit the allocated buffer without
* checksum.
*/
- public final Type addTransmitWithoutChecksum() {
- return addTransmit(-1 /* ipOfs */);
- }
+ public abstract Type addTransmitWithoutChecksum();
/**
* Add an instruction to the end of the program to transmit the allocated buffer.
@@ -152,6 +163,8 @@
return append(new Instruction(ExtendedOpcodes.TRANSMIT, Rbit0).addU8(ipOfs).addU8(255));
}
+ protected abstract boolean handleOptimizedTransmit(int ipOfs, int csumOfs, int csumStart, int partialCsum, boolean isUdp);
+
/**
* Add an instruction to the end of the program to transmit the allocated buffer.
*/
@@ -165,6 +178,8 @@
throw new IllegalArgumentException("L4 checksum requires csum offset of "
+ csumOfs + " < 255");
}
+ if (handleOptimizedTransmit(ipOfs, csumOfs, csumStart, partialCsum, isUdp))
+ return self();
return append(new Instruction(ExtendedOpcodes.TRANSMIT, isUdp ? Rbit1 : Rbit0)
.addU8(ipOfs).addU8(csumOfs).addU8(csumStart).addU16(partialCsum));
}
@@ -237,19 +252,39 @@
}
/**
- * Add an instruction to the end of the program to copy data from APF program/data region to
+ * Add instructions to the end of the program to copy data from APF program/data region to
* output buffer and auto-increment the output buffer pointer.
* This method requires the {@code addData} method to be called beforehand.
* It will first attempt to match {@code content} with existing data bytes. If not exist, then
* append the {@code content} to the data bytes.
+ * The method copies the content using multiple datacopy instructions if the content size
+ * exceeds 255 bytes. Each instruction will copy a maximum of 255 bytes.
*/
public final Type addDataCopy(@NonNull byte[] content) throws IllegalInstructionException {
if (mInstructions.isEmpty()) {
throw new IllegalInstructionException("There is no instructions");
}
Objects.requireNonNull(content);
- int copySrc = mInstructions.get(0).maybeUpdateBytesImm(content);
- return addDataCopy(copySrc, content.length);
+ final int chunkSize = 255;
+ for (int fromIndex = 0; fromIndex < content.length; fromIndex += chunkSize) {
+ final int toIndex = Math.min(content.length, fromIndex + chunkSize);
+ final int copySrc = mInstructions.get(0).maybeUpdateBytesImm(content, fromIndex,
+ toIndex);
+ addDataCopy(copySrc, (toIndex - fromIndex));
+ }
+ return self();
+ }
+
+ /**
+ * Add the content to the data region if it wasn't exist.
+ */
+ public final Type maybeUpdateDataRegion(@NonNull byte[] content)
+ throws IllegalInstructionException {
+ if (mInstructions.isEmpty()) {
+ throw new IllegalInstructionException("There are no instructions");
+ }
+ mInstructions.get(0).maybeUpdateBytesImm(content, 0, content.length);
+ return self();
}
/**
@@ -334,7 +369,7 @@
* Drops packets if packets are corrupted.
*/
public final Type addJumpIfPktAtR0DoesNotContainDnsQ(@NonNull byte[] qnames, int qtype,
- @NonNull String tgt) {
+ short tgt) {
validateNames(qnames);
return append(new Instruction(ExtendedOpcodes.JDNSQMATCH, Rbit0).setTargetLabel(tgt).addU8(
qtype).setBytesImm(qnames));
@@ -345,7 +380,7 @@
* corrupted.
*/
public final Type addJumpIfPktAtR0DoesNotContainDnsQSafe(@NonNull byte[] qnames, int qtype,
- @NonNull String tgt) {
+ short tgt) {
validateNames(qnames);
return append(new Instruction(ExtendedOpcodes.JDNSQMATCHSAFE, Rbit0).setTargetLabel(
tgt).addU8(qtype).setBytesImm(qnames));
@@ -354,12 +389,59 @@
/**
* Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP
* payload's DNS questions contain the QNAMEs specified in {@code qnames} and qtype
+ * equals any of {@code qtypes}. Examines the payload starting at the offset in R0.
+ * Drops packets if packets are corrupted.
+ */
+ public abstract Type addJumpIfPktAtR0ContainDnsQ(@NonNull byte[] qnames, @NonNull int[] qtypes,
+ short tgt);
+
+ /**
+ * Add an instruction to the end of the program to count and drop if the bytes of the
+ * packet at an offset specified by {@code offset} match any of the elements in
+ * {@code bytesList}.
+ * This method will use JBSPTRMATCH in APFv6.1 when possible.
+ */
+ public abstract Type addCountAndDropIfBytesAtOffsetEqualsAnyOf(int offset,
+ @NonNull List<byte[]> bytesList, ApfCounterTracker.Counter cnt)
+ throws IllegalInstructionException;
+
+ /**
+ * Add an instruction to the end of the program to count and pass if the bytes of the
+ * packet at an offset specified by {@code offset} match any of the elements in
+ * {@code bytesList}.
+ * This method will use JBSPTRMATCH in APFv6.1 when possible.
+ */
+ public abstract Type addCountAndPassIfBytesAtOffsetEqualsAnyOf(int offset,
+ @NonNull List<byte[]> bytesList, ApfCounterTracker.Counter cnt)
+ throws IllegalInstructionException;
+
+ /**
+ * Add an instruction to the end of the program to count and drop if the bytes of the
+ * packet at an offset specified by {@code offset} match none the elements in {@code bytesList}.
+ * This method will use JBSPTRMATCH in APFv6.1 when possible.
+ */
+ public abstract Type addCountAndDropIfBytesAtOffsetEqualsNoneOf(int offset,
+ @NonNull List<byte[]> bytesList, ApfCounterTracker.Counter cnt)
+ throws IllegalInstructionException;
+
+ /**
+ * Add an instruction to the end of the program to count and pass if the bytes of the
+ * packet at an offset specified by {@code offset} match none of the elements in
+ * {@code bytesList}.
+ * This method will use JBSPTRMATCH in APFv6.1 when possible.
+ */
+ public abstract Type addCountAndPassIfBytesAtOffsetEqualsNoneOf(int offset,
+ @NonNull List<byte[]> bytesList, ApfCounterTracker.Counter cnt)
+ throws IllegalInstructionException;
+
+ /**
+ * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP
+ * payload's DNS questions contain the QNAMEs specified in {@code qnames} and qtype
* equals {@code qtype}. Examines the payload starting at the offset in R0.
* R = 1 means check for "contain".
* Drops packets if packets are corrupted.
*/
- public final Type addJumpIfPktAtR0ContainDnsQ(@NonNull byte[] qnames, int qtype,
- @NonNull String tgt) {
+ public final Type addJumpIfPktAtR0ContainDnsQ(@NonNull byte[] qnames, int qtype, short tgt) {
validateNames(qnames);
return append(new Instruction(ExtendedOpcodes.JDNSQMATCH, Rbit1).setTargetLabel(tgt).addU8(
qtype).setBytesImm(qnames));
@@ -370,7 +452,7 @@
* corrupted.
*/
public final Type addJumpIfPktAtR0ContainDnsQSafe(@NonNull byte[] qnames, int qtype,
- @NonNull String tgt) {
+ short tgt) {
validateNames(qnames);
return append(new Instruction(ExtendedOpcodes.JDNSQMATCHSAFE, Rbit1).setTargetLabel(
tgt).addU8(qtype).setBytesImm(qnames));
@@ -383,8 +465,7 @@
* R = 0 means check for "does not contain".
* Drops packets if packets are corrupted.
*/
- public final Type addJumpIfPktAtR0DoesNotContainDnsA(@NonNull byte[] names,
- @NonNull String tgt) {
+ public final Type addJumpIfPktAtR0DoesNotContainDnsA(@NonNull byte[] names, short tgt) {
validateNames(names);
return append(new Instruction(ExtendedOpcodes.JDNSAMATCH, Rbit0).setTargetLabel(tgt)
.setBytesImm(names));
@@ -394,8 +475,7 @@
* Same as {@link #addJumpIfPktAtR0DoesNotContainDnsA} except passes packets if packets are
* corrupted.
*/
- public final Type addJumpIfPktAtR0DoesNotContainDnsASafe(@NonNull byte[] names,
- @NonNull String tgt) {
+ public final Type addJumpIfPktAtR0DoesNotContainDnsASafe(@NonNull byte[] names, short tgt) {
validateNames(names);
return append(new Instruction(ExtendedOpcodes.JDNSAMATCHSAFE, Rbit0).setTargetLabel(tgt)
.setBytesImm(names));
@@ -408,8 +488,7 @@
* R = 1 means check for "contain".
* Drops packets if packets are corrupted.
*/
- public final Type addJumpIfPktAtR0ContainDnsA(@NonNull byte[] names,
- @NonNull String tgt) {
+ public final Type addJumpIfPktAtR0ContainDnsA(@NonNull byte[] names, short tgt) {
validateNames(names);
return append(new Instruction(ExtendedOpcodes.JDNSAMATCH, Rbit1).setTargetLabel(
tgt).setBytesImm(names));
@@ -419,8 +498,7 @@
* Same as {@link #addJumpIfPktAtR0ContainDnsA} except passes packets if packets are
* corrupted.
*/
- public final Type addJumpIfPktAtR0ContainDnsASafe(@NonNull byte[] names,
- @NonNull String tgt) {
+ public final Type addJumpIfPktAtR0ContainDnsASafe(@NonNull byte[] names, short tgt) {
validateNames(names);
return append(new Instruction(ExtendedOpcodes.JDNSAMATCHSAFE, Rbit1).setTargetLabel(
tgt).setBytesImm(names));
@@ -431,14 +509,14 @@
* packet at an offset specified by register0 match {@code bytes}.
* R=1 means check for equal.
*/
- public final Type addJumpIfBytesAtR0Equal(@NonNull byte[] bytes, String tgt)
+ public final Type addJumpIfBytesAtR0Equal(@NonNull byte[] bytes, short tgt)
throws IllegalInstructionException {
validateBytes(bytes);
return append(new Instruction(Opcodes.JBSMATCH, R1).addUnsigned(
bytes.length).setTargetLabel(tgt).setBytesImm(bytes));
}
- private Type addJumpIfBytesAtR0EqualsHelper(@NonNull List<byte[]> bytesList, String tgt,
+ private Type addJumpIfBytesAtR0EqualsHelper(@NonNull List<byte[]> bytesList, short tgt,
boolean jumpOnMatch) {
final List<byte[]> deduplicatedList = validateDeduplicateBytesList(bytesList);
final int elementSize = deduplicatedList.get(0).length;
@@ -461,7 +539,7 @@
* packet at an offset specified by register0 match any of the elements in {@code bytesSet}.
* R=1 means check for equal.
*/
- public final Type addJumpIfBytesAtR0EqualsAnyOf(@NonNull List<byte[]> bytesList, String tgt) {
+ public final Type addJumpIfBytesAtR0EqualsAnyOf(@NonNull List<byte[]> bytesList, short tgt) {
return addJumpIfBytesAtR0EqualsHelper(bytesList, tgt, true /* jumpOnMatch */);
}
@@ -470,19 +548,35 @@
* packet at an offset specified by register0 match none of the elements in {@code bytesSet}.
* R=0 means check for not equal.
*/
- public final Type addJumpIfBytesAtR0EqualNoneOf(@NonNull List<byte[]> bytesList, String tgt) {
+ public final Type addJumpIfBytesAtR0EqualsNoneOf(@NonNull List<byte[]> bytesList, short tgt) {
return addJumpIfBytesAtR0EqualsHelper(bytesList, tgt, false /* jumpOnMatch */);
}
+ /**
+ * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the
+ * packet at an offset specified by {@code offset} match any of the elements in
+ * {@code bytesSet}.
+ */
+ public abstract Type addJumpIfBytesAtOffsetEqualsAnyOf(int offset,
+ @NonNull List<byte[]> bytesList, short tgt) throws IllegalInstructionException;
/**
- * Check if the byte is valid dns character: A-Z,0-9,-,_
+ * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the
+ * packet at an offset specified by {@code offset} match none of the elements in
+ * {@code bytesSet}.
+ */
+ public abstract Type addJumpIfBytesAtOffsetEqualsNoneOf(int offset,
+ @NonNull List<byte[]> bytesList, short tgt) throws IllegalInstructionException;
+
+ /**
+ * Check if the byte is valid dns character: A-Z,0-9,-,_,%,@
*/
private static boolean isValidDnsCharacter(byte c) {
- return (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '%';
+ return (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '%'
+ || c == '@';
}
- private static void validateNames(@NonNull byte[] names) {
+ static void validateNames(@NonNull byte[] names) {
final int len = names.length;
if (len < 4) {
throw new IllegalArgumentException("qnames must have at least length 4");
@@ -517,7 +611,7 @@
}
private Type addJumpIfOneOfHelper(Register reg, @NonNull Set<Long> values,
- boolean jumpOnMatch, @NonNull String tgt) {
+ boolean jumpOnMatch, short tgt) {
if (values == null || values.size() < 2 || values.size() > 33) {
throw new IllegalArgumentException(
"size of values set must be >= 2 and <= 33, current size: " + values.size());
@@ -560,8 +654,7 @@
* Add an instruction to the end of the program to jump to {@code tgt} if {@code reg} is
* one of the {@code values}.
*/
- public final Type addJumpIfOneOf(Register reg, @NonNull Set<Long> values,
- @NonNull String tgt) {
+ public final Type addJumpIfOneOf(Register reg, @NonNull Set<Long> values, short tgt) {
return addJumpIfOneOfHelper(reg, values, true /* jumpOnMatch */, tgt);
}
@@ -569,8 +662,7 @@
* Add an instruction to the end of the program to jump to {@code tgt} if {@code reg} is
* not one of the {@code values}.
*/
- public final Type addJumpIfNoneOf(Register reg, @NonNull Set<Long> values,
- @NonNull String tgt) {
+ public final Type addJumpIfNoneOf(Register reg, @NonNull Set<Long> values, short tgt) {
return addJumpIfOneOfHelper(reg, values, false /* jumpOnMatch */, tgt);
}
@@ -579,6 +671,18 @@
append(new Instruction(opcode, R0)); // APFv6+: R0 op= R1
}
+ @Override
+ public final Type addAdd(long val) {
+ if (val == 0) return self();
+ return append(new Instruction(Opcodes.ADD).addTwosCompSigned(val));
+ }
+
+ @Override
+ public final Type addAnd(long val) {
+ if (val == 0) return addLoadImmediate(R0, 0);
+ return append(new Instruction(Opcodes.AND).addTwosCompSigned(val));
+ }
+
/**
* Add an instruction to the end of the program to increment the counter value and
* immediately return PASS.
@@ -604,196 +708,6 @@
}
@Override
- public final Type addCountAndDropIfR0Equals(long val, ApfCounterTracker.Counter cnt)
- throws IllegalInstructionException {
- final String tgt = getUniqueLabel();
- return addJumpIfR0NotEquals(val, tgt).addCountAndDrop(cnt).defineLabel(tgt);
- }
-
- @Override
- public final Type addCountAndPassIfR0Equals(long val, ApfCounterTracker.Counter cnt)
- throws IllegalInstructionException {
- final String tgt = getUniqueLabel();
- return addJumpIfR0NotEquals(val, tgt).addCountAndPass(cnt).defineLabel(tgt);
- }
-
- @Override
- public final Type addCountAndDropIfR0NotEquals(long val, ApfCounterTracker.Counter cnt)
- throws IllegalInstructionException {
- final String tgt = getUniqueLabel();
- return addJumpIfR0Equals(val, tgt).addCountAndDrop(cnt).defineLabel(tgt);
- }
-
- @Override
- public final Type addCountAndPassIfR0NotEquals(long val, ApfCounterTracker.Counter cnt)
- throws IllegalInstructionException {
- final String tgt = getUniqueLabel();
- return addJumpIfR0Equals(val, tgt).addCountAndPass(cnt).defineLabel(tgt);
- }
-
- @Override
- public Type addCountAndDropIfR0AnyBitsSet(long val, ApfCounterTracker.Counter cnt)
- throws IllegalInstructionException {
- final String countAndDropLabel = getUniqueLabel();
- final String skipLabel = getUniqueLabel();
- return addJumpIfR0AnyBitsSet(val, countAndDropLabel)
- .addJump(skipLabel)
- .defineLabel(countAndDropLabel)
- .addCountAndDrop(cnt)
- .defineLabel(skipLabel);
- }
-
- @Override
- public Type addCountAndPassIfR0AnyBitsSet(long val, ApfCounterTracker.Counter cnt)
- throws IllegalInstructionException {
- final String countAndPassLabel = getUniqueLabel();
- final String skipLabel = getUniqueLabel();
- return addJumpIfR0AnyBitsSet(val, countAndPassLabel)
- .addJump(skipLabel)
- .defineLabel(countAndPassLabel)
- .addCountAndPass(cnt)
- .defineLabel(skipLabel);
- }
-
- @Override
- public final Type addCountAndDropIfR0LessThan(long val, ApfCounterTracker.Counter cnt)
- throws IllegalInstructionException {
- if (val <= 0) {
- throw new IllegalArgumentException("val must > 0, current val: " + val);
- }
- final String tgt = getUniqueLabel();
- return addJumpIfR0GreaterThan(val - 1, tgt).addCountAndDrop(cnt).defineLabel(tgt);
- }
-
- @Override
- public final Type addCountAndPassIfR0LessThan(long val, ApfCounterTracker.Counter cnt)
- throws IllegalInstructionException {
- if (val <= 0) {
- throw new IllegalArgumentException("val must > 0, current val: " + val);
- }
- final String tgt = getUniqueLabel();
- return addJumpIfR0GreaterThan(val - 1, tgt).addCountAndPass(cnt).defineLabel(tgt);
- }
-
- @Override
- public Type addCountAndDropIfR0GreaterThan(long val, ApfCounterTracker.Counter cnt)
- throws IllegalInstructionException {
- if (val < 0 || val >= 4294967295L) {
- throw new IllegalArgumentException("val must >= 0 and < 2^32-1, current val: " + val);
- }
- final String tgt = getUniqueLabel();
- return addJumpIfR0LessThan(val + 1, tgt).addCountAndDrop(cnt).defineLabel(tgt);
- }
-
- @Override
- public Type addCountAndPassIfR0GreaterThan(long val, ApfCounterTracker.Counter cnt)
- throws IllegalInstructionException {
- if (val < 0 || val >= 4294967295L) {
- throw new IllegalArgumentException("val must >= 0 and < 2^32-1, current val: " + val);
- }
- final String tgt = getUniqueLabel();
- return addJumpIfR0LessThan(val + 1, tgt).addCountAndPass(cnt).defineLabel(tgt);
- }
-
- @Override
- public final Type addCountAndDropIfBytesAtR0NotEqual(byte[] bytes,
- ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
- final String tgt = getUniqueLabel();
- return addJumpIfBytesAtR0Equal(bytes, tgt).addCountAndDrop(cnt).defineLabel(tgt);
- }
-
- @Override
- public final Type addCountAndPassIfBytesAtR0NotEqual(byte[] bytes,
- ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
- final String tgt = getUniqueLabel();
- return addJumpIfBytesAtR0Equal(bytes, tgt).addCountAndPass(cnt).defineLabel(tgt);
- }
-
- @Override
- public Type addCountAndPassIfR0IsOneOf(@NonNull Set<Long> values,
- ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
- if (values.isEmpty()) {
- throw new IllegalArgumentException("values cannot be empty");
- }
- if (values.size() == 1) {
- return addCountAndPassIfR0Equals(values.iterator().next(), cnt);
- }
- final String tgt = getUniqueLabel();
- return addJumpIfNoneOf(R0, values, tgt).addCountAndPass(cnt).defineLabel(tgt);
- }
-
- @Override
- public Type addCountAndDropIfR0IsOneOf(@NonNull Set<Long> values,
- ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
- if (values.isEmpty()) {
- throw new IllegalArgumentException("values cannot be empty");
- }
- if (values.size() == 1) {
- return addCountAndDropIfR0Equals(values.iterator().next(), cnt);
- }
- final String tgt = getUniqueLabel();
- return addJumpIfNoneOf(R0, values, tgt).addCountAndDrop(cnt).defineLabel(tgt);
- }
-
- @Override
- public Type addCountAndPassIfR0IsNoneOf(@NonNull Set<Long> values,
- ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
- if (values.isEmpty()) {
- throw new IllegalArgumentException("values cannot be empty");
- }
- if (values.size() == 1) {
- return addCountAndPassIfR0NotEquals(values.iterator().next(), cnt);
- }
- final String tgt = getUniqueLabel();
- return addJumpIfOneOf(R0, values, tgt).addCountAndPass(cnt).defineLabel(tgt);
- }
-
- @Override
- public Type addCountAndDropIfBytesAtR0EqualsAnyOf(@NonNull List<byte[]> bytesList,
- ApfCounterTracker.Counter cnt)
- throws IllegalInstructionException {
- final String tgt = getUniqueLabel();
- return addJumpIfBytesAtR0EqualNoneOf(bytesList, tgt).addCountAndDrop(cnt).defineLabel(tgt);
- }
-
- @Override
- public Type addCountAndPassIfBytesAtR0EqualsAnyOf(@NonNull List<byte[]> bytesList,
- ApfCounterTracker.Counter cnt)
- throws IllegalInstructionException {
- final String tgt = getUniqueLabel();
- return addJumpIfBytesAtR0EqualNoneOf(bytesList, tgt).addCountAndPass(cnt).defineLabel(tgt);
- }
-
- @Override
- public Type addCountAndDropIfBytesAtR0EqualsNoneOf(@NonNull List<byte[]> bytesList,
- ApfCounterTracker.Counter cnt)
- throws IllegalInstructionException {
- final String tgt = getUniqueLabel();
- return addJumpIfBytesAtR0EqualsAnyOf(bytesList, tgt).addCountAndDrop(cnt).defineLabel(tgt);
- }
-
- @Override
- public Type addCountAndPassIfBytesAtR0EqualsNoneOf(@NonNull List<byte[]> bytesList,
- ApfCounterTracker.Counter cnt)
- throws IllegalInstructionException {
- final String tgt = getUniqueLabel();
- return addJumpIfBytesAtR0EqualsAnyOf(bytesList, tgt).addCountAndPass(cnt).defineLabel(tgt);
- }
-
- @Override
- public Type addCountAndDropIfR0IsNoneOf(@NonNull Set<Long> values,
- ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
- if (values.isEmpty()) {
- throw new IllegalArgumentException("values cannot be empty");
- }
- if (values.size() == 1) {
- return addCountAndDropIfR0NotEquals(values.iterator().next(), cnt);
- }
- final String tgt = getUniqueLabel();
- return addJumpIfOneOf(R0, values, tgt).addCountAndDrop(cnt).defineLabel(tgt);
- }
-
- @Override
public final Type addLoadCounter(Register register, ApfCounterTracker.Counter counter)
throws IllegalInstructionException {
return append(new Instruction(Opcodes.LDDW, register).addUnsigned(counter.value()));
@@ -812,4 +726,10 @@
public final Type addCountTrampoline() {
return self();
}
+
+ @Override
+ public final int getDefaultPacketHandlingSizeOverEstimate() {
+ // addCountAndPass(PASSED_IPV6_ICMP); -> 2 bytes
+ return 2;
+ }
}
diff --git a/src/android/net/apf/BaseApfGenerator.java b/src/android/net/apf/BaseApfGenerator.java
index 2eab5ab..21d8be3 100644
--- a/src/android/net/apf/BaseApfGenerator.java
+++ b/src/android/net/apf/BaseApfGenerator.java
@@ -21,15 +21,12 @@
import static android.net.apf.BaseApfGenerator.Register.R0;
import android.annotation.NonNull;
+import android.util.SparseArray;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.net.module.util.ByteUtils;
-import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.HexDump;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.List;
import java.util.Objects;
@@ -109,7 +106,33 @@
// 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);
+ PKTDATACOPY(25),
+ // JSET with reverse condition (jump if no bits set)
+ JNSET(26),
+ // APFv6.1: Compare byte sequence [R=0 not] equal, e.g. "jbsptrne 22,16,label,<dataptr>"
+ // imm1 is jmp target
+ // imm2(u8) is offset [0..255] into packet
+ // imm3(u8) is (count - 1) * 16 + (compare_len - 1), thus both count & compare_len are in
+ // [1..16] which is followed by compare_len u8 'even offset' ptrs into max 526 byte data
+ // section to compare against - ie. they are multipied by 2 and have 3 added to them
+ // (to skip over 'datajmp u16')
+ // Warning: do not specify the same byte sequence multiple times.
+ JBSPTRMATCH(27),
+ // APFv6.1: Bytecode optimized allocate | transmit instruction.
+ // R=1 -> allocate(266 + imm * 8)
+ // R=0 -> transmit
+ // immlen=0 -> no checksum offload (transmit ip_ofs=255)
+ // immlen>0 -> with checksum offload (transmit(udp) ip_ofs=14 ...)
+ // imm & 7 | type of offload | ip_ofs | udp | csum_start | csum_ofs | partial_csum |
+ // 0 | ip4/udp | 14 | X | 14+20-8 =26 | 14+20 +6=40 | imm >> 3 |
+ // 1 | ip4/tcp | 14 | | 14+20-8 =26 | 14+20 +10=44 | --"-- |
+ // 2 | ip4/icmp | 14 | | 14+20 =34 | 14+20 +2=36 | --"-- |
+ // 3 | ip4/routeralert/icmp | 14 | | 14+20+4 =38 | 14+20+4 +2=40 | --"-- |
+ // 4 | ip6/udp | 14 | X | 14+40-32=22 | 14+40 +6=60 | --"-- |
+ // 5 | ip6/tcp | 14 | | 14+40-32=22 | 14+40 +10=64 | --"-- |
+ // 6 | ip6/icmp | 14 | | 14+40-32=22 | 14+40 +2=56 | --"-- |
+ // 7 | ip6/routeralert/icmp | 14 | | 14+40-32=22 | 14+40+8 +2=64 | --"-- |
+ ALLOC_XMIT(28);
final int value;
@@ -191,11 +214,25 @@
// bottom 1 bit - =0 jmp if in set, =1 if not in set
// imm4(imm3 * 1/2/3/4 bytes): the *UNIQUE* values to compare against
JONEOF(47),
- /* Specify length of exception buffer, which is populated on abnormal program termination.
- * imm1: Extended opcode
- * imm2(u16): Length of exception buffer (located *immediately* after the program itself)
- */
- EXCEPTIONBUFFER(48);
+ // Specify length of exception buffer, which is populated on abnormal program termination.
+ // imm1: Extended opcode
+ // imm2(u16): Length of exception buffer (located *immediately* after the program itself)
+ EXCEPTIONBUFFER(48),
+ // Jumps if the UDP payload content (starting at R0) does [not] match one
+ // of the specified QNAMEs in question records, applying case insensitivity.
+ // The qtypes in the input packet can match either of the two supplied qtypes.
+ // SAFE version PASSES corrupt packets, while the other one DROPS.
+ // R=0/1 meaning 'does not match'/'matches'
+ // R0: Offset to UDP payload content
+ // imm1: Extended opcode
+ // imm2: Jump label offset
+ // imm3(u8): Question type1 (PTR/SRV/TXT/A/AAAA)
+ // imm4(u8): Question type2 (PTR/SRV/TXT/A/AAAA)
+ // imm5(bytes): null terminated list of null terminated LV-encoded QNAMEs
+ // e.g.: "jdnsqeq2 R0,label,A,AAAA,\002aa\005local\0\0",
+ // "jdnsqne2 R0,label,A,AAAA,\002aa\005local\0\0"
+ JDNSQMATCH2(51),
+ JDNSQMATCHSAFE2(53);
final int value;
@@ -353,9 +390,10 @@
// When mOpcode is a jump:
private int mTargetLabelSize;
private int mImmSizeOverride = -1;
- private String mTargetLabel;
- // When mOpcode == Opcodes.LABEL:
- private String mLabel;
+ // mTargetLabel == -1 indicates it is uninitialized. mTargetLabel < -1 indicates a label
+ // within the program used for offset calculation. mTargetLabel >= 0 indicates a pass/drop
+ // label, its offset is mTargetLabel + program size.
+ private short mTargetLabel = -1;
public byte[] mBytesImm;
// Offset in bytes from the beginning of this program.
// Set by {@link BaseApfGenerator#generate}.
@@ -457,19 +495,18 @@
return this;
}
- Instruction setLabel(String label) throws IllegalInstructionException {
- if (mLabels.containsKey(label)) {
+ Instruction setLabel(short label) throws IllegalInstructionException {
+ if (mLabels.get(label) != null) {
throw new IllegalInstructionException("duplicate label " + label);
}
if (mOpcode != Opcodes.LABEL) {
throw new IllegalStateException("adding label to non-label instruction");
}
- mLabel = label;
mLabels.put(label, this);
return this;
}
- Instruction setTargetLabel(String label) {
+ Instruction setTargetLabel(short label) {
mTargetLabel = label;
mTargetLabelSize = 4; // May shrink later on in generate().
return this;
@@ -485,15 +522,16 @@
return this;
}
- /**
- * Attempts to match {@code content} with existing data bytes. If not exist, then
- * append the {@code content} to the data bytes.
- * Returns the start offset of the content from the beginning of the program.
- */
- int maybeUpdateBytesImm(byte[] content) throws IllegalInstructionException {
+ int findMatchInDataBytes(@NonNull byte[] content, int fromIndex, int toIndex)
+ throws IllegalInstructionException {
+ if (fromIndex >= toIndex || fromIndex < 0 || toIndex > content.length) {
+ throw new IllegalArgumentException(
+ String.format("fromIndex: %d, toIndex: %d, content length: %d", fromIndex,
+ toIndex, content.length));
+ }
if (mOpcode != Opcodes.JMP || mBytesImm == null) {
throw new IllegalInstructionException(String.format(
- "maybeUpdateBytesImm() is only valid for jump data instruction, mOpcode "
+ "this method is only valid for jump data instruction, mOpcode "
+ ":%s, mBytesImm: %s", Opcodes.JMP,
mBytesImm == null ? "(empty)" : HexDump.toHexString(mBytesImm)));
}
@@ -501,10 +539,45 @@
throw new IllegalInstructionException(
"mImmSizeOverride must be 2, mImmSizeOverride: " + mImmSizeOverride);
}
- int offsetInDataBytes = CollectionUtils.indexOfSubArray(mBytesImm, content);
+ final int subArrayLength = toIndex - fromIndex;
+ for (int i = 0; i < mBytesImm.length - subArrayLength + 1; i++) {
+ boolean found = true;
+ for (int j = 0; j < subArrayLength; j++) {
+ if (mBytesImm[i + j] != content[fromIndex + j]) {
+ found = false;
+ break;
+ }
+ }
+ if (found) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private static byte[] concat(byte[] prefix, byte[] suffix, int suffixFrom, int suffixTo) {
+ final byte[] newArray = new byte[prefix.length + suffixTo - suffixFrom];
+ System.arraycopy(prefix, 0, newArray, 0, prefix.length);
+ System.arraycopy(suffix, suffixFrom, newArray, prefix.length, suffixTo - suffixFrom);
+ return newArray;
+ }
+
+ /**
+ * Manages and updates the data region.
+ * <p>
+ * Searches for the specified subarray within the existing data region. If the subarray
+ * is not found, it is appended to the data region. The subarray is defined as the
+ * portion of the {@code content} starting at {@code fromIndex} (inclusive)
+ * and ending at {@code toIndex} (exclusive).
+ * <p>
+ * @return The starting position of the subarray within the data region.
+ */
+ int maybeUpdateBytesImm(byte[] content, int fromIndex, int toIndex)
+ throws IllegalInstructionException {
+ int offsetInDataBytes = findMatchInDataBytes(content, fromIndex, toIndex);
if (offsetInDataBytes == -1) {
offsetInDataBytes = mBytesImm.length;
- mBytesImm = ByteUtils.concat(mBytesImm, content);
+ mBytesImm = concat(mBytesImm, content, fromIndex, toIndex);
// Update the length immediate (first imm) value. Due to mValue within
// IntImmediate being final, we must remove and re-add the value to apply changes.
mIntImms.remove(0);
@@ -543,7 +616,7 @@
for (IntImmediate imm : mIntImms) {
size += imm.getEncodingSize(indeterminateSize);
}
- if (mTargetLabel != null) {
+ if (mTargetLabel != -1) {
size += indeterminateSize;
}
if (mBytesImm != null) {
@@ -558,7 +631,7 @@
* @return {@code true} if shrunk.
*/
boolean shrink() throws IllegalInstructionException {
- if (mTargetLabel == null) {
+ if (mTargetLabel == -1) {
return false;
}
int oldTargetLabelSize = mTargetLabelSize;
@@ -618,7 +691,7 @@
writingOffset = mIntImms.get(startOffset++).writeValue(bytecode, writingOffset,
indeterminateSize);
}
- if (mTargetLabel != null) {
+ if (mTargetLabel != -1) {
writingOffset = writeValue(calculateTargetLabelOffset(), bytecode, writingOffset,
indeterminateSize);
}
@@ -667,20 +740,18 @@
}
private int calculateTargetLabelOffset() throws IllegalInstructionException {
- Instruction targetLabelInstruction;
- if (mTargetLabel == DROP_LABEL) {
- targetLabelInstruction = mDropLabel;
- } else if (mTargetLabel == PASS_LABEL) {
- targetLabelInstruction = mPassLabel;
+ int targetOffset;
+ if (mTargetLabel >= 0) {
+ targetOffset = mTotalSize + mTargetLabel;
} else {
- targetLabelInstruction = mLabels.get(mTargetLabel);
+ final Instruction targetLabelInstruction = mLabels.get(mTargetLabel);
+ if (targetLabelInstruction == null) {
+ throw new IllegalInstructionException("label not found: " + mTargetLabel);
+ }
+ targetOffset = targetLabelInstruction.offset;
}
- if (targetLabelInstruction == null) {
- throw new IllegalInstructionException("label not found: " + mTargetLabel);
- }
- // Calculate distance from end of this instruction to instruction.offset.
- final int targetLabelOffset = targetLabelInstruction.offset - (offset + size());
- return targetLabelOffset;
+ // Calculate distance from end of this instruction to targetOffset.
+ return targetOffset - (offset + size());
}
}
@@ -725,24 +796,12 @@
void checkPassCounterRange(ApfCounterTracker.Counter cnt) {
if (mDisableCounterRangeCheck) return;
- if (cnt.value() < ApfCounterTracker.MIN_PASS_COUNTER.value()
- || cnt.value() > ApfCounterTracker.MAX_PASS_COUNTER.value()) {
- throw new IllegalArgumentException(
- String.format("Counter %s, is not in range [%s, %s]", cnt,
- ApfCounterTracker.MIN_PASS_COUNTER,
- ApfCounterTracker.MAX_PASS_COUNTER));
- }
+ cnt.getJumpPassLabel();
}
void checkDropCounterRange(ApfCounterTracker.Counter cnt) {
if (mDisableCounterRangeCheck) return;
- if (cnt.value() < ApfCounterTracker.MIN_DROP_COUNTER.value()
- || cnt.value() > ApfCounterTracker.MAX_DROP_COUNTER.value()) {
- throw new IllegalArgumentException(
- String.format("Counter %s, is not in range [%s, %s]", cnt,
- ApfCounterTracker.MIN_DROP_COUNTER,
- ApfCounterTracker.MAX_DROP_COUNTER));
- }
+ cnt.getJumpDropLabel();
}
/**
@@ -758,6 +817,8 @@
*/
abstract void updateExceptionBufferSize(int programSize) throws IllegalInstructionException;
+ private int mTotalSize;
+
/**
* Generate the bytecode for the APF program.
* @return the bytecode.
@@ -771,7 +832,6 @@
throw new IllegalStateException("Can only generate() once!");
}
mGenerated = true;
- int total_size;
boolean shrunk;
// Shrink the immediate value fields of instructions.
// As we shrink the instructions some branch offset
@@ -781,10 +841,7 @@
// Limit iterations to avoid O(n^2) behavior.
int iterations_remaining = 10;
do {
- total_size = updateInstructionOffsets();
- // Update drop and pass label offsets.
- mDropLabel.offset = total_size + 1;
- mPassLabel.offset = total_size;
+ mTotalSize = updateInstructionOffsets();
// Limit run-time in aberant circumstances.
if (iterations_remaining-- == 0) break;
// Attempt to shrink instructions.
@@ -796,8 +853,8 @@
}
} while (shrunk);
// Generate bytecode for instructions.
- byte[] bytecode = new byte[total_size];
- updateExceptionBufferSize(total_size);
+ byte[] bytecode = new byte[mTotalSize];
+ updateExceptionBufferSize(mTotalSize);
for (Instruction instruction : mInstructions) {
instruction.generate(bytecode);
}
@@ -850,27 +907,30 @@
}
}
- private int mLabelCount = 0;
+ private short mLabelCount = 0;
/**
* Return a unique label string.
*/
- @VisibleForTesting
- public String getUniqueLabel() {
- return "LABEL_" + mLabelCount++;
+ public short getUniqueLabel() {
+ final short nextLabel = (short) -(2 + mLabelCount++);
+ if (nextLabel == Short.MIN_VALUE) {
+ throw new IllegalStateException("Running out of unique labels");
+ }
+ return nextLabel;
}
/**
* Jump to this label to terminate the program and indicate the packet
* should be dropped.
*/
- public static final String DROP_LABEL = "__DROP__";
+ public static final short DROP_LABEL = 1;
/**
* Jump to this label to terminate the program and indicate the packet
* should be passed to the AP.
*/
- public static final String PASS_LABEL = "__PASS__";
+ public static final short PASS_LABEL = 0;
/**
* Number of memory slots available for access via APF stores to memory and loads from memory.
@@ -952,12 +1012,12 @@
public static final int APF_VERSION_3 = 3;
public static final int APF_VERSION_4 = 4;
public static final int APF_VERSION_6 = 6000;
+ // TODO: update the version code once we finalized APFv6.1.
+ public static final int APF_VERSION_61 = 20250228;
final ArrayList<Instruction> mInstructions = new ArrayList<Instruction>();
- private final HashMap<String, Instruction> mLabels = new HashMap<String, Instruction>();
- private final Instruction mDropLabel = new Instruction(Opcodes.LABEL);
- private final Instruction mPassLabel = new Instruction(Opcodes.LABEL);
+ private final SparseArray<Instruction> mLabels = new SparseArray<>();
public final int mVersion;
public final int mRamSize;
public final int mClampSize;
diff --git a/src/android/net/apf/DnsUtils.java b/src/android/net/apf/DnsUtils.java
deleted file mode 100644
index ad5d20c..0000000
--- a/src/android/net/apf/DnsUtils.java
+++ /dev/null
@@ -1,414 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.apf;
-
-import static android.net.apf.BaseApfGenerator.MemorySlot;
-import static android.net.apf.BaseApfGenerator.Register.R0;
-import static android.net.apf.BaseApfGenerator.Register.R1;
-
-import static com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN;
-import static com.android.net.module.util.NetworkStackConstants.UDP_HEADER_LEN;
-
-import android.annotation.NonNull;
-
-/**
- * Utility class that generates generating APF filters for DNS packets.
- */
-public class DnsUtils {
-
- /** Length of the DNS header. */
- private static final int DNS_HEADER_LEN = 12;
- /** Offset of the qdcount field within the DNS header. */
- private static final int DNS_QDCOUNT_OFFSET = 4;
-
- // Static labels
- private static final String LABEL_START_MATCH = "start_match";
- private static final String LABEL_PARSE_DNS_LABEL = "parse_dns_label";
- private static final String LABEL_FIND_NEXT_DNS_QUESTION = "find_next_dns_question";
-
- // Length of the pointers used by compressed names.
- private static final int LABEL_SIZE = Byte.BYTES;
- private static final int POINTER_SIZE = Short.BYTES;
- private static final int QUESTION_HEADER_SIZE = Short.BYTES + Short.BYTES;
- private static final int LABEL_AND_QUESTION_HEADER_SIZE = LABEL_SIZE + QUESTION_HEADER_SIZE;
- private static final int POINTER_AND_QUESTION_HEADER_SIZE = POINTER_SIZE + QUESTION_HEADER_SIZE;
-
- /** Memory slot that stores the offset within the packet of the DNS header. */
- private static final MemorySlot SLOT_DNS_HEADER_OFFSET = MemorySlot.SLOT_1;
- /** Memory slot that stores the current parsing offset. */
- private static final MemorySlot SLOT_CURRENT_PARSE_OFFSET = MemorySlot.SLOT_2;
- /**
- * Memory slot that stores the offset after the current question, if the code is currently
- * parsing a pointer, or 0 if it is not.
- */
- private static final MemorySlot SLOT_AFTER_POINTER_OFFSET = MemorySlot.SLOT_3;
- /**
- * Contains qdcount remaining, as a negative number. For example, will be -1 when starting to
- * parse a DNS packet with one question in it. It's stored as a negative number because adding 1
- * is much easier than subtracting 1 (which can't be done just by adding -1, because that just
- * adds 254).
- */
- private static final MemorySlot SLOT_NEGATIVE_QDCOUNT_REMAINING = MemorySlot.SLOT_4;
- /** Memory slot used by the jump table. */
- private static final MemorySlot SLOT_RETURN_VALUE_INDEX = MemorySlot.SLOT_5;
-
- /**
- * APF function: parse_dns_label
- *
- * Parses a label potentially containing a pointer, and calculates the label length and the
- * offset of the label data.
- *
- * Inputs:
- * - m[SLOT_DNS_HEADER_OFFSET]: offset of DNS header
- * - m[SLOT_CURRENT_PARSE_OFFSET]: current parsing offset
- * - m[SLOT_AFTER_POINTER_OFFSET]: offset after the question (e.g., offset of the next question,
- * or offset of the answer section) if a pointer is being chased, 0 otherwise
- * - m[SLOT_RETURN_VALUE_INDEX]: index into return jump table
- *
- * Outputs:
- * - R1: label length
- * - m[SLOT_CURRENT_PARSE_OFFSET]: offset of label text
- */
- private static void genParseDnsLabel(ApfV4Generator gen, JumpTable jumpTable) throws Exception {
- final String labelParseDnsLabelReal = "parse_dns_label_real";
- final String labelPointerOffsetStored = "pointer_offset_stored";
-
- /**
- * :parse_dns_label
- * // Load parsing offset.
- * LDM R1, 2 // R1 = parsing offset. (All indexed loads use R1.)
- */
- gen.defineLabel(LABEL_PARSE_DNS_LABEL);
- gen.addLoadFromMemory(R1, SLOT_CURRENT_PARSE_OFFSET);
-
-
- /**
- * // Check that we’re in the DNS packet, i.e., that R1 >= m[SLOT_DNS_HEADER_OFFSET].
- * LDM R0, 1 // R0 = DNS header offset
- * JGT R0, R1, DROP // Bad pointer. Drop.
- */
- gen.addLoadFromMemory(R0, SLOT_DNS_HEADER_OFFSET);
- gen.addJumpIfR0GreaterThanR1(ApfV4Generator.DROP_LABEL);
-
- /**
- * // Now parse the label.
- * LDBX R0, [R1] // R0 = label length, R1 = parsing offset
- * AND R0, 0xc0 // Is this a pointer?
- *
- * JEQ R0, 0, :parse_dns_label_real
- */
- gen.addLoad8Indexed(R0, 0);
- gen.addAnd(0xc0);
- gen.addJumpIfR0Equals(0, labelParseDnsLabelReal);
-
-
- /**
- * // If we’re not already chasing a pointer, store offset after pointer into
- * // m[SLOT_AFTER_POINTER_OFFSET].
- * LDM R0, 3 // R0 = previous offset after pointer
- * JNE 0, :pointer_offset_stored
- * MOV R0, R1 // R0 = R1
- * ADD R0, 6 // R0 = offset after pointer and record
- * STM R0, 3 // Store offset after pointer
- */
- gen.addLoadFromMemory(R0, SLOT_AFTER_POINTER_OFFSET);
- gen.addJumpIfR0NotEquals(0, labelPointerOffsetStored);
- gen.addMove(R0);
- gen.addAdd(POINTER_AND_QUESTION_HEADER_SIZE);
- gen.addStoreToMemory(SLOT_AFTER_POINTER_OFFSET, R0);
-
- /**
- * :pointer_offset_stored
- * LDHX R0, [R1] // R0 = 2-byte pointer value
- * AND R0, 0x3ff // R0 = pointer destination offset (from DNS header)
- * LDM R1, 1 // R1 = offset in packet of DNS header
- * ADD R0, R1 // R0 = pointer destination offset
- * LDM R1, 2 // R1 = current parsing offset
- * JEQ R0, R1, DROP // Drop if pointer points here...
- * JGT R0, R1, DROP // ... or after here (must point backwards)
- * STM R0, 2 // Set next parsing offset to pointer destination
- */
- gen.defineLabel(labelPointerOffsetStored);
- gen.addLoad16Indexed(R0, 0);
- gen.addAnd(0x3ff);
- gen.addLoadFromMemory(R1, SLOT_DNS_HEADER_OFFSET);
- gen.addAddR1ToR0();
- gen.addLoadFromMemory(R1, SLOT_CURRENT_PARSE_OFFSET);
- gen.addJumpIfR0EqualsR1(ApfV4Generator.DROP_LABEL);
- gen.addJumpIfR0GreaterThanR1(ApfV4Generator.DROP_LABEL);
- gen.addStoreToMemory(SLOT_CURRENT_PARSE_OFFSET, R0);
-
- /** // Pointer chased. Parse starting from the pointer destination (which may also be a
- * pointer).
- * JMP :parse_dns_label
- */
- gen.addJump(LABEL_PARSE_DNS_LABEL);
-
- /**
- * :parse_real_label
- * // This is where the real (non-pointer) label starts.
- * // Load label length into R1, and return to caller.
- * // m[SLOT_CURRENT_PARSE_OFFSET] already contains label offset.
- * LDHX R1, [R1] // R1 = label length
- */
- gen.defineLabel(labelParseDnsLabelReal);
- gen.addLoad8Indexed(R1, 0);
-
- /** // Return
- * LDM R0, 10
- * JMP :jump_table
- */
- gen.addLoadFromMemory(R0, SLOT_RETURN_VALUE_INDEX);
- gen.addJump(jumpTable.getStartLabel());
- }
-
- /**
- * APF function: find_next_dns_question
- *
- * Finds the next question in the question section, or drops the packet if there is none.
- *
- * Inputs:
- * - m[SLOT_CURRENT_PARSE_OFFSET]: current parsing offset
- * - m[SLOT_AFTER_POINTER_OFFSET]: offset after first pointer in name, or 0 if not chasing a
- * pointer
- * - m[SLOT_NEGATIVE_QDCOUNT_REMAINING]: qdcount remaining, as a negative number. This is
- * because adding 1 is much easier than subtracting 1 (which can't be done just by
- * adding -1, because that just adds 254)
- * - m[SLOT_RETURN_VALUE_INDEX]: index into return jump table
- *
- * Outputs:
- * None
- */
- private static void genFindNextDnsQuestion(ApfV4Generator gen, JumpTable jumpTable)
- throws Exception {
- final String labelFindNextDnsQuestionFollow = "find_next_dns_question_follow";
- final String labelFindNextDnsQuestionLabel = "find_next_dns_question_label";
- final String labelFindNextDnsQuestionLoop = "find_next_dns_question_loop";
- final String labelFindNextDnsQuestionNoPointer = "find_next_dns_question_no_pointer";
- final String labelFindNextDnsQuestionReturn = "find_next_dns_question_return";
-
- // Function entry point.
- gen.defineLabel(LABEL_FIND_NEXT_DNS_QUESTION);
-
- // Are we chasing a pointer?
- gen.addLoadFromMemory(R0, SLOT_AFTER_POINTER_OFFSET);
- gen.addJumpIfR0Equals(0, labelFindNextDnsQuestionFollow);
-
- // If so, offset after the pointer and question is stored in m[SLOT_AFTER_POINTER_OFFSET].
- // Move parsing offset there, clear m[SLOT_AFTER_POINTER_OFFSET], and return.
- gen.addStoreToMemory(SLOT_CURRENT_PARSE_OFFSET, R0);
- gen.addLoadImmediate(R0, 0);
- gen.addStoreToMemory(SLOT_AFTER_POINTER_OFFSET, R0);
- gen.addJump(labelFindNextDnsQuestionReturn);
-
- // We weren't chasing a pointer. Loop, following the label chain, until we reach a
- // zero-length label or a pointer. At the beginning of the loop, the current parsing offset
- // is m[SLOT_CURRENT_PARSE_OFFSET]. Move it to R1 and keep it in R1 throughout the loop.
- gen.defineLabel(labelFindNextDnsQuestionFollow);
- gen.addLoadFromMemory(R1, SLOT_CURRENT_PARSE_OFFSET);
-
- // Load label length.
- gen.defineLabel(labelFindNextDnsQuestionLoop);
- gen.addLoad8Indexed(R0, 0);
- // Is it a pointer?
- gen.addAnd(0xc0);
- gen.addJumpIfR0Equals(0, labelFindNextDnsQuestionNoPointer);
- // It's a pointer. Skip the pointer and question, and return.
- gen.addLoadImmediate(R0, POINTER_AND_QUESTION_HEADER_SIZE);
- gen.addAddR1ToR0();
- gen.addStoreToMemory(SLOT_CURRENT_PARSE_OFFSET, R0);
- gen.addJump(labelFindNextDnsQuestionReturn);
-
- // R1 still contains parsing offset.
- gen.defineLabel(labelFindNextDnsQuestionNoPointer);
- gen.addLoad8Indexed(R0, 0);
-
- // Zero-length label? We're done.
- // Skip the label (1 byte) and query (2 bytes qtype, 2 bytes qclass) and return.
- gen.addJumpIfR0NotEquals(0, labelFindNextDnsQuestionLabel);
- gen.addLoadImmediate(R0, LABEL_AND_QUESTION_HEADER_SIZE);
- gen.addAddR1ToR0();
- gen.addStoreToMemory(SLOT_CURRENT_PARSE_OFFSET, R0);
- gen.addJump(labelFindNextDnsQuestionReturn);
-
- // Non-zero length label. Consume it and continue.
- gen.defineLabel(labelFindNextDnsQuestionLabel);
- gen.addAdd(1);
- gen.addAddR1ToR0();
- gen.addMove(R1);
- gen.addJump(labelFindNextDnsQuestionLoop);
-
- gen.defineLabel(labelFindNextDnsQuestionReturn);
-
- // Is this the last question? If so, drop.
- gen.addLoadFromMemory(R0, SLOT_NEGATIVE_QDCOUNT_REMAINING);
- gen.addAdd(1);
- gen.addStoreToMemory(SLOT_NEGATIVE_QDCOUNT_REMAINING, R0);
- gen.addJumpIfR0Equals(0, ApfV4Generator.DROP_LABEL);
-
- // If not, return.
- gen.addJump(jumpTable.getStartLabel());
- }
-
- /** @return jump label that points to the start of a DNS label's parsing code. */
- private static String getStartMatchLabel(int labelIndex) {
- return "dns_parse_" + labelIndex;
- }
-
- /** @return jump label used while parsing the specified DNS label. */
- private static String getPostMatchJumpTargetForLabel(int labelIndex) {
- return "dns_parsed_" + labelIndex;
- }
-
- /** @return jump label used when the match for the specified DNS label fails. */
- private static String getNoMatchLabel(int labelIndex) {
- return "dns_nomatch_" + labelIndex;
- }
-
- private static void addMatchLabel(@NonNull ApfV4Generator gen, @NonNull JumpTable jumpTable,
- int labelIndex, @NonNull String label, @NonNull String nextLabel) throws Exception {
- final String parsedLabel = getPostMatchJumpTargetForLabel(labelIndex);
- final String noMatchLabel = getNoMatchLabel(labelIndex);
- gen.defineLabel(getStartMatchLabel(labelIndex));
-
- // Store return address.
- gen.addLoadImmediate(R0, jumpTable.getIndex(parsedLabel));
- gen.addStoreToMemory(SLOT_RETURN_VALUE_INDEX, R0);
-
- // Call the parse_label function.
- gen.addJump(LABEL_PARSE_DNS_LABEL);
-
- gen.defineLabel(parsedLabel);
-
- // If label length is 0, this is the end of the name and the match failed.
- gen.addSwap(); // Move label length from R1 to R0
- gen.addJumpIfR0Equals(0, noMatchLabel);
-
- // Label parsed, check it matches what we're looking for.
- gen.addJumpIfR0NotEquals(label.length(), noMatchLabel);
- gen.addLoadFromMemory(R0, SLOT_CURRENT_PARSE_OFFSET);
- gen.addAdd(1);
- gen.addJumpIfBytesAtR0NotEqual(label.getBytes(), noMatchLabel);
-
- // Prep offset of next label.
- gen.addAdd(label.length());
- gen.addStoreToMemory(SLOT_CURRENT_PARSE_OFFSET, R0);
-
- // Match, go to next label.
- gen.addJump(nextLabel);
-
- // Match failed. Go to next name, and restart from the first match.
- gen.defineLabel(noMatchLabel);
- gen.addLoadImmediate(R1, jumpTable.getIndex(LABEL_START_MATCH));
- gen.addStoreToMemory(SLOT_RETURN_VALUE_INDEX, R1);
- gen.addJump(LABEL_FIND_NEXT_DNS_QUESTION);
- }
-
- /**
- * Generates a filter that accepts DNS packet that ask for the specified name.
- *
- * The filter supports compressed DNS names and scanning through multiple questions in the same
- * packet, e.g., as used by MDNS. However, it currently only supports one DNS name.
- *
- * Limitations:
- * <ul>
- * <li>Filter size is just under 300 bytes for a typical question.
- * <li>Because the bytecode extensively uses backwards jumps, it can hit the APF interpreter
- * instruction limit. This limit causes the APF interpreter to accept the packet once it has
- * executed a number of instructions equal to the program length in bytes.
- * A program that consists *only* of this filter will be able to execute just under 300
- * instructions, and will be able to correctly drop packets with two questions but not three
- * questions. In a real APF setup, there will be other code (e.g., RA filtering) which counts
- * against the limit, so the filter should be able to parse packets with more questions.
- * <li>Matches are case-sensitive. This is due to the use of JNEBS to match DNS labels and is
- * likely impossible to overcome without interpreter changes.
- * </ul>
- *
- * TODO:
- * <ul>
- * <li>Add unit tests for the parse_dns_label and find_next_dns_question functions.
- * <li>Support accepting more than one name.
- * <li>For devices where power saving is a priority (e.g., flat panel TVs), add support for
- * dropping packets with more than X queries, to ensure the filter will drop the packet rather
- * than hit the instruction limit.
- * </ul>
- */
- public static void generateFilter(ApfV4Generator gen, String[] labels) throws Exception {
- final int etherPlusUdpLen = ETHER_HEADER_LEN + UDP_HEADER_LEN;
-
- final String labelJumpTable = "jump_table";
-
- // Initialize parsing
- /**
- * - R1: length of IP header.
- * - m[SLOT_DNS_HEADER_OFFSET]: offset of DNS header
- * - m[SLOT_CURRENT_PARSE_OFFSET]: current parsing offset (start of question section)
- * - m[SLOT_AFTER_POINTER_OFFSET]: offset after first pointer in name, must be 0 when
- * starting a new name
- * - m[SLOT_NEGATIVE_QDCOUNT_REMAINING]: negative qdcount
- */
- // Move IP header length to R0 and use it to find the DNS header offset.
- // TODO: this uses R1 for consistency with ApfFilter#generateMdnsFilterLocked. Evaluate
- // using R0 instead.
- gen.addMove(R0);
- gen.addAdd(etherPlusUdpLen);
- gen.addStoreToMemory(SLOT_DNS_HEADER_OFFSET, R0);
-
- gen.addAdd(DNS_QDCOUNT_OFFSET);
- gen.addMove(R1);
- gen.addLoad16Indexed(R1, 0);
- gen.addNeg(R1);
- gen.addStoreToMemory(SLOT_NEGATIVE_QDCOUNT_REMAINING, R1);
-
- gen.addAdd(DNS_HEADER_LEN - DNS_QDCOUNT_OFFSET);
- gen.addStoreToMemory(SLOT_CURRENT_PARSE_OFFSET, R0);
-
- gen.addLoadImmediate(R0, 0);
- gen.addStoreToMemory(SLOT_AFTER_POINTER_OFFSET, R0);
-
- gen.addJump(LABEL_START_MATCH);
-
- // Create JumpTable but
- final JumpTable table = new JumpTable(labelJumpTable, SLOT_RETURN_VALUE_INDEX);
-
- // Generate bytecode for parse_label function.
- genParseDnsLabel(gen, table);
- genFindNextDnsQuestion(gen, table);
-
- // Populate jump table. Should be before the code that calls to it (i.e., the addMatchLabel
- // calls below) because otherwise all the jumps are backwards, and backwards jumps are more
- // expensive (5 bytes of bytecode)
- for (int i = 0; i < labels.length; i++) {
- table.addLabel(getPostMatchJumpTargetForLabel(i));
- }
- table.addLabel(LABEL_START_MATCH);
- table.generate(gen);
-
- // Add match statements for name.
- gen.defineLabel(LABEL_START_MATCH);
- for (int i = 0; i < labels.length; i++) {
- final String nextLabel = (i == labels.length - 1)
- ? ApfV4Generator.PASS_LABEL
- : getStartMatchLabel(i + 1);
- addMatchLabel(gen, table, i, labels[i], nextLabel);
- }
- gen.addJump(ApfV4Generator.DROP_LABEL);
- }
-
- private DnsUtils() {
- }
-}
diff --git a/src/android/net/apf/JumpTable.java b/src/android/net/apf/JumpTable.java
deleted file mode 100644
index 367c901..0000000
--- a/src/android/net/apf/JumpTable.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.apf;
-
-import static android.net.apf.BaseApfGenerator.MemorySlot;
-import static android.net.apf.BaseApfGenerator.Register.R0;
-
-import android.annotation.NonNull;
-
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.Objects;
-
-/**
- * A table that stores program labels to jump to.
- *
- * This is needed to implement subroutines because APF jump targets must be known at compile
- * time and cannot be computed dynamically.
- *
- * At compile time, any code that calls a subroutine must:
- *
- * <ul>
- * <li>Define a label (via {@link ApfV4Generator#defineLabel}) immediately after the code that
- * invokes the subroutine.
- * <li>Add the label to the jump table using {@link #addLabel}.
- * <li>Generate the jump table in the program.
- * </ul>
- *
- * <p>At runtime, before invoking the subroutine, the APF code must store the index of the return
- * label (obtained via {@link #getIndex}) into the jump table's return address memory slot, and then
- * jump to the subroutine. To return to the caller, the subroutine must jump to the label returned
- * by {@link #getStartLabel}, and the jump table will then jump to the return label.
- *
- * <p>Implementation details:
- * <ul>
- * <li>The jumps are added to the program in the same order as the labels were added.
- * <li>Using the jump table will overwrite the value of register R0.
- * <li>If, before calling a subroutine, the APF code stores a nonexistent return label index, then
- * the jump table will pass the packet. This cannot happen if the code correctly obtains the
- * label using {@link #getIndex}, as that would throw an exception when generating the program.
- * </ul>
- *
- * For example:
- * <pre>
- * JumpTable t = new JumpTable("my_jump_table", 7);
- * t.addLabel("jump_1");
- * ...
- * t.addLabel("after_parsing");
- * ...
- * t.addLabel("after_subroutine");
- * t.generate(gen);
- *</pre>
- * generates the following APF code:
- * <pre>
- * :my_jump_table
- * ldm r0, 7
- * jeq r0, 0, jump_1
- * jeq r0, 1, after_parsing
- * jeq r0, 2, after_subroutine
- * jmp DROP
- * </pre>
- */
-public class JumpTable {
- /** Maps jump indices to jump labels. LinkedHashMap guarantees iteration in insertion order. */
- private final Map<String, Integer> mJumpLabels = new LinkedHashMap<>();
- /** Label to jump to to execute this jump table. */
- private final String mStartLabel;
- /** Memory slot that contains the return value index. */
- private final MemorySlot mReturnAddressMemorySlot;
-
- private int mIndex = 0;
-
- public JumpTable(@NonNull String startLabel, MemorySlot returnAddressMemorySlot) {
- Objects.requireNonNull(startLabel);
- mStartLabel = startLabel;
- if (returnAddressMemorySlot.value < 0
- || returnAddressMemorySlot.value >= MemorySlot.FIRST_PREFILLED.value) {
- throw new IllegalArgumentException(
- "Invalid memory slot " + returnAddressMemorySlot.value);
- }
- mReturnAddressMemorySlot = returnAddressMemorySlot;
- }
-
- /** Returns the label to jump to to start executing the table. */
- @NonNull
- public String getStartLabel() {
- return mStartLabel;
- }
-
- /**
- * Adds a jump label to this table. Passing a label that was already added is not an error.
- *
- * @param label the label to add
- */
- public void addLabel(@NonNull String label) {
- Objects.requireNonNull(label);
- if (mJumpLabels.putIfAbsent(label, mIndex) == null) mIndex++;
- }
-
- /**
- * Gets the index of a previously-added label.
- * @return the label's index.
- * @throws NoSuchElementException if the label was never added.
- */
- public int getIndex(@NonNull String label) {
- final Integer index = mJumpLabels.get(label);
- if (index == null) throw new NoSuchElementException("Unknown label " + label);
- return index;
- }
-
- /** Generates APF code for this jump table */
- public void generate(@NonNull ApfV4Generator gen)
- throws ApfV4Generator.IllegalInstructionException {
- gen.defineLabel(mStartLabel);
- gen.addLoadFromMemory(R0, mReturnAddressMemorySlot);
- for (Map.Entry<String, Integer> e : mJumpLabels.entrySet()) {
- gen.addJumpIfR0Equals(e.getValue(), e.getKey());
- }
- // Cannot happen unless the program is malformed (i.e., the APF code loads an invalid return
- // label index before jumping to the subroutine.
- gen.addJump(ApfV4Generator.PASS_LABEL);
- }
-}
diff --git a/src/android/net/apf/LegacyApfFilter.java b/src/android/net/apf/LegacyApfFilter.java
deleted file mode 100644
index 2cd0eec..0000000
--- a/src/android/net/apf/LegacyApfFilter.java
+++ /dev/null
@@ -1,2439 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.apf;
-
-import static android.net.apf.BaseApfGenerator.MemorySlot;
-import static android.net.apf.BaseApfGenerator.Register.R0;
-import static android.net.apf.BaseApfGenerator.Register.R1;
-import static android.net.util.SocketUtils.makePacketSocketAddress;
-import static android.system.OsConstants.AF_PACKET;
-import static android.system.OsConstants.ETH_P_ARP;
-import static android.system.OsConstants.ETH_P_IP;
-import static android.system.OsConstants.ETH_P_IPV6;
-import static android.system.OsConstants.IPPROTO_ICMPV6;
-import static android.system.OsConstants.IPPROTO_TCP;
-import static android.system.OsConstants.IPPROTO_UDP;
-import static android.system.OsConstants.SOCK_RAW;
-
-import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST;
-import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
-import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
-import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
-import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION;
-import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_LEN;
-
-import android.annotation.Nullable;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.LinkAddress;
-import android.net.LinkProperties;
-import android.net.NattKeepalivePacketDataParcelable;
-import android.net.TcpKeepalivePacketDataParcelable;
-import android.net.apf.ApfCounterTracker.Counter;
-import android.net.apf.BaseApfGenerator.IllegalInstructionException;
-import android.net.ip.IpClient.IpClientCallbacksWrapper;
-import android.net.metrics.ApfProgramEvent;
-import android.net.metrics.ApfStats;
-import android.net.metrics.IpConnectivityLog;
-import android.net.metrics.RaEvent;
-import android.os.PowerManager;
-import android.os.SystemClock;
-import android.stats.connectivity.NetworkQuirkEvent;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.text.format.DateUtils;
-import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.HexDump;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.net.module.util.CollectionUtils;
-import com.android.net.module.util.ConnectivityUtils;
-import com.android.net.module.util.InterfaceParams;
-import com.android.net.module.util.SocketUtils;
-import com.android.networkstack.metrics.ApfSessionInfoMetrics;
-import com.android.networkstack.metrics.IpClientRaInfoMetrics;
-import com.android.networkstack.metrics.NetworkQuirkMetrics;
-import com.android.networkstack.util.NetworkStackUtils;
-
-import java.io.ByteArrayOutputStream;
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.SocketAddress;
-import java.net.SocketException;
-import java.net.UnknownHostException;
-import java.nio.BufferUnderflowException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-
-/**
- * For networks that support packet filtering via APF programs, {@code ApfFilter}
- * listens for IPv6 ICMPv6 router advertisements (RAs) and generates APF programs to
- * filter out redundant duplicate ones.
- *
- * Threading model:
- * A collection of RAs we've received is kept in mRas. Generating APF programs uses mRas to
- * know what RAs to filter for, thus generating APF programs is dependent on mRas.
- * mRas can be accessed by multiple threads:
- * - ReceiveThread, which listens for RAs and adds them to mRas, and generates APF programs.
- * - callers of:
- * - setMulticastFilter(), which can cause an APF program to be generated.
- * - dump(), which dumps mRas among other things.
- * - shutdown(), which clears mRas.
- * So access to mRas is synchronized.
- *
- * @hide
- */
-public class LegacyApfFilter implements AndroidPacketFilter {
-
- // Enums describing the outcome of receiving an RA packet.
- private static enum ProcessRaResult {
- MATCH, // Received RA matched a known RA
- DROPPED, // Received RA ignored due to MAX_RAS
- PARSE_ERROR, // Received RA could not be parsed
- ZERO_LIFETIME, // Received RA had 0 lifetime
- UPDATE_NEW_RA, // APF program updated for new RA
- UPDATE_EXPIRY // APF program updated for expiry
- }
-
- /**
- * When APFv4 is supported, loads R1 with the offset of the specified counter.
- */
- private void maybeSetupCounter(ApfV4Generator gen, Counter c) {
- if (hasDataAccess(mApfVersionSupported)) {
- gen.addLoadImmediate(R1, c.offset());
- }
- }
-
- // When APFv4 is supported, these point to the trampolines generated by emitEpilogue().
- // Otherwise, they're just aliases for PASS_LABEL and DROP_LABEL.
- private final String mCountAndPassLabel;
- private final String mCountAndDropLabel;
-
- /** 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();
- }
- }
-
- // Thread to listen for RAs.
- @VisibleForTesting
- public class ReceiveThread extends Thread {
- private final byte[] mPacket = new byte[1514];
- private final FileDescriptor mSocket;
- private final long mStart = mClock.elapsedRealtime();
-
- private int mReceivedRas = 0;
- private int mMatchingRas = 0;
- private int mDroppedRas = 0;
- private int mParseErrors = 0;
- private int mZeroLifetimeRas = 0;
- private int mProgramUpdates = 0;
-
- private volatile boolean mStopped;
-
- public ReceiveThread(FileDescriptor socket) {
- mSocket = socket;
- }
-
- public void halt() {
- mStopped = true;
- // Interrupts the read() call the thread is blocked in.
- SocketUtils.closeSocketQuietly(mSocket);
- }
-
- @Override
- public void run() {
- log("begin monitoring");
- while (!mStopped) {
- try {
- int length = Os.read(mSocket, mPacket, 0, mPacket.length);
- updateStats(processRa(mPacket, length));
- } catch (IOException|ErrnoException e) {
- if (!mStopped) {
- Log.e(TAG, "Read error", e);
- }
- }
- }
- logStats();
- }
-
- private void updateStats(ProcessRaResult result) {
- mReceivedRas++;
- switch(result) {
- case MATCH:
- mMatchingRas++;
- return;
- case DROPPED:
- mDroppedRas++;
- return;
- case PARSE_ERROR:
- mParseErrors++;
- return;
- case ZERO_LIFETIME:
- mZeroLifetimeRas++;
- return;
- case UPDATE_EXPIRY:
- mMatchingRas++;
- mProgramUpdates++;
- return;
- case UPDATE_NEW_RA:
- mProgramUpdates++;
- return;
- }
- }
-
- private void logStats() {
- final long nowMs = mClock.elapsedRealtime();
- synchronized (LegacyApfFilter.this) {
- final ApfStats stats = new ApfStats.Builder()
- .setReceivedRas(mReceivedRas)
- .setMatchingRas(mMatchingRas)
- .setDroppedRas(mDroppedRas)
- .setParseErrors(mParseErrors)
- .setZeroLifetimeRas(mZeroLifetimeRas)
- .setProgramUpdates(mProgramUpdates)
- .setDurationMs(nowMs - mStart)
- .setMaxProgramSize(mMaximumApfProgramSize)
- .setProgramUpdatesAll(mNumProgramUpdates)
- .setProgramUpdatesAllowingMulticast(mNumProgramUpdatesAllowingMulticast)
- .build();
- mMetricsLog.log(stats);
- logApfProgramEventLocked(nowMs / DateUtils.SECOND_IN_MILLIS);
- }
- }
- }
-
- private static final String TAG = "ApfFilter";
- private static final boolean DBG = true;
- private static final boolean VDBG = false;
-
- private static final int ETH_HEADER_LEN = 14;
- private static final int ETH_DEST_ADDR_OFFSET = 0;
- private static final int ETH_ETHERTYPE_OFFSET = 12;
- private static final int ETH_TYPE_MIN = 0x0600;
- private static final int ETH_TYPE_MAX = 0xFFFF;
- // TODO: Make these offsets relative to end of link-layer header; don't include ETH_HEADER_LEN.
- private static final int IPV4_TOTAL_LENGTH_OFFSET = ETH_HEADER_LEN + 2;
- private static final int IPV4_FRAGMENT_OFFSET_OFFSET = ETH_HEADER_LEN + 6;
- // Endianness is not an issue for this constant because the APF interpreter always operates in
- // network byte order.
- private static final int IPV4_FRAGMENT_OFFSET_MASK = 0x1fff;
- private static final int IPV4_PROTOCOL_OFFSET = ETH_HEADER_LEN + 9;
- private static final int IPV4_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 16;
- private static final int IPV4_ANY_HOST_ADDRESS = 0;
- private static final int IPV4_BROADCAST_ADDRESS = -1; // 255.255.255.255
- private static final int IPV4_HEADER_LEN = 20; // Without options
-
- // Traffic class and Flow label are not byte aligned. Luckily we
- // don't care about either value so we'll consider bytes 1-3 of the
- // IPv6 header as don't care.
- private static final int IPV6_FLOW_LABEL_OFFSET = ETH_HEADER_LEN + 1;
- private static final int IPV6_FLOW_LABEL_LEN = 3;
- private static final int IPV6_NEXT_HEADER_OFFSET = ETH_HEADER_LEN + 6;
- private static final int IPV6_SRC_ADDR_OFFSET = ETH_HEADER_LEN + 8;
- private static final int IPV6_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 24;
- private static final int IPV6_HEADER_LEN = 40;
- // The IPv6 all nodes address ff02::1
- private static final byte[] IPV6_ALL_NODES_ADDRESS =
- { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
-
- private static final int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN;
-
- private static final int IPPROTO_HOPOPTS = 0;
-
- // NOTE: this must be added to the IPv4 header length in MemorySlot.IPV4_HEADER_SIZE
- private static final int UDP_DESTINATION_PORT_OFFSET = ETH_HEADER_LEN + 2;
- private static final int UDP_HEADER_LEN = 8;
-
- private static final int TCP_HEADER_SIZE_OFFSET = 12;
-
- private static final int DHCP_CLIENT_PORT = 68;
- // NOTE: this must be added to the IPv4 header length in MemorySlot.IPV4_HEADER_SIZE
- private static final int DHCP_CLIENT_MAC_OFFSET = ETH_HEADER_LEN + UDP_HEADER_LEN + 28;
-
- private static final int ARP_HEADER_OFFSET = ETH_HEADER_LEN;
- private static final byte[] ARP_IPV4_HEADER = {
- 0, 1, // Hardware type: Ethernet (1)
- 8, 0, // Protocol type: IP (0x0800)
- 6, // Hardware size: 6
- 4, // Protocol size: 4
- };
- private static final int ARP_OPCODE_OFFSET = ARP_HEADER_OFFSET + 6;
- // Opcode: ARP request (0x0001), ARP reply (0x0002)
- private static final short ARP_OPCODE_REQUEST = 1;
- private static final short ARP_OPCODE_REPLY = 2;
- private static final int ARP_SOURCE_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 14;
- private static final int ARP_TARGET_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 24;
- // Do not log ApfProgramEvents whose actual lifetimes was less than this.
- private static final int APF_PROGRAM_EVENT_LIFETIME_THRESHOLD = 2;
- // Limit on the Black List size to cap on program usage for this
- // TODO: Select a proper max length
- private static final int APF_MAX_ETH_TYPE_BLACK_LIST_LEN = 20;
-
- private static final byte[] ETH_MULTICAST_MDNS_V4_MAC_ADDRESS =
- {(byte) 0x01, (byte) 0x00, (byte) 0x5e, (byte) 0x00, (byte) 0x00, (byte) 0xfb};
- private static final byte[] ETH_MULTICAST_MDNS_V6_MAC_ADDRESS =
- {(byte) 0x33, (byte) 0x33, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xfb};
- private static final int MDNS_PORT = 5353;
- private static final int DNS_HEADER_LEN = 12;
- private static final int DNS_QDCOUNT_OFFSET = 4;
- // NOTE: this must be added to the IPv4 header length in MemorySlot.IPV4_HEADER_SIZE, or the
- // IPv6 header length.
- private static final int MDNS_QDCOUNT_OFFSET =
- ETH_HEADER_LEN + UDP_HEADER_LEN + DNS_QDCOUNT_OFFSET;
- private static final int MDNS_QNAME_OFFSET =
- ETH_HEADER_LEN + UDP_HEADER_LEN + DNS_HEADER_LEN;
-
-
- public final int mApfVersionSupported;
- public final int mMaximumApfProgramSize;
- private final IpClientCallbacksWrapper mIpClientCallback;
- private final InterfaceParams mInterfaceParams;
- private final IpConnectivityLog mMetricsLog;
-
- @VisibleForTesting
- public byte[] mHardwareAddress;
- @VisibleForTesting
- public ReceiveThread mReceiveThread;
- @GuardedBy("this")
- private long mUniqueCounter;
- @GuardedBy("this")
- private boolean mMulticastFilter;
- @GuardedBy("this")
- private boolean mInDozeMode;
- private final boolean mDrop802_3Frames;
- private final int[] mEthTypeBlackList;
-
- private final ApfCounterTracker mApfCounterTracker = new ApfCounterTracker();
- @GuardedBy("this")
- private long mSessionStartMs = 0;
- @GuardedBy("this")
- private int mNumParseErrorRas = 0;
- @GuardedBy("this")
- private int mNumZeroLifetimeRas = 0;
- @GuardedBy("this")
- private int mLowestRouterLifetimeSeconds = Integer.MAX_VALUE;
- @GuardedBy("this")
- private long mLowestPioValidLifetimeSeconds = Long.MAX_VALUE;
- @GuardedBy("this")
- private long mLowestRioRouteLifetimeSeconds = Long.MAX_VALUE;
- @GuardedBy("this")
- private long mLowestRdnssLifetimeSeconds = Long.MAX_VALUE;
-
- // Ignore non-zero RDNSS lifetimes below this value.
- private final int mMinRdnssLifetimeSec;
-
- // Minimum session time for metrics, duration less than this time will not be logged.
- private final long mMinMetricsSessionDurationMs;
-
- private final Clock mClock;
- private final NetworkQuirkMetrics mNetworkQuirkMetrics;
- private final IpClientRaInfoMetrics mIpClientRaInfoMetrics;
- private final ApfSessionInfoMetrics mApfSessionInfoMetrics;
-
- // Detects doze mode state transitions.
- private final BroadcastReceiver mDeviceIdleReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (action.equals(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)) {
- PowerManager powerManager =
- (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- final boolean deviceIdle = powerManager.isDeviceIdleMode();
- setDozeMode(deviceIdle);
- }
- }
- };
- private final Context mContext;
-
- // Our IPv4 address, if we have just one, otherwise null.
- @GuardedBy("this")
- private byte[] mIPv4Address;
- // The subnet prefix length of our IPv4 network. Only valid if mIPv4Address is not null.
- @GuardedBy("this")
- private int mIPv4PrefixLength;
-
- // mIsRunning is reflects the state of the LegacyApfFilter during integration tests.
- // LegacyApfFilter can be paused using "adb shell cmd apf <iface> <cmd>" commands. A paused
- // LegacyApfFilter will not install any new programs, but otherwise operates normally.
- private volatile boolean mIsRunning = true;
-
- private final ApfFilter.Dependencies mDependencies;
-
- @VisibleForTesting
- public LegacyApfFilter(Context context, ApfFilter.ApfConfiguration config,
- InterfaceParams ifParams, IpClientCallbacksWrapper ipClientCallback,
- IpConnectivityLog log, NetworkQuirkMetrics networkQuirkMetrics) {
- this(context, config, ifParams, ipClientCallback, log, networkQuirkMetrics,
- new ApfFilter.Dependencies(context), new Clock());
- }
-
- @VisibleForTesting
- public LegacyApfFilter(Context context, ApfFilter.ApfConfiguration config,
- InterfaceParams ifParams, IpClientCallbacksWrapper ipClientCallback,
- IpConnectivityLog log, NetworkQuirkMetrics networkQuirkMetrics,
- ApfFilter.Dependencies dependencies, Clock clock) {
- mApfVersionSupported = config.apfVersionSupported;
- mMaximumApfProgramSize = config.apfRamSize;
- mIpClientCallback = ipClientCallback;
- mInterfaceParams = ifParams;
- mMulticastFilter = config.multicastFilter;
- mDrop802_3Frames = config.ieee802_3Filter;
- mMinRdnssLifetimeSec = config.minRdnssLifetimeSec;
- mContext = context;
- mClock = clock;
- mDependencies = dependencies;
- mNetworkQuirkMetrics = networkQuirkMetrics;
- mIpClientRaInfoMetrics = dependencies.getIpClientRaInfoMetrics();
- mApfSessionInfoMetrics = dependencies.getApfSessionInfoMetrics();
- mSessionStartMs = mClock.elapsedRealtime();
- mMinMetricsSessionDurationMs = config.minMetricsSessionDurationMs;
-
- if (hasDataAccess(mApfVersionSupported)) {
- mCountAndPassLabel = "countAndPass";
- mCountAndDropLabel = "countAndDrop";
- } else {
- // APFv4 unsupported: turn jumps to the counter trampolines to immediately PASS or DROP,
- // preserving the original pre-APFv4 behavior.
- mCountAndPassLabel = ApfV4Generator.PASS_LABEL;
- mCountAndDropLabel = ApfV4Generator.DROP_LABEL;
- }
-
- // Now fill the black list from the passed array
- mEthTypeBlackList = filterEthTypeBlackList(config.ethTypeBlackList);
-
- mMetricsLog = log;
-
- // TODO: ApfFilter should not generate programs until IpClient sends provisioning success.
- maybeStartFilter();
-
- // Listen for doze-mode transition changes to enable/disable the IPv6 multicast filter.
- mContext.registerReceiver(mDeviceIdleReceiver,
- new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED));
-
- mDependencies.onApfFilterCreated(this);
- // mReceiveThread is created in maybeStartFilter() and halted in shutdown().
- mDependencies.onThreadCreated(mReceiveThread);
- }
-
- @Override
- public synchronized String setDataSnapshot(byte[] data) {
- mDataSnapshot = data;
- if (mIsRunning) {
- mApfCounterTracker.updateCountersFromData(data);
- }
- return mApfCounterTracker.getCounters().toString();
- }
-
- private void log(String s) {
- Log.d(TAG, "(" + mInterfaceParams.name + "): " + s);
- }
-
- @GuardedBy("this")
- private long getUniqueNumberLocked() {
- return mUniqueCounter++;
- }
-
- private static int[] filterEthTypeBlackList(int[] ethTypeBlackList) {
- ArrayList<Integer> bl = new ArrayList<Integer>();
-
- for (int p : ethTypeBlackList) {
- // Check if the protocol is a valid ether type
- if ((p < ETH_TYPE_MIN) || (p > ETH_TYPE_MAX)) {
- continue;
- }
-
- // Check if the protocol is not repeated in the passed array
- if (bl.contains(p)) {
- continue;
- }
-
- // Check if list reach its max size
- if (bl.size() == APF_MAX_ETH_TYPE_BLACK_LIST_LEN) {
- Log.w(TAG, "Passed EthType Black List size too large (" + bl.size() +
- ") using top " + APF_MAX_ETH_TYPE_BLACK_LIST_LEN + " protocols");
- break;
- }
-
- // Now add the protocol to the list
- bl.add(p);
- }
-
- return bl.stream().mapToInt(Integer::intValue).toArray();
- }
-
- /**
- * Attempt to start listening for RAs and, if RAs are received, generating and installing
- * filters to ignore useless RAs.
- */
- @VisibleForTesting
- public void maybeStartFilter() {
- FileDescriptor socket;
- try {
- mHardwareAddress = mInterfaceParams.macAddr.toByteArray();
- synchronized(this) {
- // Clear the APF memory to reset all counters upon connecting to the first AP
- // in an SSID. This is limited to APFv4 devices because this large write triggers
- // a crash on some older devices (b/78905546).
- if (mIsRunning && hasDataAccess(mApfVersionSupported)) {
- byte[] zeroes = new byte[mMaximumApfProgramSize];
- if (!mIpClientCallback.installPacketFilter(zeroes)) {
- sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE);
- }
- }
-
- // Install basic filters
- installNewProgramLocked();
- }
- socket = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IPV6);
- SocketAddress addr = makePacketSocketAddress(ETH_P_IPV6, mInterfaceParams.index);
- Os.bind(socket, addr);
- NetworkStackUtils.attachRaFilter(socket);
- } catch(SocketException|ErrnoException e) {
- Log.e(TAG, "Error starting filter", e);
- return;
- }
- mReceiveThread = new ReceiveThread(socket);
- mReceiveThread.start();
- }
-
- // Returns seconds since device boot.
- @VisibleForTesting
- protected long currentTimeSeconds() {
- return mClock.elapsedRealtime() / DateUtils.SECOND_IN_MILLIS;
- }
-
- public static class InvalidRaException extends Exception {
- public InvalidRaException(String m) {
- super(m);
- }
- }
-
- /**
- * Class to keep track of a section in a packet.
- */
- private static class PacketSection {
- public enum Type {
- MATCH, // A field that should be matched (e.g., the router IP address).
- IGNORE, // An ignored field such as the checksum of the flow label. Not matched.
- LIFETIME, // A lifetime. Not matched, and generally counts toward minimum RA lifetime.
- }
-
- /** The type of section. */
- public final Type type;
- /** Offset into the packet at which this section begins. */
- public final int start;
- /** Length of this section in bytes. */
- public final int length;
- /** If this is a lifetime, the ICMP option that defined it. 0 for router lifetime. */
- public final int option;
- /** If this is a lifetime, the lifetime value. */
- public final long lifetime;
-
- PacketSection(int start, int length, Type type, int option, long lifetime) {
- this.start = start;
- this.length = length;
- this.type = type;
- this.option = option;
- this.lifetime = lifetime;
- }
-
- public String toString() {
- if (type == Type.LIFETIME) {
- return String.format("%s: (%d, %d) %d %d", type, start, length, option, lifetime);
- } else {
- return String.format("%s: (%d, %d)", type, start, length);
- }
- }
- }
-
- // A class to hold information about an RA.
- @VisibleForTesting
- public class Ra {
- // From RFC4861:
- private static final int ICMP6_RA_HEADER_LEN = 16;
- private static final int ICMP6_RA_CHECKSUM_OFFSET =
- ETH_HEADER_LEN + IPV6_HEADER_LEN + 2;
- private static final int ICMP6_RA_CHECKSUM_LEN = 2;
- private static final int ICMP6_RA_OPTION_OFFSET =
- ETH_HEADER_LEN + IPV6_HEADER_LEN + ICMP6_RA_HEADER_LEN;
- private static final int ICMP6_RA_ROUTER_LIFETIME_OFFSET =
- ETH_HEADER_LEN + IPV6_HEADER_LEN + 6;
- private static final int ICMP6_RA_ROUTER_LIFETIME_LEN = 2;
- // Prefix information option.
- private static final int ICMP6_PREFIX_OPTION_TYPE = 3;
- private static final int ICMP6_PREFIX_OPTION_LEN = 32;
- private static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET = 4;
- private static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_LEN = 4;
- private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET = 8;
- private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_LEN = 4;
-
- // From RFC6106: Recursive DNS Server option
- private static final int ICMP6_RDNSS_OPTION_TYPE = 25;
- // From RFC6106: DNS Search List option
- private static final int ICMP6_DNSSL_OPTION_TYPE = 31;
-
- // From RFC4191: Route Information option
- private static final int ICMP6_ROUTE_INFO_OPTION_TYPE = 24;
- // Above three options all have the same format:
- private static final int ICMP6_4_BYTE_LIFETIME_OFFSET = 4;
- private static final int ICMP6_4_BYTE_LIFETIME_LEN = 4;
-
- // Note: mPacket's position() cannot be assumed to be reset.
- private final ByteBuffer mPacket;
-
- // List of sections in the packet.
- private final ArrayList<PacketSection> mPacketSections = new ArrayList<>();
-
- // Router lifetime in packet
- private final int mRouterLifetime;
- // Minimum valid lifetime of PIOs in packet, Long.MAX_VALUE means not seen.
- private long mMinPioValidLifetime = Long.MAX_VALUE;
- // Minimum route lifetime of RIOs in packet, Long.MAX_VALUE means not seen.
- private long mMinRioRouteLifetime = Long.MAX_VALUE;
- // Minimum lifetime of RDNSSs in packet, Long.MAX_VALUE means not seen.
- private long mMinRdnssLifetime = Long.MAX_VALUE;
- // Minimum lifetime in packet
- long mMinLifetime;
- // When the packet was last captured, in seconds since Unix Epoch
- long mLastSeen;
-
- // For debugging only. Offsets into the packet where PIOs are.
- private final ArrayList<Integer> mPrefixOptionOffsets = new ArrayList<>();
-
- // For debugging only. Offsets into the packet where RDNSS options are.
- private final ArrayList<Integer> mRdnssOptionOffsets = new ArrayList<>();
-
- // For debugging only. Offsets into the packet where RIO options are.
- private final ArrayList<Integer> mRioOptionOffsets = new ArrayList<>();
-
- // For debugging only. How many times this RA was seen.
- int seenCount = 0;
-
- // For debugging only. Returns the hex representation of the last matching packet.
- String getLastMatchingPacket() {
- return HexDump.toHexString(mPacket.array(), 0, mPacket.capacity(),
- false /* lowercase */);
- }
-
- // For debugging only. Returns the string representation of the IPv6 address starting at
- // position pos in the packet.
- private String IPv6AddresstoString(int pos) {
- try {
- byte[] array = mPacket.array();
- // Can't just call copyOfRange() and see if it throws, because if it reads past the
- // end it pads with zeros instead of throwing.
- if (pos < 0 || pos + 16 > array.length || pos + 16 < pos) {
- return "???";
- }
- byte[] addressBytes = Arrays.copyOfRange(array, pos, pos + 16);
- InetAddress address = (Inet6Address) InetAddress.getByAddress(addressBytes);
- return address.getHostAddress();
- } catch (UnsupportedOperationException e) {
- // array() failed. Cannot happen, mPacket is array-backed and read-write.
- return "???";
- } catch (ClassCastException|UnknownHostException e) {
- // Cannot happen.
- return "???";
- }
- }
-
- // Can't be static because it's in a non-static inner class.
- // TODO: Make this static once RA is its own class.
- private void prefixOptionToString(StringBuffer sb, int offset) {
- String prefix = IPv6AddresstoString(offset + 16);
- int length = getUint8(mPacket, offset + 2);
- long valid = getUint32(mPacket, offset + 4);
- long preferred = getUint32(mPacket, offset + 8);
- sb.append(String.format("%s/%d %ds/%ds ", prefix, length, valid, preferred));
- }
-
- private void rdnssOptionToString(StringBuffer sb, int offset) {
- int optLen = getUint8(mPacket, offset + 1) * 8;
- if (optLen < 24) return; // Malformed or empty.
- long lifetime = getUint32(mPacket, offset + 4);
- int numServers = (optLen - 8) / 16;
- sb.append("DNS ").append(lifetime).append("s");
- for (int server = 0; server < numServers; server++) {
- sb.append(" ").append(IPv6AddresstoString(offset + 8 + 16 * server));
- }
- sb.append(" ");
- }
-
- private void rioOptionToString(StringBuffer sb, int offset) {
- int optLen = getUint8(mPacket, offset + 1) * 8;
- if (optLen < 8 || optLen > 24) return; // Malformed or empty.
- int prefixLen = getUint8(mPacket, offset + 2);
- long lifetime = getUint32(mPacket, offset + 4);
-
- // This read is variable length because the prefix can be 0, 8 or 16 bytes long.
- // We can't use any of the ByteBuffer#get methods here because they all start reading
- // from the buffer's current position.
- byte[] prefix = new byte[IPV6_ADDR_LEN];
- System.arraycopy(mPacket.array(), offset + 8, prefix, 0, optLen - 8);
- sb.append("RIO ").append(lifetime).append("s ");
- try {
- InetAddress address = (Inet6Address) InetAddress.getByAddress(prefix);
- sb.append(address.getHostAddress());
- } catch (UnknownHostException impossible) {
- sb.append("???");
- }
- sb.append("/").append(prefixLen).append(" ");
- }
-
- public String toString() {
- try {
- StringBuffer sb = new StringBuffer();
- sb.append(String.format("RA %s -> %s %ds ",
- IPv6AddresstoString(IPV6_SRC_ADDR_OFFSET),
- IPv6AddresstoString(IPV6_DEST_ADDR_OFFSET),
- getUint16(mPacket, ICMP6_RA_ROUTER_LIFETIME_OFFSET)));
- for (int i: mPrefixOptionOffsets) {
- prefixOptionToString(sb, i);
- }
- for (int i: mRdnssOptionOffsets) {
- rdnssOptionToString(sb, i);
- }
- for (int i: mRioOptionOffsets) {
- rioOptionToString(sb, i);
- }
- return sb.toString();
- } catch (BufferUnderflowException|IndexOutOfBoundsException e) {
- return "<Malformed RA>";
- }
- }
-
- /**
- * Add a packet section that should be matched, starting from the current position.
- * @param length the length of the section
- */
- private void addMatchSection(int length) {
- // Don't generate JNEBS instruction for 0 bytes as they will fail the
- // ASSERT_FORWARD_IN_PROGRAM(pc + cmp_imm - 1) check (where cmp_imm is
- // the number of bytes to compare) and immediately pass the packet.
- // The code does not attempt to generate such matches, but add a safety
- // check to prevent doing so in the presence of bugs or malformed or
- // truncated packets.
- if (length == 0) return;
- mPacketSections.add(
- new PacketSection(mPacket.position(), length, PacketSection.Type.MATCH, 0, 0));
- mPacket.position(mPacket.position() + length);
- }
-
- /**
- * Add a packet section that should be matched, starting from the current position.
- * @param end the offset in the packet before which the section ends
- */
- private void addMatchUntil(int end) {
- addMatchSection(end - mPacket.position());
- }
-
- /**
- * Add a packet section that should be ignored, starting from the current position.
- * @param length the length of the section in bytes
- */
- private void addIgnoreSection(int length) {
- mPacketSections.add(
- new PacketSection(mPacket.position(), length, PacketSection.Type.IGNORE, 0, 0));
- mPacket.position(mPacket.position() + length);
- }
-
- /**
- * Add a packet section that represents a lifetime, starting from the current position.
- * @param length the length of the section in bytes
- * @param optionType the RA option containing this lifetime, or 0 for router lifetime
- * @param lifetime the lifetime
- */
- private void addLifetimeSection(int length, int optionType, long lifetime) {
- mPacketSections.add(
- new PacketSection(mPacket.position(), length, PacketSection.Type.LIFETIME,
- optionType, lifetime));
- mPacket.position(mPacket.position() + length);
- }
-
- /**
- * Adds packet sections for an RA option with a 4-byte lifetime 4 bytes into the option
- * @param optionType the RA option that is being added
- * @param optionLength the length of the option in bytes
- */
- private long add4ByteLifetimeOption(int optionType, int optionLength) {
- addMatchSection(ICMP6_4_BYTE_LIFETIME_OFFSET);
- final long lifetime = getUint32(mPacket, mPacket.position());
- addLifetimeSection(ICMP6_4_BYTE_LIFETIME_LEN, optionType, lifetime);
- addMatchSection(optionLength - ICMP6_4_BYTE_LIFETIME_OFFSET
- - ICMP6_4_BYTE_LIFETIME_LEN);
- return lifetime;
- }
-
- /**
- * Return the router lifetime of the RA
- */
- public int routerLifetime() {
- return mRouterLifetime;
- }
-
- /**
- * Return the minimum valid lifetime in PIOs
- */
- public long minPioValidLifetime() {
- return mMinPioValidLifetime;
- }
-
- /**
- * Return the minimum route lifetime in RIOs
- */
- public long minRioRouteLifetime() {
- return mMinRioRouteLifetime;
- }
-
- /**
- * Return the minimum lifetime in RDNSSs
- */
- public long minRdnssLifetime() {
- return mMinRdnssLifetime;
- }
-
- // http://b/66928272 http://b/65056012
- // DnsServerRepository ignores RDNSS servers with lifetimes that are too low. Ignore these
- // lifetimes for the purpose of filter lifetime calculations.
- private boolean shouldIgnoreLifetime(int optionType, long lifetime) {
- return optionType == ICMP6_RDNSS_OPTION_TYPE
- && lifetime != 0 && lifetime < mMinRdnssLifetimeSec;
- }
-
- private boolean isRelevantLifetime(PacketSection section) {
- return section.type == PacketSection.Type.LIFETIME
- && !shouldIgnoreLifetime(section.option, section.lifetime);
- }
-
- // Note that this parses RA and may throw InvalidRaException (from
- // Buffer.position(int) or due to an invalid-length option) or IndexOutOfBoundsException
- // (from ByteBuffer.get(int) ) if parsing encounters something non-compliant with
- // specifications.
- @VisibleForTesting
- public Ra(byte[] packet, int length) throws InvalidRaException {
- if (length < ICMP6_RA_OPTION_OFFSET) {
- throw new InvalidRaException("Not an ICMP6 router advertisement: too short");
- }
-
- mPacket = ByteBuffer.wrap(Arrays.copyOf(packet, length));
- mLastSeen = currentTimeSeconds();
-
- // Check packet in case a packet arrives before we attach RA filter
- // to our packet socket. b/29586253
- if (getUint16(mPacket, ETH_ETHERTYPE_OFFSET) != ETH_P_IPV6 ||
- getUint8(mPacket, IPV6_NEXT_HEADER_OFFSET) != IPPROTO_ICMPV6 ||
- getUint8(mPacket, ICMP6_TYPE_OFFSET) != ICMPV6_ROUTER_ADVERTISEMENT) {
- throw new InvalidRaException("Not an ICMP6 router advertisement");
- }
-
-
- RaEvent.Builder builder = new RaEvent.Builder();
-
- // Ignore the flow label and low 4 bits of traffic class.
- addMatchUntil(IPV6_FLOW_LABEL_OFFSET);
- addIgnoreSection(IPV6_FLOW_LABEL_LEN);
-
- // Ignore checksum.
- addMatchUntil(ICMP6_RA_CHECKSUM_OFFSET);
- addIgnoreSection(ICMP6_RA_CHECKSUM_LEN);
-
- // Parse router lifetime
- addMatchUntil(ICMP6_RA_ROUTER_LIFETIME_OFFSET);
- mRouterLifetime = getUint16(mPacket, ICMP6_RA_ROUTER_LIFETIME_OFFSET);
- addLifetimeSection(ICMP6_RA_ROUTER_LIFETIME_LEN, 0, mRouterLifetime);
- builder.updateRouterLifetime(mRouterLifetime);
-
- // Add remaining fields (reachable time and retransmission timer) to match section.
- addMatchUntil(ICMP6_RA_OPTION_OFFSET);
-
- while (mPacket.hasRemaining()) {
- final int position = mPacket.position();
- final int optionType = getUint8(mPacket, position);
- final int optionLength = getUint8(mPacket, position + 1) * 8;
- long lifetime;
- switch (optionType) {
- case ICMP6_PREFIX_OPTION_TYPE:
- mPrefixOptionOffsets.add(position);
-
- // Parse valid lifetime
- addMatchSection(ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET);
- lifetime = getUint32(mPacket, mPacket.position());
- addLifetimeSection(ICMP6_PREFIX_OPTION_VALID_LIFETIME_LEN,
- ICMP6_PREFIX_OPTION_TYPE, lifetime);
- builder.updatePrefixValidLifetime(lifetime);
- mMinPioValidLifetime = getMinForPositiveValue(
- mMinPioValidLifetime, lifetime);
-
- // Parse preferred lifetime
- lifetime = getUint32(mPacket, mPacket.position());
- addLifetimeSection(ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_LEN,
- ICMP6_PREFIX_OPTION_TYPE, lifetime);
- builder.updatePrefixPreferredLifetime(lifetime);
-
- addMatchSection(4); // Reserved bytes
- addMatchSection(IPV6_ADDR_LEN); // The prefix itself
- break;
- // These three options have the same lifetime offset and size, and
- // are processed with the same specialized add4ByteLifetimeOption:
- case ICMP6_RDNSS_OPTION_TYPE:
- mRdnssOptionOffsets.add(position);
- lifetime = add4ByteLifetimeOption(optionType, optionLength);
- builder.updateRdnssLifetime(lifetime);
- mMinRdnssLifetime = getMinForPositiveValue(mMinRdnssLifetime, lifetime);
- break;
- case ICMP6_ROUTE_INFO_OPTION_TYPE:
- mRioOptionOffsets.add(position);
- lifetime = add4ByteLifetimeOption(optionType, optionLength);
- builder.updateRouteInfoLifetime(lifetime);
- mMinRioRouteLifetime = getMinForPositiveValue(
- mMinRioRouteLifetime, lifetime);
- break;
- case ICMP6_DNSSL_OPTION_TYPE:
- lifetime = add4ByteLifetimeOption(optionType, optionLength);
- builder.updateDnsslLifetime(lifetime);
- break;
- default:
- // RFC4861 section 4.2 dictates we ignore unknown options for forwards
- // compatibility.
- mPacket.position(position + optionLength);
- break;
- }
- if (optionLength <= 0) {
- throw new InvalidRaException(String.format(
- "Invalid option length opt=%d len=%d", optionType, optionLength));
- }
- }
- mMinLifetime = minLifetime();
- mMetricsLog.log(builder.build());
- }
-
- // Considering only the MATCH sections, does {@code packet} match this RA?
- boolean matches(byte[] packet, int length) {
- if (length != mPacket.capacity()) return false;
- byte[] referencePacket = mPacket.array();
- for (PacketSection section : mPacketSections) {
- if (section.type != PacketSection.Type.MATCH) continue;
- for (int i = section.start; i < (section.start + section.length); i++) {
- if (packet[i] != referencePacket[i]) return false;
- }
- }
- return true;
- }
-
- // What is the minimum of all lifetimes within {@code packet} in seconds?
- // Precondition: matches(packet, length) already returned true.
- long minLifetime() {
- long minLifetime = Long.MAX_VALUE;
- for (PacketSection section : mPacketSections) {
- if (isRelevantLifetime(section)) {
- minLifetime = Math.min(minLifetime, section.lifetime);
- }
- }
- return minLifetime;
- }
-
- // How many seconds does this RA's have to live, taking into account the fact
- // that we might have seen it a while ago.
- long currentLifetime() {
- return mMinLifetime - (currentTimeSeconds() - mLastSeen);
- }
-
- boolean isExpired() {
- // TODO: We may want to handle 0 lifetime RAs differently, if they are common. We'll
- // have to calculate the filter lifetime specially as a fraction of 0 is still 0.
- return currentLifetime() <= 0;
- }
-
- // Filter for a fraction of the lifetime and adjust for the age of the RA.
- @GuardedBy("LegacyApfFilter.this")
- int filterLifetime() {
- return (int) (mMinLifetime / FRACTION_OF_LIFETIME_TO_FILTER)
- - (int) (mProgramBaseTime - mLastSeen);
- }
-
- @GuardedBy("LegacyApfFilter.this")
- boolean shouldFilter() {
- return filterLifetime() > 0;
- }
-
- // Append a filter for this RA to {@code gen}. Jump to DROP_LABEL if it should be dropped.
- // Jump to the next filter if packet doesn't match this RA.
- // Return Long.MAX_VALUE if we don't install any filter program for this RA. As the return
- // value of this function is used to calculate the program min lifetime (which corresponds
- // to the smallest generated filter lifetime). Returning Long.MAX_VALUE in the case no
- // filter gets generated makes sure the program lifetime stays unaffected.
- @GuardedBy("LegacyApfFilter.this")
- long generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException {
- String nextFilterLabel = "Ra" + getUniqueNumberLocked();
- // Skip if packet is not the right size
- gen.addLoadFromMemory(R0, MemorySlot.PACKET_SIZE);
- gen.addJumpIfR0NotEquals(mPacket.capacity(), nextFilterLabel);
- // Skip filter if expired
- gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_SECONDS);
- gen.addJumpIfR0GreaterThan(filterLifetime(), nextFilterLabel);
- for (PacketSection section : mPacketSections) {
- // Generate code to match the packet bytes.
- if (section.type == PacketSection.Type.MATCH) {
- gen.addLoadImmediate(R0, section.start);
- gen.addJumpIfBytesAtR0NotEqual(
- Arrays.copyOfRange(mPacket.array(), section.start,
- section.start + section.length),
- nextFilterLabel);
- }
-
- // Generate code to test the lifetimes haven't gone down too far.
- // The packet is accepted if any non-ignored lifetime is lower than filterLifetime.
- if (isRelevantLifetime(section)) {
- switch (section.length) {
- case 4: gen.addLoad32(R0, section.start); break;
- case 2: gen.addLoad16(R0, section.start); break;
- default:
- throw new IllegalStateException(
- "bogus lifetime size " + section.length);
- }
- gen.addJumpIfR0LessThan(filterLifetime(), nextFilterLabel);
- }
- }
- maybeSetupCounter(gen, Counter.DROPPED_RA);
- gen.addJump(mCountAndDropLabel);
- gen.defineLabel(nextFilterLabel);
- return filterLifetime();
- }
- }
-
- // TODO: Refactor these subclasses to avoid so much repetition.
- private abstract static class KeepalivePacket {
- // Note that the offset starts from IP header.
- // These must be added ether header length when generating program.
- static final int IP_HEADER_OFFSET = 0;
- static final int IPV4_SRC_ADDR_OFFSET = IP_HEADER_OFFSET + 12;
-
- // Append a filter for this keepalive ack to {@code gen}.
- // Jump to drop if it matches the keepalive ack.
- // Jump to the next filter if packet doesn't match the keepalive ack.
- abstract void generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException;
- }
-
- // A class to hold NAT-T keepalive ack information.
- private class NattKeepaliveResponse extends KeepalivePacket {
- static final int UDP_LENGTH_OFFSET = 4;
- static final int UDP_HEADER_LEN = 8;
-
- protected class NattKeepaliveResponseData {
- public final byte[] srcAddress;
- public final int srcPort;
- public final byte[] dstAddress;
- public final int dstPort;
-
- NattKeepaliveResponseData(final NattKeepalivePacketDataParcelable sentKeepalivePacket) {
- srcAddress = sentKeepalivePacket.dstAddress;
- srcPort = sentKeepalivePacket.dstPort;
- dstAddress = sentKeepalivePacket.srcAddress;
- dstPort = sentKeepalivePacket.srcPort;
- }
- }
-
- protected final NattKeepaliveResponseData mPacket;
- protected final byte[] mSrcDstAddr;
- protected final byte[] mPortFingerprint;
- // NAT-T keepalive packet
- protected final byte[] mPayload = {(byte) 0xff};
-
- NattKeepaliveResponse(final NattKeepalivePacketDataParcelable sentKeepalivePacket) {
- mPacket = new NattKeepaliveResponseData(sentKeepalivePacket);
- mSrcDstAddr = concatArrays(mPacket.srcAddress, mPacket.dstAddress);
- mPortFingerprint = generatePortFingerprint(mPacket.srcPort, mPacket.dstPort);
- }
-
- byte[] generatePortFingerprint(int srcPort, int dstPort) {
- final ByteBuffer fp = ByteBuffer.allocate(4);
- fp.order(ByteOrder.BIG_ENDIAN);
- fp.putShort((short) srcPort);
- fp.putShort((short) dstPort);
- return fp.array();
- }
-
- @Override
- @GuardedBy("LegacyApfFilter.this")
- void generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException {
- final String nextFilterLabel = "natt_keepalive_filter" + getUniqueNumberLocked();
-
- gen.addLoadImmediate(R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET);
- gen.addJumpIfBytesAtR0NotEqual(mSrcDstAddr, nextFilterLabel);
-
- // A NAT-T keepalive packet contains 1 byte payload with the value 0xff
- // Check payload length is 1
- gen.addLoadFromMemory(R0, MemorySlot.IPV4_HEADER_SIZE);
- gen.addAdd(UDP_HEADER_LEN);
- gen.addSwap();
- gen.addLoad16(R0, IPV4_TOTAL_LENGTH_OFFSET);
- gen.addNeg(R1);
- gen.addAddR1ToR0();
- gen.addJumpIfR0NotEquals(1, nextFilterLabel);
-
- // Check that the ports match
- gen.addLoadFromMemory(R0, MemorySlot.IPV4_HEADER_SIZE);
- gen.addAdd(ETH_HEADER_LEN);
- gen.addJumpIfBytesAtR0NotEqual(mPortFingerprint, nextFilterLabel);
-
- // Payload offset = R0 + UDP header length
- gen.addAdd(UDP_HEADER_LEN);
- gen.addJumpIfBytesAtR0NotEqual(mPayload, nextFilterLabel);
-
- maybeSetupCounter(gen, Counter.DROPPED_IPV4_NATT_KEEPALIVE);
- gen.addJump(mCountAndDropLabel);
- gen.defineLabel(nextFilterLabel);
- }
-
- public String toString() {
- try {
- return String.format("%s -> %s",
- ConnectivityUtils.addressAndPortToString(
- InetAddress.getByAddress(mPacket.srcAddress), mPacket.srcPort),
- ConnectivityUtils.addressAndPortToString(
- InetAddress.getByAddress(mPacket.dstAddress), mPacket.dstPort));
- } catch (UnknownHostException e) {
- return "Unknown host";
- }
- }
- }
-
- // A class to hold TCP keepalive ack information.
- private abstract static class TcpKeepaliveAck extends KeepalivePacket {
- protected static class TcpKeepaliveAckData {
- public final byte[] srcAddress;
- public final int srcPort;
- public final byte[] dstAddress;
- public final int dstPort;
- public final int seq;
- public final int ack;
-
- // Create the characteristics of the ack packet from the sent keepalive packet.
- TcpKeepaliveAckData(final TcpKeepalivePacketDataParcelable sentKeepalivePacket) {
- srcAddress = sentKeepalivePacket.dstAddress;
- srcPort = sentKeepalivePacket.dstPort;
- dstAddress = sentKeepalivePacket.srcAddress;
- dstPort = sentKeepalivePacket.srcPort;
- seq = sentKeepalivePacket.ack;
- ack = sentKeepalivePacket.seq + 1;
- }
- }
-
- protected final TcpKeepaliveAckData mPacket;
- protected final byte[] mSrcDstAddr;
- protected final byte[] mPortSeqAckFingerprint;
-
- TcpKeepaliveAck(final TcpKeepaliveAckData packet, final byte[] srcDstAddr) {
- mPacket = packet;
- mSrcDstAddr = srcDstAddr;
- mPortSeqAckFingerprint = generatePortSeqAckFingerprint(mPacket.srcPort,
- mPacket.dstPort, mPacket.seq, mPacket.ack);
- }
-
- static byte[] generatePortSeqAckFingerprint(int srcPort, int dstPort, int seq, int ack) {
- final ByteBuffer fp = ByteBuffer.allocate(12);
- fp.order(ByteOrder.BIG_ENDIAN);
- fp.putShort((short) srcPort);
- fp.putShort((short) dstPort);
- fp.putInt(seq);
- fp.putInt(ack);
- return fp.array();
- }
-
- public String toString() {
- try {
- return String.format("%s -> %s , seq=%d, ack=%d",
- ConnectivityUtils.addressAndPortToString(
- InetAddress.getByAddress(mPacket.srcAddress), mPacket.srcPort),
- ConnectivityUtils.addressAndPortToString(
- InetAddress.getByAddress(mPacket.dstAddress), mPacket.dstPort),
- Integer.toUnsignedLong(mPacket.seq),
- Integer.toUnsignedLong(mPacket.ack));
- } catch (UnknownHostException e) {
- return "Unknown host";
- }
- }
-
- // Append a filter for this keepalive ack to {@code gen}.
- // Jump to drop if it matches the keepalive ack.
- // Jump to the next filter if packet doesn't match the keepalive ack.
- abstract void generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException;
- }
-
- private class TcpKeepaliveAckV4 extends TcpKeepaliveAck {
-
- TcpKeepaliveAckV4(final TcpKeepalivePacketDataParcelable sentKeepalivePacket) {
- this(new TcpKeepaliveAckData(sentKeepalivePacket));
- }
- TcpKeepaliveAckV4(final TcpKeepaliveAckData packet) {
- super(packet, concatArrays(packet.srcAddress, packet.dstAddress) /* srcDstAddr */);
- }
-
- @Override
- @GuardedBy("LegacyApfFilter.this")
- void generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException {
- final String nextFilterLabel = "keepalive_ack" + getUniqueNumberLocked();
-
- gen.addLoadImmediate(R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET);
- gen.addJumpIfBytesAtR0NotEqual(mSrcDstAddr, nextFilterLabel);
-
- // Skip to the next filter if it's not zero-sized :
- // TCP_HEADER_SIZE + IPV4_HEADER_SIZE - ipv4_total_length == 0
- // Load the IP header size into R1
- gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE);
- // Load the TCP header size into R0 (it's indexed by R1)
- gen.addLoad8Indexed(R0, ETH_HEADER_LEN + TCP_HEADER_SIZE_OFFSET);
- // Size offset is in the top nibble, but it must be multiplied by 4, and the two
- // top bits of the low nibble are guaranteed to be zeroes. Right-shift R0 by 2.
- gen.addRightShift(2);
- // R0 += R1 -> R0 contains TCP + IP headers length
- gen.addAddR1ToR0();
- // Load IPv4 total length
- gen.addLoad16(R1, IPV4_TOTAL_LENGTH_OFFSET);
- gen.addNeg(R0);
- gen.addAddR1ToR0();
- gen.addJumpIfR0NotEquals(0, nextFilterLabel);
- // Add IPv4 header length
- gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE);
- gen.addLoadImmediate(R0, ETH_HEADER_LEN);
- gen.addAddR1ToR0();
- gen.addJumpIfBytesAtR0NotEqual(mPortSeqAckFingerprint, nextFilterLabel);
-
- maybeSetupCounter(gen, Counter.DROPPED_IPV4_KEEPALIVE_ACK);
- gen.addJump(mCountAndDropLabel);
- gen.defineLabel(nextFilterLabel);
- }
- }
-
- private class TcpKeepaliveAckV6 extends TcpKeepaliveAck {
- TcpKeepaliveAckV6(final TcpKeepalivePacketDataParcelable sentKeepalivePacket) {
- this(new TcpKeepaliveAckData(sentKeepalivePacket));
- }
- TcpKeepaliveAckV6(final TcpKeepaliveAckData packet) {
- super(packet, concatArrays(packet.srcAddress, packet.dstAddress) /* srcDstAddr */);
- }
-
- @Override
- void generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException {
- throw new UnsupportedOperationException("IPv6 TCP Keepalive is not supported yet");
- }
- }
-
- // Maximum number of RAs to filter for.
- private static final int MAX_RAS = 10;
-
- @GuardedBy("this")
- private ArrayList<Ra> mRas = new ArrayList<>();
- @GuardedBy("this")
- private SparseArray<KeepalivePacket> mKeepalivePackets = new SparseArray<>();
- @GuardedBy("this")
- private final List<String[]> mMdnsAllowList = new ArrayList<>();
-
- // There is always some marginal benefit to updating the installed APF program when an RA is
- // seen because we can extend the program's lifetime slightly, but there is some cost to
- // updating the program, so don't bother unless the program is going to expire soon. This
- // constant defines "soon" in seconds.
- private static final long MAX_PROGRAM_LIFETIME_WORTH_REFRESHING = 30;
- // We don't want to filter an RA for it's whole lifetime as it'll be expired by the time we ever
- // see a refresh. Using half the lifetime might be a good idea except for the fact that
- // packets may be dropped, so let's use 6.
- private static final int FRACTION_OF_LIFETIME_TO_FILTER = 6;
-
- // The base time for this filter program. In seconds since Unix Epoch.
- // This is the time when the APF program was generated. All filters in the program should use
- // this base time as their current time for consistency purposes.
- @GuardedBy("this")
- private long mProgramBaseTime;
- // When did we last install a filter program? In seconds since Unix Epoch.
- @GuardedBy("this")
- private long mLastTimeInstalledProgram;
- // How long should the last installed filter program live for? In seconds.
- @GuardedBy("this")
- private long mLastInstalledProgramMinLifetime;
- @GuardedBy("this")
- private ApfProgramEvent.Builder mLastInstallEvent;
-
- // For debugging only. The last program installed.
- @GuardedBy("this")
- private byte[] mLastInstalledProgram;
-
- /**
- * For debugging only. Contains the latest APF buffer snapshot captured from the firmware.
- *
- * A typical size for this buffer is 4KB. It is present only if the WiFi HAL supports
- * IWifiStaIface#readApfPacketFilterData(), and the APF interpreter advertised support for
- * the opcodes to access the data buffer (LDDW and STDW).
- */
- @GuardedBy("this") @Nullable
- private byte[] mDataSnapshot;
-
- // How many times the program was updated since we started.
- @GuardedBy("this")
- private int mNumProgramUpdates = 0;
- // The maximum program size that updated since we started.
- @GuardedBy("this")
- private int mMaxProgramSize = 0;
- // The maximum number of distinct RAs
- @GuardedBy("this")
- private int mMaxDistinctRas = 0;
- // How many times the program was updated since we started for allowing multicast traffic.
- @GuardedBy("this")
- private int mNumProgramUpdatesAllowingMulticast = 0;
-
- /**
- * Generate filter code to process ARP packets. Execution of this code ends in either the
- * DROP_LABEL or PASS_LABEL and does not fall off the end.
- * Preconditions:
- * - Packet being filtered is ARP
- */
- @GuardedBy("this")
- private void generateArpFilterLocked(ApfV4Generator gen) throws IllegalInstructionException {
- // Here's a basic summary of what the ARP filter program does:
- //
- // if not ARP IPv4
- // pass
- // if not ARP IPv4 reply or request
- // pass
- // if ARP reply source ip is 0.0.0.0
- // drop
- // if unicast ARP reply
- // pass
- // if interface has no IPv4 address
- // if target ip is 0.0.0.0
- // drop
- // else
- // if target ip is not the interface ip
- // drop
- // pass
-
- final String checkTargetIPv4 = "checkTargetIPv4";
-
- // Pass if not ARP IPv4.
- gen.addLoadImmediate(R0, ARP_HEADER_OFFSET);
- maybeSetupCounter(gen, Counter.PASSED_ARP_NON_IPV4);
- gen.addJumpIfBytesAtR0NotEqual(ARP_IPV4_HEADER, mCountAndPassLabel);
-
- // Pass if unknown ARP opcode.
- gen.addLoad16(R0, ARP_OPCODE_OFFSET);
- gen.addJumpIfR0Equals(ARP_OPCODE_REQUEST, checkTargetIPv4); // Skip to unicast check
- maybeSetupCounter(gen, Counter.PASSED_ARP_UNKNOWN);
- gen.addJumpIfR0NotEquals(ARP_OPCODE_REPLY, mCountAndPassLabel);
-
- // Drop if ARP reply source IP is 0.0.0.0
- gen.addLoad32(R0, ARP_SOURCE_IP_ADDRESS_OFFSET);
- maybeSetupCounter(gen, Counter.DROPPED_ARP_REPLY_SPA_NO_HOST);
- gen.addJumpIfR0Equals(IPV4_ANY_HOST_ADDRESS, mCountAndDropLabel);
-
- // Pass if unicast reply.
- gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET);
- maybeSetupCounter(gen, Counter.PASSED_ARP_UNICAST_REPLY);
- gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel);
-
- // Either a unicast request, a unicast reply, or a broadcast reply.
- gen.defineLabel(checkTargetIPv4);
- if (mIPv4Address == null) {
- // When there is no IPv4 address, drop GARP replies (b/29404209).
- gen.addLoad32(R0, ARP_TARGET_IP_ADDRESS_OFFSET);
- maybeSetupCounter(gen, Counter.DROPPED_GARP_REPLY);
- gen.addJumpIfR0Equals(IPV4_ANY_HOST_ADDRESS, mCountAndDropLabel);
- } else {
- // When there is an IPv4 address, drop unicast/broadcast requests
- // and broadcast replies with a different target IPv4 address.
- gen.addLoadImmediate(R0, ARP_TARGET_IP_ADDRESS_OFFSET);
- maybeSetupCounter(gen, Counter.DROPPED_ARP_OTHER_HOST);
- gen.addJumpIfBytesAtR0NotEqual(mIPv4Address, mCountAndDropLabel);
- }
-
- maybeSetupCounter(gen, Counter.PASSED_ARP);
- gen.addJump(mCountAndPassLabel);
- }
-
- /**
- * Generate filter code to process IPv4 packets. Execution of this code ends in either the
- * DROP_LABEL or PASS_LABEL and does not fall off the end.
- * Preconditions:
- * - Packet being filtered is IPv4
- */
- @GuardedBy("this")
- private void generateIPv4FilterLocked(ApfV4Generator gen) throws IllegalInstructionException {
- // Here's a basic summary of what the IPv4 filter program does:
- //
- // if filtering multicast (i.e. multicast lock not held):
- // if it's DHCP destined to our MAC:
- // pass
- // if it's L2 broadcast:
- // drop
- // if it's IPv4 multicast:
- // drop
- // if it's IPv4 broadcast:
- // drop
- // if keepalive ack
- // drop
- // pass
-
- if (mMulticastFilter) {
- final String skipDhcpv4Filter = "skip_dhcp_v4_filter";
-
- // Pass DHCP addressed to us.
- // Check it's UDP.
- gen.addLoad8(R0, IPV4_PROTOCOL_OFFSET);
- gen.addJumpIfR0NotEquals(IPPROTO_UDP, skipDhcpv4Filter);
- // Check it's not a fragment. This matches the BPF filter installed by the DHCP client.
- gen.addLoad16(R0, IPV4_FRAGMENT_OFFSET_OFFSET);
- gen.addJumpIfR0AnyBitsSet(IPV4_FRAGMENT_OFFSET_MASK, skipDhcpv4Filter);
- // Check it's addressed to DHCP client port.
- gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE);
- gen.addLoad16Indexed(R0, UDP_DESTINATION_PORT_OFFSET);
- gen.addJumpIfR0NotEquals(DHCP_CLIENT_PORT, skipDhcpv4Filter);
- // Check it's DHCP to our MAC address.
- gen.addLoadImmediate(R0, DHCP_CLIENT_MAC_OFFSET);
- // NOTE: Relies on R1 containing IPv4 header offset.
- gen.addAddR1ToR0();
- gen.addJumpIfBytesAtR0NotEqual(mHardwareAddress, skipDhcpv4Filter);
- maybeSetupCounter(gen, Counter.PASSED_DHCP);
- gen.addJump(mCountAndPassLabel);
-
- // Drop all multicasts/broadcasts.
- gen.defineLabel(skipDhcpv4Filter);
-
- // If IPv4 destination address is in multicast range, drop.
- gen.addLoad8(R0, IPV4_DEST_ADDR_OFFSET);
- gen.addAnd(0xf0);
- maybeSetupCounter(gen, Counter.DROPPED_IPV4_MULTICAST);
- gen.addJumpIfR0Equals(0xe0, mCountAndDropLabel);
-
- // If IPv4 broadcast packet, drop regardless of L2 (b/30231088).
- maybeSetupCounter(gen, Counter.DROPPED_IPV4_BROADCAST_ADDR);
- gen.addLoad32(R0, IPV4_DEST_ADDR_OFFSET);
- gen.addJumpIfR0Equals(IPV4_BROADCAST_ADDRESS, mCountAndDropLabel);
- if (mIPv4Address != null && mIPv4PrefixLength < 31) {
- maybeSetupCounter(gen, Counter.DROPPED_IPV4_BROADCAST_NET);
- int broadcastAddr = ipv4BroadcastAddress(mIPv4Address, mIPv4PrefixLength);
- gen.addJumpIfR0Equals(broadcastAddr, mCountAndDropLabel);
- }
-
- // If any TCP keepalive filter matches, drop
- generateV4KeepaliveFilters(gen);
-
- // If any NAT-T keepalive filter matches, drop
- generateV4NattKeepaliveFilters(gen);
-
- // Otherwise, this is an IPv4 unicast, pass
- // If L2 broadcast packet, drop.
- // TODO: can we invert this condition to fall through to the common pass case below?
- maybeSetupCounter(gen, Counter.PASSED_IPV4_UNICAST);
- gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET);
- gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel);
- maybeSetupCounter(gen, Counter.DROPPED_IPV4_L2_BROADCAST);
- gen.addJump(mCountAndDropLabel);
- } else {
- generateV4KeepaliveFilters(gen);
- generateV4NattKeepaliveFilters(gen);
- }
-
- // Otherwise, pass
- maybeSetupCounter(gen, Counter.PASSED_IPV4);
- gen.addJump(mCountAndPassLabel);
- }
-
- @GuardedBy("this")
- private void generateKeepaliveFilters(ApfV4Generator gen, Class<?> filterType, int proto,
- int offset, String label) throws IllegalInstructionException {
- final boolean haveKeepaliveResponses = CollectionUtils.any(mKeepalivePackets,
- ack -> filterType.isInstance(ack));
-
- // If no keepalive packets of this type
- if (!haveKeepaliveResponses) return;
-
- // If not the right proto, skip keepalive filters
- gen.addLoad8(R0, offset);
- gen.addJumpIfR0NotEquals(proto, label);
-
- // Drop Keepalive responses
- for (int i = 0; i < mKeepalivePackets.size(); ++i) {
- final KeepalivePacket response = mKeepalivePackets.valueAt(i);
- if (filterType.isInstance(response)) response.generateFilterLocked(gen);
- }
-
- gen.defineLabel(label);
- }
-
- @GuardedBy("this")
- private void generateV4KeepaliveFilters(ApfV4Generator gen) throws IllegalInstructionException {
- generateKeepaliveFilters(gen, TcpKeepaliveAckV4.class, IPPROTO_TCP, IPV4_PROTOCOL_OFFSET,
- "skip_v4_keepalive_filter");
- }
-
- @GuardedBy("this")
- private void generateV4NattKeepaliveFilters(ApfV4Generator gen)
- throws IllegalInstructionException {
- generateKeepaliveFilters(gen, NattKeepaliveResponse.class,
- IPPROTO_UDP, IPV4_PROTOCOL_OFFSET, "skip_v4_nattkeepalive_filter");
- }
-
- /**
- * Generate filter code to process IPv6 packets. Execution of this code ends in either the
- * DROP_LABEL or PASS_LABEL, or falls off the end for ICMPv6 packets.
- * Preconditions:
- * - Packet being filtered is IPv6
- */
- @GuardedBy("this")
- private void generateIPv6FilterLocked(ApfV4Generator gen) throws IllegalInstructionException {
- // Here's a basic summary of what the IPv6 filter program does:
- //
- // if there is a hop-by-hop option present (e.g. MLD query)
- // pass
- // if we're dropping multicast
- // if it's not IPCMv6 or it's ICMPv6 but we're in doze mode:
- // if it's multicast:
- // drop
- // pass
- // if it's ICMPv6 RS to any:
- // drop
- // if it's ICMPv6 NA to anything in ff02::/120
- // drop
- // if keepalive ack
- // drop
-
- gen.addLoad8(R0, IPV6_NEXT_HEADER_OFFSET);
-
- // MLD packets set the router-alert hop-by-hop option.
- // TODO: be smarter about not blindly passing every packet with HBH options.
- maybeSetupCounter(gen, Counter.PASSED_MLD);
- gen.addJumpIfR0Equals(IPPROTO_HOPOPTS, mCountAndPassLabel);
-
- // Drop multicast if the multicast filter is enabled.
- if (mMulticastFilter) {
- final String skipIPv6MulticastFilterLabel = "skipIPv6MulticastFilter";
- final String dropAllIPv6MulticastsLabel = "dropAllIPv6Multicast";
-
- // While in doze mode, drop ICMPv6 multicast pings, let the others pass.
- // While awake, let all ICMPv6 multicasts through.
- if (mInDozeMode) {
- // Not ICMPv6? -> Proceed to multicast filtering
- gen.addJumpIfR0NotEquals(IPPROTO_ICMPV6, dropAllIPv6MulticastsLabel);
-
- // ICMPv6 but not ECHO? -> Skip the multicast filter.
- // (ICMPv6 ECHO requests will go through the multicast filter below).
- gen.addLoad8(R0, ICMP6_TYPE_OFFSET);
- gen.addJumpIfR0NotEquals(ICMPV6_ECHO_REQUEST_TYPE, skipIPv6MulticastFilterLabel);
- } else {
- gen.addJumpIfR0Equals(IPPROTO_ICMPV6, skipIPv6MulticastFilterLabel);
- }
-
- // Drop all other packets sent to ff00::/8 (multicast prefix).
- gen.defineLabel(dropAllIPv6MulticastsLabel);
- maybeSetupCounter(gen, Counter.DROPPED_IPV6_NON_ICMP_MULTICAST);
- gen.addLoad8(R0, IPV6_DEST_ADDR_OFFSET);
- gen.addJumpIfR0Equals(0xff, mCountAndDropLabel);
- // If any keepalive filter matches, drop
- generateV6KeepaliveFilters(gen);
- // Not multicast. Pass.
- maybeSetupCounter(gen, Counter.PASSED_IPV6_UNICAST_NON_ICMP);
- gen.addJump(mCountAndPassLabel);
- gen.defineLabel(skipIPv6MulticastFilterLabel);
- } else {
- generateV6KeepaliveFilters(gen);
- // If not ICMPv6, pass.
- maybeSetupCounter(gen, Counter.PASSED_IPV6_NON_ICMP);
- gen.addJumpIfR0NotEquals(IPPROTO_ICMPV6, mCountAndPassLabel);
- }
-
- // If we got this far, the packet is ICMPv6. Drop some specific types.
-
- // Add unsolicited multicast neighbor announcements filter
- String skipUnsolicitedMulticastNALabel = "skipUnsolicitedMulticastNA";
- gen.addLoad8(R0, ICMP6_TYPE_OFFSET);
- // Drop all router solicitations (b/32833400)
- maybeSetupCounter(gen, Counter.DROPPED_IPV6_ROUTER_SOLICITATION);
- gen.addJumpIfR0Equals(ICMPV6_ROUTER_SOLICITATION, mCountAndDropLabel);
- // If not neighbor announcements, skip filter.
- gen.addJumpIfR0NotEquals(ICMPV6_NEIGHBOR_ADVERTISEMENT, skipUnsolicitedMulticastNALabel);
- // Drop all multicast NA to ff02::/120.
- // This is a way to cover ff02::1 and ff02::2 with a single JNEBS.
- // TODO: Drop only if they don't contain the address of on-link neighbours.
- final byte[] unsolicitedNaDropPrefix = Arrays.copyOf(IPV6_ALL_NODES_ADDRESS, 15);
- gen.addLoadImmediate(R0, IPV6_DEST_ADDR_OFFSET);
- gen.addJumpIfBytesAtR0NotEqual(unsolicitedNaDropPrefix, skipUnsolicitedMulticastNALabel);
-
- maybeSetupCounter(gen, Counter.DROPPED_IPV6_MULTICAST_NA);
- gen.addJump(mCountAndDropLabel);
- gen.defineLabel(skipUnsolicitedMulticastNALabel);
- }
-
- /** Encodes qname in TLV pattern. */
- @VisibleForTesting
- public static byte[] encodeQname(String[] labels) {
- final ByteArrayOutputStream out = new ByteArrayOutputStream();
- for (String label : labels) {
- byte[] labelBytes = label.getBytes(StandardCharsets.UTF_8);
- out.write(labelBytes.length);
- out.write(labelBytes, 0, labelBytes.length);
- }
- out.write(0);
- return out.toByteArray();
- }
-
- /**
- * Generate filter code to process mDNS packets. Execution of this code ends in * DROP_LABEL
- * or PASS_LABEL if the packet is mDNS packets. Otherwise, skip this check.
- */
- @GuardedBy("this")
- private void generateMdnsFilterLocked(ApfV4Generator gen)
- throws IllegalInstructionException {
- final String skipMdnsv4Filter = "skip_mdns_v4_filter";
- final String skipMdnsFilter = "skip_mdns_filter";
- final String checkMdnsUdpPort = "check_mdns_udp_port";
- final String mDnsAcceptPacket = "mdns_accept_packet";
- final String mDnsDropPacket = "mdns_drop_packet";
-
- // Only turn on the filter if multicast filter is on and the qname allowlist is non-empty.
- if (!mMulticastFilter || mMdnsAllowList.isEmpty()) {
- return;
- }
-
- // Here's a basic summary of what the mDNS filter program does:
- //
- // if it is a multicast mDNS packet
- // if QDCOUNT != 1
- // pass
- // else if the QNAME is in the allowlist
- // pass
- // else:
- // drop
- //
- // A packet is considered as a multicast mDNS packet if it matches all the following
- // conditions
- // 1. its destination MAC address matches 01:00:5E:00:00:FB or 33:33:00:00:00:FB, for
- // v4 and v6 respectively.
- // 2. it is an IPv4/IPv6 packet
- // 3. it is a UDP packet with port 5353
-
- // Check it's L2 mDNS multicast address.
- gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET);
- gen.addJumpIfBytesAtR0NotEqual(ETH_MULTICAST_MDNS_V4_MAC_ADDRESS,
- skipMdnsv4Filter);
-
- // Checks it's IPv4.
- gen.addLoad16(R0, ETH_ETHERTYPE_OFFSET);
- gen.addJumpIfR0NotEquals(ETH_P_IP, skipMdnsFilter);
-
- // Checks it's UDP.
- gen.addLoad8(R0, IPV4_PROTOCOL_OFFSET);
- gen.addJumpIfR0NotEquals(IPPROTO_UDP, skipMdnsFilter);
- // Set R1 to IPv4 header.
- gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE);
- gen.addJump(checkMdnsUdpPort);
-
- gen.defineLabel(skipMdnsv4Filter);
-
- // Checks it's L2 mDNS multicast address.
- // Relies on R0 containing the ethernet destination mac address offset.
- gen.addJumpIfBytesAtR0NotEqual(ETH_MULTICAST_MDNS_V6_MAC_ADDRESS, skipMdnsFilter);
-
- // Checks it's IPv6.
- gen.addLoad16(R0, ETH_ETHERTYPE_OFFSET);
- gen.addJumpIfR0NotEquals(ETH_P_IPV6, skipMdnsFilter);
-
- // Checks it's UDP.
- gen.addLoad8(R0, IPV6_NEXT_HEADER_OFFSET);
- gen.addJumpIfR0NotEquals(IPPROTO_UDP, skipMdnsFilter);
-
- // Set R1 to IPv6 header.
- gen.addLoadImmediate(R1, IPV6_HEADER_LEN);
-
- // Checks it's mDNS UDP port
- gen.defineLabel(checkMdnsUdpPort);
- gen.addLoad16Indexed(R0, UDP_DESTINATION_PORT_OFFSET);
- gen.addJumpIfR0NotEquals(MDNS_PORT, skipMdnsFilter);
-
- gen.addLoad16Indexed(R0, MDNS_QDCOUNT_OFFSET);
- // If QDCOUNT != 1, pass the packet
- gen.addJumpIfR0NotEquals(1, mDnsAcceptPacket);
-
- // If QDCOUNT == 1, matches the QNAME with allowlist.
- // Load offset for the first QNAME.
- gen.addLoadImmediate(R0, MDNS_QNAME_OFFSET);
- gen.addAddR1ToR0();
-
- // Check first QNAME against allowlist
- for (int i = 0; i < mMdnsAllowList.size(); ++i) {
- final String mDnsNextAllowedQnameCheck = "mdns_next_allowed_qname_check" + i;
- final byte[] encodedQname = encodeQname(mMdnsAllowList.get(i));
- gen.addJumpIfBytesAtR0NotEqual(encodedQname, mDnsNextAllowedQnameCheck);
- // QNAME matched
- gen.addJump(mDnsAcceptPacket);
- // QNAME not matched
- gen.defineLabel(mDnsNextAllowedQnameCheck);
- }
- // If QNAME doesn't match any entries in allowlist, drop the packet.
- gen.defineLabel(mDnsDropPacket);
- maybeSetupCounter(gen, Counter.DROPPED_MDNS);
- gen.addJump(mCountAndDropLabel);
-
- gen.defineLabel(mDnsAcceptPacket);
- maybeSetupCounter(gen, Counter.PASSED_MDNS);
- gen.addJump(mCountAndPassLabel);
-
-
- gen.defineLabel(skipMdnsFilter);
- }
-
- @GuardedBy("this")
- private void generateV6KeepaliveFilters(ApfV4Generator gen) throws IllegalInstructionException {
- generateKeepaliveFilters(gen, TcpKeepaliveAckV6.class, IPPROTO_TCP, IPV6_NEXT_HEADER_OFFSET,
- "skip_v6_keepalive_filter");
- }
-
- /**
- * Begin generating an APF program to:
- * <ul>
- * <li>Drop/Pass 802.3 frames (based on policy)
- * <li>Drop packets with EtherType within the Black List
- * <li>Drop ARP requests not for us, if mIPv4Address is set,
- * <li>Drop IPv4 broadcast packets, except DHCP destined to our MAC,
- * <li>Drop IPv4 multicast packets, if mMulticastFilter,
- * <li>Pass all other IPv4 packets,
- * <li>Drop all broadcast non-IP non-ARP packets.
- * <li>Pass all non-ICMPv6 IPv6 packets,
- * <li>Pass all non-IPv4 and non-IPv6 packets,
- * <li>Drop IPv6 ICMPv6 NAs to anything in ff02::/120.
- * <li>Drop IPv6 ICMPv6 RSs.
- * <li>Filter IPv4 packets (see generateIPv4FilterLocked())
- * <li>Filter IPv6 packets (see generateIPv6FilterLocked())
- * <li>Let execution continue off the end of the program for IPv6 ICMPv6 packets. This allows
- * insertion of RA filters here, or if there aren't any, just passes the packets.
- * </ul>
- */
- @GuardedBy("this")
- protected ApfV4Generator emitPrologueLocked() throws IllegalInstructionException {
- // This is guaranteed to succeed because of the check in maybeCreate.
- ApfV4Generator gen = new ApfV4Generator(mApfVersionSupported, mMaximumApfProgramSize,
- mMaximumApfProgramSize);
-
- if (hasDataAccess(mApfVersionSupported)) {
- // Increment TOTAL_PACKETS
- maybeSetupCounter(gen, Counter.TOTAL_PACKETS);
- gen.addLoadData(R0, 0); // load counter
- gen.addAdd(1);
- gen.addStoreData(R0, 0); // write-back counter
-
- maybeSetupCounter(gen, Counter.FILTER_AGE_SECONDS);
- gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_SECONDS);
- gen.addStoreData(R0, 0); // store 'counter'
-
- // requires a new enough APFv5+ interpreter, otherwise will be 0
- maybeSetupCounter(gen, Counter.FILTER_AGE_16384THS);
- gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_16384THS);
- gen.addStoreData(R0, 0); // store 'counter'
-
- // requires a new enough APFv5+ interpreter, otherwise will be 0
- maybeSetupCounter(gen, Counter.APF_VERSION);
- gen.addLoadFromMemory(R0, MemorySlot.APF_VERSION);
- gen.addStoreData(R0, 0); // store 'counter'
-
- // store this program's sequential id, for later comparison
- maybeSetupCounter(gen, Counter.APF_PROGRAM_ID);
- gen.addLoadImmediate(R0, mNumProgramUpdates);
- gen.addStoreData(R0, 0); // store 'counter'
- }
-
- // Here's a basic summary of what the initial program does:
- //
- // if it's a 802.3 Frame (ethtype < 0x0600):
- // drop or pass based on configurations
- // if it has a ether-type that belongs to the black list
- // drop
- // if it's ARP:
- // insert ARP filter to drop or pass these appropriately
- // if it's IPv4:
- // insert IPv4 filter to drop or pass these appropriately
- // if it's not IPv6:
- // if it's broadcast:
- // drop
- // pass
- // insert IPv6 filter to drop, pass, or fall off the end for ICMPv6 packets
-
- gen.addLoad16(R0, ETH_ETHERTYPE_OFFSET);
-
- if (mDrop802_3Frames) {
- // drop 802.3 frames (ethtype < 0x0600)
- maybeSetupCounter(gen, Counter.DROPPED_802_3_FRAME);
- gen.addJumpIfR0LessThan(ETH_TYPE_MIN, mCountAndDropLabel);
- }
-
- // Handle ether-type black list
- maybeSetupCounter(gen, Counter.DROPPED_ETHERTYPE_NOT_ALLOWED);
- for (int p : mEthTypeBlackList) {
- gen.addJumpIfR0Equals(p, mCountAndDropLabel);
- }
-
- // Add ARP filters:
- String skipArpFiltersLabel = "skipArpFilters";
- gen.addJumpIfR0NotEquals(ETH_P_ARP, skipArpFiltersLabel);
- generateArpFilterLocked(gen);
- gen.defineLabel(skipArpFiltersLabel);
-
- // Add mDNS filter:
- generateMdnsFilterLocked(gen);
- gen.addLoad16(R0, ETH_ETHERTYPE_OFFSET);
-
- // Add IPv4 filters:
- String skipIPv4FiltersLabel = "skipIPv4Filters";
- gen.addJumpIfR0NotEquals(ETH_P_IP, skipIPv4FiltersLabel);
- generateIPv4FilterLocked(gen);
- gen.defineLabel(skipIPv4FiltersLabel);
-
- // Check for IPv6:
- // NOTE: Relies on R0 containing ethertype. This is safe because if we got here, we did
- // not execute the IPv4 filter, since this filter do not fall through, but either drop or
- // pass.
- String ipv6FilterLabel = "IPv6Filters";
- gen.addJumpIfR0Equals(ETH_P_IPV6, ipv6FilterLabel);
-
- // Drop non-IP non-ARP broadcasts, pass the rest
- gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET);
- maybeSetupCounter(gen, Counter.PASSED_NON_IP_UNICAST);
- gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel);
- maybeSetupCounter(gen, Counter.DROPPED_ETH_BROADCAST);
- gen.addJump(mCountAndDropLabel);
-
- // Add IPv6 filters:
- gen.defineLabel(ipv6FilterLabel);
- generateIPv6FilterLocked(gen);
- return gen;
- }
-
- /**
- * Append packet counting epilogue to the APF program.
- *
- * Currently, the epilogue consists of two trampolines which count passed and dropped packets
- * before jumping to the actual PASS and DROP labels.
- */
- @GuardedBy("this")
- private void emitEpilogue(ApfV4Generator gen) throws IllegalInstructionException {
- // If APFv4 is unsupported, no epilogue is necessary: if execution reached this far, it
- // will just fall-through to the PASS label.
- if (!hasDataAccess(mApfVersionSupported)) return;
-
- // Execution will reach the bottom of the program if none of the filters match,
- // which will pass the packet to the application processor.
- maybeSetupCounter(gen, Counter.PASSED_IPV6_ICMP);
-
- // Append the count & pass trampoline, which increments the counter at the data address
- // pointed to by R1, then jumps to the pass label. This saves a few bytes over inserting
- // the entire sequence inline for every counter.
- gen.defineLabel(mCountAndPassLabel);
- gen.addLoadData(R0, 0); // R0 = *(R1 + 0)
- gen.addAdd(1); // R0++
- gen.addStoreData(R0, 0); // *(R1 + 0) = R0
- gen.addJump(gen.PASS_LABEL);
-
- // Same as above for the count & drop trampoline.
- gen.defineLabel(mCountAndDropLabel);
- gen.addLoadData(R0, 0); // R0 = *(R1 + 0)
- gen.addAdd(1); // R0++
- gen.addStoreData(R0, 0); // *(R1 + 0) = R0
- gen.addJump(gen.DROP_LABEL);
- }
-
- /**
- * Generate and install a new filter program.
- */
- @GuardedBy("this")
- // errorprone false positive on ra#shouldFilter and ra#generateFilterLocked
- @SuppressWarnings("GuardedBy")
- @VisibleForTesting
- public void installNewProgramLocked() {
- purgeExpiredRasLocked();
- ArrayList<Ra> rasToFilter = new ArrayList<>();
- final byte[] program;
- long programMinLifetime = Long.MAX_VALUE;
- long maximumApfProgramSize = mMaximumApfProgramSize;
- if (hasDataAccess(mApfVersionSupported)) {
- // Reserve space for the counters.
- maximumApfProgramSize -= Counter.totalSize();
- }
-
- mProgramBaseTime = currentTimeSeconds();
- try {
- // Step 1: Determine how many RA filters we can fit in the program.
- ApfV4Generator gen = emitPrologueLocked();
-
- // The epilogue normally goes after the RA filters, but add it early to include its
- // length when estimating the total.
- emitEpilogue(gen);
-
- // Can't fit the program even without any RA filters?
- if (gen.programLengthOverEstimate() > maximumApfProgramSize) {
- Log.e(TAG, "Program exceeds maximum size " + maximumApfProgramSize);
- sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_OVER_SIZE_FAILURE);
- return;
- }
-
- for (Ra ra : mRas) {
- if (!ra.shouldFilter()) continue;
- ra.generateFilterLocked(gen);
- // Stop if we get too big.
- if (gen.programLengthOverEstimate() > maximumApfProgramSize) {
- if (VDBG) Log.d(TAG, "Past maximum program size, skipping RAs");
- sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_OVER_SIZE_FAILURE);
- break;
- }
-
- rasToFilter.add(ra);
- }
-
- // Step 2: Actually generate the program
- gen = emitPrologueLocked();
- for (Ra ra : rasToFilter) {
- programMinLifetime = Math.min(programMinLifetime, ra.generateFilterLocked(gen));
- }
- emitEpilogue(gen);
- program = gen.generate();
- } catch (IllegalInstructionException|IllegalStateException e) {
- Log.e(TAG, "Failed to generate APF program.", e);
- sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_GENERATE_FILTER_EXCEPTION);
- return;
- }
- if (mIsRunning && !mIpClientCallback.installPacketFilter(program)) {
- sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE);
- }
- mLastTimeInstalledProgram = mProgramBaseTime;
- mLastInstalledProgramMinLifetime = programMinLifetime;
- mLastInstalledProgram = program;
- mNumProgramUpdates++;
- mMaxProgramSize = Math.max(mMaxProgramSize, program.length);
-
- if (VDBG) {
- hexDump("Installing filter: ", program, program.length);
- }
- logApfProgramEventLocked(mProgramBaseTime);
- mLastInstallEvent = new ApfProgramEvent.Builder()
- .setLifetime(programMinLifetime)
- .setFilteredRas(rasToFilter.size())
- .setCurrentRas(mRas.size())
- .setProgramLength(program.length)
- .setFlags(mIPv4Address != null, mMulticastFilter);
- }
-
- @GuardedBy("this")
- private void logApfProgramEventLocked(long now) {
- if (mLastInstallEvent == null) {
- return;
- }
- ApfProgramEvent.Builder ev = mLastInstallEvent;
- mLastInstallEvent = null;
- final long actualLifetime = now - mLastTimeInstalledProgram;
- ev.setActualLifetime(actualLifetime);
- if (actualLifetime < APF_PROGRAM_EVENT_LIFETIME_THRESHOLD) {
- return;
- }
- mMetricsLog.log(ev.build());
- }
-
- /**
- * Returns {@code true} if a new program should be installed because the current one dies soon.
- */
- @GuardedBy("this")
- private boolean shouldInstallnewProgram() {
- long expiry = mLastTimeInstalledProgram + mLastInstalledProgramMinLifetime;
- return expiry < currentTimeSeconds() + MAX_PROGRAM_LIFETIME_WORTH_REFRESHING;
- }
-
- private void hexDump(String msg, byte[] packet, int length) {
- log(msg + HexDump.toHexString(packet, 0, length, false /* lowercase */));
- }
-
- @GuardedBy("this")
- private void purgeExpiredRasLocked() {
- for (int i = 0; i < mRas.size();) {
- if (mRas.get(i).isExpired()) {
- log("Expiring " + mRas.get(i));
- mRas.remove(i);
- } else {
- i++;
- }
- }
- }
-
- // Get the minimum value excludes zero. This is used for calculating the lowest lifetime values
- // in RA packets. Zero lifetimes are excluded because we want to detect whether there is any
- // unusually small lifetimes but zero lifetime is actually valid (cease to be a default router
- // or the option is no longer be used). Number of zero lifetime RAs is collected in a different
- // Metrics.
- private long getMinForPositiveValue(long oldMinValue, long value) {
- if (value < 1) return oldMinValue;
- return Math.min(oldMinValue, value);
- }
-
- private int getMinForPositiveValue(int oldMinValue, int value) {
- return (int) getMinForPositiveValue((long) oldMinValue, (long) value);
- }
-
- /**
- * Process an RA packet, updating the list of known RAs and installing a new APF program
- * if the current APF program should be updated.
- * @return a ProcessRaResult enum describing what action was performed.
- */
- @VisibleForTesting
- public synchronized ProcessRaResult processRa(byte[] packet, int length) {
- if (VDBG) hexDump("Read packet = ", packet, length);
-
- // Have we seen this RA before?
- for (int i = 0; i < mRas.size(); i++) {
- Ra ra = mRas.get(i);
- if (ra.matches(packet, length)) {
- if (VDBG) log("matched RA " + ra);
- // Update lifetimes.
- ra.mLastSeen = currentTimeSeconds();
- ra.seenCount++;
-
- // Keep mRas in LRU order so as to prioritize generating filters for recently seen
- // RAs. LRU prioritizes this because RA filters are generated in order from mRas
- // until the filter program exceeds the maximum filter program size allowed by the
- // chipset, so RAs appearing earlier in mRas are more likely to make it into the
- // filter program.
- // TODO: consider sorting the RAs in order of increasing expiry time as well.
- // Swap to front of array.
- mRas.add(0, mRas.remove(i));
-
- // If the current program doesn't expire for a while, don't update.
- if (shouldInstallnewProgram()) {
- installNewProgramLocked();
- return ProcessRaResult.UPDATE_EXPIRY;
- }
- return ProcessRaResult.MATCH;
- }
- }
- purgeExpiredRasLocked();
-
- mMaxDistinctRas = Math.max(mMaxDistinctRas, mRas.size() + 1);
-
- // TODO: figure out how to proceed when we've received more than MAX_RAS RAs.
- if (mRas.size() >= MAX_RAS) {
- return ProcessRaResult.DROPPED;
- }
- final Ra ra;
- try {
- ra = new Ra(packet, length);
- } catch (Exception e) {
- Log.e(TAG, "Error parsing RA", e);
- mNumParseErrorRas++;
- return ProcessRaResult.PARSE_ERROR;
- }
-
- // Update info for Metrics
- mLowestRouterLifetimeSeconds = getMinForPositiveValue(
- mLowestRouterLifetimeSeconds, ra.routerLifetime());
- mLowestPioValidLifetimeSeconds = getMinForPositiveValue(
- mLowestPioValidLifetimeSeconds, ra.minPioValidLifetime());
- mLowestRioRouteLifetimeSeconds = getMinForPositiveValue(
- mLowestRioRouteLifetimeSeconds, ra.minRioRouteLifetime());
- mLowestRdnssLifetimeSeconds = getMinForPositiveValue(
- mLowestRdnssLifetimeSeconds, ra.minRdnssLifetime());
-
- // Ignore 0 lifetime RAs.
- if (ra.isExpired()) {
- mNumZeroLifetimeRas++;
- return ProcessRaResult.ZERO_LIFETIME;
- }
- log("Adding " + ra);
- mRas.add(ra);
- installNewProgramLocked();
- return ProcessRaResult.UPDATE_NEW_RA;
- }
-
- /**
- * Create an {@link LegacyApfFilter} if {@code apfCapabilities} indicates support for packet
- * filtering using APF programs.
- */
- public static LegacyApfFilter maybeCreate(Context context, ApfFilter.ApfConfiguration config,
- InterfaceParams ifParams, IpClientCallbacksWrapper ipClientCallback,
- NetworkQuirkMetrics networkQuirkMetrics) {
- if (context == null || config == null || ifParams == null) return null;
- if (!ApfV4Generator.supportsVersion(config.apfVersionSupported)) {
- return null;
- }
- if (config.apfRamSize < 512) {
- Log.e(TAG, "Unacceptably small APF limit: " + config.apfRamSize);
- return null;
- }
-
- return new LegacyApfFilter(context, config, ifParams, ipClientCallback,
- new IpConnectivityLog(), networkQuirkMetrics);
- }
-
- private synchronized void collectAndSendMetrics() {
- if (mIpClientRaInfoMetrics == null || mApfSessionInfoMetrics == null) return;
- final long sessionDurationMs = mClock.elapsedRealtime() - mSessionStartMs;
- if (sessionDurationMs < mMinMetricsSessionDurationMs) return;
-
- // Collect and send IpClientRaInfoMetrics.
- mIpClientRaInfoMetrics.setMaxNumberOfDistinctRas(mMaxDistinctRas);
- mIpClientRaInfoMetrics.setNumberOfZeroLifetimeRas(mNumZeroLifetimeRas);
- mIpClientRaInfoMetrics.setNumberOfParsingErrorRas(mNumParseErrorRas);
- mIpClientRaInfoMetrics.setLowestRouterLifetimeSeconds(mLowestRouterLifetimeSeconds);
- mIpClientRaInfoMetrics.setLowestPioValidLifetimeSeconds(mLowestPioValidLifetimeSeconds);
- mIpClientRaInfoMetrics.setLowestRioRouteLifetimeSeconds(mLowestRioRouteLifetimeSeconds);
- mIpClientRaInfoMetrics.setLowestRdnssLifetimeSeconds(mLowestRdnssLifetimeSeconds);
- mIpClientRaInfoMetrics.statsWrite();
-
- // Collect and send ApfSessionInfoMetrics.
- mApfSessionInfoMetrics.setVersion(mApfVersionSupported);
- mApfSessionInfoMetrics.setMemorySize(mMaximumApfProgramSize);
- mApfSessionInfoMetrics.setApfSessionDurationSeconds(
- (int) (sessionDurationMs / DateUtils.SECOND_IN_MILLIS));
- mApfSessionInfoMetrics.setNumOfTimesApfProgramUpdated(mNumProgramUpdates);
- mApfSessionInfoMetrics.setMaxProgramSize(mMaxProgramSize);
- for (Map.Entry<Counter, Long> entry : mApfCounterTracker.getCounters().entrySet()) {
- if (entry.getValue() > 0) {
- mApfSessionInfoMetrics.addApfCounter(entry.getKey(), entry.getValue());
- }
- }
- mApfSessionInfoMetrics.statsWrite();
- }
-
- public synchronized void shutdown() {
- collectAndSendMetrics();
- if (mReceiveThread != null) {
- log("shutting down");
- mReceiveThread.halt(); // Also closes socket.
- mReceiveThread = null;
- }
- mRas.clear();
- mContext.unregisterReceiver(mDeviceIdleReceiver);
- }
-
- public synchronized void setMulticastFilter(boolean isEnabled) {
- if (mMulticastFilter == isEnabled) return;
- mMulticastFilter = isEnabled;
- if (!isEnabled) {
- mNumProgramUpdatesAllowingMulticast++;
- }
- installNewProgramLocked();
- }
-
- /** Adds qname to the mDNS allowlist */
- public synchronized void addToMdnsAllowList(String[] labels) {
- mMdnsAllowList.add(labels);
- if (mMulticastFilter) {
- installNewProgramLocked();
- }
- }
-
- /** Removes qname from the mDNS allowlist */
- public synchronized void removeFromAllowList(String[] labels) {
- mMdnsAllowList.removeIf(e -> Arrays.equals(labels, e));
- if (mMulticastFilter) {
- installNewProgramLocked();
- }
- }
-
- @VisibleForTesting
- public synchronized void setDozeMode(boolean isEnabled) {
- if (mInDozeMode == isEnabled) return;
- mInDozeMode = isEnabled;
- installNewProgramLocked();
- }
-
- /** Find the single IPv4 LinkAddress if there is one, otherwise return null. */
- private static LinkAddress findIPv4LinkAddress(LinkProperties lp) {
- LinkAddress ipv4Address = null;
- for (LinkAddress address : lp.getLinkAddresses()) {
- if (!(address.getAddress() instanceof Inet4Address)) {
- continue;
- }
- if (ipv4Address != null && !ipv4Address.isSameAddressAs(address)) {
- // More than one IPv4 address, abort.
- return null;
- }
- ipv4Address = address;
- }
- return ipv4Address;
- }
-
- public synchronized void setLinkProperties(LinkProperties lp) {
- // NOTE: Do not keep a copy of LinkProperties as it would further duplicate state.
- final LinkAddress ipv4Address = findIPv4LinkAddress(lp);
- final byte[] addr = (ipv4Address != null) ? ipv4Address.getAddress().getAddress() : null;
- final int prefix = (ipv4Address != null) ? ipv4Address.getPrefixLength() : 0;
- if ((prefix == mIPv4PrefixLength) && Arrays.equals(addr, mIPv4Address)) {
- return;
- }
- mIPv4Address = addr;
- mIPv4PrefixLength = prefix;
- installNewProgramLocked();
- }
-
- /**
- * Add TCP keepalive ack packet filter.
- * This will add a filter to drop acks to the keepalive packet passed as an argument.
- *
- * @param slot The index used to access the filter.
- * @param sentKeepalivePacket The attributes of the sent keepalive packet.
- */
- public synchronized void addTcpKeepalivePacketFilter(final int slot,
- final TcpKeepalivePacketDataParcelable sentKeepalivePacket) {
- log("Adding keepalive ack(" + slot + ")");
- if (null != mKeepalivePackets.get(slot)) {
- throw new IllegalArgumentException("Keepalive slot " + slot + " is occupied");
- }
- final int ipVersion = sentKeepalivePacket.srcAddress.length == 4 ? 4 : 6;
- mKeepalivePackets.put(slot, (ipVersion == 4)
- ? new TcpKeepaliveAckV4(sentKeepalivePacket)
- : new TcpKeepaliveAckV6(sentKeepalivePacket));
- installNewProgramLocked();
- }
-
- /**
- * Add NAT-T keepalive packet filter.
- * This will add a filter to drop NAT-T keepalive packet which is passed as an argument.
- *
- * @param slot The index used to access the filter.
- * @param sentKeepalivePacket The attributes of the sent keepalive packet.
- */
- public synchronized void addNattKeepalivePacketFilter(final int slot,
- final NattKeepalivePacketDataParcelable sentKeepalivePacket) {
- log("Adding NAT-T keepalive packet(" + slot + ")");
- if (null != mKeepalivePackets.get(slot)) {
- throw new IllegalArgumentException("NAT-T Keepalive slot " + slot + " is occupied");
- }
-
- // TODO : update ApfFilter to support dropping v6 keepalives
- if (sentKeepalivePacket.srcAddress.length != 4) {
- return;
- }
-
- mKeepalivePackets.put(slot, new NattKeepaliveResponse(sentKeepalivePacket));
- installNewProgramLocked();
- }
-
- /**
- * Remove keepalive packet filter.
- *
- * @param slot The index used to access the filter.
- */
- public synchronized void removeKeepalivePacketFilter(int slot) {
- log("Removing keepalive packet(" + slot + ")");
- mKeepalivePackets.remove(slot);
- installNewProgramLocked();
- }
-
- public synchronized void dump(IndentingPrintWriter pw) {
- pw.println(String.format(
- "Capabilities: { apfVersionSupported: %d, maximumApfProgramSize: %d }",
- mApfVersionSupported, mMaximumApfProgramSize));
- pw.println("Filter update status: " + (mIsRunning ? "RUNNING" : "PAUSED"));
- pw.println("Receive thread: " + (mReceiveThread != null ? "RUNNING" : "STOPPED"));
- pw.println("Multicast: " + (mMulticastFilter ? "DROP" : "ALLOW"));
- pw.println("Minimum RDNSS lifetime: " + mMinRdnssLifetimeSec);
- try {
- pw.println("IPv4 address: " + InetAddress.getByAddress(mIPv4Address).getHostAddress());
- } catch (UnknownHostException|NullPointerException e) {}
-
- if (mLastTimeInstalledProgram == 0) {
- pw.println("No program installed.");
- return;
- }
- pw.println("Program updates: " + mNumProgramUpdates);
- pw.println(String.format(
- "Last program length %d, installed %ds ago, lifetime %ds",
- mLastInstalledProgram.length, currentTimeSeconds() - mLastTimeInstalledProgram,
- mLastInstalledProgramMinLifetime));
-
- pw.print("Denylisted Ethertypes:");
- for (int p : mEthTypeBlackList) {
- pw.print(String.format(" %04x", p));
- }
- pw.println();
- pw.println("RA filters:");
- pw.increaseIndent();
- for (Ra ra: mRas) {
- pw.println(ra);
- pw.increaseIndent();
- pw.println(String.format(
- "Seen: %d, last %ds ago", ra.seenCount, currentTimeSeconds() - ra.mLastSeen));
- if (DBG) {
- pw.println("Last match:");
- pw.increaseIndent();
- pw.println(ra.getLastMatchingPacket());
- pw.decreaseIndent();
- }
- pw.decreaseIndent();
- }
- pw.decreaseIndent();
-
- pw.println("TCP Keepalive filters:");
- pw.increaseIndent();
- for (int i = 0; i < mKeepalivePackets.size(); ++i) {
- final KeepalivePacket keepalivePacket = mKeepalivePackets.valueAt(i);
- if (keepalivePacket instanceof TcpKeepaliveAck) {
- pw.print("Slot ");
- pw.print(mKeepalivePackets.keyAt(i));
- pw.print(": ");
- pw.println(keepalivePacket);
- }
- }
- pw.decreaseIndent();
-
- pw.println("NAT-T Keepalive filters:");
- pw.increaseIndent();
- for (int i = 0; i < mKeepalivePackets.size(); ++i) {
- final KeepalivePacket keepalivePacket = mKeepalivePackets.valueAt(i);
- if (keepalivePacket instanceof NattKeepaliveResponse) {
- pw.print("Slot ");
- pw.print(mKeepalivePackets.keyAt(i));
- pw.print(": ");
- pw.println(keepalivePacket);
- }
- }
- pw.decreaseIndent();
-
- if (DBG) {
- pw.println("Last program:");
- pw.increaseIndent();
- pw.println(HexDump.toHexString(mLastInstalledProgram, false /* lowercase */));
- pw.decreaseIndent();
- }
-
- pw.println("APF packet counters: ");
- pw.increaseIndent();
- if (!hasDataAccess(mApfVersionSupported)) {
- pw.println("APF counters not supported");
- } else if (mDataSnapshot == null) {
- pw.println("No last snapshot.");
- } else {
- try {
- Counter[] counters = Counter.class.getEnumConstants();
- for (Counter c : Arrays.asList(counters).subList(1, counters.length)) {
- long value = ApfCounterTracker.getCounterValue(mDataSnapshot, c);
- // Only print non-zero counters
- if (value != 0) {
- pw.println(c.toString() + ": " + value);
- }
-
- // If the counter's value decreases, it may have been cleaned up or there may be
- // a bug.
- if (value < mApfCounterTracker.getCounters().getOrDefault(c, 0L)) {
- Log.e(TAG, "Error: Counter value unexpectedly decreased.");
- }
- }
- } catch (ArrayIndexOutOfBoundsException e) {
- pw.println("Uh-oh: " + e);
- }
- if (VDBG) {
- pw.println("Raw data dump: ");
- pw.println(HexDump.dumpHexString(mDataSnapshot));
- }
- }
- pw.decreaseIndent();
- }
-
- // TODO: move to android.net.NetworkUtils
- @VisibleForTesting
- public static int ipv4BroadcastAddress(byte[] addrBytes, int prefixLength) {
- return bytesToBEInt(addrBytes) | (int) (Integer.toUnsignedLong(-1) >>> prefixLength);
- }
-
- private static int uint8(byte b) {
- return b & 0xff;
- }
-
- private static int getUint16(ByteBuffer buffer, int position) {
- return buffer.getShort(position) & 0xffff;
- }
-
- private static long getUint32(ByteBuffer buffer, int position) {
- return Integer.toUnsignedLong(buffer.getInt(position));
- }
-
- private static int getUint8(ByteBuffer buffer, int position) {
- return uint8(buffer.get(position));
- }
-
- private static int bytesToBEInt(byte[] bytes) {
- return (uint8(bytes[0]) << 24)
- + (uint8(bytes[1]) << 16)
- + (uint8(bytes[2]) << 8)
- + (uint8(bytes[3]));
- }
-
- private static byte[] concatArrays(final byte[]... arr) {
- int size = 0;
- for (byte[] a : arr) {
- size += a.length;
- }
- final byte[] result = new byte[size];
- int offset = 0;
- for (byte[] a : arr) {
- System.arraycopy(a, 0, result, offset, a.length);
- offset += a.length;
- }
- return result;
- }
-
- private void sendNetworkQuirkMetrics(final NetworkQuirkEvent event) {
- if (mNetworkQuirkMetrics == null) return;
- mNetworkQuirkMetrics.setEvent(event);
- mNetworkQuirkMetrics.statsWrite();
- }
-
- /**
- * Indicates whether the ApfFilter is currently running / paused for test and debugging
- * purposes.
- */
- public boolean isRunning() {
- return mIsRunning;
- }
-
- /** Pause ApfFilter updates for testing purposes. */
- public void pause() {
- mIsRunning = false;
- }
-
- /** Resume ApfFilter updates for testing purposes. */
- public void resume() {
- mIsRunning = true;
- }
-
- /** Return hex string of current APF snapshot for testing purposes. */
- public synchronized @Nullable String getDataSnapshotHexString() {
- if (mDataSnapshot == null) {
- return null;
- }
- return HexDump.toHexString(mDataSnapshot, 0, mDataSnapshot.length, false /* lowercase */);
- }
-}
diff --git a/src/android/net/apf/MdnsOffloadRule.java b/src/android/net/apf/MdnsOffloadRule.java
index 454f35a..06d875f 100644
--- a/src/android/net/apf/MdnsOffloadRule.java
+++ b/src/android/net/apf/MdnsOffloadRule.java
@@ -41,10 +41,15 @@
@NonNull
public final List<Matcher> mMatchers;
+ @NonNull
+ public final String mFullServiceName;
+
/**
* Construct an mDNS offload rule.
*/
- public MdnsOffloadRule(@NonNull List<Matcher> matchers, @Nullable byte[] offloadPayload) {
+ public MdnsOffloadRule(@NonNull String fullServiceName, @NonNull List<Matcher> matchers,
+ @Nullable byte[] offloadPayload) {
+ mFullServiceName = fullServiceName;
mMatchers = matchers;
mOffloadPayload = offloadPayload;
}
@@ -86,34 +91,32 @@
/**
* The QTYPE from the mDNS query that this rule matches.
*/
- public final int mQtype;
+ public final int[] mQtypes;
/**
* Creates a new Matcher.
*/
- public Matcher(byte[] qnames, int qtype) {
+ public Matcher(byte[] qnames, int[] qtypes) {
mQnames = qnames;
- mQtype = qtype;
+ mQtypes = qtypes;
}
@Override
public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof Matcher that)) return false;
- return mQtype == that.mQtype && Arrays.equals(mQnames, that.mQnames);
+ if (!(o instanceof Matcher matcher)) return false;
+ return Objects.deepEquals(mQnames, matcher.mQnames) && Objects.deepEquals(
+ mQtypes, matcher.mQtypes);
}
@Override
public int hashCode() {
- int result = Objects.hash(mQtype);
- result = 31 * result + Arrays.hashCode(mQnames);
- return result;
+ return Objects.hash(Arrays.hashCode(mQnames), Arrays.hashCode(mQtypes));
}
@Override
public String toString() {
- return "Matcher{" + "mQnames=" + HexDump.toHexString(mQnames) + ", mQtype="
- + mQtype + '}';
+ return "Matcher{" + "mQnames=" + HexDump.toHexString(mQnames) + ", mQtypes="
+ + Arrays.toString(mQtypes) + '}';
}
}
diff --git a/src/android/net/apf/ProcfsParsingUtils.java b/src/android/net/apf/ProcfsParsingUtils.java
index 4bac0f8..16fd4b1 100644
--- a/src/android/net/apf/ProcfsParsingUtils.java
+++ b/src/android/net/apf/ProcfsParsingUtils.java
@@ -15,18 +15,23 @@
*/
package android.net.apf;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ALL_HOST_MULTICAST;
+
import android.annotation.NonNull;
import android.net.MacAddress;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.HexDump;
+import com.android.net.module.util.HexDump;
import java.io.BufferedReader;
import java.io.IOException;
+import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
@@ -39,7 +44,9 @@
private static final String IPV6_CONF_PATH = "/proc/sys/net/ipv6/conf/";
private static final String IPV6_ANYCAST_PATH = "/proc/net/anycast6";
private static final String ETHER_MCAST_PATH = "/proc/net/dev_mcast";
+ private static final String IPV4_MCAST_PATH = "/proc/net/igmp";
private static final String IPV6_MCAST_PATH = "/proc/net/igmp6";
+ private static final String IPV4_DEFAULT_TTL_PATH = "/proc/sys/net/ipv4/ip_default_ttl";
private ProcfsParsingUtils() {
}
@@ -85,6 +92,23 @@
}
/**
+ * Parses the default TTL value from the procfs file lines.
+ */
+ @VisibleForTesting
+ public static int parseDefaultTtl(final List<String> lines) {
+ if (lines.size() != 1) {
+ return 64; // default ttl value as per rfc1700
+ }
+ try {
+ // ttl must be in the range [1, 255]
+ return Math.max(1, Math.min(255, Integer.parseInt(lines.get(0))));
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "failed to parse default ttl.", e);
+ return 64; // default ttl value as per rfc1700
+ }
+ }
+
+ /**
* Parses anycast6 addresses associated with a specific interface from a list of strings.
*
* This function searches the input list for a line containing the specified interface name.
@@ -172,6 +196,90 @@
return addresses;
}
+
+ /**
+ * Parses IPv4 multicast addresses associated with a specific interface from a list of strings.
+ *
+ * @param lines A list of strings, each containing interface and IPv4 address information.
+ * @param ifname The name of the network interface for which to extract multicast addresses.
+ * @param endian The byte order of the address, almost always use native order.
+ * @return A list of Inet4Address objects representing the parsed IPv4 multicast addresses.
+ * If an error occurs during parsing,
+ * a list contains IPv4 all host (224.0.0.1) is returned.
+ */
+ @VisibleForTesting
+ public static List<Inet4Address> parseIPv4MulticastAddresses(
+ @NonNull List<String> lines, @NonNull String ifname, @NonNull ByteOrder endian) {
+ final List<Inet4Address> ipAddresses = new ArrayList<>();
+
+ try {
+ String name = "";
+ // parse output similar to `ip maddr` command (iproute2/ip/ipmaddr.c#read_igmp())
+ for (String line : lines) {
+ final String[] parts = line.trim().split("\\s+");
+ if (!line.startsWith("\t")) {
+ name = parts[1];
+ if (name.endsWith(":")) {
+ name = name.substring(0, name.length() - 1);
+ }
+ continue;
+ }
+
+ if (!name.equals(ifname)) {
+ continue;
+ }
+
+ final String hexIp = parts[0];
+ final byte[] ipArray = HexDump.hexStringToByteArray(hexIp);
+ final byte[] convertArray =
+ (endian == ByteOrder.LITTLE_ENDIAN)
+ ? convertIPv4BytesToBigEndian(ipArray) : ipArray;
+ final Inet4Address ipv4Address =
+ (Inet4Address) InetAddress.getByAddress(convertArray);
+
+ ipAddresses.add(ipv4Address);
+ }
+ } catch (Exception e) {
+ Log.wtf(TAG, "failed to convert to Inet4Address.", e);
+ // always return IPv4 all host address (224.0.0.1) if any error during parsing.
+ // this aligns with kernel behavior, it will join 224.0.0.1 when the interface is up.
+ ipAddresses.clear();
+ ipAddresses.add(IPV4_ADDR_ALL_HOST_MULTICAST);
+ }
+
+ return ipAddresses;
+ }
+
+ /**
+ * Converts an IPv4 address from little-endian byte order to big-endian byte order.
+ *
+ * @param bytes The IPv4 address in little-endian byte order.
+ * @return The IPv4 address in big-endian byte order.
+ */
+ private static byte[] convertIPv4BytesToBigEndian(byte[] bytes) {
+ final ByteBuffer buffer = ByteBuffer.wrap(bytes);
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ final ByteBuffer bigEndianBuffer = ByteBuffer.allocate(4);
+ bigEndianBuffer.order(ByteOrder.BIG_ENDIAN);
+ bigEndianBuffer.putInt(buffer.getInt());
+ return bigEndianBuffer.array();
+ }
+
+ /**
+ * Returns the default TTL value for IPv4 packets.
+ */
+ public static int getIpv4DefaultTtl() {
+ return parseDefaultTtl(readFile(IPV4_DEFAULT_TTL_PATH));
+ }
+
+ /**
+ * Returns the default HopLimit value for IPv6 packets.
+ */
+ public static int getIpv6DefaultHopLimit(@NonNull String ifname) {
+ final String hopLimitPath = IPV6_CONF_PATH + ifname + "/hop_limit";
+ return parseDefaultTtl(readFile(hopLimitPath));
+ }
+
/**
* Returns the traffic class for the specified interface.
* The function loads the existing traffic class from the file
@@ -228,4 +336,19 @@
final List<String> lines = readFile(IPV6_MCAST_PATH);
return parseIPv6MulticastAddresses(lines, ifname);
}
+
+ /**
+ * The function loads the existing IPv4 multicast addresses from the file `/proc/net/igmp6`.
+ * If the file does not exist or the interface is not found, the function returns empty list.
+ *
+ * @param ifname The name of the network interface to query.
+ * @return A list of Inet4Address objects representing the IPv4 multicast addresses
+ * found for the interface.
+ * If the file cannot be read or there are no addresses, an empty list is returned.
+ */
+ public static List<Inet4Address> getIPv4MulticastAddresses(@NonNull String ifname) {
+ final List<String> lines = readFile(IPV4_MCAST_PATH);
+ // follow the same pattern as NetlinkMonitor#handlePacket() for device's endian order
+ return parseIPv4MulticastAddresses(lines, ifname, ByteOrder.nativeOrder());
+ }
}
diff --git a/src/android/net/ip/ConnectivityPacketTracker.java b/src/android/net/ip/ConnectivityPacketTracker.java
index ce4f6ae..35a71c7 100644
--- a/src/android/net/ip/ConnectivityPacketTracker.java
+++ b/src/android/net/ip/ConnectivityPacketTracker.java
@@ -48,6 +48,7 @@
import java.io.FileDescriptor;
import java.io.IOException;
import java.util.Arrays;
+import java.util.Locale;
import java.util.Objects;
@@ -79,14 +80,23 @@
}
/**
- * Create a socket to read RAs.
+ * Creates a raw packet socket for reading network packets.
+ *
+ * This method creates a socket and binds it to the specified network interface index,
+ * and optionally attaches a control packet filter.
+ *
+ * @param ifIndex The index of the network interface to bind the socket to.
+ * @param attachFilter If true, attaches a control packet filter to the socket.
+ * @return The FileDescriptor of the created socket, or null if an error occurred.
*/
@Nullable
- public FileDescriptor createPacketReaderSocket(int ifIndex) {
+ public FileDescriptor createPacketReaderSocket(int ifIndex, boolean attachFilter) {
FileDescriptor socket = null;
try {
socket = Os.socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, 0);
- NetworkStackUtils.attachControlPacketFilter(socket);
+ if (attachFilter) {
+ NetworkStackUtils.attachControlPacketFilter(socket);
+ }
Os.bind(socket, makePacketSocketAddress(ETH_P_ALL, ifIndex));
} catch (ErrnoException | IOException e) {
final String msg = "Failed to create packet tracking socket: ";
@@ -98,6 +108,12 @@
return socket;
}
+
+ /**
+ * Gets the maximum size of a captured packet.
+ *
+ * @return The maximum capture packet size.
+ */
public int getMaxCapturePktSize() {
return MAX_CAPTURE_PACKET_SIZE;
}
@@ -129,13 +145,18 @@
// store packet hex string in uppercase as key, receive packet count as value
private final LruCache<String, Integer> mPacketCache;
private final Dependencies mDependencies;
+ private final boolean mAttachFilter;
private long mLastRateLimitLogTimeMs = 0;
private boolean mRunning;
private boolean mCapturing;
private String mDisplayName;
- public ConnectivityPacketTracker(Handler h, InterfaceParams ifParams, LocalLog log) {
- this(h, ifParams, log, new Dependencies(log));
+ public ConnectivityPacketTracker(
+ Handler h,
+ InterfaceParams ifParams,
+ LocalLog log,
+ boolean attachFilter) {
+ this(h, ifParams, log, new Dependencies(log), attachFilter);
}
/**
@@ -163,7 +184,9 @@
* @return The count of packets matching the pattern, or 0 if no matches are found
*/
public int getMatchedPacketCount(String packet) {
- final Integer count = mPacketCache.get(packet);
+ // always convert to upper case since we use upper case when capturing
+ final String packetPattern = packet.toUpperCase(Locale.ROOT);
+ final Integer count = mPacketCache.get(packetPattern);
return (count != null) ? count : 0;
}
@@ -189,9 +212,12 @@
@NonNull Handler handler,
@NonNull InterfaceParams ifParams,
@NonNull LocalLog log,
- @NonNull Dependencies dependencies) {
+ @NonNull Dependencies dependencies,
+ boolean attachFilter
+ ) {
mTag = TAG + "." + Objects.requireNonNull(ifParams).name;
mLog = log;
+ mAttachFilter = attachFilter;
mPacketListener = new PacketListener(handler, ifParams);
mDependencies = dependencies;
mPacketCache = new LruCache<>(mDependencies.getMaxCapturePktSize());
@@ -207,7 +233,7 @@
@Override
protected FileDescriptor createFd() {
- return mDependencies.createPacketReaderSocket(mInterface.index);
+ return mDependencies.createPacketReaderSocket(mInterface.index, mAttachFilter);
}
@Override
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java
index 493f36f..7631a1c 100644
--- a/src/android/net/ip/IpClient.java
+++ b/src/android/net/ip/IpClient.java
@@ -16,7 +16,7 @@
package android.net.ip;
-import static android.content.pm.PackageManager.FEATURE_WATCH;
+import static android.content.pm.PackageManager.FEATURE_LEANBACK;
import static android.net.IIpMemoryStore.NETWORK_EVENT_NUD_FAILURE_ROAM;
import static android.net.IIpMemoryStore.NETWORK_EVENT_NUD_FAILURE_CONFIRM;
import static android.net.IIpMemoryStore.NETWORK_EVENT_NUD_FAILURE_ORGANIC;
@@ -52,11 +52,13 @@
import static android.net.ip.IpClient.IpClientCommands.EVENT_NUD_FAILURE_QUERY_FAILURE;
import static android.net.ip.IpClient.IpClientCommands.EVENT_NUD_FAILURE_QUERY_SUCCESS;
import static android.net.ip.IpClient.IpClientCommands.EVENT_NUD_FAILURE_QUERY_TIMEOUT;
+import static android.net.ip.IpClient.IpClientCommands.EVENT_PIO_PREFIX_UPDATE;
import static android.net.ip.IpClient.IpClientCommands.EVENT_PRE_DHCP_ACTION_COMPLETE;
import static android.net.ip.IpClient.IpClientCommands.EVENT_PROVISIONING_TIMEOUT;
import static android.net.ip.IpClient.IpClientCommands.EVENT_READ_PACKET_FILTER_COMPLETE;
import static android.net.ip.IpClientLinkObserver.IpClientNetlinkMonitor;
import static android.net.ip.IpClientLinkObserver.IpClientNetlinkMonitor.INetlinkMessageProcessor;
+import static android.net.ip.IpClientLinkObserver.PrefixInfo;
import static android.net.ip.IpReachabilityMonitor.INVALID_REACHABILITY_LOSS_TYPE;
import static android.net.ip.IpReachabilityMonitor.nudEventTypeToInt;
import static android.net.util.SocketUtils.makePacketSocketAddress;
@@ -80,16 +82,25 @@
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_ENABLE;
import static com.android.networkstack.util.NetworkStackUtils.APF_HANDLE_ARP_OFFLOAD;
+import static com.android.networkstack.util.NetworkStackUtils.APF_HANDLE_IGMP_OFFLOAD;
+import static com.android.networkstack.util.NetworkStackUtils.APF_HANDLE_IGMP_OFFLOAD_VERSION;
+import static com.android.networkstack.util.NetworkStackUtils.APF_HANDLE_MLD_OFFLOAD;
+import static com.android.networkstack.util.NetworkStackUtils.APF_HANDLE_MLD_OFFLOAD_VERSION;
import static com.android.networkstack.util.NetworkStackUtils.APF_HANDLE_ND_OFFLOAD;
-import static com.android.networkstack.util.NetworkStackUtils.APF_NEW_RA_FILTER_VERSION;
+import static com.android.networkstack.util.NetworkStackUtils.APF_HANDLE_PING4_OFFLOAD;
+import static com.android.networkstack.util.NetworkStackUtils.APF_HANDLE_PING4_OFFLOAD_VERSION;
+import static com.android.networkstack.util.NetworkStackUtils.APF_HANDLE_PING6_OFFLOAD;
+import static com.android.networkstack.util.NetworkStackUtils.APF_HANDLE_PING6_OFFLOAD_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_DHCPV6_PD_PREFERRED_FLAG_VERSION;
import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_IGNORE_LOW_RA_LIFETIME_VERSION;
import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_POPULATE_LINK_ADDRESS_LIFETIME_VERSION;
+import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_REPLACE_NETD_WITH_NETLINK_VERSION;
import static com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_IGNORE_NUD_FAILURE_VERSION;
import static com.android.networkstack.util.NetworkStackUtils.createInet6AddressFromEui64;
+import static com.android.networkstack.util.NetworkStackUtils.isAtLeast25Q2;
import static com.android.networkstack.util.NetworkStackUtils.macAddressToEui64;
import static com.android.server.util.PermissionUtil.enforceNetworkStackCallingPermission;
@@ -115,10 +126,8 @@
import android.net.RouteInfo;
import android.net.TcpKeepalivePacketDataParcelable;
import android.net.Uri;
-import android.net.apf.AndroidPacketFilter;
import android.net.apf.ApfCapabilities;
import android.net.apf.ApfFilter;
-import android.net.apf.LegacyApfFilter;
import android.net.dhcp.DhcpClient;
import android.net.dhcp.DhcpPacket;
import android.net.dhcp6.Dhcp6Client;
@@ -185,6 +194,7 @@
import com.android.networkstack.apishim.common.ShimUtils;
import com.android.networkstack.metrics.IpProvisioningMetrics;
import com.android.networkstack.metrics.NetworkQuirkMetrics;
+import com.android.networkstack.metrics.NetworkStackStatsLog;
import com.android.networkstack.packets.NeighborAdvertisement;
import com.android.networkstack.packets.NeighborSolicitation;
import com.android.networkstack.util.NetworkStackUtils;
@@ -210,6 +220,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Random;
import java.util.Set;
import java.util.StringJoiner;
import java.util.concurrent.CompletableFuture;
@@ -322,6 +333,7 @@
private final NetworkInformationShim mShim;
private final boolean mApfDebug;
+ private final Random mRandom = new Random();
@VisibleForTesting
protected IpClientCallbacksWrapper(IIpClientCallbacks callback, @NonNull SharedLog log,
@@ -385,6 +397,13 @@
*/
public void onProvisioningSuccess(LinkProperties newLp) {
log("onProvisioningSuccess({" + newLp + "})");
+ // We log this error, which has a 1 in 1,000,000 probability, as a heartbeat for
+ // terrible error reporting.
+ if (mRandom.nextInt(1000000) == 0) {
+ NetworkStackStatsLog.write(
+ NetworkStackStatsLog.CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED,
+ NetworkStackStatsLog.CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED__ERROR_TYPE__TYPE_UNKNOWN);
+ }
try {
mCallback.onProvisioningSuccess(mShim.makeSensitiveFieldsParcelingCopy(newLp));
} catch (RemoteException e) {
@@ -449,8 +468,9 @@
/**
* Called to indicate that a new APF program must be installed to filter incoming packets.
*/
- public boolean installPacketFilter(byte[] filter) {
- log("installPacketFilter(byte[" + filter.length + "])");
+ public boolean installPacketFilter(byte[] filter, @NonNull String filterConfig) {
+ log("installPacketFilter(byte[" + filter.length + "])" + " config: "
+ + filterConfig);
try {
if (mApfDebug) {
mApfLog.log("updated APF program: " + HexDump.toHexString(filter));
@@ -608,6 +628,7 @@
static final int EVENT_NUD_FAILURE_QUERY_TIMEOUT = 21;
static final int EVENT_NUD_FAILURE_QUERY_SUCCESS = 22;
static final int EVENT_NUD_FAILURE_QUERY_FAILURE = 23;
+ static final int EVENT_PIO_PREFIX_UPDATE = 24;
// Internal commands to use instead of trying to call transitionTo() inside
// a given State's enter() method. Calling transitionTo() from enter/exit
// encounters a Log.wtf() that can cause trouble on eng builds.
@@ -633,6 +654,8 @@
@VisibleForTesting
static final String CONFIG_ACCEPT_RA_MIN_LFT = "ipclient_accept_ra_min_lft";
+ @VisibleForTesting
+ static final int DEFAULT_ACCEPT_RA_MIN_LFT = 180;
@VisibleForTesting
static final String CONFIG_APF_COUNTER_POLLING_INTERVAL_SECS =
@@ -689,7 +712,7 @@
private static final int IPMEMORYSTORE_TIMEOUT_MS = 1000;
@VisibleForTesting
- static final long SIX_HOURS_IN_MS = 6 * 3600 * 1000L;
+ public static final long SIX_HOURS_IN_MS = 6 * 3600 * 1000L;
@VisibleForTesting
public static final long ONE_DAY_IN_MS = 4 * SIX_HOURS_IN_MS;
@VisibleForTesting
@@ -768,6 +791,7 @@
private final String mInterfaceName;
@VisibleForTesting
protected final IpClientCallbacksWrapper mCallback;
+ private final ApfFilter.IApfController mIpClientApfController;
private final Dependencies mDependencies;
private final ConnectivityManager mCm;
private final INetd mNetd;
@@ -798,16 +822,21 @@
private final int mNudFailureCountDailyThreshold;
private final int mNudFailureCountWeeklyThreshold;
- // Experiment flag read from device config.
- private final boolean mDhcp6PrefixDelegationEnabled;
- private final boolean mUseNewApfFilter;
+ // Experiment flags read from device config.
private final boolean mIsAcceptRaMinLftEnabled;
private final boolean mEnableApfPollingCounters;
private final boolean mPopulateLinkAddressLifetime;
- private final boolean mApfShouldHandleArpOffload;
- private final boolean mApfShouldHandleNdOffload;
- private final boolean mApfShouldHandleMdnsOffload;
+ private final boolean mEnableApf;
+ private final boolean mApfHandleArpOffload;
+ private final boolean mApfHandleNdOffload;
+ private final boolean mApfHandleMdnsOffload;
+ private final boolean mApfHandleIgmpOffload;
+ private final boolean mApfHandleMldOffload;
+ private final boolean mApfHandleIpv4PingOffload;
+ private final boolean mApfHandleIpv6PingOffload;
private final boolean mIgnoreNudFailureEnabled;
+ private final boolean mDhcp6PdPreferredFlagEnabled;
+ private final boolean mReplaceNetdWithNetlinkEnabled;
private InterfaceParams mInterfaceParams;
@@ -822,7 +851,7 @@
private DhcpResults mDhcpResults;
private String mTcpBufferSizes;
private ProxyInfo mHttpProxy;
- private AndroidPacketFilter mApfFilter;
+ private ApfFilter 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
@@ -978,17 +1007,12 @@
* APF programs.
* @see ApfFilter#maybeCreate
*/
- public AndroidPacketFilter maybeCreateApfFilter(Handler handler, Context context,
+ public ApfFilter maybeCreateApfFilter(Handler handler, Context context,
ApfFilter.ApfConfiguration config, InterfaceParams ifParams,
- IpClientCallbacksWrapper cb, NetworkQuirkMetrics networkQuirkMetrics,
- boolean useNewApfFilter) {
- if (useNewApfFilter) {
- return ApfFilter.maybeCreate(handler, context, config, ifParams, cb,
- networkQuirkMetrics);
- } else {
- return LegacyApfFilter.maybeCreate(context, config, ifParams, cb,
- networkQuirkMetrics);
- }
+ ApfFilter.IApfController apfController,
+ NetworkQuirkMetrics networkQuirkMetrics) {
+ return ApfFilter.maybeCreate(handler, context, config, ifParams, apfController,
+ networkQuirkMetrics);
}
/**
@@ -1012,8 +1036,10 @@
* Create an IpClientNetlinkMonitor instance.
*/
public IpClientNetlinkMonitor makeIpClientNetlinkMonitor(Handler h, SharedLog log,
- String tag, int sockRcvbufSize, INetlinkMessageProcessor p) {
- return new IpClientNetlinkMonitor(h, log, tag, sockRcvbufSize, p);
+ String tag, int sockRcvbufSize, boolean isDhcp6PdPreferredFlagEnabled,
+ INetlinkMessageProcessor p) {
+ return new IpClientNetlinkMonitor(h, log, tag, sockRcvbufSize,
+ isDhcp6PdPreferredFlagEnabled, p);
}
}
@@ -1050,34 +1076,61 @@
mApfDebug = Log.isLoggable(ApfFilter.class.getSimpleName(), Log.DEBUG);
mMsgStateLogger = new MessageHandlingLogger();
mCallback = new IpClientCallbacksWrapper(callback, mLog, mApfLog, mShim, mApfDebug);
+ mIpClientApfController = new ApfFilter.IApfController() {
+ @Override
+ public boolean installPacketFilter(byte[] filter, String filterConfig) {
+ return mCallback.installPacketFilter(filter, filterConfig);
+ }
+
+ @Override
+ public void readPacketFilterRam(String event) {
+ mCallback.startReadPacketFilter(event);
+ }
+ };
// TODO: Consider creating, constructing, and passing in some kind of
// InterfaceController.Dependencies class.
mNetd = deps.getNetd(mContext);
mInterfaceCtrl = new InterfaceController(mInterfaceName, mNetd, mLog);
- mDhcp6PrefixDelegationEnabled = mDependencies.isFeatureEnabled(mContext,
- IPCLIENT_DHCPV6_PREFIX_DELEGATION_VERSION);
-
- final boolean isWatch = mContext.getPackageManager().hasSystemFeature(FEATURE_WATCH);
mAcceptRaMinLft = mDependencies.getDeviceConfigPropertyInt(CONFIG_ACCEPT_RA_MIN_LFT,
- isWatch ? 900 : 180);
+ 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 = SdkLevel.isAtLeastV() || mDependencies.isFeatureNotChickenedOut(context,
- APF_NEW_RA_FILTER_VERSION);
mEnableApfPollingCounters = mDependencies.isFeatureEnabled(context,
APF_POLLING_COUNTERS_VERSION);
mIsAcceptRaMinLftEnabled =
SdkLevel.isAtLeastV() || mDependencies.isFeatureEnabled(context,
IPCLIENT_IGNORE_LOW_RA_LIFETIME_VERSION);
- mApfShouldHandleArpOffload = mDependencies.isFeatureNotChickenedOut(
+ mEnableApf = mDependencies.isFeatureNotChickenedOut(mContext, APF_ENABLE);
+ mApfHandleArpOffload = mDependencies.isFeatureNotChickenedOut(
mContext, APF_HANDLE_ARP_OFFLOAD);
- mApfShouldHandleNdOffload = mDependencies.isFeatureNotChickenedOut(
+ mApfHandleNdOffload = mDependencies.isFeatureNotChickenedOut(
mContext, APF_HANDLE_ND_OFFLOAD);
- // TODO: turn on APF mDNS offload.
- mApfShouldHandleMdnsOffload = false;
+ // TODO: turn on APF mDNS offload on handhelds.
+ mApfHandleMdnsOffload = isAtLeast25Q2() && context.getPackageManager().hasSystemFeature(
+ FEATURE_LEANBACK);
+ mApfHandleIgmpOffload =
+ mDependencies.isFeatureNotChickenedOut(mContext, APF_HANDLE_IGMP_OFFLOAD)
+ && (isAtLeast25Q2()
+ || mDependencies.isFeatureEnabled(context, APF_HANDLE_IGMP_OFFLOAD_VERSION)
+ );
+ mApfHandleMldOffload =
+ mDependencies.isFeatureNotChickenedOut(mContext, APF_HANDLE_MLD_OFFLOAD)
+ && (isAtLeast25Q2()
+ || mDependencies.isFeatureEnabled(context, APF_HANDLE_MLD_OFFLOAD_VERSION)
+ );
+ mApfHandleIpv4PingOffload =
+ mDependencies.isFeatureNotChickenedOut(mContext, APF_HANDLE_PING4_OFFLOAD)
+ && (isAtLeast25Q2()
+ || mDependencies.isFeatureEnabled(context, APF_HANDLE_PING4_OFFLOAD_VERSION)
+ );
+ mApfHandleIpv6PingOffload =
+ mDependencies.isFeatureNotChickenedOut(mContext, APF_HANDLE_PING6_OFFLOAD)
+ && (isAtLeast25Q2()
+ || mDependencies.isFeatureEnabled(context, APF_HANDLE_PING6_OFFLOAD_VERSION)
+ );
mPopulateLinkAddressLifetime = mDependencies.isFeatureEnabled(context,
IPCLIENT_POPULATE_LINK_ADDRESS_LIFETIME_VERSION);
mIgnoreNudFailureEnabled = mDependencies.isFeatureEnabled(mContext,
@@ -1088,9 +1141,12 @@
mNudFailureCountWeeklyThreshold = mDependencies.getDeviceConfigPropertyInt(
CONFIG_NUD_FAILURE_COUNT_WEEKLY_THRESHOLD,
DEFAULT_NUD_FAILURE_COUNT_WEEKLY_THRESHOLD);
-
+ mDhcp6PdPreferredFlagEnabled =
+ mDependencies.isFeatureEnabled(mContext, IPCLIENT_DHCPV6_PD_PREFERRED_FLAG_VERSION);
+ mReplaceNetdWithNetlinkEnabled = mDependencies.isFeatureEnabled(mContext,
+ IPCLIENT_REPLACE_NETD_WITH_NETLINK_VERSION);
IpClientLinkObserver.Configuration config = new IpClientLinkObserver.Configuration(
- mAcceptRaMinLft, mPopulateLinkAddressLifetime);
+ mAcceptRaMinLft, mPopulateLinkAddressLifetime, mDhcp6PdPreferredFlagEnabled);
mLinkObserver = new IpClientLinkObserver(
mContext, getHandler(),
@@ -1125,7 +1181,7 @@
// If Apf is not supported or Apf doesn't support ND offload, then
// configure the vendor ND offload feature based on the Clat
// interface state.
- if (mApfFilter == null || !mApfFilter.supportNdOffload()) {
+ if (mApfFilter == null || !mApfFilter.enableNdOffload()) {
// Clat interface information is spliced into LinkProperties by
// ConnectivityService, so it cannot be added to the LinkProperties
// here as those propagate back to ConnectivityService.
@@ -1137,6 +1193,12 @@
}
});
}
+
+ @Override
+ public void onNewPrefix(PrefixInfo info) {
+ if (!mDhcp6PdPreferredFlagEnabled) return;
+ sendMessage(EVENT_PIO_PREFIX_UPDATE, info);
+ }
},
config, mLog, mDependencies
);
@@ -1288,10 +1350,6 @@
mLinkObserver.shutdown();
}
- private boolean isGratuitousArpNaRoamingEnabled() {
- return mDependencies.isFeatureNotChickenedOut(mContext, IPCLIENT_GARP_NA_ROAMING_VERSION);
- }
-
@VisibleForTesting
static MacAddress getInitialBssid(final Layer2Information layer2Info,
final ScanResultInfo scanResultInfo, boolean isAtLeastS) {
@@ -1337,15 +1395,6 @@
doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
return;
}
-
- 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;
- }
sendMessage(CMD_START, new android.net.shared.ProvisioningConfiguration(req));
}
@@ -1477,7 +1526,7 @@
}
// Thread-unsafe access to mApfFilter but just used for debugging.
- final AndroidPacketFilter apfFilter = mApfFilter;
+ final ApfFilter apfFilter = mApfFilter;
final android.net.shared.ProvisioningConfiguration provisioningConfig = mConfiguration;
final ApfCapabilities apfCapabilities = mCurrentApfCapabilities;
@@ -1489,7 +1538,12 @@
apfCapabilities.apfVersionSupported)) {
// Request a new snapshot, then wait for it.
mApfDataSnapshotComplete.close();
- mCallback.startReadPacketFilter("dumpsys");
+ // To ensure long-term flexibility and support for different APF controller
+ // implementations (e.g., Ethtool-based), we use apfFilter.getApfController()
+ // instead of directly accessing mIpClientApfController. This approach makes
+ // code reusable and simplifies future transitions to alternative APF
+ // controllers.
+ apfFilter.getApfController().readPacketFilterRam("dumpsys");
if (!mApfDataSnapshotComplete.block(1000)) {
pw.print("TIMEOUT: DUMPING STALE APF SNAPSHOT");
}
@@ -1570,7 +1624,7 @@
}
// Request a new snapshot, then wait for it.
mApfDataSnapshotComplete.close();
- mCallback.startReadPacketFilter("shell command");
+ mApfFilter.getApfController().readPacketFilterRam("shell command");
if (!mApfDataSnapshotComplete.block(5000 /* ms */)) {
throw new RuntimeException("Error: Failed to read APF program");
}
@@ -1601,7 +1655,8 @@
if (mApfFilter.isRunning()) {
throw new IllegalStateException("APF filter must first be paused");
}
- mCallback.installPacketFilter(HexDump.hexStringToByteArray(optarg));
+ mApfFilter.getApfController().installPacketFilter(
+ HexDump.hexStringToByteArray(optarg), "program from shell command");
result.complete("success");
break;
case "capabilities":
@@ -1981,7 +2036,7 @@
// 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)) {
+ if (isIpv6StableDelegatedAddress(la)) {
final IpPrefix prefix = new IpPrefix(la.getAddress(), RFC7421_PREFIX_LENGTH);
mDelegatedPrefixes.remove(prefix);
}
@@ -1989,7 +2044,7 @@
}
for (LinkAddress la : results.added) {
- if (mDhcp6PrefixDelegationEnabled && isIpv6StableDelegatedAddress(la)) {
+ if (isIpv6StableDelegatedAddress(la)) {
final IpPrefix prefix = new IpPrefix(la.getAddress(), RFC7421_PREFIX_LENGTH);
mDelegatedPrefixes.add(prefix);
}
@@ -2032,22 +2087,20 @@
}
// [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);
- }
+ 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);
}
// [5] Add in TCP buffer sizes and HTTP Proxy config, if available.
@@ -2290,8 +2343,7 @@
// doesn't complete with success after timeout. This check also handles IPv6-only link
// local mode case, since there will be no IPv6 default route in that mode even with Prefix
// Delegation experiment flag enabled.
- if (mDhcp6PrefixDelegationEnabled
- && newLp.hasIpv6DefaultRoute()
+ if (newLp.hasIpv6DefaultRoute()
&& mIpv6AutoconfTimeoutAlarm == null) {
mIpv6AutoconfTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
mTag + ".EVENT_IPV6_AUTOCONF_TIMEOUT", EVENT_IPV6_AUTOCONF_TIMEOUT);
@@ -2507,7 +2559,6 @@
}
private void startDhcp6PrefixDelegation() {
- if (!mDhcp6PrefixDelegationEnabled) return;
if (mDhcp6Client != null) {
Log.wtf(mTag, "Dhcp6Client should never be non-null in startDhcp6PrefixDelegation");
return;
@@ -2630,7 +2681,7 @@
setIpv6Sysctl(ACCEPT_RA, 2);
setIpv6Sysctl(ACCEPT_RA_DEFRTR, 1);
maybeRestoreDadTransmits();
- if (mUseNewApfFilter && mIsAcceptRaMinLftEnabled
+ if (mIsAcceptRaMinLftEnabled
&& mDependencies.hasIpv6Sysctl(mInterfaceName, ACCEPT_RA_MIN_LFT)) {
setIpv6Sysctl(ACCEPT_RA_MIN_LFT, 0 /* sysctl default */);
}
@@ -2658,11 +2709,18 @@
if (params.defaultMtu == mInterfaceParams.defaultMtu) return;
- try {
- mNetd.interfaceSetMtu(mInterfaceName, mInterfaceParams.defaultMtu);
- } catch (RemoteException | ServiceSpecificException e) {
- logError("Couldn't reset MTU on " + mInterfaceName + " from "
- + params.defaultMtu + " to " + mInterfaceParams.defaultMtu, e);
+ if (mReplaceNetdWithNetlinkEnabled) {
+ if (!NetlinkUtils.setInterfaceMtu(mInterfaceName, mInterfaceParams.defaultMtu)) {
+ logError("Couldn't reset MTU on " + mInterfaceName + " from "
+ + params.defaultMtu + " to " + mInterfaceParams.defaultMtu);
+ }
+ } else {
+ try {
+ mNetd.interfaceSetMtu(mInterfaceName, mInterfaceParams.defaultMtu);
+ } catch (RemoteException | ServiceSpecificException e) {
+ logError("Couldn't reset MTU on " + mInterfaceName + " from "
+ + params.defaultMtu + " to " + mInterfaceParams.defaultMtu, e);
+ }
}
}
@@ -2695,10 +2753,8 @@
// Before trigger probing to the critical neighbors, send Gratuitous ARP
// and Neighbor Advertisment in advance to propgate host's IPv4/v6 addresses.
- if (isGratuitousArpNaRoamingEnabled()) {
- maybeSendGratuitousARP(mLinkProperties);
- maybeSendGratuitousNAs(mLinkProperties, true /* isGratuitousNaAfterRoaming */);
- }
+ maybeSendGratuitousARP(mLinkProperties);
+ maybeSendGratuitousNAs(mLinkProperties, true /* isGratuitousNaAfterRoaming */);
// Check whether attempting to refresh previous IP lease on specific networks or need to
// probe the critical neighbors proactively on L2 roaming happened. The NUD probe on the
@@ -2721,15 +2777,20 @@
}
@Nullable
- private AndroidPacketFilter maybeCreateApfFilter(final ApfCapabilities apfCaps) {
+ private ApfFilter maybeCreateApfFilter(final ApfCapabilities apfCaps) {
ApfFilter.ApfConfiguration apfConfig = new ApfFilter.ApfConfiguration();
- if (apfCaps == null) {
+ if (apfCaps == null || !mEnableApf) {
return null;
}
// For now only support generating programs for Ethernet frames. If this restriction is
// lifted the program generator will need its offsets adjusted.
if (apfCaps.apfPacketFormat != ARPHRD_ETHER) return null;
- if (SdkLevel.isAtLeastS()) {
+ // For devices declare APFv3+ support but have less than 1024 bytes of RAM available for
+ // the APF, set the APF version to v2. The counter region will use a few hundred bytes of
+ // RAM. If the RAM size is too small, we should reserve that region for program use.
+ if (apfCaps.apfVersionSupported >= 3 && apfCaps.maximumApfProgramSize < 1024) {
+ apfConfig.apfVersionSupported = 2;
+ } else if (SdkLevel.isAtLeastS()) {
apfConfig.apfVersionSupported = apfCaps.apfVersionSupported;
} else {
// In Android R, ApfCapabilities#hasDataAccess() can be modified by OEMs. The
@@ -2761,7 +2822,7 @@
// Check the feature flag first before reading IPv6 sysctl, which can prevent from
// triggering a potential kernel bug about the sysctl.
// TODO: add unit test to check if the setIpv6Sysctl() is called or not.
- if (mIsAcceptRaMinLftEnabled && mUseNewApfFilter
+ if (mIsAcceptRaMinLftEnabled
&& mDependencies.hasIpv6Sysctl(mInterfaceName, ACCEPT_RA_MIN_LFT)) {
setIpv6Sysctl(ACCEPT_RA_MIN_LFT, mAcceptRaMinLft);
final Integer acceptRaMinLft = getIpv6Sysctl(ACCEPT_RA_MIN_LFT);
@@ -2769,13 +2830,19 @@
} else {
apfConfig.acceptRaMinLft = 0;
}
- apfConfig.shouldHandleArpOffload = mApfShouldHandleArpOffload;
- apfConfig.shouldHandleNdOffload = mApfShouldHandleNdOffload;
- apfConfig.shouldHandleMdnsOffload = mApfShouldHandleMdnsOffload;
+ apfConfig.handleArpOffload = mApfHandleArpOffload;
+ apfConfig.handleNdOffload = mApfHandleNdOffload;
+ apfConfig.handleMdnsOffload = mApfHandleMdnsOffload;
+ apfConfig.handleIgmpOffload = mApfHandleIgmpOffload;
+ // TODO: Turn on MLD offload on devices with 2048 ~ 2999 bytes of APF RAM.
+ apfConfig.handleMldOffload = mApfHandleMldOffload && apfConfig.apfRamSize >= 3000;
+ apfConfig.handleIpv4PingOffload = mApfHandleIpv4PingOffload;
+ // TODO: Turn on Ping6 offload on devices with 2048 ~ 2999 bytes of APF RAM.
+ apfConfig.handleIpv6PingOffload = mApfHandleIpv6PingOffload && apfConfig.apfRamSize >= 3000;
apfConfig.minMetricsSessionDurationMs = mApfCounterPollingIntervalMs;
apfConfig.hasClatInterface = mHasSeenClatInterface;
return mDependencies.maybeCreateApfFilter(getHandler(), mContext, apfConfig,
- mInterfaceParams, mCallback, mNetworkQuirkMetrics, mUseNewApfFilter);
+ mInterfaceParams, mIpClientApfController, mNetworkQuirkMetrics);
}
private boolean handleUpdateApfCapabilities(@NonNull final ApfCapabilities apfCapabilities) {
@@ -2796,6 +2863,17 @@
return apfCapabilities != null;
}
+ private void handleProvisioningConfiguration(@NonNull final ProvisioningConfiguration config) {
+ mCurrentBssid = getInitialBssid(config.mLayer2Info, config.mScanResultInfo,
+ ShimUtils.isAtLeastS());
+ mCurrentApfCapabilities = config.mApfCapabilities;
+ mCreatorUid = config.mCreatorUid;
+ if (config.mLayer2Info != null) {
+ mL2Key = config.mLayer2Info.mL2Key;
+ mCluster = config.mLayer2Info.mCluster;
+ }
+ }
+
class StoppedState extends State {
@Override
public void enter() {
@@ -2830,6 +2908,7 @@
case CMD_START:
mConfiguration = (android.net.shared.ProvisioningConfiguration) msg.obj;
+ handleProvisioningConfiguration(mConfiguration);
transitionTo(mIgnoreNudFailureEnabled
? mNudFailureQueryState
: mClearingIpAddressesState);
@@ -3328,7 +3407,7 @@
mHasSeenClatInterface = false;
mApfFilter = maybeCreateApfFilter(mCurrentApfCapabilities);
// If Apf supports ND offload, then turn off the vendor ND offload feature.
- if (mApfFilter != null && mApfFilter.supportNdOffload()) {
+ if (mApfFilter != null && mApfFilter.enableNdOffload()) {
mCallback.setNeighborDiscoveryOffload(false);
}
// TODO: investigate the effects of any multicast filtering racing/interfering with the
@@ -3407,7 +3486,10 @@
private ConnectivityPacketTracker createPacketTracker() {
try {
return new ConnectivityPacketTracker(
- getHandler(), mInterfaceParams, mConnectivityPacketLog);
+ getHandler(),
+ mInterfaceParams,
+ mConnectivityPacketLog,
+ true /* attachFilter */);
} catch (IllegalArgumentException e) {
return null;
}
@@ -3789,14 +3871,23 @@
if (handleUpdateApfCapabilities(apfCapabilities)) {
mApfFilter = maybeCreateApfFilter(apfCapabilities);
// If Apf supports ND offload, then turn off the vendor ND offload feature.
- if (mApfFilter != null && mApfFilter.supportNdOffload()) {
+ if (mApfFilter != null && mApfFilter.enableNdOffload()) {
mCallback.setNeighborDiscoveryOffload(false);
}
}
break;
case CMD_UPDATE_APF_DATA_SNAPSHOT:
- mCallback.startReadPacketFilter("polling");
+ if (mApfFilter != null) {
+ // We prevents calls to readPacketFilterRam() when mApfFilter is null.
+ // This is correct because any data read would be discarded when
+ // processing the EVENT_READ_PACKET_FILTER_COMPLETE event if no
+ // ApfFilter exists.
+ mApfFilter.getApfController().readPacketFilterRam("polling");
+ }
+ // Even if mApfFilter is currently null, periodic checks are necessary to
+ // read APF RAM when an ApfFilter becomes available, as APF capabilities can
+ // be updated which result in mApfFilter being created.
sendMessageDelayed(CMD_UPDATE_APF_DATA_SNAPSHOT, mApfCounterPollingIntervalMs);
break;
@@ -3928,7 +4019,11 @@
return coll.stream().map(Object::toString).collect(Collectors.joining(delimiter));
}
- static <T> T find(Iterable<T> coll, Predicate<T> fn) {
+ /**
+ * Find a specific element which satisfies the predicate in a collection.
+ */
+ @VisibleForTesting
+ public static <T> T find(Iterable<T> coll, Predicate<T> fn) {
for (T t: coll) {
if (fn.test(t)) {
return t;
diff --git a/src/android/net/ip/IpClientLinkObserver.java b/src/android/net/ip/IpClientLinkObserver.java
index 8f2856f..516ab01 100644
--- a/src/android/net/ip/IpClientLinkObserver.java
+++ b/src/android/net/ip/IpClientLinkObserver.java
@@ -20,6 +20,7 @@
import static android.system.OsConstants.AF_UNSPEC;
import static android.system.OsConstants.IFF_LOOPBACK;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_PIO;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
import static com.android.net.module.util.NetworkStackConstants.INFINITE_LEASE;
import static com.android.net.module.util.netlink.NetlinkConstants.IFF_LOWER_UP;
@@ -55,12 +56,14 @@
import com.android.net.module.util.netlink.NetlinkMessage;
import com.android.net.module.util.netlink.RtNetlinkAddressMessage;
import com.android.net.module.util.netlink.RtNetlinkLinkMessage;
+import com.android.net.module.util.netlink.RtNetlinkPrefixMessage;
import com.android.net.module.util.netlink.RtNetlinkRouteMessage;
import com.android.net.module.util.netlink.StructIfacacheInfo;
import com.android.net.module.util.netlink.StructIfaddrMsg;
import com.android.net.module.util.netlink.StructIfinfoMsg;
import com.android.net.module.util.netlink.StructNdOptPref64;
import com.android.net.module.util.netlink.StructNdOptRdnss;
+import com.android.net.module.util.netlink.StructPrefixMsg;
import com.android.networkstack.apishim.NetworkInformationShimImpl;
import com.android.networkstack.apishim.common.NetworkInformationShim;
@@ -131,16 +134,41 @@
* False: clat interface was removed.
*/
void onClatInterfaceStateUpdate(boolean add);
+
+ /**
+ * Called when the prefix information was updated via RTM_NEWPREFIX netlink message.
+ *
+ * @param info prefix information.
+ */
+ void onNewPrefix(PrefixInfo info);
}
/** Configuration parameters for IpClientLinkObserver. */
public static class Configuration {
public final int minRdnssLifetime;
public final boolean populateLinkAddressLifetime;
+ public final boolean isDhcp6PdPreferredFlagEnabled;
- public Configuration(int minRdnssLifetime, boolean populateLinkAddressLifetime) {
+ public Configuration(int minRdnssLifetime, boolean populateLinkAddressLifetime,
+ boolean isDhcp6PdPreferredFlagEnabled) {
this.minRdnssLifetime = minRdnssLifetime;
this.populateLinkAddressLifetime = populateLinkAddressLifetime;
+ this.isDhcp6PdPreferredFlagEnabled = isDhcp6PdPreferredFlagEnabled;
+ }
+ }
+
+ /** Prefix information received from RTM_NEWPREFIX netlink message. */
+ public static class PrefixInfo {
+ public final IpPrefix prefix;
+ public short flags;
+ public long preferred;
+ public long valid;
+
+ public PrefixInfo(@NonNull final IpPrefix prefix, short flags, long preferred, long valid) {
+ this.prefix = prefix;
+ this.flags = flags;
+ this.preferred = preferred;
+ this.valid = valid;
}
}
@@ -200,6 +228,7 @@
mDependencies = deps;
mNetlinkMonitor = deps.makeIpClientNetlinkMonitor(h, log, mTag,
getSocketReceiveBufferSize(),
+ config.isDhcp6PdPreferredFlagEnabled,
(nlMsg, whenMs) -> processNetlinkMessage(nlMsg, whenMs));
mShim = NetworkInformationShimImpl.newInstance();
mExpirePref64Alarm = new IpClientObserverAlarmListener();
@@ -381,15 +410,19 @@
private final Handler mHandler;
private final INetlinkMessageProcessor mNetlinkMessageProcessor;
+ private static final int NETLINK_MONITOR_BIND_GROUPS =
+ NetlinkConstants.RTMGRP_ND_USEROPT
+ | NetlinkConstants.RTMGRP_LINK
+ | NetlinkConstants.RTMGRP_IPV4_IFADDR
+ | NetlinkConstants.RTMGRP_IPV6_IFADDR
+ | NetlinkConstants.RTMGRP_IPV6_ROUTE;
IpClientNetlinkMonitor(Handler h, SharedLog log, String tag, int sockRcvbufSize,
- INetlinkMessageProcessor p) {
+ boolean isDhcp6PdPreferredFlagEnabled, INetlinkMessageProcessor p) {
super(h, log, tag, OsConstants.NETLINK_ROUTE,
- (NetlinkConstants.RTMGRP_ND_USEROPT
- | NetlinkConstants.RTMGRP_LINK
- | NetlinkConstants.RTMGRP_IPV4_IFADDR
- | NetlinkConstants.RTMGRP_IPV6_IFADDR
- | NetlinkConstants.RTMGRP_IPV6_ROUTE),
+ isDhcp6PdPreferredFlagEnabled
+ ? NETLINK_MONITOR_BIND_GROUPS | NetlinkConstants.RTMGRP_IPV6_PREFIX
+ : NETLINK_MONITOR_BIND_GROUPS,
sockRcvbufSize);
mHandler = h;
mNetlinkMessageProcessor = p;
@@ -630,6 +663,18 @@
}
}
+ private void processRtNetlinkPrefixMessage(RtNetlinkPrefixMessage msg) {
+ final StructPrefixMsg prefixmsg = msg.getPrefixMsg();
+ if (prefixmsg.prefix_family != AF_INET6) return;
+ if (prefixmsg.prefix_ifindex != mIfindex) return;
+ if (prefixmsg.prefix_type != ICMPV6_ND_OPTION_PIO) return;
+ final PrefixInfo info = new PrefixInfo(msg.getPrefix(),
+ prefixmsg.prefix_flags,
+ msg.getPreferredLifetime(),
+ msg.getValidLifetime());
+ mCallback.onNewPrefix(info);
+ }
+
private void processNetlinkMessage(NetlinkMessage nlMsg, long whenMs) {
if (nlMsg instanceof NduseroptMessage) {
processNduseroptMessage((NduseroptMessage) nlMsg, whenMs);
@@ -639,6 +684,8 @@
processRtNetlinkAddressMessage((RtNetlinkAddressMessage) nlMsg);
} else if (nlMsg instanceof RtNetlinkRouteMessage) {
processRtNetlinkRouteMessage((RtNetlinkRouteMessage) nlMsg);
+ } else if (nlMsg instanceof RtNetlinkPrefixMessage) {
+ processRtNetlinkPrefixMessage((RtNetlinkPrefixMessage) nlMsg);
} else {
Log.e(mTag, "Unknown netlink message: " + nlMsg);
}
diff --git a/src/android/net/ip/IpReachabilityMonitor.java b/src/android/net/ip/IpReachabilityMonitor.java
index 462de90..7676f76 100644
--- a/src/android/net/ip/IpReachabilityMonitor.java
+++ b/src/android/net/ip/IpReachabilityMonitor.java
@@ -21,9 +21,6 @@
import static android.net.metrics.IpReachabilityEvent.PROVISIONING_LOST;
import static android.net.metrics.IpReachabilityEvent.PROVISIONING_LOST_ORGANIC;
-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_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION;
import static com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION;
import static com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_MCAST_RESOLICIT_VERSION;
import static com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_ROUTER_MAC_CHANGE_FAILURE_ONLY_AFTER_ROAM_VERSION;
@@ -243,11 +240,8 @@
@NonNull
private final Callback mCallback;
private final boolean mMulticastResolicitEnabled;
- private final boolean mIgnoreIncompleteIpv6DnsServerEnabled;
- private final boolean mIgnoreIncompleteIpv6DefaultRouterEnabled;
private final boolean mMacChangeFailureOnlyAfterRoam;
private final boolean mIgnoreOrganicNudFailure;
- private final boolean mIgnoreNeverReachableNeighbor;
// A set to track whether a neighbor has ever entered NUD_REACHABLE state before.
private final Set<InetAddress> mEverReachableNeighbors = new ArraySet<>();
@@ -273,16 +267,10 @@
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,
- IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION);
mMacChangeFailureOnlyAfterRoam = dependencies.isFeatureNotChickenedOut(context,
IP_REACHABILITY_ROUTER_MAC_CHANGE_FAILURE_ONLY_AFTER_ROAM_VERSION);
mIgnoreOrganicNudFailure = dependencies.isFeatureEnabled(context,
IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION);
- mIgnoreNeverReachableNeighbor = dependencies.isFeatureEnabled(context,
- IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION);
mMetricsLog = metricsLog;
mNetd = netd;
Preconditions.checkNotNull(mNetd);
@@ -473,19 +461,6 @@
maybeRestoreNeighborParameters();
}
- private boolean shouldIgnoreIncompleteNeighbor(@Nullable final NeighborEvent prev,
- @NonNull final NeighborEvent event) {
- // mIgnoreNeverReachableNeighbor already takes care of incomplete IPv6 neighbors, so do not
- // apply this logic.
- if (mIgnoreNeverReachableNeighbor) return false;
-
- // For on-link IPv4/v6 DNS server or default router that never ever responds to
- // address resolution(e.g. ARP or NS), kernel will send RTM_NEWNEIGH with NUD_FAILED
- // to user space directly, and there is no netlink neighbor events related to this
- // neighbor received before.
- return (prev == null && event.nudState == StructNdMsg.NUD_FAILED);
- }
-
private void handleNeighborLost(@Nullable final NeighborEvent prev,
@NonNull final NeighborEvent event) {
final LinkProperties whatIfLp = new LinkProperties(mLinkProperties);
@@ -508,7 +483,7 @@
// Pretend neighbors that have never been reachable are still there. Leaving them
// inside whatIfLp has the benefit that the logic that compares provisioning loss
// below works consistently independent of the current event being processed.
- if (mIgnoreNeverReachableNeighbor && !mEverReachableNeighbors.contains(ip)) continue;
+ if (!mEverReachableNeighbors.contains(ip)) continue;
for (RouteInfo route : mLinkProperties.getRoutes()) {
if (ip.equals(route.getGateway())) {
@@ -522,42 +497,10 @@
}
}
- // TODO: cleanup below code(checking if the incomplete IPv6 neighbor should be ignored)
- // once the feature of ignoring the neighbor was never ever reachable rolls out.
- final boolean ignoreIncompleteIpv6DnsServer =
- mIgnoreIncompleteIpv6DnsServerEnabled
- && isNeighborDnsServer(event)
- && shouldIgnoreIncompleteNeighbor(prev, event);
-
- // Generally Router Advertisement should take SLLA option, then device won't do address
- // resolution for default router's IPv6 link-local address automatically. But sometimes
- // it may miss SLLA option, also add a flag to check these cases.
- final boolean ignoreIncompleteIpv6DefaultRouter =
- mIgnoreIncompleteIpv6DefaultRouterEnabled
- && isNeighborDefaultRouter(event)
- && shouldIgnoreIncompleteNeighbor(prev, event);
-
- // Only ignore the incomplete IPv6 neighbor iff IPv4 is still provisioned. For IPv6-only
- // networks, we MUST not ignore any incomplete IPv6 neighbor.
- final boolean ignoreIncompleteIpv6Neighbor =
- (ignoreIncompleteIpv6DnsServer || ignoreIncompleteIpv6DefaultRouter)
- && whatIfLp.isIpv4Provisioned();
-
- // It's better to remove the incompleted on-link IPv6 DNS server or default router from
- // watch list, otherwise, when wifi invokes probeAll later (e.g. post roam) to send probe
- // to an incompleted on-link DNS server or default router, it should fail to send netlink
- // message to kernel as there is no neighbor cache entry for it at all.
- if (ignoreIncompleteIpv6Neighbor) {
- Log.d(TAG, "remove incomplete IPv6 neighbor " + event.ip
- + " which fails to respond to address resolution from watch list.");
- mNeighborWatchList.remove(event.ip);
- }
-
final boolean lostIpv4Provisioning =
mLinkProperties.isIpv4Provisioned() && !whatIfLp.isIpv4Provisioned();
final boolean lostIpv6Provisioning =
- mLinkProperties.isIpv6Provisioned() && !whatIfLp.isIpv6Provisioned()
- && !ignoreIncompleteIpv6Neighbor;
+ mLinkProperties.isIpv6Provisioned() && !whatIfLp.isIpv6Provisioned();
final boolean lostProvisioning = lostIpv4Provisioning || lostIpv6Provisioning;
final NudEventType type = getNudFailureEventType(isFromProbe(),
isNudFailureDueToRoam(), lostProvisioning);
@@ -593,7 +536,7 @@
// Skip the neighbor which is never ever reachable, we ignore the NUD failure for it,
// pretend neighbor that has never been reachable is still there no matter of neighbor
// event state.
- if (mIgnoreNeverReachableNeighbor && !mEverReachableNeighbors.contains(ip)) continue;
+ if (!mEverReachableNeighbors.contains(ip)) continue;
// If an entry is null, consider that probing for that neighbour has completed.
if (val == null || val.nudState != StructNdMsg.NUD_REACHABLE) return;
diff --git a/src/android/net/ip/MulticastReportMonitor.java b/src/android/net/ip/MulticastReportMonitor.java
new file mode 100644
index 0000000..537d563
--- /dev/null
+++ b/src/android/net/ip/MulticastReportMonitor.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.net.ip;
+
+import android.os.Handler;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.InterfaceParams;
+import com.android.net.module.util.PacketReader;
+
+import java.io.FileDescriptor;
+
+/**
+ * Monitor IGMP/MLD report packets and notify listeners the multicast address changes.
+ *
+ * <p>This class uses a {@link PacketReader} to listen for IGMP/MLD report packets on a given
+ * interface. When a packet is received, it notifies the provided {@link Callback} of the change
+ * in the multicast address.
+ *
+ * <p>To use this class, create a new instance with the desired {@link Handler},
+ * {@link InterfaceParams}, {@link Callback}, and {@link FileDescriptor}. Then, call {@link #start()}
+ * to start listening for packets. To stop listening, call {@link #stop()}.
+ */
+public class MulticastReportMonitor {
+ public interface Callback {
+ /**
+ * Notifies the system or other components about a change in the multicast address.
+ */
+ void notifyMulticastAddrChange();
+ }
+
+ private static final String TAG = MulticastReportMonitor.class.getSimpleName();
+ private final PacketReader mPacketListener;
+
+ /**
+ * Creates a new {@link MulticastReportMonitor}.
+ *
+ * @param handler The {@link Handler} to use for the {@link PacketReader}.
+ * @param ifParams The {@link InterfaceParams} for the interface to listen on.
+ * @param callback The {@link Callback} to notify the multicast address changes.
+ * @param fd The {@link FileDescriptor} to use for the {@link PacketReader}.
+ */
+ public MulticastReportMonitor(
+ @NonNull Handler handler,
+ @NonNull InterfaceParams ifParams,
+ @NonNull Callback callback,
+ @NonNull FileDescriptor fd) {
+ mPacketListener = new PacketListener(handler, ifParams, callback, fd);
+ }
+
+ /**
+ * Starts the packet listener.
+ */
+ public void start() {
+ mPacketListener.start();
+ }
+
+ /**
+ * Stops the packet listener.
+ */
+ public void stop() {
+ mPacketListener.stop();
+ }
+
+ private static final class PacketListener extends PacketReader {
+ private final Callback mCallback;
+ private final FileDescriptor mFd;
+
+ PacketListener(Handler h, InterfaceParams ifParams, Callback callback, FileDescriptor fd) {
+ super(h, ifParams.defaultMtu);
+ mCallback = callback;
+ mFd = fd;
+ }
+
+ @Override
+ protected FileDescriptor createFd() {
+ return mFd;
+ }
+
+ @Override
+ protected void handlePacket(@NonNull byte[] recvbuf, int length) {
+ mCallback.notifyMulticastAddrChange();
+ }
+ }
+}
diff --git a/src/android/net/util/RawPacketTracker.java b/src/android/net/util/RawPacketTracker.java
index e73834b..b5adcd1 100644
--- a/src/android/net/util/RawPacketTracker.java
+++ b/src/android/net/util/RawPacketTracker.java
@@ -52,8 +52,10 @@
static class Dependencies {
public @NonNull ConnectivityPacketTracker createPacketTracker(
Handler handler, InterfaceParams ifParams, int maxPktRecords) {
+ // A BPF filter is unnecessary here, as the caller uses this device to send packets
+ // and verify the APF offload reply packets received from the remote device.
return new ConnectivityPacketTracker(
- handler, ifParams, new LocalLog(maxPktRecords));
+ handler, ifParams, new LocalLog(maxPktRecords), false /* attachFilter */);
}
public @NonNull HandlerThread createHandlerThread() {
diff --git a/src/com/android/networkstack/NetworkStackNotifier.java b/src/com/android/networkstack/NetworkStackNotifier.java
index acf3c95..909336c 100644
--- a/src/com/android/networkstack/NetworkStackNotifier.java
+++ b/src/com/android/networkstack/NetworkStackNotifier.java
@@ -287,7 +287,8 @@
@NonNull CharSequence networkIdentifier) {
return new Notification.Builder(mContext, channelId)
.setContentTitle(networkIdentifier)
- .setSmallIcon(R.drawable.icon_wifi);
+ .setSmallIcon(R.drawable.icon_wifi)
+ .setLocalOnly(true);
}
/**
diff --git a/src/com/android/networkstack/ipmemorystore/IpMemoryStoreDatabase.java b/src/com/android/networkstack/ipmemorystore/IpMemoryStoreDatabase.java
index b00c03d..6f99fa4 100644
--- a/src/com/android/networkstack/ipmemorystore/IpMemoryStoreDatabase.java
+++ b/src/com/android/networkstack/ipmemorystore/IpMemoryStoreDatabase.java
@@ -471,21 +471,24 @@
final ContentValues cv = toContentValues(key, attributes, expiry);
db.beginTransaction();
try {
- // Unfortunately SQLite does not have any way to do INSERT OR UPDATE. Options are
- // to either insert with on conflict ignore then update (like done here), or to
- // construct a custom SQL INSERT statement with nested select.
- final long resultId = db.insertWithOnConflict(NetworkAttributesContract.TABLENAME,
- null, cv, SQLiteDatabase.CONFLICT_IGNORE);
- if (resultId < 0) {
- db.update(NetworkAttributesContract.TABLENAME, cv, SELECT_L2KEY, new String[]{key});
+ try {
+ // Unfortunately SQLite does not have any way to do INSERT OR UPDATE. Options are
+ // to either insert with on conflict ignore then update (like done here), or to
+ // construct a custom SQL INSERT statement with nested select.
+ final long resultId = db.insertWithOnConflict(NetworkAttributesContract.TABLENAME,
+ null, cv, SQLiteDatabase.CONFLICT_IGNORE);
+ if (resultId < 0) {
+ db.update(NetworkAttributesContract.TABLENAME,
+ cv, SELECT_L2KEY, new String[]{key});
+ }
+ db.setTransactionSuccessful();
+ return Status.SUCCESS;
+ } finally {
+ db.endTransaction();
}
- db.setTransactionSuccessful();
- return Status.SUCCESS;
} catch (SQLiteException e) {
// No space left on disk or something
Log.e(TAG, "Could not write to the memory store", e);
- } finally {
- db.endTransaction();
}
return Status.ERROR_STORAGE;
}
@@ -548,53 +551,55 @@
for (int remainingRetries = 3; remainingRetries > 0; --remainingRetries) {
db.beginTransaction();
try {
- db.delete(NetworkAttributesContract.TABLENAME, null, null);
- db.delete(PrivateDataContract.TABLENAME, null, null);
- db.delete(NetworkEventsContract.TABLENAME, null, null);
- try (Cursor cursorNetworkAttributes = db.query(
- // table name
- NetworkAttributesContract.TABLENAME,
- // column name
- new String[] { NetworkAttributesContract.COLNAME_L2KEY },
- null, // selection
- null, // selectionArgs
- null, // groupBy
- null, // having
- null, // orderBy
- "1")) { // limit
- if (0 != cursorNetworkAttributes.getCount()) continue;
+ try {
+ db.delete(NetworkAttributesContract.TABLENAME, null, null);
+ db.delete(PrivateDataContract.TABLENAME, null, null);
+ db.delete(NetworkEventsContract.TABLENAME, null, null);
+ try (Cursor cursorNetworkAttributes = db.query(
+ // table name
+ NetworkAttributesContract.TABLENAME,
+ // column name
+ new String[] { NetworkAttributesContract.COLNAME_L2KEY },
+ null, // selection
+ null, // selectionArgs
+ null, // groupBy
+ null, // having
+ null, // orderBy
+ "1")) { // limit
+ if (0 != cursorNetworkAttributes.getCount()) continue;
+ }
+ try (Cursor cursorPrivateData = db.query(
+ // table name
+ PrivateDataContract.TABLENAME,
+ // column name
+ new String[] { PrivateDataContract.COLNAME_L2KEY },
+ null, // selection
+ null, // selectionArgs
+ null, // groupBy
+ null, // having
+ null, // orderBy
+ "1")) { // limit
+ if (0 != cursorPrivateData.getCount()) continue;
+ }
+ try (Cursor cursorNetworkEvents = db.query(
+ // table name
+ NetworkEventsContract.TABLENAME,
+ // column name
+ new String[] { NetworkEventsContract.COLNAME_CLUSTER },
+ null, // selection
+ null, // selectionArgs
+ null, // groupBy
+ null, // having
+ null, // orderBy
+ "1")) { // limit
+ if (0 != cursorNetworkEvents.getCount()) continue;
+ }
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
}
- try (Cursor cursorPrivateData = db.query(
- // table name
- PrivateDataContract.TABLENAME,
- // column name
- new String[] { PrivateDataContract.COLNAME_L2KEY },
- null, // selection
- null, // selectionArgs
- null, // groupBy
- null, // having
- null, // orderBy
- "1")) { // limit
- if (0 != cursorPrivateData.getCount()) continue;
- }
- try (Cursor cursorNetworkEvents = db.query(
- // table name
- NetworkEventsContract.TABLENAME,
- // column name
- new String[] { NetworkEventsContract.COLNAME_CLUSTER },
- null, // selection
- null, // selectionArgs
- null, // groupBy
- null, // having
- null, // orderBy
- "1")) { // limit
- if (0 != cursorNetworkEvents.getCount()) continue;
- }
- db.setTransactionSuccessful();
} catch (SQLiteException e) {
Log.e(TAG, "Could not wipe the data in database", e);
- } finally {
- db.endTransaction();
}
}
}
@@ -714,6 +719,10 @@
/**
* Delete a single entry by key.
*
+ * The NetworkAttributes table is indexed by a L2 key although it also has a cluster column,
+ * so this API only targets the NetworkAttributes table. For deleting the entries by cluster,
+ * see {@link deleteCluster}.
+ *
* If |needWipe| is true, the data will be wiped from disk immediately. Otherwise, it will
* only be marked deleted, and overwritten by subsequent writes or reclaimed during the next
* maintenance window.
@@ -724,11 +733,21 @@
static StatusAndCount delete(@NonNull final SQLiteDatabase db, @NonNull final String l2key,
final boolean needWipe) {
return deleteEntriesWithColumn(db,
- NetworkAttributesContract.COLNAME_L2KEY, l2key, needWipe);
+ NetworkAttributesContract.TABLENAME, // table
+ NetworkAttributesContract.COLNAME_L2KEY, // column
+ l2key, // value
+ needWipe);
}
/**
- * Delete all entries that have a particular cluster value.
+ * Delete all entries that have a particular cluster value in NetworkAttributes and
+ * NetworkEvents tables.
+ *
+ * So far the cluster column exists in both the NetworkAttributes and NetworkEvents
+ * tables, and this API is only called when WiFi attempts to remove a network, see
+ * {@link WifiHealthMonitor.OnNetworkUpdateListener#onNetworkRemoved} and
+ * {@link WifiNetworkSuggestionManager#remove}. It makes more sense to delete the
+ * cluster column from both tables when this API is called.
*
* If |needWipe| is true, the data will be wiped from disk immediately. Otherwise, it will
* only be marked deleted, and overwritten by subsequent writes or reclaimed during the next
@@ -739,26 +758,50 @@
*/
static StatusAndCount deleteCluster(@NonNull final SQLiteDatabase db,
@NonNull final String cluster, final boolean needWipe) {
- return deleteEntriesWithColumn(db,
- NetworkAttributesContract.COLNAME_CLUSTER, cluster, needWipe);
+ // Delete all entries that have cluster value from NetworkAttributes table.
+ final StatusAndCount naDeleteResult = deleteEntriesWithColumn(db,
+ NetworkAttributesContract.TABLENAME, // table
+ NetworkAttributesContract.COLNAME_CLUSTER, // column
+ cluster, // value
+ needWipe);
+ // And then delete all entries that have cluster value from NetworkEvents table.
+ final StatusAndCount neDeleteResult = deleteEntriesWithColumn(db,
+ NetworkEventsContract.TABLENAME, // table
+ NetworkEventsContract.COLNAME_CLUSTER, // column
+ cluster, // value
+ needWipe);
+ int status = Status.ERROR_GENERIC;
+ if (naDeleteResult.status == Status.SUCCESS && neDeleteResult.status == Status.SUCCESS) {
+ status = Status.SUCCESS;
+ } else if (naDeleteResult.status != Status.SUCCESS) {
+ // If deleteCluster fails on both tables, return the status code on deleting the entries
+ // from the NetworkAttributes table, keep consistent with previous behavior.
+ status = naDeleteResult.status;
+ } else {
+ status = neDeleteResult.status;
+ }
+ return new StatusAndCount(status, naDeleteResult.count + neDeleteResult.count);
}
// Delete all entries where the given column has the given value.
private static StatusAndCount deleteEntriesWithColumn(@NonNull final SQLiteDatabase db,
- @NonNull final String column, @NonNull final String value, final boolean needWipe) {
+ @NonNull final String table, @NonNull final String column, @NonNull final String value,
+ final boolean needWipe) {
db.beginTransaction();
int deleted = 0;
try {
- deleted = db.delete(NetworkAttributesContract.TABLENAME,
- column + "= ?", new String[] { value });
- db.setTransactionSuccessful();
+ try {
+ deleted = db.delete(table,
+ column + "= ?", new String[] { value });
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
} catch (SQLiteException e) {
Log.e(TAG, "Could not delete from the memory store", e);
// Unclear what might have happened ; deleting records is not supposed to be able
// to fail barring a syntax error in the SQL query.
return new StatusAndCount(Status.ERROR_UNKNOWN, 0);
- } finally {
- db.endTransaction();
}
if (needWipe) {
@@ -774,21 +817,23 @@
static int dropAllExpiredRecords(@NonNull final SQLiteDatabase db) {
db.beginTransaction();
try {
- final long currentTimestamp = System.currentTimeMillis();
- // Deletes NetworkAttributes that have expired.
- db.delete(NetworkAttributesContract.TABLENAME,
- NetworkAttributesContract.COLNAME_EXPIRYDATE + " < ?",
- new String[]{Long.toString(currentTimestamp)});
- // Deletes NetworkEvents that have expired.
- db.delete(NetworkEventsContract.TABLENAME,
- NetworkEventsContract.COLNAME_EXPIRY + " < ?",
- new String[]{Long.toString(currentTimestamp)});
- db.setTransactionSuccessful();
+ try {
+ final long currentTimestamp = System.currentTimeMillis();
+ // Deletes NetworkAttributes that have expired.
+ db.delete(NetworkAttributesContract.TABLENAME,
+ NetworkAttributesContract.COLNAME_EXPIRYDATE + " < ?",
+ new String[]{Long.toString(currentTimestamp)});
+ // Deletes NetworkEvents that have expired.
+ db.delete(NetworkEventsContract.TABLENAME,
+ NetworkEventsContract.COLNAME_EXPIRY + " < ?",
+ new String[]{Long.toString(currentTimestamp)});
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
} catch (SQLiteException e) {
Log.e(TAG, "Could not delete data from memory store", e);
return Status.ERROR_STORAGE;
- } finally {
- db.endTransaction();
}
// Execute vacuuming here if above operation has no exception. If above operation got
@@ -827,16 +872,18 @@
db.beginTransaction();
try {
- // Deletes NetworkAttributes that expiryDate are lower than given value.
- db.delete(NetworkAttributesContract.TABLENAME,
- NetworkAttributesContract.COLNAME_EXPIRYDATE + " <= ?",
- new String[]{Long.toString(expiryDate)});
- db.setTransactionSuccessful();
+ try {
+ // Deletes NetworkAttributes that expiryDate are lower than given value.
+ db.delete(NetworkAttributesContract.TABLENAME,
+ NetworkAttributesContract.COLNAME_EXPIRYDATE + " <= ?",
+ new String[]{Long.toString(expiryDate)});
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
} catch (SQLiteException e) {
Log.e(TAG, "Could not delete data from memory store", e);
return Status.ERROR_STORAGE;
- } finally {
- db.endTransaction();
}
// Execute vacuuming here if above operation has no exception. If above operation got
@@ -868,20 +915,22 @@
final ContentValues cv = toContentValues(cluster, timestamp, expiry, eventType);
db.beginTransaction();
try {
- final long resultId = db.insertOrThrow(NetworkEventsContract.TABLENAME,
- null /* nullColumnHack */, cv);
- if (resultId < 0) {
- // Should not fail to insert a row to NetworkEvents table which doesn't have
- // uniqueness constraint.
- return Status.ERROR_STORAGE;
+ try {
+ final long resultId = db.insertOrThrow(NetworkEventsContract.TABLENAME,
+ null /* nullColumnHack */, cv);
+ if (resultId < 0) {
+ // Should not fail to insert a row to NetworkEvents table which doesn't have
+ // uniqueness constraint.
+ return Status.ERROR_STORAGE;
+ }
+ db.setTransactionSuccessful();
+ return Status.SUCCESS;
+ } finally {
+ db.endTransaction();
}
- db.setTransactionSuccessful();
- return Status.SUCCESS;
} catch (SQLiteException e) {
// No space left on disk or something
Log.e(TAG, "Could not write to the memory store", e);
- } finally {
- db.endTransaction();
}
return Status.ERROR_STORAGE;
}
diff --git a/src/com/android/networkstack/metrics/ApfSessionInfoMetrics.java b/src/com/android/networkstack/metrics/ApfSessionInfoMetrics.java
index c2c51f6..14cb6ff 100644
--- a/src/com/android/networkstack/metrics/ApfSessionInfoMetrics.java
+++ b/src/com/android/networkstack/metrics/ApfSessionInfoMetrics.java
@@ -20,83 +20,97 @@
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_NON_IPV4;
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_OTHER_HOST;
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_REPLY_SPA_NO_HOST;
-import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_REQUEST_ANYHOST;
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_REQUEST_REPLIED;
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_UNKNOWN;
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_V6_ONLY;
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ETHERTYPE_NOT_ALLOWED;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ETHER_OUR_SRC_MAC;
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ETH_BROADCAST;
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_GARP_REPLY;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IGMP_INVALID;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IGMP_REPORT;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IGMP_V2_GENERAL_QUERY_REPLIED;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IGMP_V3_GENERAL_QUERY_REPLIED;
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_BROADCAST_ADDR;
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_BROADCAST_NET;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_ICMP_INVALID;
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_KEEPALIVE_ACK;
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_L2_BROADCAST;
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_MULTICAST;
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_NATT_KEEPALIVE;
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_NON_DHCP4;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_PING_REQUEST_REPLIED;
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_TCP_PORT7_UNICAST;
-import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_KEEPALIVE_ACK;
-import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MULTICAST;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_ICMP6_ECHO_REQUEST_INVALID;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_ICMP6_ECHO_REQUEST_REPLIED;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MLD_INVALID;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MLD_REPORT;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MLD_V1_GENERAL_QUERY_REPLIED;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MLD_V2_GENERAL_QUERY_REPLIED;
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MULTICAST_NA;
-import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MULTICAST_PING;
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NON_ICMP_MULTICAST;
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_INVALID;
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_OTHER_HOST;
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_REPLIED_NON_DAD;
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_ROUTER_SOLICITATION;
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_MDNS;
+import static android.net.apf.ApfCounterTracker.Counter.DROPPED_MDNS_REPLIED;
import static android.net.apf.ApfCounterTracker.Counter.DROPPED_RA;
-import static android.net.apf.ApfCounterTracker.Counter.PASSED_ARP;
import static android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_BROADCAST_REPLY;
-import static android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_NON_IPV4;
import static android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_REQUEST;
import static android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_UNICAST_REPLY;
-import static android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_UNKNOWN;
import static android.net.apf.ApfCounterTracker.Counter.PASSED_DHCP;
+import static android.net.apf.ApfCounterTracker.Counter.PASSED_ETHER_OUR_SRC_MAC;
import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV4;
import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV4_FROM_DHCPV4_SERVER;
import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV4_UNICAST;
+import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_HOPOPTS;
import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_ICMP;
import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NON_ICMP;
-import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_DAD;
-import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_NO_ADDRESS;
-import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_NO_SLLA_OPTION;
-import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_TENTATIVE;
import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_UNICAST_NON_ICMP;
-import static android.net.apf.ApfCounterTracker.Counter.PASSED_MDNS;
-import static android.net.apf.ApfCounterTracker.Counter.PASSED_MLD;
import static android.net.apf.ApfCounterTracker.Counter.PASSED_NON_IP_UNICAST;
+import static android.net.apf.ApfCounterTracker.Counter.RESERVED_OOB;
import static android.net.apf.ApfCounterTracker.Counter.TOTAL_PACKETS;
import static android.stats.connectivity.CounterName.CN_DROPPED_802_3_FRAME;
import static android.stats.connectivity.CounterName.CN_DROPPED_ARP_NON_IPV4;
import static android.stats.connectivity.CounterName.CN_DROPPED_ARP_OTHER_HOST;
import static android.stats.connectivity.CounterName.CN_DROPPED_ARP_REPLY_SPA_NO_HOST;
-import static android.stats.connectivity.CounterName.CN_DROPPED_ARP_REQUEST_ANYHOST;
import static android.stats.connectivity.CounterName.CN_DROPPED_ARP_REQUEST_REPLIED;
import static android.stats.connectivity.CounterName.CN_DROPPED_ARP_UNKNOWN;
import static android.stats.connectivity.CounterName.CN_DROPPED_ARP_V6_ONLY;
import static android.stats.connectivity.CounterName.CN_DROPPED_ETHERTYPE_NOT_ALLOWED;
+import static android.stats.connectivity.CounterName.CN_DROPPED_ETHER_OUR_SRC_MAC;
import static android.stats.connectivity.CounterName.CN_DROPPED_ETH_BROADCAST;
import static android.stats.connectivity.CounterName.CN_DROPPED_GARP_REPLY;
+import static android.stats.connectivity.CounterName.CN_DROPPED_IGMP_INVALID;
+import static android.stats.connectivity.CounterName.CN_DROPPED_IGMP_REPORT;
+import static android.stats.connectivity.CounterName.CN_DROPPED_IGMP_V2_GENERAL_QUERY_REPLIED;
+import static android.stats.connectivity.CounterName.CN_DROPPED_IGMP_V3_GENERAL_QUERY_REPLIED;
import static android.stats.connectivity.CounterName.CN_DROPPED_IPV4_BROADCAST_ADDR;
import static android.stats.connectivity.CounterName.CN_DROPPED_IPV4_BROADCAST_NET;
+import static android.stats.connectivity.CounterName.CN_DROPPED_IPV4_ICMP_INVALID;
import static android.stats.connectivity.CounterName.CN_DROPPED_IPV4_KEEPALIVE_ACK;
import static android.stats.connectivity.CounterName.CN_DROPPED_IPV4_L2_BROADCAST;
import static android.stats.connectivity.CounterName.CN_DROPPED_IPV4_MULTICAST;
import static android.stats.connectivity.CounterName.CN_DROPPED_IPV4_NATT_KEEPALIVE;
import static android.stats.connectivity.CounterName.CN_DROPPED_IPV4_NON_DHCP4;
-import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_KEEPALIVE_ACK;
-import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_MULTICAST;
+import static android.stats.connectivity.CounterName.CN_DROPPED_IPV4_PING_REQUEST_REPLIED;
+import static android.stats.connectivity.CounterName.CN_DROPPED_IPV4_TCP_PORT7_UNICAST;
+import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_ICMP6_ECHO_REQUEST_INVALID;
+import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_ICMP6_ECHO_REQUEST_REPLIED;
+import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_MLD_INVALID;
+import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_MLD_REPORT;
+import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_MLD_V1_GENERAL_QUERY_REPLIED;
+import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_MLD_V2_GENERAL_QUERY_REPLIED;
import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_MULTICAST_NA;
-import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_MULTICAST_PING;
import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_NON_ICMP_MULTICAST;
import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_NS_INVALID;
import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_NS_OTHER_HOST;
import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_NS_REPLIED_NON_DAD;
import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_ROUTER_SOLICITATION;
import static android.stats.connectivity.CounterName.CN_DROPPED_MDNS;
+import static android.stats.connectivity.CounterName.CN_DROPPED_MDNS_REPLIED;
import static android.stats.connectivity.CounterName.CN_DROPPED_RA;
-import static android.stats.connectivity.CounterName.CN_PASSED_ARP;
import static android.stats.connectivity.CounterName.CN_PASSED_ARP_BROADCAST_REPLY;
import static android.stats.connectivity.CounterName.CN_PASSED_ARP_REQUEST;
import static android.stats.connectivity.CounterName.CN_PASSED_ARP_UNICAST_REPLY;
@@ -104,16 +118,12 @@
import static android.stats.connectivity.CounterName.CN_PASSED_IPV4;
import static android.stats.connectivity.CounterName.CN_PASSED_IPV4_FROM_DHCPV4_SERVER;
import static android.stats.connectivity.CounterName.CN_PASSED_IPV4_UNICAST;
+import static android.stats.connectivity.CounterName.CN_PASSED_IPV6_HOPOPTS;
import static android.stats.connectivity.CounterName.CN_PASSED_IPV6_ICMP;
import static android.stats.connectivity.CounterName.CN_PASSED_IPV6_NON_ICMP;
-import static android.stats.connectivity.CounterName.CN_PASSED_IPV6_NS_DAD;
-import static android.stats.connectivity.CounterName.CN_PASSED_IPV6_NS_NO_ADDRESS;
-import static android.stats.connectivity.CounterName.CN_PASSED_IPV6_NS_NO_SLLA_OPTION;
-import static android.stats.connectivity.CounterName.CN_PASSED_IPV6_NS_TENTATIVE;
import static android.stats.connectivity.CounterName.CN_PASSED_IPV6_UNICAST_NON_ICMP;
-import static android.stats.connectivity.CounterName.CN_PASSED_MDNS;
-import static android.stats.connectivity.CounterName.CN_PASSED_MLD;
import static android.stats.connectivity.CounterName.CN_PASSED_NON_IP_UNICAST;
+import static android.stats.connectivity.CounterName.CN_PASSED_OUR_SRC_MAC;
import static android.stats.connectivity.CounterName.CN_TOTAL_PACKETS;
import static android.stats.connectivity.CounterName.CN_UNKNOWN;
@@ -137,41 +147,44 @@
public static final int MAX_NUM_OF_COUNTERS = Counter.class.getEnumConstants().length - 1;
private static final EnumMap<Counter, CounterName> apfCounterMetricsMap = new EnumMap<>(
Map.ofEntries(
+ Map.entry(RESERVED_OOB, CN_UNKNOWN),
Map.entry(TOTAL_PACKETS, CN_TOTAL_PACKETS),
// The counter sequence should be keep the same in ApfCounterTracker.java
- Map.entry(PASSED_ARP, CN_PASSED_ARP),
Map.entry(PASSED_ARP_BROADCAST_REPLY, CN_PASSED_ARP_BROADCAST_REPLY),
- // deprecated in ApfFilter, PASSED_ARP_NON_IPV4 ==> DROPPED_ARP_NON_IPV4
- Map.entry(PASSED_ARP_NON_IPV4, CN_UNKNOWN),
Map.entry(PASSED_ARP_REQUEST, CN_PASSED_ARP_REQUEST),
Map.entry(PASSED_ARP_UNICAST_REPLY, CN_PASSED_ARP_UNICAST_REPLY),
- // deprecated in ApfFilter, PASSED_ARP_UNKNOWN ==> DROPPED_ARP_UNKNOWN
- Map.entry(PASSED_ARP_UNKNOWN, CN_UNKNOWN),
Map.entry(PASSED_DHCP, CN_PASSED_DHCP),
+ Map.entry(PASSED_ETHER_OUR_SRC_MAC, CN_PASSED_OUR_SRC_MAC),
Map.entry(PASSED_IPV4, CN_PASSED_IPV4),
Map.entry(PASSED_IPV4_FROM_DHCPV4_SERVER, CN_PASSED_IPV4_FROM_DHCPV4_SERVER),
Map.entry(PASSED_IPV4_UNICAST, CN_PASSED_IPV4_UNICAST),
+ Map.entry(PASSED_IPV6_HOPOPTS, CN_PASSED_IPV6_HOPOPTS),
Map.entry(PASSED_IPV6_ICMP, CN_PASSED_IPV6_ICMP),
Map.entry(PASSED_IPV6_NON_ICMP, CN_PASSED_IPV6_NON_ICMP),
- Map.entry(PASSED_IPV6_NS_DAD, CN_PASSED_IPV6_NS_DAD),
- Map.entry(PASSED_IPV6_NS_NO_ADDRESS, CN_PASSED_IPV6_NS_NO_ADDRESS),
- Map.entry(PASSED_IPV6_NS_NO_SLLA_OPTION, CN_PASSED_IPV6_NS_NO_SLLA_OPTION),
- Map.entry(PASSED_IPV6_NS_TENTATIVE, CN_PASSED_IPV6_NS_TENTATIVE),
Map.entry(PASSED_IPV6_UNICAST_NON_ICMP, CN_PASSED_IPV6_UNICAST_NON_ICMP),
Map.entry(PASSED_NON_IP_UNICAST, CN_PASSED_NON_IP_UNICAST),
- Map.entry(PASSED_MDNS, CN_PASSED_MDNS),
- Map.entry(PASSED_MLD, CN_PASSED_MLD),
Map.entry(DROPPED_ETH_BROADCAST, CN_DROPPED_ETH_BROADCAST),
+ Map.entry(DROPPED_ETHER_OUR_SRC_MAC, CN_DROPPED_ETHER_OUR_SRC_MAC),
Map.entry(DROPPED_RA, CN_DROPPED_RA),
Map.entry(DROPPED_IPV4_L2_BROADCAST, CN_DROPPED_IPV4_L2_BROADCAST),
Map.entry(DROPPED_IPV4_BROADCAST_ADDR, CN_DROPPED_IPV4_BROADCAST_ADDR),
Map.entry(DROPPED_IPV4_BROADCAST_NET, CN_DROPPED_IPV4_BROADCAST_NET),
+ Map.entry(DROPPED_IPV4_ICMP_INVALID, CN_DROPPED_IPV4_ICMP_INVALID),
Map.entry(DROPPED_IPV4_MULTICAST, CN_DROPPED_IPV4_MULTICAST),
Map.entry(DROPPED_IPV4_NON_DHCP4, CN_DROPPED_IPV4_NON_DHCP4),
+ Map.entry(DROPPED_IPV4_PING_REQUEST_REPLIED, CN_DROPPED_IPV4_PING_REQUEST_REPLIED),
+ Map.entry(DROPPED_IPV6_ICMP6_ECHO_REQUEST_INVALID,
+ CN_DROPPED_IPV6_ICMP6_ECHO_REQUEST_INVALID),
+ Map.entry(DROPPED_IPV6_ICMP6_ECHO_REQUEST_REPLIED,
+ CN_DROPPED_IPV6_ICMP6_ECHO_REQUEST_REPLIED),
Map.entry(DROPPED_IPV6_ROUTER_SOLICITATION, CN_DROPPED_IPV6_ROUTER_SOLICITATION),
+ Map.entry(DROPPED_IPV6_MLD_INVALID, CN_DROPPED_IPV6_MLD_INVALID),
+ Map.entry(DROPPED_IPV6_MLD_REPORT, CN_DROPPED_IPV6_MLD_REPORT),
+ Map.entry(DROPPED_IPV6_MLD_V1_GENERAL_QUERY_REPLIED,
+ CN_DROPPED_IPV6_MLD_V1_GENERAL_QUERY_REPLIED),
+ Map.entry(DROPPED_IPV6_MLD_V2_GENERAL_QUERY_REPLIED,
+ CN_DROPPED_IPV6_MLD_V2_GENERAL_QUERY_REPLIED),
Map.entry(DROPPED_IPV6_MULTICAST_NA, CN_DROPPED_IPV6_MULTICAST_NA),
- Map.entry(DROPPED_IPV6_MULTICAST, CN_DROPPED_IPV6_MULTICAST),
- Map.entry(DROPPED_IPV6_MULTICAST_PING, CN_DROPPED_IPV6_MULTICAST_PING),
Map.entry(DROPPED_IPV6_NON_ICMP_MULTICAST, CN_DROPPED_IPV6_NON_ICMP_MULTICAST),
Map.entry(DROPPED_IPV6_NS_INVALID, CN_DROPPED_IPV6_NS_INVALID),
Map.entry(DROPPED_IPV6_NS_OTHER_HOST, CN_DROPPED_IPV6_NS_OTHER_HOST),
@@ -179,18 +192,22 @@
Map.entry(DROPPED_802_3_FRAME, CN_DROPPED_802_3_FRAME),
Map.entry(DROPPED_ETHERTYPE_NOT_ALLOWED, CN_DROPPED_ETHERTYPE_NOT_ALLOWED),
Map.entry(DROPPED_IPV4_KEEPALIVE_ACK, CN_DROPPED_IPV4_KEEPALIVE_ACK),
- Map.entry(DROPPED_IPV6_KEEPALIVE_ACK, CN_DROPPED_IPV6_KEEPALIVE_ACK),
Map.entry(DROPPED_IPV4_NATT_KEEPALIVE, CN_DROPPED_IPV4_NATT_KEEPALIVE),
Map.entry(DROPPED_MDNS, CN_DROPPED_MDNS),
- // TODO: Not supported yet in the metrics backend.
- Map.entry(DROPPED_IPV4_TCP_PORT7_UNICAST, CN_UNKNOWN),
+ Map.entry(DROPPED_MDNS_REPLIED, CN_DROPPED_MDNS_REPLIED),
+ Map.entry(DROPPED_IPV4_TCP_PORT7_UNICAST, CN_DROPPED_IPV4_TCP_PORT7_UNICAST),
Map.entry(DROPPED_ARP_NON_IPV4, CN_DROPPED_ARP_NON_IPV4),
Map.entry(DROPPED_ARP_OTHER_HOST, CN_DROPPED_ARP_OTHER_HOST),
Map.entry(DROPPED_ARP_REPLY_SPA_NO_HOST, CN_DROPPED_ARP_REPLY_SPA_NO_HOST),
- Map.entry(DROPPED_ARP_REQUEST_ANYHOST, CN_DROPPED_ARP_REQUEST_ANYHOST),
Map.entry(DROPPED_ARP_REQUEST_REPLIED, CN_DROPPED_ARP_REQUEST_REPLIED),
Map.entry(DROPPED_ARP_UNKNOWN, CN_DROPPED_ARP_UNKNOWN),
Map.entry(DROPPED_ARP_V6_ONLY, CN_DROPPED_ARP_V6_ONLY),
+ Map.entry(DROPPED_IGMP_V2_GENERAL_QUERY_REPLIED,
+ CN_DROPPED_IGMP_V2_GENERAL_QUERY_REPLIED),
+ Map.entry(DROPPED_IGMP_V3_GENERAL_QUERY_REPLIED,
+ CN_DROPPED_IGMP_V3_GENERAL_QUERY_REPLIED),
+ Map.entry(DROPPED_IGMP_INVALID, CN_DROPPED_IGMP_INVALID),
+ Map.entry(DROPPED_IGMP_REPORT, CN_DROPPED_IGMP_REPORT),
Map.entry(DROPPED_GARP_REPLY, CN_DROPPED_GARP_REPLY)
)
);
diff --git a/src/com/android/networkstack/metrics/stats.proto b/src/com/android/networkstack/metrics/stats.proto
index 43bc414..972e1f1 100644
--- a/src/com/android/networkstack/metrics/stats.proto
+++ b/src/com/android/networkstack/metrics/stats.proto
@@ -239,8 +239,7 @@
/**
* Logs APF session information event.
* Logged from:
- * packages/modules/NetworkStack/src/android/net/apf/ApfFilter.java or
- * packages/modules/NetworkStack/src/android/net/apf/LegacyApfFilter.java
+ * packages/modules/NetworkStack/src/android/net/apf/ApfFilter.java
*/
message ApfSessionInfoReported {
// The version of APF, where version = -1 equals APF disable.
diff --git a/src/com/android/networkstack/util/NetworkStackUtils.java b/src/com/android/networkstack/util/NetworkStackUtils.java
index fce06e4..b0252dc 100755
--- a/src/com/android/networkstack/util/NetworkStackUtils.java
+++ b/src/com/android/networkstack/util/NetworkStackUtils.java
@@ -16,13 +16,21 @@
package com.android.networkstack.util;
+import static android.net.apf.ApfConstants.IPV6_SOLICITED_NODES_PREFIX;
+import static android.os.Build.VERSION.CODENAME;
+import static android.os.Build.VERSION.SDK_INT;
+import static android.system.OsConstants.IFA_F_DEPRECATED;
+import static android.system.OsConstants.IFA_F_TENTATIVE;
+
import android.content.Context;
import android.net.IpPrefix;
import android.net.LinkAddress;
+import android.net.LinkProperties;
import android.net.MacAddress;
import android.system.ErrnoException;
import android.util.Log;
+import androidx.annotation.ChecksSdkIntAtLeast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -64,7 +72,7 @@
public static final String CAPTIVE_PORTAL_OTHER_HTTP_URLS = "captive_portal_other_http_urls";
/**
- * A comma separated list of URLs used for network validation. in addition to the HTTPS url
+ * A comma separated list of URLs used for network validation in addition to the HTTPS url
* associated with the CAPTIVE_PORTAL_HTTPS_URL settings.
*/
public static final String CAPTIVE_PORTAL_OTHER_HTTPS_URLS = "captive_portal_other_https_urls";
@@ -186,13 +194,6 @@
public static final String VALIDATION_METRICS_VERSION = "validation_metrics_version";
/**
- * Experiment flag to enable sending Gratuitous APR and Gratuitous Neighbor Advertisement for
- * all assigned IPv4 and IPv6 GUAs after completing L2 roaming.
- */
- public static final String IPCLIENT_GARP_NA_ROAMING_VERSION =
- "ipclient_garp_na_roaming_version";
-
- /**
* Experiment flag to enable "mcast_resolicit" neighbor parameter in IpReachabilityMonitor,
* set it to 3 by default.
*/
@@ -200,20 +201,6 @@
"ip_reachability_mcast_resolicit_version";
/**
- * Experiment flag to attempt to ignore the on-link IPv6 DNS server which fails to respond to
- * address resolution.
- */
- public static final String IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION =
- "ip_reachability_ignore_incompleted_ipv6_dns_server_version";
-
- /**
- * Experiment flag to attempt to ignore the IPv6 default router which fails to respond to
- * address resolution.
- */
- public static final String IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION =
- "ip_reachability_ignore_incompleted_ipv6_default_router_version";
-
- /**
* Experiment flag to treat router MAC address changes as a failure only on roam.
*/
public static final String IP_REACHABILITY_ROUTER_MAC_CHANGE_FAILURE_ONLY_AFTER_ROAM_VERSION =
@@ -226,24 +213,6 @@
"ip_reachability_ignore_organic_nud_failure_version";
/**
- * Experiment flag to ignore all NUD failures from the neighbor that has never ever entered the
- * reachable state.
- */
- public static final String IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION =
- "ip_reachability_ignore_never_reachable_neighbor_version";
-
- /**
- * Experiment flag to enable DHCPv6 Prefix Delegation(RFC8415) in IpClient.
- */
- public static final String IPCLIENT_DHCPV6_PREFIX_DELEGATION_VERSION =
- "ipclient_dhcpv6_prefix_delegation_version";
-
- /**
- * Experiment flag to enable new ra filter.
- */
- public static final String APF_NEW_RA_FILTER_VERSION = "apf_new_ra_filter_version";
-
- /**
* Experiment flag to enable the feature of polling counters in Apf.
*/
public static final String APF_POLLING_COUNTERS_VERSION = "apf_polling_counters_version";
@@ -275,6 +244,12 @@
"ipclient_dhcpv6_pd_preferred_flag_version";
/**
+ * Experiment flag to replace INetd usage with netlink in IpClient.
+ */
+ public static final String IPCLIENT_REPLACE_NETD_WITH_NETLINK_VERSION =
+ "ipclient_replace_netd_with_netlink_version";
+
+ /**
* Experiment flag to enable Discovery of Designated Resolvers (DDR).
* This flag requires networkmonitor_async_privdns_resolution flag.
*/
@@ -286,6 +261,30 @@
public static final String IP_REACHABILITY_IGNORE_NUD_FAILURE_VERSION =
"ip_reachability_ignore_nud_failure_version";
+ /**
+ * Experiment flag to enable the feature of handle IPv4 ping offload in Apf.
+ */
+ public static final String APF_HANDLE_PING4_OFFLOAD_VERSION =
+ "apf_handle_ping_offload_version";
+
+ /**
+ * Experiment flag to enable the feature of handle IPv6 ping offload in Apf.
+ */
+ public static final String APF_HANDLE_PING6_OFFLOAD_VERSION =
+ "apf_handle_ping6_offload_version";
+
+ /**
+ * Experiment flag to enable the feature of handle IGMP offload in Apf.
+ */
+ public static final String APF_HANDLE_IGMP_OFFLOAD_VERSION =
+ "apf_handle_igmp_offload_version";
+
+ /**
+ * Experiment flag to enable the feature of handle MLD offload in Apf.
+ */
+ public static final String APF_HANDLE_MLD_OFFLOAD_VERSION =
+ "apf_handle_mld_offload_version";
+
/**** BEGIN Feature Kill Switch Flags ****/
/**
@@ -306,6 +305,9 @@
public static final String IGNORE_TCP_INFO_FOR_BLOCKED_UIDS =
"ignore_tcp_info_for_blocked_uids";
+ /** Kill switch to force disable APF */
+ public static final String APF_ENABLE = "apf_enable";
+
/**
* Kill switch flag to disable the feature of handle arp offload in Apf.
* Warning: the following flag String is incorrect. The feature that is not chickened out is
@@ -318,11 +320,45 @@
*/
public static final String APF_HANDLE_ND_OFFLOAD = "apf_handle_nd_offload";
+ /**
+ * Kill switch flag to disable the feature of handle IGMP offload in Apf.
+ */
+ public static final String APF_HANDLE_IGMP_OFFLOAD = "apf_handle_igmp_offload";
+
+ /**
+ * Kill switch flag to disable the feature of handle MLD offload in Apf.
+ */
+ public static final String APF_HANDLE_MLD_OFFLOAD = "apf_handle_mld_offload";
+
+ /**
+ * Kill switch flag to disable the feature of handle IPv4 ping offload in Apf.
+ */
+ public static final String APF_HANDLE_PING4_OFFLOAD = "apf_handle_ping4_offload";
+
+ /**
+ * Kill switch flag to disable the feature of handle IPv6 ping offload in Apf.
+ */
+ public static final String APF_HANDLE_PING6_OFFLOAD = "apf_handle_ping6_offload";
static {
System.loadLibrary("networkstackutilsjni");
}
/**
+ * Convert IPv4 multicast address to ethernet multicast address in network order.
+ */
+ public static MacAddress ipv4MulticastToEthernetMulticast(@NonNull final Inet4Address addr) {
+ final byte[] etherMulticast = new byte[6];
+ final byte[] ipv4Multicast = addr.getAddress();
+ etherMulticast[0] = (byte) 0x01;
+ etherMulticast[1] = (byte) 0x00;
+ etherMulticast[2] = (byte) 0x5e;
+ etherMulticast[3] = (byte) (ipv4Multicast[1] & 0x7f);
+ etherMulticast[4] = ipv4Multicast[2];
+ etherMulticast[5] = ipv4Multicast[3];
+ return MacAddress.fromBytes(etherMulticast);
+ }
+
+ /**
* Convert IPv6 multicast address to ethernet multicast address in network order.
*/
public static MacAddress ipv6MulticastToEthernetMulticast(@NonNull final Inet6Address addr) {
@@ -361,6 +397,24 @@
}
/**
+ * Checks if the given IPv6 address is a solicited-node multicast address.
+ *
+ * <p>Solicited-node multicast addresses are used for Neighbor Discovery in IPv6.
+ * They have a specific prefix (FF02::1:FFxx:xxxx) where the last 64 bits are derived
+ * from the interface's link-layer address. This function only checks if the address
+ * has the correct prefix; it does *not* verify the lower 64 bits.
+ */
+ public static boolean isIPv6AddressSolicitedNodeMulticast(@NonNull final Inet6Address addr) {
+ for (int i = 0; i < IPV6_SOLICITED_NODES_PREFIX.length; i++) {
+ if (addr.getAddress()[i] != IPV6_SOLICITED_NODES_PREFIX[i]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
* Check whether a link address is IPv6 global preferred unicast address.
*/
public static boolean isIPv6GUA(@NonNull final LinkAddress address) {
@@ -409,6 +463,58 @@
}
}
+ /** Checks if the device is running on a release version of Android Baklava or newer */
+ @ChecksSdkIntAtLeast(api = 36 /* BUILD_VERSION_CODES.Baklava */)
+ public static boolean isAtLeast25Q2() {
+ return SDK_INT >= 36 || (SDK_INT == 35 && isAtLeastPreReleaseCodename("Baklava"));
+ }
+
+ private static boolean isAtLeastPreReleaseCodename(@NonNull String codename) {
+ // Special case "REL", which means the build is not a pre-release build.
+ if ("REL".equals(CODENAME)) {
+ return false;
+ }
+
+ // Otherwise lexically compare them. Return true if the build codename is equal to or
+ // greater than the requested codename.
+ return CODENAME.compareTo(codename) >= 0;
+ }
+
+ /**
+ * Select the preferred IPv6 link-local address based on the rules defined in rfc3484,
+ * Section 5.
+ * <p>
+ * The address selection criteria are as follows:
+ * 1. Select a non-tentative, non-deprecated address, if available.
+ * 2. If no such address exists, select any non-tentative address.
+ */
+ public static Inet6Address selectPreferredIPv6LinkLocalAddress(@NonNull LinkProperties lp) {
+ Inet6Address preferredAddress = null;
+ for (LinkAddress linkAddress : lp.getLinkAddresses()) {
+ final InetAddress inetAddress = linkAddress.getAddress();
+ final int flags = linkAddress.getFlags();
+
+ if (!(inetAddress instanceof Inet6Address)) {
+ continue;
+ }
+
+ if (!inetAddress.isLinkLocalAddress()) {
+ continue;
+ }
+
+ if ((flags & IFA_F_TENTATIVE) != 0) {
+ continue;
+ }
+
+ preferredAddress = (Inet6Address) inetAddress;
+ if ((flags & IFA_F_DEPRECATED) == 0L) {
+ return preferredAddress;
+ }
+ }
+
+ return preferredAddress;
+ }
+
/**
* Attaches a socket filter that accepts DHCP packets to the given socket.
*/
@@ -437,6 +543,27 @@
addArpEntry(ethAddr.toByteArray(), ipv4Addr.getAddress(), ifname, fd);
}
+ /**
+ * Attaches a socket filter that accepts egress IGMPv2/IGMPv3 reports to the given socket.
+ *
+ * This filter doesn't include IGMPv1 report since device will not send out IGMPv1 report
+ * when the device leaves a multicast address group.
+ *
+ * @param fd the socket's {@link FileDescriptor}.
+ */
+ public static native void attachEgressIgmpReportFilter(FileDescriptor fd) throws ErrnoException;
+
+ /**
+ * Attaches a socket filter that accepts egress IGMPv2/v3, MLDv1/v2 reports to the given socket.
+ *
+ * This filter doesn't include IGMPv1 report since device will not send out IGMPv1 report
+ * when the device leaves a multicast address group.
+ *
+ * @param fd the socket's {@link FileDescriptor}.
+ */
+ public static native void attachEgressMulticastReportFilter(
+ FileDescriptor fd) throws ErrnoException;
+
private static native void addArpEntry(byte[] ethAddr, byte[] netAddr, String ifname,
FileDescriptor fd) throws IOException;
diff --git a/src/com/android/server/NetworkStackService.java b/src/com/android/server/NetworkStackService.java
index 686a399..68d5a21 100644
--- a/src/com/android/server/NetworkStackService.java
+++ b/src/com/android/server/NetworkStackService.java
@@ -107,9 +107,6 @@
/**
* Create a binder connector for the system server to communicate with the network stack.
- *
- * <p>On platforms where the network stack runs in the system server process, this method may
- * be called directly instead of obtaining the connector by binding to the service.
*/
public static synchronized IBinder makeConnector(Context context) {
if (sConnector == null) {
@@ -364,8 +361,8 @@
}
@Override
- public void makeNetworkMonitor(Network network, String name, INetworkMonitorCallbacks cb)
- throws RemoteException {
+ public void makeNetworkMonitor(Network network, @Nullable String name,
+ INetworkMonitorCallbacks cb) throws RemoteException {
mPermChecker.enforceNetworkStackCallingPermission();
updateNetworkStackAidlVersion(cb.getInterfaceVersion(), cb.getInterfaceHash());
final SharedLog log = addValidationLogs(network, name);
diff --git a/src/com/android/server/connectivity/NetworkMonitor.java b/src/com/android/server/connectivity/NetworkMonitor.java
index 197ed69..3ae8557 100755
--- a/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/src/com/android/server/connectivity/NetworkMonitor.java
@@ -883,7 +883,8 @@
} else {
mCallback.notifyNetworkTestedWithExtras(result);
}
- } catch (RemoteException e) {
+ } catch (RemoteException | RuntimeException e) {
+ // TODO: stop catching RuntimeException once all mainline devices use the tethering APEX
Log.e(TAG, "Error sending network test result", e);
}
}
@@ -914,7 +915,8 @@
private void notifyProbeStatusChanged(int probesCompleted, int probesSucceeded) {
try {
mCallback.notifyProbeStatusChanged(probesCompleted, probesSucceeded);
- } catch (RemoteException e) {
+ } catch (RemoteException | RuntimeException e) {
+ // TODO: stop catching RuntimeException once all mainline devices use the tethering APEX
Log.e(TAG, "Error sending probe status", e);
}
}
@@ -922,7 +924,8 @@
private void showProvisioningNotification(String action) {
try {
mCallback.showProvisioningNotification(action, mContext.getPackageName());
- } catch (RemoteException e) {
+ } catch (RemoteException | RuntimeException e) {
+ // TODO: stop catching RuntimeException once all mainline devices use the tethering APEX
Log.e(TAG, "Error showing provisioning notification", e);
}
}
@@ -930,7 +933,8 @@
private void hideProvisioningNotification() {
try {
mCallback.hideProvisioningNotification();
- } catch (RemoteException e) {
+ } catch (RemoteException | RuntimeException e) {
+ // TODO: stop catching RuntimeException once all mainline devices use the tethering APEX
Log.e(TAG, "Error hiding provisioning notification", e);
}
}
@@ -938,7 +942,8 @@
private void notifyDataStallSuspected(@NonNull DataStallReportParcelable p) {
try {
mCallback.notifyDataStallSuspected(p);
- } catch (RemoteException e) {
+ } catch (RemoteException | RuntimeException e) {
+ // TODO: stop catching RuntimeException once all mainline devices use the tethering APEX
Log.e(TAG, "Error sending notification for suspected data stall", e);
}
}
@@ -2089,7 +2094,8 @@
private void notifyPrivateDnsConfigResolved(@NonNull PrivateDnsConfig config) {
try {
mCallback.notifyPrivateDnsConfigResolved(config.toParcel());
- } catch (RemoteException e) {
+ } catch (RemoteException | RuntimeException e) {
+ // TODO: stop catching RuntimeException once all mainline devices use the tethering APEX
Log.e(TAG, "Error sending private DNS config resolved notification", e);
}
}
@@ -4154,7 +4160,8 @@
if (data == null) return;
try {
data.notifyChanged(mCallback);
- } catch (RemoteException e) {
+ } catch (RemoteException | RuntimeException e) {
+ // TODO: stop catching RuntimeException once all mainline devices use the tethering APEX
Log.e(TAG, "Error notifying ConnectivityService of new capport data", e);
}
}
diff --git a/tests/integration/AndroidTest_Coverage.xml b/tests/integration/AndroidTest_Coverage.xml
index 5e5fbfb..eb0ba16 100644
--- a/tests/integration/AndroidTest_Coverage.xml
+++ b/tests/integration/AndroidTest_Coverage.xml
@@ -32,5 +32,7 @@
<option name="test-filter-dir" value="/data/data/{PACKAGE}/cache" />
<option name="hidden-api-checks" value="false"/>
<option name="device-listeners" value="com.android.modules.utils.testing.NativeCoverageHackInstrumentationListener" />
+ <!-- b/393391701 -->
+ <option name="exclude-filter" value="com.android.net.module.util.RealtimeSchedulerTest" />
</test>
</configuration>
diff --git a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
index ee23c99..7ec958a 100644
--- a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
+++ b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
@@ -45,6 +45,7 @@
import static android.net.ip.IpClient.DEFAULT_APF_COUNTER_POLLING_INTERVAL_SECS;
import static android.net.ip.IpClient.DEFAULT_NUD_FAILURE_COUNT_DAILY_THRESHOLD;
import static android.net.ip.IpClient.DEFAULT_NUD_FAILURE_COUNT_WEEKLY_THRESHOLD;
+import static android.net.ip.IpClient.NETWORK_EVENT_NUD_FAILURE_TYPES;
import static android.net.ip.IpClient.ONE_DAY_IN_MS;
import static android.net.ip.IpClient.ONE_WEEK_IN_MS;
import static android.net.ip.IpClient.SIX_HOURS_IN_MS;
@@ -85,9 +86,6 @@
import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_AUTONOMOUS;
import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_ON_LINK;
import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_POPULATE_LINK_ADDRESS_LIFETIME_VERSION;
-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_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION;
import static com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_IGNORE_NUD_FAILURE_VERSION;
import static com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION;
import static com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_ROUTER_MAC_CHANGE_FAILURE_ONLY_AFTER_ROAM_VERSION;
@@ -116,7 +114,6 @@
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
@@ -126,7 +123,6 @@
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.AlarmManager;
@@ -176,6 +172,7 @@
import android.net.ipmemorystore.NetworkAttributes;
import android.net.ipmemorystore.OnNetworkAttributesRetrievedListener;
import android.net.ipmemorystore.OnNetworkEventCountRetrievedListener;
+import android.net.ipmemorystore.OnStatusListener;
import android.net.ipmemorystore.Status;
import android.net.networkstack.TestNetworkStackServiceClient;
import android.net.networkstack.aidl.dhcp.DhcpOption;
@@ -297,7 +294,7 @@
private static final String TAG = IpClientIntegrationTestCommon.class.getSimpleName();
private static final int DATA_BUFFER_LEN = 4096;
private static final int PACKET_TIMEOUT_MS = 5_000;
- private static final String TEST_CLUSTER = "some cluster";
+ protected static final String TEST_CLUSTER = "some cluster";
private static final int TEST_LEASE_DURATION_S = 3_600; // 1 hour
private static final int TEST_IPV6_ONLY_WAIT_S = 1_800; // 30 min
private static final int TEST_LOWER_IPV6_ONLY_WAIT_S = (int) (MIN_V6ONLY_WAIT_MS / 1000 - 1);
@@ -321,6 +318,9 @@
// should be enough between the timestamp when the IP provisioning completes successfully and
// when IpClientLinkObserver sees the RTM_NEWADDR netlink events.
private static final long TEST_LIFETIME_TOLERANCE_MS = 4_000L;
+ private static final long TEST_POLL_NEIGHBOR_PARAMETER_MS = 500L;
+ private static final int TEST_ARP_LOCKTIME_MS = 1500;
+ private static final int TEST_DELAY_FIRST_PROBE_TIME_S = 2;
@Rule
public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
@@ -426,7 +426,6 @@
private static final int DHCP_HEADER_OFFSET = ETH_HEADER_LEN + IPV4_HEADER_LEN
+ UDP_HEADER_LEN;
private static final int DHCP_MESSAGE_OP_CODE_OFFSET = DHCP_HEADER_OFFSET + 0;
- private static final int DHCP_TRANSACTION_ID_OFFSET = DHCP_HEADER_OFFSET + 4;
private static final int DHCP_OPTION_MAGIC_COOKIE_OFFSET = DHCP_HEADER_OFFSET + 236;
// DHCPv6 header
@@ -445,12 +444,15 @@
private static final String IPV4_TEST_SUBNET_PREFIX = "192.168.1.0/24";
private static final String IPV4_ANY_ADDRESS_PREFIX = "0.0.0.0/0";
private static final String HOSTNAME = "testhostname";
+ private static final String TEST_IPV6_PREFIX = "2001:db8:1::/64";
private static final String IPV6_OFF_LINK_DNS_SERVER = "2001:4860:4860::64";
private static final String IPV6_ON_LINK_DNS_SERVER = "2001:db8:1::64";
private static final int TEST_DEFAULT_MTU = 1500;
private static final int TEST_MIN_MTU = 1280;
private static final MacAddress ROUTER_MAC = MacAddress.fromString("00:1A:11:22:33:44");
private static final byte[] ROUTER_MAC_BYTES = ROUTER_MAC.toByteArray();
+ private static final MacAddress ON_LINK_DNS_SERVER_MAC =
+ MacAddress.fromString("00:1A:11:AA:BB:CC");
private static final Inet6Address ROUTER_LINK_LOCAL = ipv6Addr("fe80::1");
private static final byte[] ROUTER_DUID = new byte[] {
// type: Link-layer address, hardware type: EUI64(27)
@@ -464,16 +466,12 @@
private static final String TEST_HOST_NAME = "AOSP on Crosshatch";
private static final String TEST_HOST_NAME_TRANSLITERATION = "AOSP-on-Crosshatch";
private static final String TEST_CAPTIVE_PORTAL_URL = "https://example.com/capportapi";
- private static final byte[] TEST_HOTSPOT_OUI = new byte[] {
- (byte) 0x00, (byte) 0x17, (byte) 0xF2
- };
private static final byte LEGACY_TEST_VENDOR_SPECIFIC_IE_TYPE = 0x11;
private static final byte TEST_VENDOR_SPECIFIC_IE_TYPE = 0x21;
private static final int TEST_VENDOR_SPECIFIC_IE_ID = 0xdd;
private static final String TEST_DEFAULT_SSID = "test_ssid";
private static final String TEST_DEFAULT_BSSID = "00:11:22:33:44:55";
- private static final String TEST_DHCP_ROAM_SSID = "0001docomo";
private static final String TEST_DHCP_ROAM_BSSID = "00:4e:35:17:98:55";
private static final String TEST_DHCP_ROAM_L2KEY = "roaming_l2key";
private static final String TEST_DHCP_ROAM_CLUSTER = "roaming_cluster";
@@ -752,9 +750,9 @@
mIsSignatureRequiredTest = testMethod.getAnnotation(SignatureRequiredTest.class) != null;
assumeFalse(testSkipped());
- // Enable DHCPv6 Prefix Delegation.
- setFeatureEnabled(NetworkStackUtils.IPCLIENT_DHCPV6_PREFIX_DELEGATION_VERSION,
- true /* isDhcp6PrefixDelegationEnabled */);
+ // Enable replacement of netd usage with netlink in IpClient.
+ setFeatureEnabled(NetworkStackUtils.IPCLIENT_REPLACE_NETD_WITH_NETLINK_VERSION,
+ true /* isIpClientReplaceNetdWithNetlinkEnabled */);
// Set flags based on test method annotations.
final Flag[] flags = testMethod.getAnnotationsByType(Flag.class);
@@ -834,7 +832,19 @@
when(mPackageManager.getPackagesForUid(TEST_DEVICE_OWNER_APP_UID)).thenReturn(
new String[] { TEST_DEVICE_OWNER_APP_PACKAGE });
- // Retrieve the network event count.
+ // Store a network event to db.
+ doAnswer(invocation -> {
+ final String cluster = invocation.getArgument(0);
+ final long timestamp = invocation.getArgument(1);
+ final long expiry = invocation.getArgument(2);
+ final int eventType = invocation.getArgument(3);
+ storeNetworkEvent(cluster, timestamp, expiry, eventType);
+ ((OnStatusListener) invocation.getArgument(4)).onComplete(new Status(SUCCESS));
+ return null;
+ }).when(mIpMemoryStore).storeNetworkEvent(eq(TEST_CLUSTER), anyLong(), anyLong(), anyInt(),
+ any());
+
+ // Retrieve the network event count from db.
doAnswer(invocation -> {
final String cluster = invocation.getArgument(0);
final long[] sinceTimes = invocation.getArgument(1);
@@ -949,6 +959,48 @@
}
}
+ /**
+ * Set the sysctl "delay_first_probe_time" by executing a shell command as root.
+ *
+ * Directly using "su root echo delay > /proc/sys/net/ipv4/neigh/delay_first_probe_time" fails
+ * because only the "echo delay" command runs as root; the redirection ">" and the following
+ * sysctl path string is not performed by the root shell, resulting in permission errors.
+ *
+ * Theoretically we want to run "su root sh -c 'echo delay > /proc/sys/net/ipv4(v6)/neigh/
+ * delay_first_probe_time'" to change the sysctl, however, the `executeShellCommand`
+ * function splits commands based on spaces, even within single quotes, so this doesn't work.
+ * Instead, use `executeShellCommandRw` to execute a shell command receiving from stdin.
+ */
+ private void setNudDelayFirstProbeTime(int delay, String family) throws Exception {
+ final ParcelFileDescriptor[] fds = InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation().executeShellCommandRw("su root sh");
+
+ final ParcelFileDescriptor stdout = fds[0];
+ final ParcelFileDescriptor stdin = fds[1];
+ try (ParcelFileDescriptor.AutoCloseOutputStream output =
+ new ParcelFileDescriptor.AutoCloseOutputStream(stdin)) {
+ final String cmd = "echo " + delay + " > /proc/sys/net/" + family + "/neigh/"
+ + mIfaceName + "/delay_first_probe_time";
+ output.write(cmd.getBytes());
+ }
+ // Setting a value to sysctl doesn't have any output.
+ final BufferedReader reader =
+ new BufferedReader(new FileReader(stdout.getFileDescriptor()));
+ assertNull(reader.readLine());
+
+ // Check if the sysctl "delay_first_probe_time" has updated.
+ final String delay_first_probe_time = getOneLineCommandOutput(
+ "su root cat /proc/sys/net/" + family + "/neigh/" + mIfaceName
+ + "/delay_first_probe_time");
+ assertEquals(delay, Integer.parseInt(delay_first_probe_time));
+ }
+
+ private int getNeighborParameterUcastSolicit(String ifaceName) throws IOException {
+ final String ucast_solicit = getOneLineCommandOutput(
+ "su root cat /proc/sys/net/ipv6/neigh/" + ifaceName + "/ucast_solicit");
+ return Integer.parseInt(ucast_solicit);
+ }
+
private MacAddress getIfaceMacAddr(String ifaceName) throws IOException {
// InterfaceParams.getByName requires CAP_NET_ADMIN: read the mac address with the shell
final String strMacAddr = getOneLineCommandOutput(
@@ -1041,15 +1093,6 @@
return expectAlarmSet(inOrder, tagMatch, (long) afterSeconds, mIpc.getHandler());
}
- private boolean packetContainsExpectedField(final byte[] packet, final int offset,
- final byte[] expected) {
- if (packet.length < offset + expected.length) return false;
- for (int i = 0; i < expected.length; ++i) {
- if (packet[offset + i] != expected[i]) return false;
- }
- return true;
- }
-
private boolean isDhcpPacket(final byte[] packet) {
final ByteBuffer buffer = ByteBuffer.wrap(packet);
@@ -1228,6 +1271,11 @@
mPacketReader.sendResponse(packet);
}
+ private void sendGratuitousArp(MacAddress srcMac, Inet4Address targetIp) throws IOException {
+ sendArpReply(ETHER_BROADCAST /* dstMac */, srcMac.toByteArray() /* srcMac */, targetIp,
+ targetIp /* sender IP */);
+ }
+
private void startIpClientProvisioning(final ProvisioningConfiguration cfg) throws Exception {
mIIpClient.startProvisioning(cfg.toStableParcelable());
}
@@ -1475,7 +1523,7 @@
if (shouldChangeMtu) {
// Pretend that ConnectivityService set the MTU.
- mNetd.interfaceSetMtu(mIfaceName, mtu);
+ NetlinkUtils.setInterfaceMtu(mIfaceName, mtu);
assertEquals(NetworkInterface.getByName(mIfaceName).getMTU(), mtu);
}
@@ -1489,12 +1537,9 @@
try {
mIpc.shutdown();
awaitIpClientShutdown();
- if (shouldRemoveTestInterface) {
- verify(mNetd, never()).interfaceSetMtu(mIfaceName, TEST_DEFAULT_MTU);
- } else {
+ if (!shouldRemoveTestInterface) {
// Verify that MTU indeed has been restored or not.
- verify(mNetd, times(shouldChangeMtu ? 1 : 0))
- .interfaceSetMtu(mIfaceName, TEST_DEFAULT_MTU);
+ assertEquals(NetworkInterface.getByName(mIfaceName).getMTU(), TEST_DEFAULT_MTU);
}
verifyAfterIpClientShutdown();
} catch (Exception e) {
@@ -1658,7 +1703,6 @@
verify(mCb, never()).onProvisioningFailure(any());
assertIpMemoryNeverStoreNetworkAttributes();
} else if (isDhcpIpConflictDetectEnabled) {
- int arpPacketCount = 0;
final List<ArpPacket> packetList = new ArrayList<ArpPacket>();
// Total sent ARP packets should be 5 (3 ARP Probes + 2 ARP Announcements)
ArpPacket packet;
@@ -1969,15 +2013,6 @@
}
@Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
- public void testRestoreInitialInterfaceMtu_WithException() throws Exception {
- doThrow(new RemoteException("NetdNativeService::interfaceSetMtu")).when(mNetd)
- .interfaceSetMtu(mIfaceName, TEST_DEFAULT_MTU);
-
- doRestoreInitialMtuTest(true /* shouldChangeMtu */, false /* shouldRemoveTestInterface */);
- assertEquals(NetworkInterface.getByName(mIfaceName).getMTU(), TEST_MIN_MTU);
- }
-
- @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
public void testRestoreInitialInterfaceMtu_NotFoundInterfaceWhenStopping() throws Exception {
doRestoreInitialMtuTest(true /* shouldChangeMtu */, true /* shouldRemoveTestInterface */);
}
@@ -2007,7 +2042,7 @@
assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_MIN_MTU);
// Pretend that ConnectivityService set the MTU.
- mNetd.interfaceSetMtu(mIfaceName, TEST_MIN_MTU);
+ NetlinkUtils.setInterfaceMtu(mIfaceName, TEST_MIN_MTU);
assertEquals(NetworkInterface.getByName(mIfaceName).getMTU(), TEST_MIN_MTU);
reset(mCb);
@@ -2099,9 +2134,19 @@
mPacketReader.popPacket(PACKET_TIMEOUT_MS, this::isRouterSolicitation));
}
+ private void sendGratuitousNeighborAdvertisement(final MacAddress srcMac,
+ final Inet6Address srcIp, final Inet6Address targetIp) throws Exception {
+ int flags = NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER | NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE;
+ final Inet6Address dstIp = IPV6_ADDR_ALL_NODES_MULTICAST;
+ final MacAddress dstMac = NetworkStackUtils.ipv6MulticastToEthernetMulticast(dstIp);
+ final ByteBuffer packet =
+ NeighborAdvertisement.build(srcMac, dstMac, srcIp, dstIp, flags, targetIp);
+ mPacketReader.sendResponse(packet);
+ }
+
private void sendRouterAdvertisement(boolean waitForRs, short lifetime, int valid,
int preferred) throws Exception {
- final ByteBuffer pio = buildPioOption(valid, preferred, "2001:db8:1::/64");
+ final ByteBuffer pio = buildPioOption(valid, preferred, TEST_IPV6_PREFIX);
final ByteBuffer rdnss = buildRdnssOption(3600, IPV6_OFF_LINK_DNS_SERVER);
sendRouterAdvertisement(waitForRs, lifetime, pio, rdnss);
}
@@ -2154,6 +2199,18 @@
return buildRaPacket((short) 1800, options);
}
+ private static ByteBuffer buildRaPacket(final String prefix, final String dnsServer,
+ int validLifetime, int preferredLifetime, int dnsLifetime, boolean shouldIncludeSlla)
+ throws Exception {
+ final ByteBuffer pio = buildPioOption(validLifetime, preferredLifetime, prefix);
+ final ByteBuffer rdnss = buildRdnssOption(dnsLifetime, dnsServer);
+ final List<ByteBuffer> options = new ArrayList<ByteBuffer>();
+ options.add(pio);
+ options.add(rdnss);
+ if (shouldIncludeSlla) options.add(buildSllaOption());
+ return buildRaPacket(options.toArray(new ByteBuffer[options.size()]));
+ }
+
private void disableIpv6ProvisioningDelays() throws Exception {
// Speed up the test by disabling DAD and removing router_solicitation_delay.
// We don't need to restore the default value because the interface is removed in tearDown.
@@ -2186,11 +2243,9 @@
private LinkProperties doIpv6OnlyProvisioning() throws Exception {
final InOrder inOrder = inOrder(mCb);
- final ByteBuffer pio = buildPioOption(3600, 1800, "2001:db8:1::/64");
- final ByteBuffer rdnss = buildRdnssOption(3600, IPV6_OFF_LINK_DNS_SERVER);
- final ByteBuffer slla = buildSllaOption();
- final ByteBuffer ra = buildRaPacket(pio, rdnss, slla);
-
+ final ByteBuffer ra = buildRaPacket(TEST_IPV6_PREFIX, IPV6_OFF_LINK_DNS_SERVER,
+ 3600 /* validLifetime */, 1800 /* preferredLifetime */, 3600 /* dnsLifetime */,
+ true /* shouldIncludeSlla */);
return doIpv6OnlyProvisioning(inOrder, ra);
}
@@ -2283,12 +2338,10 @@
.build();
startIpClientProvisioning(config);
- final ByteBuffer pio = buildPioOption(600, 300, "2001:db8:1::/64");
- // put an IPv6 link-local DNS server
- final ByteBuffer rdnss = buildRdnssOption(600, ROUTER_LINK_LOCAL.getHostAddress());
- // put SLLA option to avoid address resolution for "fe80::1"
- final ByteBuffer slla = buildSllaOption();
- final ByteBuffer ra = buildRaPacket(pio, rdnss, slla);
+ final ByteBuffer ra = buildRaPacket(TEST_IPV6_PREFIX,
+ ROUTER_LINK_LOCAL.getHostAddress() /* an IPv6 link-local DNS server */,
+ 600 /* validLifetime */, 300 /* preferredLifetime */, 600 /* dnsLifetime */,
+ true /* shouldIncludeSlla */);
waitForRouterSolicitation();
mPacketReader.sendResponse(ra);
@@ -3092,17 +3145,15 @@
private LinkProperties performDualStackProvisioning() throws Exception {
final Inet6Address dnsServer = ipv6Addr(IPV6_OFF_LINK_DNS_SERVER);
- final ByteBuffer pio = buildPioOption(3600, 1800, "2001:db8:1::/64");
- final ByteBuffer rdnss = buildRdnssOption(3600, IPV6_OFF_LINK_DNS_SERVER);
- final ByteBuffer slla = buildSllaOption();
- final ByteBuffer ra = buildRaPacket(pio, rdnss, slla);
+ final ByteBuffer ra = buildRaPacket(TEST_IPV6_PREFIX, IPV6_OFF_LINK_DNS_SERVER,
+ 3600 /* validLifetime */, 1800 /* preferredLifetime */, 3600 /* dnsLifetime */,
+ true /* shouldIncludeSlla */);
return performDualStackProvisioning(ra, dnsServer);
}
private LinkProperties performDualStackProvisioning(final ByteBuffer ra,
final InetAddress dnsServer) throws Exception {
- final InOrder inOrder = inOrder(mCb);
final CompletableFuture<LinkProperties> lpFuture = new CompletableFuture<>();
// Start IPv4 provisioning first and wait IPv4 provisioning to succeed, and then start
@@ -3834,8 +3885,8 @@
assertEquals(2, naList.size()); // privacy address and stable privacy address
}
- private void startGratuitousArpAndNaAfterRoamingTest(boolean isGratuitousArpNaRoamingEnabled,
- boolean hasIpv4, boolean hasIpv6) throws Exception {
+ private void startGratuitousArpAndNaAfterRoamingTest(boolean hasIpv4, boolean hasIpv6)
+ throws Exception {
final Layer2Information layer2Info = new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
MacAddress.fromString(TEST_DEFAULT_BSSID));
final ScanResultInfo scanResultInfo =
@@ -3852,12 +3903,6 @@
// not strictly necessary.
setDhcpFeatures(true /* isRapidCommitEnabled */,
false /* isDhcpIpConflictDetectEnabled */);
-
- if (isGratuitousArpNaRoamingEnabled) {
- setFeatureEnabled(NetworkStackUtils.IPCLIENT_GARP_NA_ROAMING_VERSION, true);
- } else {
- setFeatureEnabled(NetworkStackUtils.IPCLIENT_GARP_NA_ROAMING_VERSION, false);
- }
startIpClientProvisioning(prov.build());
}
@@ -3881,8 +3926,7 @@
@Test
public void testGratuitousArpAndNaAfterRoaming() throws Exception {
- startGratuitousArpAndNaAfterRoamingTest(true /* isGratuitousArpNaRoamingEnabled */,
- true /* hasIpv4 */, true /* hasIpv6 */);
+ startGratuitousArpAndNaAfterRoamingTest(true /* hasIpv4 */, true /* hasIpv6 */);
performDualStackProvisioning();
forceLayer2Roaming();
@@ -3895,23 +3939,8 @@
}
@Test
- public void testGratuitousArpAndNaAfterRoaming_disableExpFlag() throws Exception {
- startGratuitousArpAndNaAfterRoamingTest(false /* isGratuitousArpNaRoamingEnabled */,
- true /* hasIpv4 */, true /* hasIpv6 */);
- performDualStackProvisioning();
- forceLayer2Roaming();
-
- final List<ArpPacket> arpList = new ArrayList<>();
- final List<NeighborAdvertisement> naList = new ArrayList<>();
- waitForGratuitousArpAndNaPacket(arpList, naList);
- assertEquals(2, naList.size()); // NAs sent due to RFC9131 implement, not from roam
- assertEquals(0, arpList.size());
- }
-
- @Test
public void testGratuitousArpAndNaAfterRoaming_IPv6OnlyNetwork() throws Exception {
- startGratuitousArpAndNaAfterRoamingTest(true /* isGratuitousArpNaRoamingEnabled */,
- false /* hasIpv4 */, true /* hasIpv6 */);
+ startGratuitousArpAndNaAfterRoamingTest(false /* hasIpv4 */, true /* hasIpv6 */);
doIpv6OnlyProvisioning();
forceLayer2Roaming();
@@ -3925,8 +3954,7 @@
@Test
public void testGratuitousArpAndNaAfterRoaming_IPv4OnlyNetwork() throws Exception {
- startGratuitousArpAndNaAfterRoamingTest(true /* isGratuitousArpNaRoamingEnabled */,
- true /* hasIpv4 */, false /* hasIpv6 */);
+ startGratuitousArpAndNaAfterRoamingTest(true /* hasIpv4 */, false /* hasIpv6 */);
// Start IPv4 provisioning and wait until entire provisioning completes.
handleDhcpPackets(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
@@ -3946,7 +3974,6 @@
assertEquals(ETH_P_IPV6, ns.ethHdr.etherType);
assertEquals(IPPROTO_ICMPV6, ns.ipv6Hdr.nextHeader);
assertEquals(0xff, ns.ipv6Hdr.hopLimit);
- assertTrue(ns.ipv6Hdr.srcIp.isLinkLocalAddress());
assertEquals(ICMPV6_NEIGHBOR_SOLICITATION, ns.icmpv6Hdr.type);
assertEquals(0, ns.icmpv6Hdr.code);
assertEquals(0, ns.nsHdr.reserved);
@@ -3986,15 +4013,32 @@
return ns;
}
- private List<NeighborSolicitation> waitForMultipleNeighborSolicitations() throws Exception {
+ private NeighborSolicitation waitForMulticastNeighborSolicitation(final Inet6Address targetIp)
+ throws Exception {
+ NeighborSolicitation ns;
+ while ((ns = getNextNeighborSolicitation()) != null) {
+ if (ns.ipv6Hdr.dstIp.isMulticastAddress() // Solicited-node multicast address
+ && ns.nsHdr.target.equals(targetIp)) {
+ break;
+ }
+ }
+ assertNotNull("No multicast Neighbor solicitation received on interface within timeout",
+ ns);
+ assertMulticastNeighborSolicitation(ns, targetIp);
+ return ns;
+ }
+
+ private List<NeighborSolicitation> waitForMultipleNeighborSolicitations(int expectedNsCount)
+ throws Exception {
NeighborSolicitation ns;
final List<NeighborSolicitation> nsList = new ArrayList<NeighborSolicitation>();
while ((ns = getNextNeighborSolicitation()) != null) {
- // Filter out the multicast NSes used for duplicate address detetction, the target
+ // Filter out the multicast NSes used for duplicate address detection, the target
// address is the global IPv6 address inside these NSes, and multicast NSes sent from
// device's GUAs to force first-hop router to update the neighbor cache entry.
if (ns.ipv6Hdr.srcIp.isLinkLocalAddress() && ns.nsHdr.target.isLinkLocalAddress()) {
nsList.add(ns);
+ if (nsList.size() == expectedNsCount) break;
}
}
assertFalse(nsList.isEmpty());
@@ -4056,8 +4100,80 @@
verify(mCb, never()).onReachabilityLost(any());
}
+ // If the UDP packet is sent to off-link address, the targetIp should be default gateway's IP,
+ // otherwise, it should be the on-link DNS server address.
+ private void expectAndRespondToMulticastNeighborSolicitation(final Inet6Address targetIp)
+ throws Exception {
+ final NeighborSolicitation ns = waitForMulticastNeighborSolicitation(targetIp);
+ final MacAddress srcMac =
+ targetIp.equals(ROUTER_LINK_LOCAL) ? ROUTER_MAC : ON_LINK_DNS_SERVER_MAC;
+ final Inet6Address srcIp = targetIp.equals(ROUTER_LINK_LOCAL)
+ ? ROUTER_LINK_LOCAL
+ : ipv6Addr(IPV6_ON_LINK_DNS_SERVER);
+ int flag = NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER | NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED;
+ final ByteBuffer na = NeighborAdvertisement.build(srcMac,
+ ns.ethHdr.srcMac /* dstMac */, srcIp,
+ ns.ipv6Hdr.srcIp /* dstIp */, flag, targetIp);
+ mPacketReader.sendResponse(na);
+ }
+
+ private void verifyRestoringNeighborParametersToSteadyState() throws Exception {
+ final int expectedNudSolicitNum = readNudSolicitNumInSteadyStateFromResource();
+ final long startTime = System.currentTimeMillis();
+ // polling the "ucast_solicit" sysctl every 500ms to check if it's restored to steady
+ // state, i.e. probe count of 10.
+ while (getNeighborParameterUcastSolicit(mIfaceName) != expectedNudSolicitNum) {
+ if (System.currentTimeMillis() - startTime >= TEST_TIMEOUT_MS) {
+ final String msg = "neighbor parameter ucast_solicit isn't restored to "
+ + expectedNudSolicitNum + " within " + TEST_TIMEOUT_MS + "ms";
+ fail(msg);
+ }
+ Thread.sleep(TEST_POLL_NEIGHBOR_PARAMETER_MS);
+ }
+ }
+
+ /**
+ * A function helper to set up the steps to verify NUD (neighbor unreachable detection) probes.
+ * This function helper intends to respond to the multicast NS for the default gateway during
+ * address resolution, which makes the default gateway neighbor reachable, it ends up starting
+ * an L2 roam, which will trigger kernel to probe all neighbors later then. The specific test
+ * case may or may not respond to that probes, depending on whether it expectes an NUD failure
+ * from that probe.
+ *
+ * If a specific test case expects to see an NUD failure after an L2 roam, then it should not
+ * respond to any unicast NS or multicast NS (if multicast_resolicit feature is enabled). The
+ * packet order example as below, fe80::bf8e:de37:69d7:2b29 is the IPv6 link-local address of
+ * a test tap interface.
+ *
+ * 7 fe80::bf8e:de37:69d7:2b29 ff02::2 ICMPv6 76 Router Solicitation from 0a:c9:06:70:77:b3
+ * 9 fe80::1 ff02::1 ICMPv6 13 Router Advertisement
+ *
+ * tap interface does address resolution for default gateway fe80::1 due to a DNS look-up
+ * query to sent to off-link DNS server. And responding to NS with NA to make the default
+ * gateway as reachable.
+ * 13 2001:db8:1:0:babe:11e3:49d8:d4af ff02::1:ff00:1 ICMPv6 92 Neighbor Solicitation for fe80::1 from 0a:c9:06:70:77:b3
+ * 16 fe80::1 2001:db8:1:0:babe:11e3:49d8:d4af ICMPv6 92 Neighbor Advertisement fe80::1 (rtr, sol) is at 00:1a:11:22:33:44
+ * 17 2001:db8:1:0:1818:bf6:5e82:4378 2001:4860:4860::64 DNS 99 Standard query 0xa13e AAAA ipv4only.arpa
+ *
+ * Post an L2 roam, the tap interface forces kernel start another probe, it sends 5 unicast
+ * NSes, the tap interface eventually gets a NUD failure due to there is no response.
+ * 21 fe80::bf8e:de37:69d7:2b29 fe80::1 ICMPv6 92 Neighbor Solicitation for fe80::1 from 0a:c9:06:70:77:b3
+ * 23 fe80::bf8e:de37:69d7:2b29 fe80::1 ICMPv6 92 Neighbor Solicitation for fe80::1 from 0a:c9:06:70:77:b3
+ * 25 fe80::bf8e:de37:69d7:2b29 fe80::1 ICMPv6 92 Neighbor Solicitation for fe80::1 from 0a:c9:06:70:77:b3
+ * 26 fe80::bf8e:de37:69d7:2b29 fe80::1 ICMPv6 92 Neighbor Solicitation for fe80::1 from 0a:c9:06:70:77:b3
+ * 27 fe80::bf8e:de37:69d7:2b29 fe80::1 ICMPv6 92 Neighbor Solicitation for fe80::1 from 0a:c9:06:70:77:b3
+ *
+ * Extra 3 multicat NSes are sent if "mcast_resolitict" sysctl is enabled.
+ * 28 fe80::bf8e:de37:69d7:2b29 ff02::1:ff00:1 ICMPv6 92 Neighbor Solicitation for fe80::1 from 0a:c9:06:70:77:b3
+ * 29 fe80::bf8e:de37:69d7:2b29 ff02::1:ff00:1 ICMPv6 92 Neighbor Solicitation for fe80::1 from 0a:c9:06:70:77:b3
+ * 31 fe80::bf8e:de37:69d7:2b29 ff02::1:ff00:1 ICMPv6 92 Neighbor Solicitation for fe80::1 from 0a:c9:06:70:77:b3
+ */
private void prepareIpReachabilityMonitorTest(boolean isMulticastResolicitEnabled)
throws Exception {
+ mNetworkAgentThread =
+ new HandlerThread(IpClientIntegrationTestCommon.class.getSimpleName());
+ mNetworkAgentThread.start();
+
final ScanResultInfo info = makeScanResultInfo(TEST_DEFAULT_SSID, TEST_DEFAULT_BSSID);
ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
.withLayer2Information(new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
@@ -4070,18 +4186,44 @@
isMulticastResolicitEnabled);
startIpClientProvisioning(config);
verify(mCb, timeout(TEST_TIMEOUT_MS)).setFallbackMulticastFilter(true);
- doIpv6OnlyProvisioning();
- // Simulate the roaming.
+ final ByteBuffer ra = buildRaPacket(TEST_IPV6_PREFIX, IPV6_OFF_LINK_DNS_SERVER,
+ 3600 /* validLifetime */, 1800 /* preferredLifetime */, 3600 /* dnsLifetime */,
+ false /* shouldIncludeSlla */);
+ // RA doesn't include SLLA option, thus device has no IPv6 link-local address and mac
+ // address mapping for default gateway. sending a UDP packet to off-line DNS server will
+ // trigger the address resolution for default gateway, simulate that default gateway is
+ // reachable by responding to those NSes.
+ final LinkProperties lp = doIpv6OnlyProvisioning(null /* inOrder */, ra);
+ runAsShell(MANAGE_TEST_NETWORKS, () -> createTestNetworkAgentAndRegister(lp));
+ sendPacketToPeer(ipv6Addr(IPV6_OFF_LINK_DNS_SERVER));
+ expectAndRespondToMulticastNeighborSolicitation(ROUTER_LINK_LOCAL);
+ assertNeverNotifyNeighborLost();
+
+ // There is a race between the IpReachabilityMonitor handling of reachable neighbors and the
+ // following test code that forces the start of L2 roaming. Once the neighbor is confirmed
+ // to be still reachable, IpReachabilityMonitor restores the NUD parameters such as probe
+ // count and probe interval to the steady state (probe count of 10 and interval of 750 ms).
+ // Starting L2 roaming sets the NUD parameters to the post-roaming mode (probe count of 5
+ // and interval of 750 ms). If the following test code occurs early, IpReachabilityMonitor
+ // will first set the NUD parameters to the post-roaming state and then restore to the
+ // stable state, which means that the device will send more probes, which may cause flaky
+ // test because we expect to see a NUD failure event after receiving all expected probes,
+ // but sometimes this does not happen because the device is still retransmitting more.
+ // Polling the "unicast_solicit" sysctl to check if the IpReachabilityMonitor has already
+ // restore the neighbor parameter to steady state.
+ verifyRestoringNeighborParametersToSteadyState();
+
+ // Simulate the L2 roaming, this will trigger kernel to probe all neighbors again.
forceLayer2Roaming();
}
private void runIpReachabilityMonitorProbeFailedTest() throws Exception {
prepareIpReachabilityMonitorTest();
- final List<NeighborSolicitation> nsList = waitForMultipleNeighborSolicitations();
final int expectedNudSolicitNum = readNudSolicitNumPostRoamingFromResource();
- assertEquals(expectedNudSolicitNum, nsList.size());
+ final List<NeighborSolicitation> nsList =
+ waitForMultipleNeighborSolicitations(expectedNudSolicitNum);
for (NeighborSolicitation ns : nsList) {
assertUnicastNeighborSolicitation(ns, ROUTER_MAC /* dstMac */,
ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */);
@@ -4089,7 +4231,6 @@
}
@Test
- @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = false)
public void testIpReachabilityMonitor_probeFailed() throws Exception {
runIpReachabilityMonitorProbeFailedTest();
assertNotifyNeighborLost(ROUTER_LINK_LOCAL /* targetIp */,
@@ -4124,10 +4265,10 @@
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());
+ final List<NeighborSolicitation> nsList =
+ waitForMultipleNeighborSolicitations(expectedSize);
for (NeighborSolicitation ns : nsList.subList(0, expectedNudSolicitNum)) {
assertUnicastNeighborSolicitation(ns, ROUTER_MAC /* dstMac */,
ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */);
@@ -4138,7 +4279,6 @@
}
@Test
- @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = false)
public void testIpReachabilityMonitor_mcastResolicitProbeFailed() throws Exception {
runIpReachabilityMonitorMcastResolicitProbeFailedTest();
assertNotifyNeighborLost(ROUTER_LINK_LOCAL /* targetIp */,
@@ -4196,7 +4336,47 @@
NudEventType.NUD_POST_ROAMING_MAC_ADDRESS_CHANGED);
}
- private void prepareIpReachabilityMonitorIpv4AddressResolutionTest() throws Exception {
+ private ArpPacket expectAndRespondToArpRequest(MacAddress srcMac,
+ Inet4Address targetIp) throws Exception {
+ final ArpPacket request = getNextArpPacket();
+ assertArpRequest(request, targetIp);
+ sendArpReply(request.senderHwAddress.toByteArray() /* dst */, srcMac.toByteArray(),
+ request.senderIp /* target IP */, targetIp /* sender IP */);
+ return request;
+ }
+
+ /**
+ * A function helper to set up the steps to verify NUD (neighbor unreachable detection) probes
+ * for IPv4 hosts. This function helper intends to respond to the ARP probes for the default
+ * gateway during address resolution if the param shouldMakeNeighborReachableFirst is true,
+ * which makes the default gateway reachable first, and then sending a gratuitous ARP with a
+ * different mac address, it will override the ARP entry on the tap interface, as a result, it
+ * should do the address resolution again. The param shouldMakeNeighborReachableFirst depends on
+ * the specific test case.
+ *
+ * If a specific test case expectes to see an NUD failure after that, then it should not respond
+ * to any upcoming ARP probes. The packet order example as below,
+ *
+ * 37 0.0.0.0 255.255.255.255 DHCP 338 DHCP Discover - Transaction ID 0xf367c95c
+ * 38 192.168.1.100 192.168.1.2 DHCP 360 DHCP ACK - Transaction ID 0xf367c95c
+ *
+ * 45 66:64:50:87:c3:22 ARP 48 Who has 192.168.1.100? Tell 192.168.1.2
+ * 46 Google_22:33:44 ARP 48 192.168.1.100 is at 00:1a:11:22:33:44
+ * 47 192.168.1.2 192.168.1.100 UDP 148 47467 1234 Len=100
+ *
+ * 76 Google_22:33:55 ARP 48 Gratuitous ARP for 192.168.1.100 (Reply) (duplicate use of 192.168.1.100 detected!)
+ * 77 192.168.1.2 192.168.1.100 UDP 148 38374 1234 Len=100
+ *
+ * 92 66:64:50:87:c3:22 ARP 48 Who has 192.168.1.100? Tell 192.168.1.2
+ * 93 66:64:50:87:c3:22 ARP 48 Who has 192.168.1.100? Tell 192.168.1.2
+ * ...
+ * 116 66:64:50:87:c3:22 ARP 48 Who has 192.168.1.100? Tell 192.168.1.2
+ */
+ private void prepareIpReachabilityMonitorIpv4AddressResolutionTest(
+ boolean shouldMakeNeighborReachableFirst) throws Exception {
+ // Reduce the delay first probe time from 5s to 2s, speed up the test duration and we can
+ // still use PACKET_TIMEOUT_MS to wait for the next upcoming ARP packet.
+ setNudDelayFirstProbeTime(TEST_DELAY_FIRST_PROBE_TIME_S, "ipv4");
mNetworkAgentThread =
new HandlerThread(IpClientIntegrationTestCommon.class.getSimpleName());
mNetworkAgentThread.start();
@@ -4215,17 +4395,35 @@
runAsShell(MANAGE_TEST_NETWORKS, () -> createTestNetworkAgentAndRegister(lp));
- // Send a UDP packet to IPv4 DNS server to trigger address resolution process for IPv4
- // on-link DNS server or default router.
- final Random random = new Random();
- final byte[] data = new byte[100];
- random.nextBytes(data);
- sendUdpPacketToNetwork(mNetworkAgent.getNetwork(), SERVER_ADDR, 1234 /* port */, data);
+ // Send a UDP packet to IPv4 on-link DNS server to trigger address resolution process for
+ // the default gateway, respond to the broadcast ARP probe and make the default gateway as
+ // reachable.
+ sendPacketToPeer(SERVER_ADDR);
+ if (shouldMakeNeighborReachableFirst) {
+ final ArpPacket request = expectAndRespondToArpRequest(ROUTER_MAC, SERVER_ADDR);
+ assertNotNull(request);
+ verifyRestoringNeighborParametersToSteadyState();
+
+ // Wait the locktime expires then we are able to override ARP entry by sending a
+ // gratuitous ARP with a different MAC address, see locktime sysctl on
+ // https://man7.org/linux/man-pages/man7/arp.7.html.
+ Thread.sleep(TEST_ARP_LOCKTIME_MS);
+
+ // Send a gratuitous ARP to override the default gateway MAC address, this makes the
+ // default gateway become stale in the ARP entry, sending another UDP packet to the
+ // default gateway make it transit to DELAY state. If no reachability confirmation is
+ // received within DELAY_FIRST_PROBE_TIME seconds of entering the DELAY state, kernel
+ // goes to PROBE state and start probing, see RFC 4861 section 7.3.2.
+ final MacAddress newMac = MacAddress.fromString("00:1A:11:22:33:55");
+ sendGratuitousArp(newMac, SERVER_ADDR);
+ sendPacketToPeer(SERVER_ADDR);
+ }
}
private void doTestIpReachabilityMonitor_replyBroadcastArpRequestWithDiffMacAddresses(
boolean disconnect) throws Exception {
- prepareIpReachabilityMonitorIpv4AddressResolutionTest();
+ prepareIpReachabilityMonitorIpv4AddressResolutionTest(
+ false /* shouldMakeNeighborReachableFirst */);
// Respond to the broadcast ARP request.
final ArpPacket request = getNextArpPacket();
@@ -4233,7 +4431,7 @@
sendArpReply(request.senderHwAddress.toByteArray() /* dst */, ROUTER_MAC_BYTES /* srcMac */,
request.senderIp /* target IP */, SERVER_ADDR /* sender IP */);
- Thread.sleep(1500);
+ Thread.sleep(TEST_ARP_LOCKTIME_MS);
// Reply with a different MAC address but the same server IP.
final MacAddress gateway = MacAddress.fromString("00:11:22:33:44:55");
@@ -4271,7 +4469,9 @@
@Flag(name = IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION, enabled = true)
public void testIpReachabilityMonitor_ignoreIpv4DefaultRouterOrganicNudFailure()
throws Exception {
- prepareIpReachabilityMonitorIpv4AddressResolutionTest();
+ prepareIpReachabilityMonitorIpv4AddressResolutionTest(
+ true /* shouldMakeNeighborReachableFirst */
+ );
ArpPacket packet;
while ((packet = getNextArpPacket(TEST_TIMEOUT_MS)) != null) {
@@ -4281,14 +4481,14 @@
}
@Test
- @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = false)
@Flag(name = IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION, enabled = false)
public void testIpReachabilityMonitor_ignoreIpv4DefaultRouterOrganicNudFailure_flagoff()
throws Exception {
- prepareIpReachabilityMonitorIpv4AddressResolutionTest();
+ prepareIpReachabilityMonitorIpv4AddressResolutionTest(
+ true /* shouldMakeNeighborReachableFirst */);
ArpPacket packet;
- while ((packet = getNextArpPacket(TEST_TIMEOUT_MS)) != null) {
+ while ((packet = getNextArpPacket(PACKET_TIMEOUT_MS)) != null) {
// wait address resolution to complete.
}
final ArgumentCaptor<ReachabilityLossInfoParcelable> lossInfoCaptor =
@@ -4307,8 +4507,44 @@
socket.send(pkt);
}
+ /**
+ * A function helper to set up the steps to verify NUD (neighbor unreachable detection) probes
+ * for IPv6 hosts. This function helper intends to respond to the NS probes for the default
+ * gateway during address resolution if the param shouldMakeNeighborReachableFirst is true,
+ * which makes the default gateway reachable first, and then sending a gratuitous NA with a
+ * different mac address, it will override the neighbor entry on the tap interface, as a result,
+ * it should do the address resolution eventually. The param shouldMakeNeighborReachableFirst
+ * depends on the specific test case which may or may not expect a NUD failure event.
+ *
+ * 36 2025-02-12 13:19:11.316069 fe80::1e3a:4b22:df15:896c ff02::2 ICMPv6 76 Router Solicitation from 46:0c:b1:ab:47:f7
+ * 46 2025-02-12 13:19:11.409184 fe80::1 ff02::1 ICMPv6 132 Router Advertisement
+ *
+ * // Respond to the NS for the default gateway and make the default gateay as reachable.
+ * 50 2025-02-12 13:19:11.443185 2001:db8:1:0:d8d6:93c1:9620:2a37 ff02::1:ff00:1 ICMPv6 92 Neighbor Solicitation for fe80::1 from 46:0c:b1:ab:47:f7
+ * 51 2025-02-12 13:19:11.448864 2001:db8:1:0:48c7:9fe:4ee:47ad ff02::1:ff00:1 ICMPv6 92 Neighbor Solicitation for fe80::1 from 46:0c:b1:ab:47:f7
+ * 52 2025-02-12 13:19:11.522637 2001:db8:1:0:48c7:9fe:4ee:47ad ff02::1:ff00:1 ICMPv6 92 Neighbor Solicitation for fe80::1 from 46:0c:b1:ab:47:f7
+ * 53 2025-02-12 13:19:11.523851 fe80::1 2001:db8:1:0:d8d6:93c1:9620:2a37 ICMPv6 92 Neighbor Advertisement fe80::1 (rtr, sol) is at 00:1a:11:22:33:44
+ * 54 2025-02-12 13:19:11.523881 2001:db8:1:0:48c7:9fe:4ee:47ad 2001:4860:4860::64 UDP 168 49350 → 1234 Len=100
+ *
+ * 55 2025-02-12 13:19:11.535166 fe80::1 ff02::1 ICMPv6 92 Neighbor Advertisement fe80::1 (rtr, ovr) is at 00:11:22:33:44:55
+ * 56 2025-02-12 13:19:11.535853 2001:db8:1:0:48c7:9fe:4ee:47ad 2001:4860:4860::64 UDP 168 36306 → 1234 Len=100
+ *
+ * // After test_delay_first_probe_time(2s) the tap interface starts probing again.
+ * 99 2025-02-12 13:19:13.544188 fe80::1e3a:4b22:df15:896c fe80::1 ICMPv6 92 Neighbor Solicitation for fe80::1 from 46:0c:b1:ab:47:f7
+ * 107 2025-02-12 13:19:14.312212 fe80::1e3a:4b22:df15:896c fe80::1 ICMPv6 92 Neighbor Solicitation for fe80::1 from 46:0c:b1:ab:47:f7
+ * ...
+ * 133 2025-02-12 13:19:20.456209 fe80::1e3a:4b22:df15:896c fe80::1 ICMPv6 92 Neighbor Solicitation for fe80::1 from 46:0c:b1:ab:47:f7
+ * 134 2025-02-12 13:19:21.224214 fe80::1e3a:4b22:df15:896c ff02::1:ff00:1 ICMPv6 92 Neighbor Solicitation for fe80::1 from 46:0c:b1:ab:47:f7
+ * 135 2025-02-12 13:19:21.992192 fe80::1e3a:4b22:df15:896c ff02::1:ff00:1 ICMPv6 92 Neighbor Solicitation for fe80::1 from 46:0c:b1:ab:47:f7
+ * 136 2025-02-12 13:19:22.760202 fe80::1e3a:4b22:df15:896c ff02::1:ff00:1 ICMPv6 92 Neighbor Solicitation for fe80::1 from 46:0c:b1:ab:47:f7
+ */
private void prepareIpReachabilityMonitorAddressResolutionTest(final String dnsServer,
- final Inet6Address targetIp) throws Exception {
+ final Inet6Address targetIp,
+ boolean shouldMakeNeighborReachableFirst) throws Exception {
+ // Reduce the delay first probe time from 5s to 2s, speed up the test duration and we can
+ // still use PACKET_TIMEOUT_MS to wait for the next upcoming ARP packet.
+ setNudDelayFirstProbeTime(TEST_DELAY_FIRST_PROBE_TIME_S, "ipv6");
+
mNetworkAgentThread =
new HandlerThread(IpClientIntegrationTestCommon.class.getSimpleName());
mNetworkAgentThread.start();
@@ -4332,18 +4568,30 @@
startIpClientProvisioning(config);
verify(mCb, timeout(TEST_TIMEOUT_MS)).setFallbackMulticastFilter(true);
- final List<ByteBuffer> options = new ArrayList<ByteBuffer>();
- options.add(buildPioOption(3600, 1800, "2001:db8:1::/64")); // PIO
- options.add(buildRdnssOption(3600, dnsServer)); // RDNSS
- // If target IP of address resolution is default router's IPv6 link-local address,
- // then we should not take SLLA option in RA.
- if (!targetIp.equals(ROUTER_LINK_LOCAL)) {
- options.add(buildSllaOption()); // SLLA
- }
- final ByteBuffer ra = buildRaPacket(options.toArray(new ByteBuffer[options.size()]));
+ final ByteBuffer ra = buildRaPacket(TEST_IPV6_PREFIX, dnsServer,
+ 3600 /* validLifetime */, 1800 /* preferredLifetime */, 3600 /* dnsLifetime */,
+ !targetIp.equals(ROUTER_LINK_LOCAL) /* shouldIncludeSlla */);
final Inet6Address dnsServerIp = ipv6Addr(dnsServer);
final LinkProperties lp = performDualStackProvisioning(ra, dnsServerIp);
runAsShell(MANAGE_TEST_NETWORKS, () -> createTestNetworkAgentAndRegister(lp));
+
+ if (shouldMakeNeighborReachableFirst) {
+ sendPacketToPeer(dnsServerIp);
+ expectAndRespondToMulticastNeighborSolicitation(targetIp);
+ assertNeverNotifyNeighborLost();
+ verifyRestoringNeighborParametersToSteadyState();
+
+ // Send a gratuitous NA for the target neighbor with a different MAC address, this
+ // should make the neighbor state transit from REACHABLE to STALE, sending another UDP
+ // packet to neighbor will force the neighbor state transit from STALE to DELAY, and
+ // after DELAY_FIRST_PROBE_TIME the tap interface starts probing for the target neighbor
+ // again.
+ final MacAddress srcMac = MacAddress.fromString("00:11:22:33:44:55");
+ final Inet6Address srcIp = targetIp.equals(ROUTER_LINK_LOCAL)
+ ? ROUTER_LINK_LOCAL
+ : ipv6Addr(IPV6_ON_LINK_DNS_SERVER);
+ sendGratuitousNeighborAdvertisement(srcMac, srcIp, targetIp);
+ }
}
/**
@@ -4353,26 +4601,27 @@
* If dstIp is off-link, then targetIp should be the IPv6 default router.
* The ND cache should not have an entry for targetIp.
*/
- private void sendPacketToUnreachableNeighbor(Inet6Address dstIp) throws Exception {
+ private void sendPacketToPeer(final InetAddress dstIp) throws Exception {
final Random random = new Random();
final byte[] data = new byte[100];
random.nextBytes(data);
sendUdpPacketToNetwork(mNetworkAgent.getNetwork(), dstIp, 1234 /* port */, data);
}
- private void expectAndDropMulticastNses(Inet6Address targetIp, boolean expectNeighborLost)
+ private void expectAndDropMultipleNses(Inet6Address targetIp, boolean expectNeighborLost)
throws Exception {
- // Wait for the multicast NSes but never respond to them, that results in the on-link
- // DNS gets lost and onReachabilityLost callback will be invoked.
+ // Wait for the multiple NSes but never respond to them, that results in the on-link
+ // DNS or default gateway gets lost and onReachabilityLost callback will be invoked.
final List<NeighborSolicitation> nsList = new ArrayList<NeighborSolicitation>();
NeighborSolicitation ns;
while ((ns = getNextNeighborSolicitation()) != null) {
- // multicast NS for address resolution, IPv6 dst address in that NS is solicited-node
- // multicast address based on the target IP, the target IP is either on-link IPv6 DNS
- // server address or IPv6 link-local address of default gateway.
+ // multiple NSes for address resolution, a few unicast NSes will be sent first and
+ // fall back to multicast if mcast_resolicit is enabled. IPv6 dst address in multicast
+ // NS is solicited-node multicast address based on the target IP, the target IP is
+ // either on-link IPv6 DNS server address or IPv6 link-local address of default gateway.
final LinkAddress actual = new LinkAddress(ns.nsHdr.target, 64);
final LinkAddress target = new LinkAddress(targetIp, 64);
- if (actual.equals(target) && ns.ipv6Hdr.dstIp.isMulticastAddress()) {
+ if (actual.equals(target)) {
nsList.add(ns);
}
}
@@ -4387,107 +4636,102 @@
private void runIpReachabilityMonitorAddressResolutionTest(final String dnsServer,
final Inet6Address targetIp,
+ final boolean shouldMakeNeighborReachableFirst,
final boolean expectNeighborLost) throws Exception {
- prepareIpReachabilityMonitorAddressResolutionTest(dnsServer, targetIp);
- sendPacketToUnreachableNeighbor(ipv6Addr(dnsServer));
- expectAndDropMulticastNses(targetIp, expectNeighborLost);
+ prepareIpReachabilityMonitorAddressResolutionTest(dnsServer, targetIp,
+ shouldMakeNeighborReachableFirst);
+ sendPacketToPeer(ipv6Addr(dnsServer));
+ expectAndDropMultipleNses(targetIp, expectNeighborLost);
}
@Test
- @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION, enabled = true)
- @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION, enabled = false)
@Flag(name = IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION, enabled = false)
public void testIpReachabilityMonitor_incompleteIpv6DnsServerInDualStack() throws Exception {
final Inet6Address targetIp = ipv6Addr(IPV6_ON_LINK_DNS_SERVER);
- runIpReachabilityMonitorAddressResolutionTest(IPV6_ON_LINK_DNS_SERVER, targetIp,
+ runIpReachabilityMonitorAddressResolutionTest(IPV6_ON_LINK_DNS_SERVER,
+ targetIp,
+ false /* shouldMakeNeighborReachableFirst */,
false /* expectNeighborLost */);
}
@Test
- @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION, enabled = false)
- @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION, enabled = false)
@Flag(name = IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION, enabled = false)
- @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = false)
public void testIpReachabilityMonitor_incompleteIpv6DnsServerInDualStack_flagoff()
throws Exception {
final Inet6Address targetIp = ipv6Addr(IPV6_ON_LINK_DNS_SERVER);
- runIpReachabilityMonitorAddressResolutionTest(IPV6_ON_LINK_DNS_SERVER, targetIp,
- true /* expectNeighborLost */);
+ runIpReachabilityMonitorAddressResolutionTest(IPV6_ON_LINK_DNS_SERVER,
+ targetIp,
+ false /* shouldMakeNeighborReachableFirst */,
+ false /* expectNeighborLost */);
}
@Test
- @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION, enabled = false)
- @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION, enabled = true)
@Flag(name = IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION, enabled = false)
public void testIpReachabilityMonitor_incompleteIpv6DefaultRouterInDualStack()
throws Exception {
runIpReachabilityMonitorAddressResolutionTest(IPV6_OFF_LINK_DNS_SERVER,
ROUTER_LINK_LOCAL /* targetIp */,
+ false /* shouldMakeNeighborReachableFirst */,
false /* expectNeighborLost */);
}
@Test
- @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION, enabled = false)
- @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION, enabled = false)
@Flag(name = IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION, enabled = false)
- @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = false)
public void testIpReachabilityMonitor_incompleteIpv6DefaultRouterInDualStack_flagoff()
throws Exception {
runIpReachabilityMonitorAddressResolutionTest(IPV6_OFF_LINK_DNS_SERVER,
ROUTER_LINK_LOCAL /* targetIp */,
- true /* expectNeighborLost */);
- }
-
- @Test
- @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION, enabled = false)
- @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION, enabled = false)
- @Flag(name = IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION, enabled = true)
- public void testIpReachabilityMonitor_ignoreOnLinkIpv6DnsOrganicNudFailure()
- throws Exception {
- final Inet6Address targetIp = ipv6Addr(IPV6_ON_LINK_DNS_SERVER);
- runIpReachabilityMonitorAddressResolutionTest(IPV6_ON_LINK_DNS_SERVER, targetIp,
+ false /* shouldMakeNeighborReachableFirst */,
false /* expectNeighborLost */);
}
@Test
- @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION, enabled = false)
- @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION, enabled = false)
- @Flag(name = IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION, enabled = false)
- @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = false)
- public void testIpReachabilityMonitor_ignoreOnLinkIpv6DnsOrganicNudFailure_flagoff()
+ @Flag(name = IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION, enabled = true)
+ public void testIpReachabilityMonitor_ignoreOnLinkIpv6DnsOrganicNudFailure()
throws Exception {
final Inet6Address targetIp = ipv6Addr(IPV6_ON_LINK_DNS_SERVER);
- runIpReachabilityMonitorAddressResolutionTest(IPV6_ON_LINK_DNS_SERVER, targetIp,
- true /* expectNeighborLost */);
+ runIpReachabilityMonitorAddressResolutionTest(IPV6_ON_LINK_DNS_SERVER,
+ targetIp,
+ true /* shouldMakeNeighborReachableFirst */,
+ false /* expectNeighborLost */);
}
@Test
- @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION, enabled = false)
- @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION, enabled = false)
+ @Flag(name = IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION, enabled = false)
+ public void testIpReachabilityMonitor_ignoreOnLinkIpv6DnsOrganicNudFailure_flagoff()
+ throws Exception {
+ final Inet6Address targetIp = ipv6Addr(IPV6_ON_LINK_DNS_SERVER);
+ runIpReachabilityMonitorAddressResolutionTest(IPV6_ON_LINK_DNS_SERVER,
+ targetIp,
+ false /* shouldMakeNeighborReachableFirst */,
+ false /* expectNeighborLost */);
+ }
+
+ @Test
@Flag(name = IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION, enabled = true)
public void testIpReachabilityMonitor_ignoreIpv6DefaultRouterOrganicNudFailure()
throws Exception {
runIpReachabilityMonitorAddressResolutionTest(IPV6_OFF_LINK_DNS_SERVER,
ROUTER_LINK_LOCAL /* targetIp */,
+ true /* shouldMakeNeighborReachableFirst */,
false /* expectNeighborLost */);
}
@Test
- @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION, enabled = false)
- @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION, enabled = false)
@Flag(name = IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION, enabled = false)
- @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = false)
public void testIpReachabilityMonitor_ignoreIpv6DefaultRouterOrganicNudFailure_flagoff()
throws Exception {
runIpReachabilityMonitorAddressResolutionTest(IPV6_OFF_LINK_DNS_SERVER,
ROUTER_LINK_LOCAL /* targetIp */,
+ true /* shouldMakeNeighborReachableFirst */,
true /* expectNeighborLost */);
}
private void runIpReachabilityMonitorEverReachableIpv6NeighborTest(final String dnsServer,
final Inet6Address targetIp) throws Exception {
- prepareIpReachabilityMonitorAddressResolutionTest(dnsServer, targetIp);
- sendPacketToUnreachableNeighbor(ipv6Addr(dnsServer));
+ prepareIpReachabilityMonitorAddressResolutionTest(dnsServer, targetIp,
+ false /*shouldMakeNeighborReachableFirst */);
+ sendPacketToPeer(ipv6Addr(dnsServer));
// Simulate the default router/DNS was reachable by responding to multicast NS(not for DAD).
NeighborSolicitation ns;
@@ -4527,8 +4771,6 @@
}
@Test
- @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION, enabled = false)
- @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION, enabled = true)
@Flag(name = IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION, enabled = false)
public void testIpReachabilityMonitor_ignoreIpv6DefaultRouter_everReachable() throws Exception {
runIpReachabilityMonitorEverReachableIpv6NeighborTest(IPV6_OFF_LINK_DNS_SERVER,
@@ -4536,8 +4778,6 @@
}
@Test
- @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION, enabled = true)
- @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION, enabled = false)
@Flag(name = IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION, enabled = false)
public void testIpReachabilityMonitor_ignoreIpv6Dns_everReachable() throws Exception {
runIpReachabilityMonitorEverReachableIpv6NeighborTest(IPV6_ON_LINK_DNS_SERVER,
@@ -4545,14 +4785,14 @@
}
@Test
- @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = true)
public void testIpReachabilityMonitor_ignoreNeverReachableIpv6Dns() throws Exception {
runIpReachabilityMonitorAddressResolutionTest(IPV6_ON_LINK_DNS_SERVER,
- ipv6Addr(IPV6_ON_LINK_DNS_SERVER), false /* expectNeighborLost */);
+ ipv6Addr(IPV6_ON_LINK_DNS_SERVER),
+ false /* shouldMakeNeighborReachableFirst */,
+ false /* expectNeighborLost */);
}
@Test
- @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = true)
public void testIpReachabilityMonitor_ignoreNeverReachableIpv6Dns_butEverReachable()
throws Exception {
runIpReachabilityMonitorEverReachableIpv6NeighborTest(IPV6_ON_LINK_DNS_SERVER,
@@ -4560,14 +4800,14 @@
}
@Test
- @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = true)
public void testIpReachabilityMonitor_ignoreNeverReachableIpv6DefaultRouter() throws Exception {
runIpReachabilityMonitorAddressResolutionTest(IPV6_OFF_LINK_DNS_SERVER,
- ROUTER_LINK_LOCAL, false /* expectNeighborLost */);
+ ROUTER_LINK_LOCAL,
+ false /* shouldMakeNeighborReachableFirst */,
+ false /* expectNeighborLost */);
}
@Test
- @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = true)
public void testIpReachabilityMonitor_ignoreNeverReachableIpv6DefaultRouter_butEverReachable()
throws Exception {
runIpReachabilityMonitorEverReachableIpv6NeighborTest(IPV6_ON_LINK_DNS_SERVER,
@@ -4848,6 +5088,34 @@
}
}
+ @Test
+ public void testKernelDeletesIPv6AddressesOnValidLifetimeExpires() throws Exception {
+ ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
+ .withoutIPv4()
+ .build();
+ startIpClientProvisioning(config);
+
+ // Intend to set the same preferred and valid lifetime in RA PIO to 3s. All global IPv6
+ // addresses will be deleted from the interface when the valid lifetime expires, this might
+ // be caused by the loss of RA due to the DTIM config. Then a onProvisioningFailure event
+ // will be triggered if that's the IPv6-only network.
+ final ByteBuffer ra = buildRaPacket(TEST_IPV6_PREFIX, IPV6_ON_LINK_DNS_SERVER,
+ 3 /* validLifetime */, 3 /* preferredLifetime */, 600 /* dnsLifetime */,
+ true /* shouldIncludeSlla */);
+ doIpv6OnlyProvisioning(null /* inOrder */, ra);
+
+ final ArgumentCaptor<LinkProperties> captor = ArgumentCaptor.forClass(LinkProperties.class);
+ verify(mCb, timeout(PACKET_TIMEOUT_MS)).onProvisioningFailure(captor.capture());
+ final LinkProperties lp = captor.getValue();
+ assertNotNull(lp);
+ assertFalse(lp.hasGlobalIpv6Address());
+ assertEquals(1, lp.getLinkAddresses().size()); // only IPv6 Link-local address
+ // because the DNS server is on-link, if off-link, due to the loss of IPv6 address, off-link
+ // DNS dest will be removed from LP as well.
+ assertTrue(lp.hasIpv6DnsServer());
+ assertTrue(lp.hasIpv6DefaultRoute());
+ }
+
@Test @SignatureRequiredTest(reason = "requires mNetd to delete IPv6 GUAs")
public void testOnIpv6AddressRemoved() throws Exception {
ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
@@ -4858,12 +5126,25 @@
LinkProperties lp = doIpv6OnlyProvisioning();
assertNotNull(lp);
assertEquals(3, lp.getLinkAddresses().size()); // IPv6 privacy, stable privacy, link-local
- for (LinkAddress la : lp.getLinkAddresses()) {
- final Inet6Address address = (Inet6Address) la.getAddress();
- if (address.isLinkLocalAddress()) continue;
- // Remove IPv6 GUAs from interface.
- mNetd.interfaceDelAddress(mIfaceName, address.getHostAddress(), la.getPrefixLength());
- }
+
+ final LinkAddress privacyAddress =
+ IpClient.find(lp.getLinkAddresses(), this::isPrivacyAddress);
+ final LinkAddress stableAddress =
+ IpClient.find(lp.getLinkAddresses(), this::isStablePrivacyAddress);
+ assertNotNull(privacyAddress);
+ assertNotNull(stableAddress);
+
+ // Delete the temporary privacy address before deleting the stable privacy address.
+ // Otherwise, deleting the stable privacy address will also delete the associated
+ // temporary privacy address. If this happens first, then deleting the non-existent
+ // temporary privacy address will throw an EADDRNOTAVAIL error.
+ // TODO: send RTM_DELADDR instead of Netd API.
+ mNetd.interfaceDelAddress(mIfaceName,
+ ((Inet6Address) privacyAddress.getAddress()).getHostAddress(),
+ privacyAddress.getPrefixLength());
+ mNetd.interfaceDelAddress(mIfaceName,
+ ((Inet6Address) stableAddress.getAddress()).getHostAddress(),
+ stableAddress.getPrefixLength());
final ArgumentCaptor<LinkProperties> captor = ArgumentCaptor.forClass(LinkProperties.class);
verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningFailure(captor.capture());
@@ -4893,7 +5174,6 @@
@Test
@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
public void testMaxDtimMultiplier_IPv6LinkLocalOnlyMode() throws Exception {
- final InOrder inOrder = inOrder(mCb);
ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
.withoutIPv4()
.withIpv6LinkLocalOnly()
@@ -6035,7 +6315,6 @@
@Test
@Flag(name = IP_REACHABILITY_IGNORE_NUD_FAILURE_VERSION, enabled = true)
- @SignatureRequiredTest(reason = "need to delete cluster from real db in tearDown")
public void testIgnoreNudFailuresIfTooManyInPastDay() throws Exception {
// // NUD failure event count exceeds daily threshold nor weekly.
final long when = System.currentTimeMillis() - ONE_DAY_IN_MS / 2; // 12h ago
@@ -6047,9 +6326,7 @@
}
@Test
- @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = false)
@Flag(name = IP_REACHABILITY_IGNORE_NUD_FAILURE_VERSION, enabled = false)
- @SignatureRequiredTest(reason = "need to delete cluster from real db in tearDown")
public void testIgnoreNudFailuresIfTooManyInPastDay_flagOff() throws Exception {
// NUD failure event count exceeds daily threshold nor weekly.
final long when = System.currentTimeMillis() - ONE_DAY_IN_MS / 2; // 12h ago
@@ -6062,9 +6339,7 @@
}
@Test
- @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = false)
@Flag(name = IP_REACHABILITY_IGNORE_NUD_FAILURE_VERSION, enabled = true)
- @SignatureRequiredTest(reason = "need to delete cluster from real db in tearDown")
public void testIgnoreNudFailuresIfTooManyInPastDay_notUpToThreshold()
throws Exception {
// NUD failure event count doesn't exceed either weekly threshold nor daily.
@@ -6079,7 +6354,6 @@
@Test
@Flag(name = IP_REACHABILITY_IGNORE_NUD_FAILURE_VERSION, enabled = true)
- @SignatureRequiredTest(reason = "need to delete cluster from real db in tearDown")
public void testIgnoreNudFailuresIfTooManyInPastWeek() throws Exception {
// NUD failure event count exceeds the weekly threshold, but not daily threshold in the past
// day.
@@ -6096,9 +6370,7 @@
}
@Test
- @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = false)
@Flag(name = IP_REACHABILITY_IGNORE_NUD_FAILURE_VERSION, enabled = false)
- @SignatureRequiredTest(reason = "need to delete cluster from real db in tearDown")
public void testIgnoreNudFailuresIfTooManyInPastWeek_flagOff() throws Exception {
// NUD failure event count exceeds the weekly threshold, but not daily threshold in the past
// day.
@@ -6117,7 +6389,6 @@
@Test
@Flag(name = IP_REACHABILITY_IGNORE_NUD_FAILURE_VERSION, enabled = true)
- @SignatureRequiredTest(reason = "need to delete cluster from real db in tearDown")
public void testIgnoreNudFailuresIfTooManyInPastWeek_notUpToThreshold() throws Exception {
// NUD failure event count doesn't exceed either weekly threshold nor daily.
long when = System.currentTimeMillis() - ONE_WEEK_IN_MS / 2; // half a week ago
@@ -6133,26 +6404,41 @@
NudEventType.NUD_POST_ROAMING_FAILED_CRITICAL);
}
+ private void assertRetrievedNetworkEventCount(String cluster, int expectedCountInPastWeek,
+ int expectedCountInPastDay, int expectedCountInPastSixHours) {
+ final long now = System.currentTimeMillis();
+ final long[] sinceTimes = new long[3];
+ sinceTimes[0] = now - ONE_WEEK_IN_MS;
+ sinceTimes[1] = now - ONE_DAY_IN_MS;
+ sinceTimes[2] = now - SIX_HOURS_IN_MS;
+ final int[] counts = getStoredNetworkEventCount(cluster, sinceTimes,
+ NETWORK_EVENT_NUD_FAILURE_TYPES, TEST_TIMEOUT_MS);
+ assertEquals(expectedCountInPastWeek, counts[0]);
+ assertEquals(expectedCountInPastDay, counts[1]);
+ assertEquals(expectedCountInPastSixHours, counts[2]);
+ }
+
@Test
@Flag(name = IP_REACHABILITY_IGNORE_NUD_FAILURE_VERSION, enabled = true)
- @SignatureRequiredTest(reason = "need to delete cluster from real db in tearDown")
public void testIgnoreNudFailuresIfTooManyInPastWeek_stopWritingEvent() throws Exception {
long when = (long) (System.currentTimeMillis() - SIX_HOURS_IN_MS * 0.9);
long expiry = when + ONE_WEEK_IN_MS;
storeNudFailureEvents(when, expiry, 10, IIpMemoryStore.NETWORK_EVENT_NUD_FAILURE_ORGANIC);
+ // Expect that a NUD failure happens, but onReachabilityFailure callback won't be called due
+ // to the experiment flag is enabled and this event won't be written to db because the
+ // number of events has been up to the threshold, then the retrieved event count should
+ // still be 10.
runIpReachabilityMonitorAddressResolutionTest(IPV6_OFF_LINK_DNS_SERVER,
ROUTER_LINK_LOCAL /* targetIp */,
+ true /* shouldMakeNeighborReachableFirst */,
false /* expectNeighborLost */);
- verify(mIpMemoryStore, never()).storeNetworkEvent(any(), anyLong(), anyLong(),
- eq(IIpMemoryStore.NETWORK_EVENT_NUD_FAILURE_ORGANIC), any());
+ assertRetrievedNetworkEventCount(TEST_CLUSTER, 10 /* expectedCountInPastWeek */,
+ 10 /* expectedCountInPastDay */, 10 /* expectedCountInPastSixHours */);
}
@Test
- @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION, enabled = false)
- @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION, enabled = false)
@Flag(name = IP_REACHABILITY_IGNORE_NUD_FAILURE_VERSION, enabled = true)
- @SignatureRequiredTest(reason = "need to delete cluster from real db in tearDown")
public void testIgnoreNudFailuresStopWritingEvents() throws Exception {
// Add enough failures that NUD failures are ignored.
long when = (long) (System.currentTimeMillis() - SIX_HOURS_IN_MS * 1.1);
@@ -6165,44 +6451,47 @@
storeNudFailureEvents(when, expiry, 9, IIpMemoryStore.NETWORK_EVENT_NUD_FAILURE_ORGANIC);
prepareIpReachabilityMonitorAddressResolutionTest(IPV6_ON_LINK_DNS_SERVER,
- ROUTER_LINK_LOCAL);
+ ROUTER_LINK_LOCAL, true /* shouldMakeNeighborReachableFirst */);
// The first new failure is ignored and written to the database.
- // The total is 10 failures in the last 6 hours.
- sendPacketToUnreachableNeighbor(ipv6Addr(IPV6_OFF_LINK_DNS_SERVER));
- expectAndDropMulticastNses(ROUTER_LINK_LOCAL, false /* expectNeighborLost */);
- verify(mIpMemoryStore).storeNetworkEvent(any(), anyLong(), anyLong(),
- eq(IIpMemoryStore.NETWORK_EVENT_NUD_FAILURE_ORGANIC), any());
+ // The total is 10 failures in the last 6 hours, and 20 failures
+ // in the past week and day.
+ sendPacketToPeer(ipv6Addr(IPV6_OFF_LINK_DNS_SERVER));
+ expectAndDropMultipleNses(ROUTER_LINK_LOCAL, false /* expectNeighborLost */);
+ assertRetrievedNetworkEventCount(TEST_CLUSTER, 20 /* expectedCountInPastWeek */,
+ 20 /* expectedCountInPastDay */, 10 /* expectedCountInPastSixHours */);
// The second new failure is ignored, but not written.
- reset(mIpMemoryStore);
- sendPacketToUnreachableNeighbor(ipv6Addr(IPV6_ON_LINK_DNS_SERVER));
- expectAndDropMulticastNses(ipv6Addr(IPV6_ON_LINK_DNS_SERVER),
+ sendPacketToPeer(ipv6Addr(IPV6_ON_LINK_DNS_SERVER));
+ expectAndDropMultipleNses(ipv6Addr(IPV6_ON_LINK_DNS_SERVER),
false /* expectNeighborLost */);
- verifyNoMoreInteractions(mIpMemoryStore);
+ assertRetrievedNetworkEventCount(TEST_CLUSTER, 20 /* expectedCountInPastWeek */,
+ 20 /* expectedCountInPastDay */, 10 /* expectedCountInPastSixHours */);
}
@Test
- @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = false)
@Flag(name = IP_REACHABILITY_IGNORE_NUD_FAILURE_VERSION, enabled = false)
- @SignatureRequiredTest(reason = "need to delete cluster from real db in tearDown")
public void testIgnoreNudFailuresIfTooManyInPastWeek_stopWritingEvent_flagOff()
throws Exception {
long when = (long) (System.currentTimeMillis() - SIX_HOURS_IN_MS * 0.9);
long expiry = when + ONE_WEEK_IN_MS;
- storeNudFailureEvents(when, expiry, 10, IIpMemoryStore.NETWORK_EVENT_NUD_FAILURE_ORGANIC);
+ storeNudFailureEvents(when, expiry, 8, IIpMemoryStore.NETWORK_EVENT_NUD_FAILURE_ORGANIC);
runIpReachabilityMonitorAddressResolutionTest(IPV6_OFF_LINK_DNS_SERVER,
ROUTER_LINK_LOCAL /* targetIp */,
+ true /* shouldMakeNeighborReachableFirst */,
true /* expectNeighborLost */);
- verify(mIpMemoryStore, never()).storeNetworkEvent(any(), anyLong(), anyLong(),
- eq(IIpMemoryStore.NETWORK_EVENT_NUD_FAILURE_ORGANIC), any());
+
+ // Although the total NUD failure events count in the past 6 hours hasn't been up to the
+ // threshold, however, the experiment flag is disabled, therefore, the new NUD failure
+ // event will not be written to db, then the retrieved event count should still be 8 rather
+ // than 9.
+ assertRetrievedNetworkEventCount(TEST_CLUSTER, 8 /* expectedCountInPastWeek */,
+ 8 /* expectedCountInPastDay */, 8 /* expectedCountInPastSixHours */);
}
@Test
- @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = false)
@Flag(name = IP_REACHABILITY_IGNORE_NUD_FAILURE_VERSION, enabled = true)
- @SignatureRequiredTest(reason = "need to delete cluster from real db in tearDown")
public void testIgnoreNudFailuresIfTooManyInPastWeek_stopWritingEvent_notUpToThreshold()
throws Exception {
long when = (long) (System.currentTimeMillis() - SIX_HOURS_IN_MS * 0.9);
@@ -6211,10 +6500,15 @@
runIpReachabilityMonitorAddressResolutionTest(IPV6_OFF_LINK_DNS_SERVER,
ROUTER_LINK_LOCAL /* targetIp */,
+ true /* shouldMakeNeighborReachableFirst */,
true /* expectNeighborLost */);
assertNotifyNeighborLost(ROUTER_LINK_LOCAL /* targetIp */,
NudEventType.NUD_ORGANIC_FAILED_CRITICAL);
- verify(mIpMemoryStore).storeNetworkEvent(any(), anyLong(), anyLong(),
- eq(IIpMemoryStore.NETWORK_EVENT_NUD_FAILURE_ORGANIC), any());
+
+ // Given that total NUD failure event counts in the past 6 hours doesn't exceed the
+ // threshold yet, the new NUD failure event will be written to db, then the retrieved
+ // event count should be 10.
+ assertRetrievedNetworkEventCount(TEST_CLUSTER, 10 /* expectedCountInPastWeek */,
+ 10 /* expectedCountInPastDay */, 10 /* expectedCountInPastSixHours */);
}
}
diff --git a/tests/integration/root/android/net/ip/IpClientRootTest.kt b/tests/integration/root/android/net/ip/IpClientRootTest.kt
index 3a56139..b0b51fa 100644
--- a/tests/integration/root/android/net/ip/IpClientRootTest.kt
+++ b/tests/integration/root/android/net/ip/IpClientRootTest.kt
@@ -19,6 +19,7 @@
import android.Manifest.permission.INTERACT_ACROSS_USERS_FULL
import android.Manifest.permission.NETWORK_SETTINGS
import android.Manifest.permission.READ_DEVICE_CONFIG
+import android.Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG
import android.Manifest.permission.WRITE_DEVICE_CONFIG
import android.net.IIpMemoryStore
import android.net.IIpMemoryStoreCallbacks
@@ -163,6 +164,7 @@
// Delete the IpMemoryStore entry corresponding to TEST_L2KEY, make sure each test starts
// from a clean state.
mStore.delete(TEST_L2KEY, true) { _, _ -> latch.countDown() }
+ mStore.deleteCluster(TEST_CLUSTER, true) { _, _ -> latch.countDown() }
assertTrue(latch.await(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS))
}
@@ -191,7 +193,11 @@
private val mOriginalPropertyValues = ArrayMap<String, String>()
override fun setDeviceConfigProperty(name: String?, value: String?) {
- automation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG)
+ automation.adoptShellPermissionIdentity(
+ READ_DEVICE_CONFIG,
+ WRITE_DEVICE_CONFIG,
+ WRITE_ALLOWLISTED_DEVICE_CONFIG
+ )
try {
// Do not use computeIfAbsent as it would overwrite null values,
// property originally unset.
@@ -214,7 +220,11 @@
@After
fun tearDownDeviceConfigProperties() {
if (testSkipped()) return
- automation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG)
+ automation.adoptShellPermissionIdentity(
+ READ_DEVICE_CONFIG,
+ WRITE_DEVICE_CONFIG,
+ WRITE_ALLOWLISTED_DEVICE_CONFIG
+ )
try {
for (key in mOriginalPropertyValues.keys) {
if (key == null) continue
diff --git a/tests/integration/signature/android/net/util/NetworkStackUtilsIntegrationTest.kt b/tests/integration/signature/android/net/util/NetworkStackUtilsIntegrationTest.kt
index 29e6237..ea6f35a 100644
--- a/tests/integration/signature/android/net/util/NetworkStackUtilsIntegrationTest.kt
+++ b/tests/integration/signature/android/net/util/NetworkStackUtilsIntegrationTest.kt
@@ -20,6 +20,8 @@
import android.content.Context
import android.net.InetAddresses.parseNumericAddress
import android.net.IpPrefix
+import android.net.LinkAddress
+import android.net.LinkProperties
import android.net.MacAddress
import android.net.TestNetworkInterface
import android.net.TestNetworkManager
@@ -30,8 +32,12 @@
import android.system.OsConstants
import android.system.OsConstants.AF_INET
import android.system.OsConstants.AF_PACKET
+import android.system.OsConstants.ETH_P_ALL
import android.system.OsConstants.ETH_P_IPV6
+import android.system.OsConstants.IFA_F_DEPRECATED
+import android.system.OsConstants.IFA_F_TENTATIVE
import android.system.OsConstants.IPPROTO_UDP
+import android.system.OsConstants.RT_SCOPE_LINK
import android.system.OsConstants.SOCK_CLOEXEC
import android.system.OsConstants.SOCK_DGRAM
import android.system.OsConstants.SOCK_NONBLOCK
@@ -46,6 +52,7 @@
import com.android.net.module.util.Ipv6Utils
import com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN
import com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN
+import com.android.net.module.util.NetworkStackConstants.ETHER_SRC_ADDR_OFFSET
import com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ANY
import com.android.net.module.util.NetworkStackConstants.IPV4_CHECKSUM_OFFSET
import com.android.net.module.util.NetworkStackConstants.IPV4_FLAGS_OFFSET
@@ -62,6 +69,10 @@
import java.io.FileDescriptor
import java.net.Inet4Address
import java.net.Inet6Address
+import java.net.InetAddress
+import java.net.InetSocketAddress
+import java.net.MulticastSocket
+import java.net.NetworkInterface
import java.nio.ByteBuffer
import java.util.Arrays
import kotlin.reflect.KClass
@@ -92,7 +103,8 @@
private val SOLICITED_NODE_MULTICAST_PREFIX = "FF02:0:0:0:0:1:FF00::/104"
private val readerHandler = HandlerThread(
- NetworkStackUtilsIntegrationTest::class.java.simpleName)
+ NetworkStackUtilsIntegrationTest::class.java.simpleName
+ )
private lateinit var iface: TestNetworkInterface
private lateinit var reader: PollPacketReader
@@ -122,8 +134,12 @@
val socket = Os.socket(AF_INET, SOCK_DGRAM or SOCK_NONBLOCK, IPPROTO_UDP)
SocketUtils.bindSocketToInterface(socket, iface.interfaceName)
- NetworkStackUtils.addArpEntry(TEST_TARGET_IPV4_ADDR, TEST_TARGET_MAC, iface.interfaceName,
- socket)
+ NetworkStackUtils.addArpEntry(
+ TEST_TARGET_IPV4_ADDR,
+ TEST_TARGET_MAC,
+ iface.interfaceName,
+ socket
+ )
// Fake DHCP packet: would not be usable as a DHCP offer (most IPv4 addresses are all-zero,
// no gateway or DNS servers, etc).
@@ -141,8 +157,15 @@
// Not using .array as per errorprone "ByteBufferBackingArray" recommendation
val originalPacket = buffer.readAsArray()
- Os.sendto(socket, originalPacket, 0 /* bytesOffset */, originalPacket.size /* bytesCount */,
- 0 /* flags */, TEST_TARGET_IPV4_ADDR, DhcpPacket.DHCP_CLIENT.toInt() /* port */)
+ Os.sendto(
+ socket,
+ originalPacket,
+ 0 /* bytesOffset */,
+ originalPacket.size /* bytesCount */,
+ 0 /* flags */,
+ TEST_TARGET_IPV4_ADDR,
+ DhcpPacket.DHCP_CLIENT.toInt() /* port */
+ )
// Verify the packet was sent to the mac address specified in the ARP entry
// Also accept ARP requests, but expect that none is sent before the UDP packet
@@ -154,7 +177,9 @@
assertEquals(TEST_TARGET_MAC, sentTargetAddr, "Destination ethernet address does not match")
val sentDhcpPacket = sentPacket.copyOfRange(
- ETHER_HEADER_LEN + IPV4_HEADER_MIN_LEN + UDP_HEADER_LEN, sentPacket.size)
+ ETHER_HEADER_LEN + IPV4_HEADER_MIN_LEN + UDP_HEADER_LEN,
+ sentPacket.size
+ )
assertArrayEquals("Sent packet != original packet", originalPacket, sentDhcpPacket)
}
@@ -165,12 +190,20 @@
?: fail("Could not obtain interface params for ${iface.interfaceName}")
val socketAddr = SocketUtils.makePacketSocketAddress(ETH_P_IPV6, ifParams.index)
Os.bind(socket, socketAddr)
- Os.setsockoptTimeval(socket, SOL_SOCKET, SO_RCVTIMEO,
- StructTimeval.fromMillis(TEST_TIMEOUT_MS))
+ Os.setsockoptTimeval(
+ socket,
+ SOL_SOCKET,
+ SO_RCVTIMEO,
+ StructTimeval.fromMillis(TEST_TIMEOUT_MS)
+ )
// Verify that before setting any filter, the socket receives pings
- val echo = Ipv6Utils.buildEchoRequestPacket(TEST_SRC_MAC, TEST_TARGET_MAC, TEST_INET6ADDR_1,
- TEST_INET6ADDR_2)
+ val echo = Ipv6Utils.buildEchoRequestPacket(
+ TEST_SRC_MAC,
+ TEST_TARGET_MAC,
+ TEST_INET6ADDR_1,
+ TEST_INET6ADDR_2
+ )
reader.sendResponse(echo)
echo.rewind()
assertNextPacketEquals(socket, echo.readAsArray(), "ICMPv6 echo")
@@ -183,8 +216,12 @@
// Send another echo, then an RA. After setting the filter expect only the RA.
echo.rewind()
reader.sendResponse(echo)
- val pio = PrefixInformationOption.build(IpPrefix("2001:db8:1::/64"),
- 0.toByte() /* flags */, 3600 /* validLifetime */, 1800 /* preferredLifetime */)
+ val pio = PrefixInformationOption.build(
+ IpPrefix("2001:db8:1::/64"),
+ 0.toByte() /* flags */,
+ 3600 /* validLifetime */,
+ 1800 /* preferredLifetime */
+ )
val ra = Ipv6Utils.buildRaPacket(TEST_SRC_MAC, TEST_TARGET_MAC,
TEST_INET6ADDR_1 /* routerAddr */, IPV6_ADDR_ALL_NODES_MULTICAST,
0.toByte() /* flags */, 1800 /* lifetime */, 0 /* reachableTime */,
@@ -205,13 +242,340 @@
doTestAttachRaFilter(true)
}
+ @Test
+ fun testAttachEgressIgmpReportFilter() {
+ val socket = Os.socket(AF_PACKET, SOCK_RAW or SOCK_CLOEXEC, 0)
+ val ifParams = InterfaceParams.getByName(iface.interfaceName)
+ ?: fail("Could not obtain interface params for ${iface.interfaceName}")
+ val socketAddr = SocketUtils.makePacketSocketAddress(ETH_P_ALL, ifParams.index)
+ NetworkStackUtils.attachEgressIgmpReportFilter(socket)
+ Os.bind(socket, socketAddr)
+ Os.setsockoptTimeval(
+ socket,
+ SOL_SOCKET,
+ SO_RCVTIMEO,
+ StructTimeval.fromMillis(TEST_TIMEOUT_MS)
+ )
+
+ val sendSocket = Os.socket(AF_PACKET, SOCK_RAW or SOCK_CLOEXEC, 0)
+ Os.bind(sendSocket, socketAddr)
+
+ testExpectedPacketsReceived(sendSocket, socket)
+
+ // shorten the socket timeout to prevent waiting too long in the test
+ Os.setsockoptTimeval(socket, SOL_SOCKET, SO_RCVTIMEO, StructTimeval.fromMillis(100))
+
+ testExpectedPacketsNotReceived(sendSocket, socket)
+ }
+
+ @Test
+ fun testAttachEgressIgmpReportFilterForMulticastGroupChange() {
+ val socket = Os.socket(AF_PACKET, SOCK_RAW or SOCK_CLOEXEC, 0)
+ val ifParams = InterfaceParams.getByName(iface.interfaceName)
+ ?: fail("Could not obtain interface params for ${iface.interfaceName}")
+ val socketAddr = SocketUtils.makePacketSocketAddress(ETH_P_ALL, ifParams.index)
+ NetworkStackUtils.attachEgressIgmpReportFilter(socket)
+ Os.bind(socket, socketAddr)
+ Os.setsockoptTimeval(
+ socket,
+ SOL_SOCKET,
+ SO_RCVTIMEO,
+ StructTimeval.fromMillis(TEST_TIMEOUT_MS)
+ )
+
+ val multicastSock = MulticastSocket()
+ val mcastAddr = InetSocketAddress(InetAddress.getByName("239.0.0.1") as Inet4Address, 5000)
+ val networkInterface = NetworkInterface.getByName(iface.interfaceName)
+
+ multicastSock.joinGroup(mcastAddr, networkInterface)
+ // Using scapy to generate IGMPv3 membership report:
+ // ether = Ether(src='02:03:04:05:06:07', dst='01:00:5e:00:00:16')
+ // ip = IP(src='0.0.0.0', dst='224.0.0.22', id=0, flags='DF', options=[IPOption_Router_Alert()])
+ // igmp = IGMPv3(type=0x22)/IGMPv3mr(records=[IGMPv3gr(rtype=4, maddr='239.0.0.1')])
+ // pkt = ether/ip/igmp
+ val joinReport = """
+ 01005e000016020304050607080046c0002800004000010203fa00000000e0000016940400002200ea
+ fc0000000104000000ef000001
+ """.replace("\\s+".toRegex(), "").trim()
+ val srcMac = ifParams.macAddr.toString().replace(":", "")
+ val expectedJoinPkt = HexDump.hexStringToByteArray(
+ joinReport.replace("020304050607", srcMac)
+ )
+ assertNextPacketEquals(socket, expectedJoinPkt, "IGMPv3 join report")
+
+ multicastSock.leaveGroup(mcastAddr, networkInterface)
+ // Using scapy to generate IGMPv3 membership report:
+ // ether = Ether(src='02:03:04:05:06:07', dst='01:00:5e:00:00:16')
+ // ip = IP(src='0.0.0.0', dst='224.0.0.22', id=0, flags='DF', options=[IPOption_Router_Alert()])
+ // igmp = IGMPv3(type=0x22)/IGMPv3mr(records=[IGMPv3gr(rtype=3, maddr='239.0.0.1')])
+ // pkt = ether/ip/igmp
+ val leaveReport = """
+ 01005e000016020304050607080046c0002800004000010203fa00000000e0000016940400002200eb
+ fc0000000103000000ef000001
+ """.replace("\\s+".toRegex(), "").trim()
+ val expectedLeavePkt = HexDump.hexStringToByteArray(
+ leaveReport.replace("020304050607", srcMac)
+ )
+ assertNextPacketEquals(socket, expectedLeavePkt, "IGMPv3 leave report")
+ }
+
+ @Test
+ fun testAttachEgressMulticastReportFilter() {
+ val socket = Os.socket(AF_PACKET, SOCK_RAW or SOCK_CLOEXEC, 0)
+ val ifParams = InterfaceParams.getByName(iface.interfaceName)
+ ?: fail("Could not obtain interface params for ${iface.interfaceName}")
+ val socketAddr = SocketUtils.makePacketSocketAddress(ETH_P_ALL, ifParams.index)
+ NetworkStackUtils.attachEgressMulticastReportFilter(socket)
+ Os.bind(socket, socketAddr)
+ Os.setsockoptTimeval(
+ socket,
+ SOL_SOCKET,
+ SO_RCVTIMEO,
+ StructTimeval.fromMillis(TEST_TIMEOUT_MS)
+ )
+
+ val sendSocket = Os.socket(AF_PACKET, SOCK_RAW or SOCK_CLOEXEC, 0)
+ Os.bind(sendSocket, socketAddr)
+
+ testExpectedPacketsReceived(sendSocket, socket)
+
+ // Using scapy to generate MLDv1 membership report:
+ // ether = Ether(src='02:03:04:05:06:07', dst='33:33:33:11:11:11')
+ // ipv6 = IPv6(src='fe80::fc01:83ff:fea6:378b', dst='ff12::1:1111:1111', hlim=1)
+ // option = IPv6ExtHdrHopByHop(options=[RouterAlert(otype=5)])
+ // mld = ICMPv6MLReport(type=131, mladdr='ff12::1:1111:1111')
+ // pkt = ether/ipv6/option/mld
+ val mldv1ReportHexStr = """
+ 33333311111102030405060786dd6000000000200001fe80000000000000fc0183fffea6378bff12000000
+ 00000000000001111111113a000502000001008300858c00000000ff120000000000000000000111111111
+ """.replace("\\s+".toRegex(), "").trim()
+ val mldv1Report = HexDump.hexStringToByteArray(mldv1ReportHexStr)
+ Os.write(sendSocket, mldv1Report, 0, mldv1Report.size)
+ assertUntilPacketEquals(socket, mldv1Report, "MLDv1 report")
+
+ // Using scapy to generate MLDv1 membership done:
+ // ether = Ether(src='02:03:04:05:06:07', dst='33:33:33:00:00:02')
+ // ipv6 = IPv6(src='fe80::fc01:83ff:fea6:378b', dst='ff02::2', hlim=1)
+ // option = IPv6ExtHdrHopByHop(options=[RouterAlert(otype=5)])
+ // mld = ICMPv6MLReport(type=132, mladdr='ff12::1:1111:1111')
+ // pkt = ether/ipv6/option/mld
+ val mldv1DoneHexStr = """
+ 33333300000202030405060786dd6000000000200001fe80000000000000fc0183fffea6378bff02000000
+ 00000000000000000000023a000502000001008400a6bd00000000ff120000000000000000000111111111
+ """.replace("\\s+".toRegex(), "").trim()
+ val mldv1Done = HexDump.hexStringToByteArray(mldv1DoneHexStr)
+ Os.write(sendSocket, mldv1Done, 0, mldv1Done.size)
+ assertUntilPacketEquals(socket, mldv1Done, "MLDv1 done")
+
+ // Using scapy to generate MLDv2 membership report:
+ // ether = Ether(src='02:03:04:05:06:07', dst='33:33:33:00:00:16')
+ // ipv6 = IPv6(src='fe80::fc01:83ff:fea6:378b', dst='ff02::16', hlim=1)
+ // option = IPv6ExtHdrHopByHop(options=[RouterAlert(otype=5)])
+ // mld = ICMPv6MLReport2(records=[ICMPv6MLDMultAddrRec(dst='ff12::1:1111:1111')])
+ // pkt = ether/ipv6/option/mld
+ val mldv2ReportHexStr = """
+ 33333300001602030405060786dd6000000000240001fe80000000000000fc0183fffea6378bff02000000
+ 00000000000000000000163a000502000001008f0097a40000000104000000ff1200000000000000000001
+ 11111111
+ """.replace("\\s+".toRegex(), "").trim()
+ val mldv2Report = HexDump.hexStringToByteArray(mldv2ReportHexStr)
+ Os.write(sendSocket, mldv2Report, 0, mldv2Report.size)
+ assertUntilPacketEquals(socket, mldv2Report, "MLDv2 report")
+
+ // shorten the socket timeout to prevent waiting too long in the test
+ Os.setsockoptTimeval(socket, SOL_SOCKET, SO_RCVTIMEO, StructTimeval.fromMillis(100))
+
+ testExpectedPacketsNotReceived(sendSocket, socket)
+
+ // Using scapy to generate MLDv1 general query packet:
+ // ether = Ether(src='02:03:04:05:06:07', dst='33:33:33:00:00:01')
+ // ipv6 = IPv6(src='fe80::fc01:83ff:fea6:378b', dst='ff02::1', hlim=1)
+ // option = IPv6ExtHdrHopByHop(options=[RouterAlert(otype=5)])
+ // mld = ICMPv6MLQuery()
+ // pkt = ether/ipv6/option/mld
+ val mldv1GqHexStr = """
+ 33333300000102030405060786dd6000000000200001fe80000000000000fc0183fffea6378bff02000000
+ 00000000000000000000013a000502000001008200a2e42710000000000000000000000000000000000000
+ """.replace("\\s+".toRegex(), "").trim()
+ val mldv1Gq = HexDump.hexStringToByteArray(mldv1GqHexStr)
+ Os.write(sendSocket, mldv1Gq, 0, mldv1Gq.size)
+ assertUntilSocketReadErrno(
+ "MLDv1 General Query Packet should not been received",
+ socket,
+ OsConstants.EAGAIN
+ )
+
+ // Using scapy to generate MLDv2 general query packet:
+ // ether = Ether(src='02:03:04:05:06:07', dst='33:33:33:00:00:01')
+ // ipv6 = IPv6(src='fe80::fc01:83ff:fea6:378b', dst='ff02::1', hlim=1)
+ // option = IPv6ExtHdrHopByHop(options=[RouterAlert(otype=5)])
+ // mld = ICMPv6MLQuery2()
+ // pkt = ether/ipv6/option/mld
+ val mldv2GqHexStr = """
+ 33333300000102030405060786dd6000000000240001fe80000000000000fc0183fffea6378bff02000000
+ 00000000000000000000013a000502000001008200a2e02710000000000000000000000000000000000000
+ 00000000
+ """.replace("\\s+".toRegex(), "").trim()
+ val mldv2Gq = HexDump.hexStringToByteArray(mldv2GqHexStr)
+ Os.write(sendSocket, mldv2Gq, 0, mldv1Gq.size)
+ assertUntilSocketReadErrno(
+ "MLDv2 General Query Packet should not been received",
+ socket,
+ OsConstants.EAGAIN
+ )
+ }
+
+ private fun testExpectedPacketsReceived(
+ sendSocket: FileDescriptor,
+ recvSocket: FileDescriptor
+ ) {
+ // Using scapy to generate IGMPv2 membership report:
+ // ether = Ether(src='02:03:04:05:06:07', dst='01:00:5e:00:00:01')
+ // ip = IP(src='10.0.0.1', dst='239.0.0.1', id=0, flags='DF', tos=0xc0, options=[IPOption_Router_Alert()])
+ // igmp = IGMP(type=0x16, mrcode=0, gaddr='239.0.0.1')
+ // pkt = ether/ip/igmp
+ val igmpv2ReportHexStr = """
+ 01005e000001020304050607080046c00020000040000102eb150a000001ef000001940400001600fafd
+ ef000001
+ """.replace("\\s+".toRegex(), "").trim()
+ val igmpv2Report = HexDump.hexStringToByteArray(igmpv2ReportHexStr)
+ Os.write(sendSocket, igmpv2Report, 0, igmpv2Report.size)
+ assertUntilPacketEquals(recvSocket, igmpv2Report, "IGMPv2 report")
+
+ // Using scapy to generate IGMPv2 membership leave report:
+ // ether = Ether(src='02:03:04:05:06:07', dst='01:00:5e:00:00:01')
+ // ip = IP(src='10.0.0.1', dst='239.0.0.1', id=0, flags='DF', tos=0xc0, options=[IPOption_Router_Alert()])
+ // igmp = IGMP(type=0x17, mrcode=0, gaddr='239.0.0.1')
+ // pkt = ether/ip/igmp
+ val igmpv2LeaveHexStr = """
+ 01005e000001020304050607080046c00020000040000102eb150a000001ef000001940400001700f9fd
+ ef000001
+ """.replace("\\s+".toRegex(), "").trim()
+ val igmpv2Leave = HexDump.hexStringToByteArray(igmpv2LeaveHexStr)
+ Os.write(sendSocket, igmpv2Leave, 0, igmpv2Leave.size)
+ assertUntilPacketEquals(recvSocket, igmpv2Leave, "IGMPv2 leave")
+
+ // Using scapy to generate IGMPv3 membership report:
+ // ether = Ether(src='02:03:04:05:06:07', dst='01:00:5e:00:00:16')
+ // ip = IP(src='10.0.0.1', dst='224.0.0.22', id=0, flags='DF', options=[IPOption_Router_Alert()])
+ // igmp = IGMPv3(type=0x22)/IGMPv3mr(records=[IGMPv3gr(rtype=2, maddr='239.0.0.1')])
+ // pkt = ether/ip/igmp
+ val igmpv3ReportHexStr = """
+ 01005e000016020304050607080046c00028000040000102f9f80a000001e0000016940400002200ecfc
+ 0000000102000000ef000001
+ """.replace("\\s+".toRegex(), "").trim()
+ val igmpv3Report = HexDump.hexStringToByteArray(igmpv3ReportHexStr)
+ Os.write(sendSocket, igmpv3Report, 0, igmpv3Report.size)
+ assertUntilPacketEquals(recvSocket, igmpv3Report, "IGMPv3 report")
+ }
+
+ private fun testExpectedPacketsNotReceived(
+ sendSocket: FileDescriptor,
+ recvSocket: FileDescriptor
+ ) {
+ val dhcpNak = DhcpPacket.buildNakPacket(
+ DhcpPacket.ENCAP_L2,
+ 42,
+ TEST_TARGET_IPV4_ADDR, /*relayIp=*/
+ IPV4_ADDR_ANY,
+ TEST_TARGET_MAC.toByteArray(),
+ /*broadcast=*/
+ false,
+ "NAK"
+ ).readAsArray()
+ Os.write(sendSocket, dhcpNak, 0, dhcpNak.size)
+ assertUntilSocketReadErrno(
+ "DHCP Packet should not been received",
+ recvSocket,
+ OsConstants.EAGAIN
+ )
+
+ // Using scapy to generate IGMPv2 general query packet:
+ // ether = Ether(src='02:03:04:05:06:07', dst='01:00:5e:00:00:01')
+ // ip = IP(src='10.0.0.1', dst='239.0.0.1', id=0, flags='DF', tos=0xc0, options=[IPOption_Router_Alert()])
+ // igmp = IGMP(type=0x11)
+ // pkt = ether/ip/igmp
+ val igmpv2GqHexStr = """
+ 01005e000001020304050607080046c00020000040000102eb150a000001ef000001940400001114eeeb
+ 00000000
+ """.replace("\\s+".toRegex(), "").trim()
+ val igmpv2Gq = HexDump.hexStringToByteArray(igmpv2GqHexStr)
+ Os.write(sendSocket, igmpv2Gq, 0, igmpv2Gq.size)
+ assertUntilSocketReadErrno(
+ "IGMPv2 General Query Packet should not been received",
+ recvSocket,
+ OsConstants.EAGAIN
+ )
+
+ // Using scapy to generate IGMPv1 general query packet:
+ // ether = Ether(src='02:03:04:05:06:07', dst='01:00:5e:00:00:01')
+ // ip = IP(src='10.0.0.1', dst='239.0.0.1', id=0, flags='DF', tos=0xc0, options=[IPOption_Router_Alert()])
+ // igmp = IGMP(type=0x11, mrcode=0)
+ // pkt = ether/ip/igmp
+ val igmpv1GqHexStr = """
+ 01005e000001020304050607080046c00020000040000102eb150a000001ef000001940400001100eeff
+ 00000000
+ """.replace("\\s+".toRegex(), "").trim()
+ val igmpv1Gq = HexDump.hexStringToByteArray(igmpv1GqHexStr)
+ Os.write(sendSocket, igmpv1Gq, 0, igmpv1Gq.size)
+ assertUntilSocketReadErrno(
+ "IGMPv1 General Query Packet should not been received",
+ recvSocket,
+ OsConstants.EAGAIN
+ )
+ }
+
+ private fun assertUntilPacketEquals(
+ socket: FileDescriptor,
+ expected: ByteArray,
+ descr: String
+ ) {
+ val buffer = ByteArray(TEST_MTU)
+ var readBytes: Int
+ var actualPkt: ByteArray? = null
+ while (Os.read(socket, buffer, 0 /* byteOffset */, buffer.size)
+ .also { readBytes = it } > 0
+ ) {
+ actualPkt = buffer.copyOfRange(0, readBytes)
+ if (!isTestInterfaceEgressPacket(actualPkt)) break
+ }
+
+ assertNotNull(actualPkt, "no received packets")
+ assertArrayEquals(
+ "Received packet(${HexDump.toHexString(actualPkt)}) " +
+ "!= expected(${HexDump.toHexString(expected)}) $descr",
+ expected,
+ actualPkt
+ )
+ }
+
+ private fun assertUntilSocketReadErrno(msg: String, socket: FileDescriptor, errno: Int) {
+ val buffer = ByteArray(TEST_MTU)
+ var readBytes: Int
+ var actualPkt: ByteArray? = null
+ try {
+ while (Os.read(socket, buffer, 0 /* byteOffset */, buffer.size)
+ .also { readBytes = it } > 0
+ ) {
+ actualPkt = buffer.copyOfRange(0, readBytes)
+ if (!isTestInterfaceEgressPacket(actualPkt)) break
+ }
+ fail(msg + ": " + HexDump.toHexString(actualPkt))
+ } catch (expected: ErrnoException) {
+ assertEquals(errno.toLong(), expected.errno.toLong())
+ }
+ }
+
private fun assertNextPacketEquals(socket: FileDescriptor, expected: ByteArray, descr: String) {
val buffer = ByteArray(TEST_MTU)
val readPacket = Os.read(socket, buffer, 0 /* byteOffset */, buffer.size)
assertTrue(readPacket > 0, "$descr not received")
assertEquals(expected.size, readPacket, "Received packet size does not match for $descr")
- assertArrayEquals("Received packet != expected $descr",
- expected, buffer.copyOfRange(0, readPacket))
+ assertArrayEquals(
+ "Received packet != expected $descr",
+ expected,
+ buffer.copyOfRange(0, readPacket)
+ )
}
private fun assertSolicitedNodeMulticastAddress(
@@ -223,28 +587,40 @@
assertTrue(prefix.contains(expected))
assertTrue(expected.isMulticastAddress())
// check the last 3 bytes of address
- assertArrayEquals(Arrays.copyOfRange(expected.getAddress(), 13, 15),
- Arrays.copyOfRange(unicast.getAddress(), 13, 15))
+ assertArrayEquals(
+ Arrays.copyOfRange(expected.getAddress(), 13, 15),
+ Arrays.copyOfRange(unicast.getAddress(), 13, 15)
+ )
}
@Test
fun testConvertIpv6AddressToSolicitedNodeMulticast() {
val addr1 = NetworkStackUtils.ipv6AddressToSolicitedNodeMulticast(TEST_INET6ADDR_1)
assertSolicitedNodeMulticastAddress(addr1, TEST_INET6ADDR_1)
+ assertTrue(NetworkStackUtils.isIPv6AddressSolicitedNodeMulticast(addr1!!))
val addr2 = NetworkStackUtils.ipv6AddressToSolicitedNodeMulticast(TEST_INET6ADDR_2)
assertSolicitedNodeMulticastAddress(addr2, TEST_INET6ADDR_2)
+ assertTrue(NetworkStackUtils.isIPv6AddressSolicitedNodeMulticast(addr2!!))
val addr3 = NetworkStackUtils.ipv6AddressToSolicitedNodeMulticast(TEST_INET6ADDR_3)
assertSolicitedNodeMulticastAddress(addr3, TEST_INET6ADDR_3)
+ assertTrue(NetworkStackUtils.isIPv6AddressSolicitedNodeMulticast(addr3!!))
}
@Test
fun testConvertMacAddressToEui64() {
// MAC address with universal/local bit set (the first byte: 0xBA)
var expected = byteArrayOf(
- 0xB8.toByte(), 0x98.toByte(), 0x76.toByte(), 0xFF.toByte(),
- 0xFE.toByte(), 0x54.toByte(), 0x32.toByte(), 0x10.toByte())
+ 0xB8.toByte(),
+ 0x98.toByte(),
+ 0x76.toByte(),
+ 0xFF.toByte(),
+ 0xFE.toByte(),
+ 0x54.toByte(),
+ 0x32.toByte(),
+ 0x10.toByte()
+ )
val srcEui64 = NetworkStackUtils.macAddressToEui64(TEST_SRC_MAC)
assertArrayEquals(expected, srcEui64)
@@ -288,8 +664,10 @@
private fun assertNextPacketOnSocket(fd: FileDescriptor, expectedPacket: ByteBuffer) {
val received = ByteBuffer.allocate(TEST_MTU)
val len = Os.read(fd, received)
- assertEquals(toHexString(expectedPacket, expectedPacket.limit()),
- toHexString(received, len))
+ assertEquals(
+ toHexString(expectedPacket, expectedPacket.limit()),
+ toHexString(received, len)
+ )
}
private fun setMfBit(packet: ByteBuffer, set: Boolean) {
@@ -307,6 +685,15 @@
packet.putShort(checksumOffset, IpUtils.ipChecksum(packet, ETHER_HEADER_LEN))
}
+ private fun isTestInterfaceEgressPacket(packet: ByteArray): Boolean {
+ val srcMac = packet.copyOfRange(
+ ETHER_SRC_ADDR_OFFSET,
+ ETHER_SRC_ADDR_OFFSET + ETHER_ADDR_LEN
+ )
+ val ifParams = InterfaceParams.getByName(iface.interfaceName)
+ return srcMac.contentEquals(ifParams.macAddr.toByteArray())
+ }
+
private fun doTestDhcpResponseWithMfBitDropped(generic: Boolean) {
val ifindex = InterfaceParams.getByName(iface.interfaceName).index
val packetSock = Os.socket(AF_PACKET, SOCK_RAW or SOCK_NONBLOCK, /*protocol=*/0)
@@ -318,15 +705,25 @@
}
val addr = SocketUtils.makePacketSocketAddress(OsConstants.ETH_P_IP, ifindex)
Os.bind(packetSock, addr)
- val packet = DhcpPacket.buildNakPacket(DhcpPacket.ENCAP_L2, 42,
- TEST_TARGET_IPV4_ADDR, /*relayIp=*/ IPV4_ADDR_ANY, TEST_TARGET_MAC.toByteArray(),
- /*broadcast=*/ false, "NAK")
+ val packet = DhcpPacket.buildNakPacket(
+ DhcpPacket.ENCAP_L2,
+ 42,
+ TEST_TARGET_IPV4_ADDR, /*relayIp=*/
+ IPV4_ADDR_ANY,
+ TEST_TARGET_MAC.toByteArray(),
+ /*broadcast=*/
+ false,
+ "NAK"
+ )
setMfBit(packet, true)
reader.sendResponse(packet)
// Packet with MF bit set is not received.
- assertSocketReadErrno("Packet with MF bit should have been dropped",
- packetSock, OsConstants.EAGAIN)
+ assertSocketReadErrno(
+ "Packet with MF bit should have been dropped",
+ packetSock,
+ OsConstants.EAGAIN
+ )
// Identical packet, except with MF bit cleared, should be received.
setMfBit(packet, false)
@@ -346,6 +743,49 @@
fun testGenericDhcpResponseWithMfBitDropped() {
doTestDhcpResponseWithMfBitDropped(true)
}
+
+ @Test
+ fun testConvertIpv4AddressToEthernetMulticast() {
+ var mcastAddrs = listOf(
+ // ipv4 multicast address, multicast ethernet address
+ Pair(
+ InetAddress.getByName("224.0.0.1") as Inet4Address,
+ MacAddress.fromString("01:00:5e:00:00:01")
+ ),
+ Pair(
+ InetAddress.getByName("239.128.1.1") as Inet4Address,
+ MacAddress.fromString("01:00:5e:00:01:01")
+ ),
+ Pair(
+ InetAddress.getByName("239.255.255.255") as Inet4Address,
+ MacAddress.fromString("01:00:5e:7f:ff:ff")
+ )
+ )
+
+ for ((addr, expectAddr) in mcastAddrs) {
+ val ether = NetworkStackUtils.ipv4MulticastToEthernetMulticast(addr)
+ assertEquals(expectAddr, ether)
+ }
+ }
+
+ @Test
+ fun testSelectPreferredIPv6LinkLocalAddress() {
+ val addr1 = LinkAddress("fe80::1/64", IFA_F_TENTATIVE, RT_SCOPE_LINK)
+ val addr2 = LinkAddress("fe80::2/64", 0 /* flags */, RT_SCOPE_LINK)
+ val addr3 = LinkAddress("fe80::3/64", IFA_F_DEPRECATED, RT_SCOPE_LINK)
+
+ val lp1 = LinkProperties()
+ lp1.setLinkAddresses(listOf(addr1, addr2, addr3))
+ assertEquals(addr2.address, NetworkStackUtils.selectPreferredIPv6LinkLocalAddress(lp1))
+
+ val lp2 = LinkProperties()
+ lp2.setLinkAddresses(listOf(addr1, addr3))
+ assertEquals(addr3.address, NetworkStackUtils.selectPreferredIPv6LinkLocalAddress(lp2))
+
+ val lp3 = LinkProperties()
+ lp3.setLinkAddresses(listOf(addr1))
+ assertNull(NetworkStackUtils.selectPreferredIPv6LinkLocalAddress(lp3))
+ }
}
private fun ByteBuffer.readAsArray(): ByteArray {
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 7e6de1a..47fd29b 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -34,6 +34,7 @@
"net-tests-utils",
"net-utils-framework-common",
"testables",
+ "truth",
],
libs: [
"android.test.runner.stubs",
@@ -115,6 +116,7 @@
name: "libnetworkstackutilsjni_deps",
jni_libs: [
"libnativehelper_compat_libc++",
- "libnetworkstacktestsjni",
+ "libapfjniv6",
+ "libapfjninext",
],
}
diff --git a/tests/unit/jni/Android.bp b/tests/unit/jni/Android.bp
index 1ea4268..0dc31a3 100644
--- a/tests/unit/jni/Android.bp
+++ b/tests/unit/jni/Android.bp
@@ -19,8 +19,8 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-cc_library_shared {
- name: "libnetworkstacktestsjni",
+cc_defaults {
+ name: "libapfjni_defaults",
srcs: [
"**/*.cpp",
],
@@ -38,10 +38,32 @@
],
static_libs: [
"libapf",
- "libapf_v7",
"libapfdisassembler",
"libpcap",
+ "libapfbuf",
],
sdk_version: "30",
stl: "c++_static",
}
+
+cc_library_shared {
+ name: "libapfjniv6",
+ defaults: ["libapfjni_defaults"],
+ cflags: [
+ "-DAPF_INTERPRETER_V6",
+ ],
+ static_libs: [
+ "libapf_v6",
+ ],
+}
+
+cc_library_shared {
+ name: "libapfjninext",
+ defaults: ["libapfjni_defaults"],
+ cflags: [
+ "-DAPF_INTERPRETER_NEXT",
+ ],
+ static_libs: [
+ "libapf_next",
+ ],
+}
diff --git a/tests/unit/jni/apf_jni.cpp b/tests/unit/jni/apf_jni.cpp
index 873b217..98078c9 100644
--- a/tests/unit/jni/apf_jni.cpp
+++ b/tests/unit/jni/apf_jni.cpp
@@ -23,14 +23,22 @@
#include <string>
#include <vector>
-#include "apf_interpreter.h"
+#include "v4/apf_interpreter.h"
#include "disassembler.h"
#include "nativehelper/scoped_primitive_array.h"
-#include "v7/apf_interpreter.h"
-#include "v7/test_buf_allocator.h"
+
+#include "next/test_buf_allocator.h"
+
+#ifdef APF_INTERPRETER_NEXT
+#include "next/apf_interpreter.h"
+#endif
+
+#ifdef APF_INTERPRETER_V6
+#include "v6/apf_interpreter.h"
+#endif
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
-#define LOG_TAG "NetworkStackUtils-JNI"
+#define LOG_TAG "ApfJniUtils"
static int run_apf_interpreter(int apf_version, uint32_t* program,
uint32_t program_len, uint32_t ram_len,
@@ -56,12 +64,14 @@
uint32_t program_len = env->GetArrayLength(jprogram);
uint32_t data_len = jdata ? env->GetArrayLength(jdata) : 0;
// we need to guarantee room for APFv6's 5 u32 counters (20 bytes)
+ // and APFv6.1's 6 u32 counters (24 bytes)
// and we need to make sure ram_len is a multiple of 4 bytes,
// so that the counters (which are indexed from the back are aligned.
uint32_t ram_len = program_len + data_len;
if (apf_version > 4) {
ram_len += 3; ram_len &= ~3;
- if (data_len < 20) ram_len += 20;
+ uint32_t need = 24; // TODO: (apf_version > 6000) ? 24 : 20;
+ if (data_len < need) ram_len += need;
}
std::vector<uint32_t> buf((ram_len + 3) / 4, 0);
jbyte* jbuf = reinterpret_cast<jbyte*>(buf.data());
@@ -268,7 +278,11 @@
reinterpret_cast<jbyte*>(buf.data()));
std::vector<std::string> disassemble_output;
for (uint32_t pc = 0; pc < program_len;) {
- disassemble_output.emplace_back(apf_disassemble(buf.data(), program_len, &pc));
+ // TODO: Implement proper selection of APFv4 or APFv6 code for
+ // disassembly.
+ const disas_ret ret =
+ apf_disassemble(buf.data(), program_len, &pc, true /* is_v6*/);
+ disassemble_output.emplace_back(ret.content);
}
jclass stringClass = env->FindClass("java/lang/String");
jobjectArray disassembleOutput =
@@ -284,21 +298,43 @@
return disassembleOutput;
}
-jbyteArray com_android_server_ApfTest_getTransmittedPacket(JNIEnv* env,
- jclass) {
- jbyteArray jdata = env->NewByteArray((jint) apf_test_tx_packet_len);
- if (jdata == NULL) { return NULL; }
- if (apf_test_tx_packet_len == 0) { return jdata; }
+static jobjectArray com_android_server_ApfTest_getAllTransmittedPackets(JNIEnv* env,
+ jclass) {
+ jclass arrayListClass = env->FindClass("java/util/ArrayList");
+ jmethodID arrayListConstructor = env->GetMethodID(arrayListClass, "<init>", "()V");
+ jobject arrayList = env->NewObject(arrayListClass, arrayListConstructor);
- env->SetByteArrayRegion(jdata, 0, (jint) apf_test_tx_packet_len,
- reinterpret_cast<jbyte*>(apf_test_buffer));
+ jmethodID addMethod = env->GetMethodID(arrayListClass, "add", "(Ljava/lang/Object;)Z");
+ packet_buffer *ptr = head;
+ while (ptr) {
+ jbyteArray jdata = env->NewByteArray((jint) ptr->len);
+ if (jdata == NULL) {
+ return static_cast<jobjectArray>(arrayList);
+ }
- return jdata;
+ env->SetByteArrayRegion(jdata, 0, (jint) ptr->len,
+ reinterpret_cast<jbyte*>(ptr->data));
+ env->CallBooleanMethod(arrayList, addMethod, jdata);
+ env->DeleteLocalRef(jdata);
+
+ ptr = ptr->next;
+ }
+
+ env->DeleteLocalRef(arrayListClass);
+ return static_cast<jobjectArray>(arrayList);
}
void com_android_server_ApfTest_resetTransmittedPacketMemory(JNIEnv, jclass) {
- apf_test_tx_packet_len = 0;
- memset(apf_test_buffer, 0xff, sizeof(apf_test_buffer));
+ packet_buffer* current = head;
+ packet_buffer* tmp = NULL;
+ while (current) {
+ tmp = current->next;
+ free(current);
+ current = tmp;
+ }
+
+ head = NULL;
+ tail = NULL;
}
extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
@@ -319,8 +355,8 @@
(void*)com_android_server_ApfTest_dropsAllPackets },
{ "disassembleApf", "([B)[Ljava/lang/String;",
(void*)com_android_server_ApfTest_disassembleApf },
- { "getTransmittedPacket", "()[B",
- (void*)com_android_server_ApfTest_getTransmittedPacket },
+ { "getAllTransmittedPackets", "()Ljava/util/List;",
+ (void*)com_android_server_ApfTest_getAllTransmittedPackets },
{ "resetTransmittedPacketMemory", "()V",
(void*)com_android_server_ApfTest_resetTransmittedPacketMemory },
};
diff --git a/tests/unit/res/raw/apf.pcap b/tests/unit/res/raw/apf.pcap
deleted file mode 100644
index 963165f..0000000
--- a/tests/unit/res/raw/apf.pcap
+++ /dev/null
Binary files differ
diff --git a/tests/unit/src/android/net/apf/ApfFilterTest.kt b/tests/unit/src/android/net/apf/ApfFilterTest.kt
index 15ff224..5cfeaad 100644
--- a/tests/unit/src/android/net/apf/ApfFilterTest.kt
+++ b/tests/unit/src/android/net/apf/ApfFilterTest.kt
@@ -16,11 +16,14 @@
package android.net.apf
import android.content.Context
+import android.net.InetAddresses
import android.net.LinkAddress
import android.net.LinkProperties
import android.net.MacAddress
import android.net.NattKeepalivePacketDataParcelable
import android.net.TcpKeepalivePacketDataParcelable
+import android.net.apf.ApfConstants.ETH_MULTICAST_MDNS_V4_MAC_ADDRESS
+import android.net.apf.ApfConstants.ETH_MULTICAST_MDNS_V6_MAC_ADDRESS
import android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_NON_IPV4
import android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_OTHER_HOST
import android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_REPLY_SPA_NO_HOST
@@ -28,42 +31,53 @@
import android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_UNKNOWN
import android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_V6_ONLY
import android.net.apf.ApfCounterTracker.Counter.DROPPED_ETHERTYPE_NOT_ALLOWED
+import android.net.apf.ApfCounterTracker.Counter.DROPPED_ETHER_OUR_SRC_MAC
import android.net.apf.ApfCounterTracker.Counter.DROPPED_GARP_REPLY
+import android.net.apf.ApfCounterTracker.Counter.DROPPED_IGMP_INVALID
+import android.net.apf.ApfCounterTracker.Counter.DROPPED_IGMP_REPORT
+import android.net.apf.ApfCounterTracker.Counter.DROPPED_IGMP_V2_GENERAL_QUERY_REPLIED
+import android.net.apf.ApfCounterTracker.Counter.DROPPED_IGMP_V3_GENERAL_QUERY_REPLIED
import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_BROADCAST_ADDR
import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_BROADCAST_NET
+import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_ICMP_INVALID
import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_KEEPALIVE_ACK
import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_L2_BROADCAST
import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_MULTICAST
import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_NATT_KEEPALIVE
import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_NON_DHCP4
+import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_PING_REQUEST_REPLIED
import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_TCP_PORT7_UNICAST
+import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_ICMP6_ECHO_REQUEST_INVALID
+import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_ICMP6_ECHO_REQUEST_REPLIED
+import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MLD_INVALID
+import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MLD_REPORT
+import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MLD_V1_GENERAL_QUERY_REPLIED
+import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MLD_V2_GENERAL_QUERY_REPLIED
import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MULTICAST_NA
import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NON_ICMP_MULTICAST
import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_INVALID
import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_OTHER_HOST
import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_REPLIED_NON_DAD
-import android.net.apf.ApfCounterTracker.Counter.PASSED_ETHER_OUR_SRC_MAC
+import android.net.apf.ApfCounterTracker.Counter.DROPPED_MDNS
+import android.net.apf.ApfCounterTracker.Counter.DROPPED_MDNS_REPLIED
+import android.net.apf.ApfCounterTracker.Counter.DROPPED_NON_UNICAST_TDLS
+import android.net.apf.ApfCounterTracker.Counter.DROPPED_RA
import android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_BROADCAST_REPLY
import android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_REQUEST
import android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_UNICAST_REPLY
import android.net.apf.ApfCounterTracker.Counter.PASSED_DHCP
+import android.net.apf.ApfCounterTracker.Counter.PASSED_ETHER_OUR_SRC_MAC
import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV4
import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV4_FROM_DHCPV4_SERVER
import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV4_UNICAST
+import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_HOPOPTS
import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_ICMP
import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NON_ICMP
-import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_DAD
-import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_NO_ADDRESS
-import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_NO_SLLA_OPTION
-import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_TENTATIVE
-import android.net.apf.ApfCounterTracker.Counter.PASSED_MLD
+import android.net.apf.ApfCounterTracker.Counter.PASSED_MDNS
+import android.net.apf.ApfCounterTracker.Counter.PASSED_NON_IP_UNICAST
import android.net.apf.ApfFilter.Dependencies
import android.net.apf.ApfTestHelpers.Companion.TIMEOUT_MS
-import android.net.apf.ApfTestHelpers.Companion.consumeInstalledProgram
-import android.net.apf.ApfTestHelpers.Companion.verifyProgramRun
import android.net.apf.BaseApfGenerator.APF_VERSION_3
-import android.net.apf.BaseApfGenerator.APF_VERSION_6
-import android.net.ip.IpClient.IpClientCallbacksWrapper
import android.net.nsd.NsdManager
import android.net.nsd.OffloadEngine
import android.net.nsd.OffloadServiceInfo
@@ -75,6 +89,7 @@
import android.system.OsConstants.AF_UNIX
import android.system.OsConstants.IFA_F_TENTATIVE
import android.system.OsConstants.SOCK_STREAM
+import android.util.Log
import androidx.test.filters.SmallTest
import com.android.internal.annotations.GuardedBy
import com.android.net.module.util.HexDump
@@ -85,28 +100,38 @@
import com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN
import com.android.net.module.util.NetworkStackConstants.ICMPV6_NA_HEADER_LEN
import com.android.net.module.util.NetworkStackConstants.ICMPV6_NS_HEADER_LEN
+import com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_NODES_MULTICAST
+import com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_NODE_LOCAL_ALL_NODES_MULTICAST
import com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN
import com.android.net.module.util.arp.ArpPacket
import com.android.networkstack.metrics.NetworkQuirkMetrics
import com.android.networkstack.packets.NeighborAdvertisement
import com.android.networkstack.packets.NeighborSolicitation
import com.android.networkstack.util.NetworkStackUtils
+import com.android.networkstack.util.NetworkStackUtils.isAtLeast25Q2
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.quitResources
+import com.android.testutils.tryTest
+import com.android.testutils.visibleOnHandlerThread
import com.android.testutils.waitForIdle
+import com.google.common.truth.Truth.assertThat
import java.io.FileDescriptor
+import java.net.Inet4Address
import java.net.Inet6Address
import java.net.InetAddress
+import kotlin.random.Random
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
import libcore.io.IoUtils
import org.junit.After
+import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
@@ -114,14 +139,18 @@
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito
+import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import org.mockito.invocation.InvocationOnMock
+open class FromU<Type>(val value: Type)
+
/**
* Test for APF filter.
*/
@@ -131,12 +160,27 @@
class ApfFilterTest {
companion object {
private const val THREAD_QUIT_MAX_RETRY_COUNT = 3
+ private const val NO_CALLBACK_TIMEOUT_MS: Long = 500
private const val TAG = "ApfFilterTest"
+
+ @Parameterized.Parameters
+ @JvmStatic
+ fun data(): Iterable<Any?> {
+ return mutableListOf<Int?>(
+ ApfJniUtils.APF_INTERPRETER_VERSION_V6,
+ ApfJniUtils.APF_INTERPRETER_VERSION_NEXT
+ )
+ }
}
@get:Rule
val ignoreRule = DevSdkIgnoreRule()
+ // Indicates which apfInterpreter to load.
+ @Parameterized.Parameter(0)
+ @JvmField
+ var apfInterpreterVersion: Int = ApfJniUtils.APF_INTERPRETER_VERSION_NEXT
+
@Mock
private lateinit var context: Context
@@ -144,11 +188,11 @@
@Mock private lateinit var dependencies: Dependencies
- @Mock private lateinit var ipClientCallback: IpClientCallbacksWrapper
+ @Mock private lateinit var apfController: ApfFilter.IApfController
@Mock private lateinit var nsdManager: NsdManager
@GuardedBy("mApfFilterCreated")
- private val mApfFilterCreated = ArrayList<AndroidPacketFilter>()
+ private val mApfFilterCreated = ArrayList<ApfFilter>()
private val loInterfaceParams = InterfaceParams.getByName("lo")
private val ifParams =
InterfaceParams(
@@ -175,6 +219,7 @@
intArrayOf(0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x01, 0, 0, 0x1b, 0x44, 0x55, 0x66, 0x77)
.map{ it.toByte() }.toByteArray()
)
+ private val hostLinkLocalIpv6Address = InetAddresses.parseNumericAddress("fe80::3")
private val hostIpv6TentativeAddresses = listOf(
// 2001::200:1a:1234:5678
intArrayOf(0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x02, 0, 0, 0x1a, 0x12, 0x34, 0x56, 0x78)
@@ -197,16 +242,211 @@
intArrayOf(0x33, 0x33, 0xff, 0x55, 0x66, 0x77).map { it.toByte() }.toByteArray(),
// 33:33:ff:bb:cc:dd
intArrayOf(0x33, 0x33, 0xff, 0xbb, 0xcc, 0xdd).map { it.toByte() }.toByteArray(),
+ ETH_MULTICAST_MDNS_V4_MAC_ADDRESS,
+ ETH_MULTICAST_MDNS_V6_MAC_ADDRESS
)
+ // Using scapy to generate payload:
+ // answers = [
+ // DNSRR(rrname="_googlecast._tcp.local", type="PTR", ttl=120, rdata="gambit-3cb56c6253638b3641e3d289013cc0ae._googlecast._tcp.local."),
+ // DNSRR(rrname="gambit-3cb56c6253638b3641e3d289013cc0ae._googlecast._tcp.local", type="SRV", ttl=120, rdata="0 0 8009 3cb56c62-5363-8b36-41e3-d289013cc0ae.local."),
+ // DNSRR(rrname="gambit-3cb56c6253638b3641e3d289013cc0ae._googlecast._tcp.local", type="TXT", ttl=120, rdata=' "id=3cb56c6253638b3641e3d289013cc0ae cd=8ECC37F6755390D005DFC02F8EC0D4FA rm=4ABD579644ACFCCF ve=05 md=gambit ic=/setup/icon.png fn=gambit a=264709 st=0 bs=FA8FFD2242A7 nf=1 rs= ',),
+ // DNSRR(rrname="Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", type="A", ttl=120, rdata="100.89.85.228"),
+ // DNSRR(rrname="Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", type="AAAA", ttl=120, rdata="fe80:0000:0000:0000:0000:0000:0000:0003"),
+ // DNSRR(rrname="Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", type="AAAA", ttl=120, rdata="200a:0000:0000:0000:0000:0000:0000:0003"),
+ // DNSRR(rrname="Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", type="AAAA", ttl=120, rdata="200b:0000:0000:0000:0000:0000:0000:0003"),
+ // ]
+ // dns = dns_compress(DNS(qr=1, aa=1, rd=0, qd=None, an=answers))
+ private val castOffloadPayload = """
+ 0000840000000007000000000b5f676f6f676c6563617374045f746370056c6
+ f63616c00000c000100000078002a2767616d6269742d336362353663363235
+ 3336333862333634316533643238393031336363306165c00c01c0000021000
+ 100000078003430203020383030392033636235366336322d353336332d3862
+ 33362d343165332d6432383930313363633061652e6c6f63616c2e01c000001
+ 000010000007800b3b2202269643d3363623536633632353336333862333634
+ 3165336432383930313363633061652063643d3845434333374636373535333
+ 93044303035444643303246384543304434464120726d3d3441424435373936
+ 34344143464343462076653d3035206d643d67616d6269742069633d2f73657
+ 475702f69636f6e2e706e6720666e3d67616d62697420613d32363437303920
+ 73743d302062733d464138464644323234324137206e663d312072733d20284
+ 16e64726f69645f663437616331306235386363346238386263336635653761
+ 3831653539383732c01d00010001000000780004645955e4c157001c0001000
+ 000780010fe800000000000000000000000000003c157001c00010000007800
+ 10200a0000000000000000000000000003c157001c0001000000780010200b0
+ 000000000000000000000000003
+ """.replace("\\s+".toRegex(), "").trim()
+
+ // Using scapy to generate payload:
+ // answers = [
+ // DNSRR(rrname="_androidtvremote2._tcp.local", type="PTR", rdata="gambit._androidtvremote2._tcp.local", ttl=120),
+ // DNSRR(rrname="gambit._androidtvremote2._tcp.local", type="SRV", rdata="0 0 6466 Android_2570595cc11d4af4a4b7146b946eeb9e.local", ttl=120),
+ // DNSRR(rrname="gambit._androidtvremote2._tcp.local", type="TXT", rdata='''"bt=3C:4E:56:76:1E:E9"''', ttl=120),
+ // DNSRR(rrname="Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", type="A", ttl=120, rdata="100.89.85.228"),
+ // DNSRR(rrname="Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", type="AAAA", ttl=120, rdata="fe80:0000:0000:0000:0000:0000:0000:0003"),
+ // DNSRR(rrname="Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", type="AAAA", ttl=120, rdata="200a:0000:0000:0000:0000:0000:0000:0003"),
+ // DNSRR(rrname="Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", type="AAAA", ttl=120, rdata="200b:0000:0000:0000:0000:0000:0000:0003"),
+ // ]
+ // dns = dns_compress(DNS(qr=1, aa=1, rd=0, qd=None, an=answers))
+ val tvRemoteOffloadPayload = """
+ 000084000000000700000000115f616e64726f6964747672656d6f746532045
+ f746370056c6f63616c00000c00010000007800090667616d626974c00cc034
+ 00210001000000780037302030203634363620416e64726f69645f323537303
+ 53935636331316434616634613462373134366239343665656239652e6c6f63
+ 616cc03400100001000000780017162262743d33433a34453a35363a37363a3
+ 1453a45392228416e64726f69645f6634376163313062353863633462383862
+ 633366356537613831653539383732c02300010001000000780004645955e4c
+ 0a3001c0001000000780010fe800000000000000000000000000003c0a3001c
+ 0001000000780010200a0000000000000000000000000003c0a3001c0001000
+ 000780010200b0000000000000000000000000003
+ """.replace("\\s+".toRegex(), "").trim()
+
+ // answers = [
+ // DNSRR(rrname="_airplay._tcp.local", type="PTR", rdata="gambit._airplay._tcp.local", ttl=120),
+ // DNSRR(rrname="gambit._airplay._tcp.local", type="SRV", rdata="0 0 6466 Android_2570595cc11d4af4a4b7146b946eeb9e.local", ttl=120),
+ // DNSRR(rrname="gambit._airplay._tcp.local", type="TXT", rdata='"deviceid=58:55:CA:1A:E2:88 features=0x39f7 model=AppleTV2,1 srcvers=130.14"', ttl=120), DNSRR(rrname="Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", type="A", ttl=120, rdata="100.89.85.228"),
+ // DNSRR(rrname="Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", type="AAAA", ttl=120, rdata="fe80:0000:0000:0000:0000:0000:0000:0003"),
+ // DNSRR(rrname="Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", type="AAAA", ttl=120, rdata="200a:0000:0000:0000:0000:0000:0000:0003"),
+ // DNSRR(rrname="Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", type="AAAA", ttl=120, rdata="200b:0000:0000:0000:0000:0000:0000:0003"),
+ // ]
+ // dns = dns_compress(DNS(qr=1, aa=1, rd=0, qd=None, an=answers))
+ val airplayOffloadPayload = """
+ 000084000000000700000000085f616972706c6179045f746370056c6f63616
+ c00000c00010000007800090667616d626974c00cc02b002100010000007800
+ 37302030203634363620416e64726f69645f323537303539356363313164346
+ 16634613462373134366239343665656239652e6c6f63616cc02b0010000100
+ 000078004d4c2264657669636569643d35383a35353a43413a31413a45323a3
+ 8382066656174757265733d307833396637206d6f64656c3d4170706c655456
+ 322c3120737263766572733d3133302e31342228416e64726f69645f6634376
+ 163313062353863633462383862633366356537613831653539383732c01a00
+ 010001000000780004645955e4c0d0001c0001000000780010fe80000000000
+ 0000000000000000003c0d0001c0001000000780010200a0000000000000000
+ 000000000003c0d0001c0001000000780010200b00000000000000000000000
+ 00003
+ """.replace("\\s+".toRegex(), "").trim()
+
+ // answers = [
+ // DNSRR(rrname="_raop._tcp.local", type="PTR", rdata="5855CA1AE288@gambit._raop._tcp.local", ttl=120),
+ // DNSRR(rrname="5855CA1AE288@gambit._raop._tcp.local", type="SRV", rdata="0 0 6466 Android_2570595cc11d4af4a4b7146b946eeb9e.local", ttl=120),
+ // DNSRR(rrname="5855CA1AE288@gambit._raop._tcp.local", type="TXT", rdata='"txtvers=1 ch=2 cn=0,1,2,3 da=true et=0,3,5 md=0,1,2 pw=false sv=false sr=44100 ss=16 tp=UDP vn=65537 vs=130.14 am=AppleTV2,1 sf=0x4"', ttl=120),
+ // DNSRR(rrname="Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", type="A", ttl=120, rdata="100.89.85.228"),
+ // DNSRR(rrname="Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", type="AAAA", ttl=120, rdata="fe80:0000:0000:0000:0000:0000:0000:0003"),
+ // DNSRR(rrname="Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", type="AAAA", ttl=120, rdata="200a:0000:0000:0000:0000:0000:0000:0003"),
+ // DNSRR(rrname="Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", type="AAAA", ttl=120, rdata="200b:0000:0000:0000:0000:0000:0000:0003"),
+ // ]
+ // dns = dns_compress(DNS(qr=1, aa=1, rd=0, qd=None, an=answers))
+ val raopOffloadPayload = """
+ 000084000000000700000000055f72616f70045f746370056c6f63616c00000
+ c0001000000780016133538353543413141453238384067616d626974c00cc0
+ 2800210001000000780037302030203634363620416e64726f69645f3235373
+ 0353935636331316434616634613462373134366239343665656239652e6c6f
+ 63616cc028001000010000007800868522747874766572733d312063683d322
+ 0636e3d302c312c322c332064613d747275652065743d302c332c35206d643d
+ 302c312c322070773d66616c73652073763d66616c73652073723d343431303
+ 02073733d31362074703d55445020766e3d36353533372076733d3133302e31
+ 3420616d3d4170706c655456322c312073663d3078342228416e64726f69645
+ f66343761633130623538636334623838626333663565376138316535393837
+ 32c01700010001000000780004645955e4c113001c0001000000780010fe800
+ 000000000000000000000000003c113001c0001000000780010200a00000000
+ 00000000000000000003c113001c0001000000780010200b000000000000000
+ 0000000000003
+ """.replace("\\s+".toRegex(), "").trim()
+
+ private val passthroughCastOffloadInfo by lazy {
+ FromU(
+ OffloadServiceInfo(
+ OffloadServiceInfo.Key(
+ "gambit-3cb56c6253638b3641e3d289013cc0ae",
+ "_googlecast._tcp"
+ ),
+ listOf(),
+ "Android_f47ac10b58cc4b88bc3f5e7a81e59872.local",
+ null,
+ 0,
+ OffloadEngine.OFFLOAD_TYPE_REPLY.toLong()
+ )
+ )
+ }
+
+ private val castOffloadInfo by lazy {
+ FromU(
+ OffloadServiceInfo(
+ OffloadServiceInfo.Key(
+ "gambit-3cb56c6253638b3641e3d289013cc0ae",
+ "_googlecast._tcp"
+ ),
+ listOf(),
+ "Android_f47ac10b58cc4b88bc3f5e7a81e59872.local",
+ HexDump.hexStringToByteArray(castOffloadPayload),
+ 1,
+ OffloadEngine.OFFLOAD_TYPE_REPLY.toLong()
+ )
+ )
+ }
+ private val tvRemoteOffloadInfo by lazy {
+ FromU(
+ OffloadServiceInfo(
+ OffloadServiceInfo.Key("gambit", "_androidtvremote2._tcp"),
+ listOf(),
+ "Android_f47ac10b58cc4b88bc3f5e7a81e59872.local",
+ HexDump.hexStringToByteArray(tvRemoteOffloadPayload),
+ 2,
+ OffloadEngine.OFFLOAD_TYPE_REPLY.toLong()
+ )
+ )
+ }
+ private val manySubtypeOffloadInfo by lazy {
+ FromU(
+ OffloadServiceInfo(
+ OffloadServiceInfo.Key("gambit", "_testsubtype._tcp"),
+ listOf("subtype1", "subtype2", "subtype3", "subtype4", "subtype5"),
+ "Android_f47ac10b58cc4b88bc3f5e7a81e59872.local",
+ HexDump.hexStringToByteArray(castOffloadPayload),
+ 3,
+ OffloadEngine.OFFLOAD_TYPE_REPLY.toLong()
+ )
+ )
+ }
+
+ private val airplayOffloadInfo by lazy {
+ FromU(
+ OffloadServiceInfo(
+ OffloadServiceInfo.Key("gambit", "_airplay._tcp"),
+ listOf(),
+ "Android_f47ac10b58cc4b88bc3f5e7a81e59872.local",
+ HexDump.hexStringToByteArray(airplayOffloadPayload),
+ 4,
+ OffloadEngine.OFFLOAD_TYPE_REPLY.toLong()
+ )
+ )
+ }
+
+ private val raopOffloadInfo by lazy {
+ FromU(
+ OffloadServiceInfo(
+ OffloadServiceInfo.Key("5855CA1AE288@gambit", "_raop._tcp"),
+ listOf(),
+ "Android_f47ac10b58cc4b88bc3f5e7a81e59872.local",
+ HexDump.hexStringToByteArray(raopOffloadPayload),
+ 4,
+ OffloadEngine.OFFLOAD_TYPE_REPLY.toLong()
+ )
+
+ )
+ }
+ private val counterTotalSize = ApfCounterTracker.Counter.totalSize()
+
private val handlerThread by lazy {
HandlerThread("$TAG handler thread").apply { start() }
}
private val handler by lazy { Handler(handlerThread.looper) }
- private var writerSocket = FileDescriptor()
+ private lateinit var raReadSocket: FileDescriptor
+ private var raWriterSocket = FileDescriptor()
+ private var mcastWriteSocket = FileDescriptor()
+ private lateinit var apfTestHelpers: ApfTestHelpers
@Before
fun setUp() {
+ apfTestHelpers = ApfTestHelpers(apfInterpreterVersion)
MockitoAnnotations.initMocks(this)
// mock anycast6 address from /proc/net/anycast6
doReturn(hostAnycast6Addresses).`when`(dependencies).getAnycast6Addresses(any())
@@ -222,9 +462,15 @@
}
}.`when`(dependencies).onApfFilterCreated(any())
doReturn(SystemClock.elapsedRealtime()).`when`(dependencies).elapsedRealtime()
- val readSocket = FileDescriptor()
- Os.socketpair(AF_UNIX, SOCK_STREAM, 0, writerSocket, readSocket)
- doReturn(readSocket).`when`(dependencies).createPacketReaderSocket(anyInt())
+ raReadSocket = FileDescriptor()
+ Os.socketpair(AF_UNIX, SOCK_STREAM, 0, raWriterSocket, raReadSocket)
+ doReturn(raReadSocket).`when`(dependencies).createPacketReaderSocket(anyInt())
+ val mcastReadSocket = FileDescriptor()
+ Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mcastWriteSocket, mcastReadSocket)
+ doReturn(mcastReadSocket)
+ .`when`(dependencies).createEgressIgmpReportsReaderSocket(anyInt())
+ doReturn(mcastReadSocket)
+ .`when`(dependencies).createEgressMulticastReportsReaderSocket(anyInt())
doReturn(nsdManager).`when`(context).getSystemService(NsdManager::class.java)
}
@@ -235,7 +481,7 @@
mApfFilterCreated.clear()
return@quitResources ret
}
- }, { apf: AndroidPacketFilter ->
+ }, { apf: ApfFilter ->
handler.post { apf.shutdown() }
})
@@ -250,16 +496,19 @@
@After
fun tearDown() {
- IoUtils.closeQuietly(writerSocket)
+ IoUtils.closeQuietly(raWriterSocket)
+ IoUtils.closeQuietly(mcastWriteSocket)
shutdownApfFilters()
handler.waitForIdle(TIMEOUT_MS)
Mockito.framework().clearInlineMocks()
- ApfJniUtils.resetTransmittedPacketMemory()
+ apfTestHelpers.resetTransmittedPacketMemory()
handlerThread.quitSafely()
handlerThread.join()
}
- private fun getDefaultConfig(apfVersion: Int = APF_VERSION_6): ApfFilter.ApfConfiguration {
+ private fun getDefaultConfig(
+ apfVersion: Int = apfInterpreterVersion
+ ): ApfFilter.ApfConfiguration {
val config = ApfFilter.ApfConfiguration()
config.apfVersionSupported = apfVersion
// 4K is the highly recommended value in APFv6 for vendor
@@ -267,13 +516,13 @@
config.multicastFilter = false
config.ieee802_3Filter = false
config.ethTypeBlackList = IntArray(0)
- config.shouldHandleArpOffload = true
- config.shouldHandleNdOffload = true
+ config.handleArpOffload = true
+ config.handleNdOffload = true
return config
}
private fun getApfFilter(
- apfCfg: ApfFilter.ApfConfiguration = getDefaultConfig(APF_VERSION_6)
+ apfCfg: ApfFilter.ApfConfiguration = getDefaultConfig(apfInterpreterVersion)
): ApfFilter {
lateinit var apfFilter: ApfFilter
handler.post {
@@ -282,7 +531,7 @@
context,
apfCfg,
ifParams,
- ipClientCallback,
+ apfController,
metrics,
dependencies
)
@@ -291,8 +540,28 @@
return apfFilter
}
+ private fun getIgmpApfFilter(): ApfFilter {
+ val mcastAddrs = listOf(
+ InetAddress.getByName("224.0.0.1") as Inet4Address,
+ InetAddress.getByName("239.0.0.1") as Inet4Address,
+ InetAddress.getByName("239.0.0.2") as Inet4Address,
+ InetAddress.getByName("239.0.0.3") as Inet4Address
+ )
+ val apfConfig = getDefaultConfig()
+ apfConfig.handleIgmpOffload = true
+
+ // mock IPv4 multicast address from /proc/net/igmp
+ doReturn(mcastAddrs).`when`(dependencies).getIPv4MulticastAddresses(any())
+ val apfFilter = getApfFilter(apfConfig)
+ val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24)
+ val lp = LinkProperties()
+ lp.addLinkAddress(linkAddress)
+ apfFilter.setLinkProperties(lp)
+ return apfFilter
+ }
+
private fun doTestEtherTypeAllowListFilter(apfFilter: ApfFilter) {
- val program = consumeInstalledProgram(ipClientCallback, installCnt = 2)
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
// Using scapy to generate IPv4 mDNS packet:
// eth = Ether(src="E8:9F:80:66:60:BB", dst="01:00:5E:00:00:FB")
@@ -304,7 +573,7 @@
01005e0000fbe89f806660bb080045000035000100004011d812c0a80101e00000f
b14e914e900214d970000010000010000000000000161056c6f63616c00000c0001
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(mdnsPkt),
@@ -320,7 +589,7 @@
333300000001e89f806660bb86dd6000000000103afffe800000000000000000000000
000001ff0200000000000000000000000000018600600700080e100000000000000e10
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(raPkt),
@@ -330,7 +599,7 @@
// Using scapy to generate ethernet packet with type 0x88A2:
// p = Ether(type=0x88A2)/Raw(load="01")
val ethPkt = "ffffffffffff047bcb463fb588a23031"
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(ethPkt),
@@ -384,6 +653,16 @@
return naPacket
}
+ private fun updateIPv4MulticastAddrs(apfFilter: ApfFilter, mcastAddrs: List<Inet4Address>) {
+ doReturn(mcastAddrs).`when`(dependencies).getIPv4MulticastAddresses(any())
+ apfFilter.updateMulticastAddrs()
+ }
+
+ private fun updateIPv6MulticastAddrs(apfFilter: ApfFilter, mcastAddrs: List<Inet6Address>) {
+ doReturn(mcastAddrs).`when`(dependencies).getIPv6MulticastAddresses(any())
+ apfFilter.updateMulticastAddrs()
+ }
+
@Test
@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
fun testV4EtherTypeAllowListFilter() {
@@ -394,7 +673,7 @@
@Test
@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
fun testV6EtherTypeAllowListFilter() {
- val apfFilter = getApfFilter(getDefaultConfig(APF_VERSION_6))
+ val apfFilter = getApfFilter(getDefaultConfig(apfInterpreterVersion))
doTestEtherTypeAllowListFilter(apfFilter)
}
@@ -402,7 +681,7 @@
fun testIPv4PacketFilterOnV6OnlyNetwork() {
val apfFilter = getApfFilter()
apfFilter.updateClatInterfaceState(true)
- val program = consumeInstalledProgram(ipClientCallback, installCnt = 3)
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3)
// Using scapy to generate IPv4 mDNS packet:
// eth = Ether(src="E8:9F:80:66:60:BB", dst="01:00:5E:00:00:FB")
@@ -414,7 +693,7 @@
01005e0000fbe89f806660bb080045000035000100004011d812c0a80101e00000f
b14e914e900214d970000010000010000000000000161056c6f63616c00000c0001
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(mdnsPkt),
@@ -428,7 +707,7 @@
val nonUdpPkt = """
ffffffffffff00112233445508004500001400010000400cb934c0a80101ffffffff
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(nonUdpPkt),
@@ -442,7 +721,7 @@
val fragmentUdpPkt = """
ffffffffffff0011223344550800450000140001200a40119925c0a80101ffffffff
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(fragmentUdpPkt),
@@ -457,7 +736,7 @@
val nonDhcpServerPkt = """
ffffffffffff00112233445508004500001c000100004011b927c0a80101ffffffff0035004600083dba
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(nonDhcpServerPkt),
@@ -492,7 +771,7 @@
0000000000000000000000000000000000000000000000000000638253633501023604c0
a801010104ffffff000304c0a80101330400015180060408080808ff
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(dhcp4Pkt),
@@ -511,7 +790,7 @@
0000000000000000000000000000000000000000000000000000638253633501023604c0
a801010104ffffff000304c0a80101330400015180060408080808ff
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(dhcp4PktDf),
@@ -530,7 +809,7 @@
01005e0000fbe89f806660bb08004500001d000100034011f75dc0a8010ac0a8
01146f63616c00000c0001
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(fragmentedUdpPkt),
@@ -542,7 +821,7 @@
fun testLoopbackFilter() {
val apfConfig = getDefaultConfig()
val apfFilter = getApfFilter(apfConfig)
- val program = consumeInstalledProgram(ipClientCallback, installCnt = 2)
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
// Using scapy to generate echo-ed broadcast packet:
// ether = Ether(src=${ifParams.macAddr}, dst='ff:ff:ff:ff:ff:ff')
// ip = IP(src='192.168.1.1', dst='255.255.255.255', proto=21)
@@ -550,11 +829,1147 @@
val nonDhcpBcastPkt = """
ffffffffffff020304050607080045000014000100004015b92bc0a80101ffffffff
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(nonDhcpBcastPkt),
- PASSED_ETHER_OUR_SRC_MAC
+ if (isAtLeast25Q2()) DROPPED_ETHER_OUR_SRC_MAC else PASSED_ETHER_OUR_SRC_MAC
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testInvalidIgmpPacketDropped() {
+ val apfFilter = getIgmpApfFilter()
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3)
+ // Using scapy to generate invalid length IGMPv1 general query packet:
+ // ether = Ether(src='00:11:22:33:44:55', dst='01:00:5e:00:00:01')
+ // ip = IP(src='10.0.0.2', dst='224.0.0.1', len=24, proto=2)
+ // payload = Raw(b'\x11\x00\xee\xff\x01\x02\x03\x04\x05\x06')
+ // pkt = ether/ip/payload
+ val payloadLen10Pkt = """
+ 01005e00000100112233445508004500001800010000400290e00a000002e00000011100eeff010203040506
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(payloadLen10Pkt),
+ DROPPED_IGMP_INVALID
+ )
+
+ // Using scapy to generate invalid length IGMPv1 general query packet:
+ // ether = Ether(src='00:11:22:33:44:55', dst='01:00:5e:00:00:01')
+ // ip = IP(src='10.0.0.2', dst='224.0.0.1', len=20, proto=2)
+ // payload = Raw(b'\x11\x00\xee\xff\x01\x02')
+ // pkt = ether/ip/payload
+ val payloadLen7Pkt = """
+ 01005e00000100112233445508004500001400010000400290e40a000002e00000011100eeff010203
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(payloadLen7Pkt),
+ DROPPED_IGMP_INVALID
+ )
+
+ // Using scapy to generate invalid length IGMP general query which the destination IP is
+ // not 224.0.0.1:
+ // ether = Ether(src='00:11:22:33:44:55', dst='01:00:5e:00:00:05')
+ // ip = IP(src='10.0.0.2', dst='224.0.0.5')
+ // igmp = IGMP(type=0x11, mrcode=0)
+ // pkt = ether/ip/igmp
+ val pktWithWrongDst = """
+ 01005e00000300112233445508004500001c000100000102cfda0a000002e00000031100eeff00000000
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(pktWithWrongDst),
+ DROPPED_IGMP_INVALID
+ )
+
+ // Using scapy to generate invalid IGMP general query with wrong type:
+ // ether = Ether(src='00:11:22:33:44:55', dst='01:00:5e:00:00:01')
+ // ip = IP(src='10.0.0.2', dst='224.0.0.1')
+ // igmp = IGMP(type=0x51, mrcode=0)
+ // pkt = ether/ip/igmp
+ val pktWithWrongType = """
+ 01005e00000100112233445508004500001c000100000102cfdc0a000002e00000015100aeff00000000
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(pktWithWrongType),
+ DROPPED_IGMP_INVALID
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testIgmpV1ReportDropped() {
+ val apfFilter = getIgmpApfFilter()
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3)
+ // Using scapy to generate IGMPv1 report packet:
+ // ether = Ether(src='00:11:22:33:44:55', dst='01:00:5e:7f:00:01')
+ // ip = IP(src='10.0.0.2', dst='239.0.0.1')
+ // igmp = IGMP(type=0x12, mrcode=0, gaddr='239.0.0.1')
+ // pkt = ether/ip/igmp
+ val pkt = """
+ 01005e7f000100112233445508004500001c000100000102c0dc0a000002ef0000011200fefdef000001
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(pkt),
+ DROPPED_IGMP_REPORT
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testIgmpV1GeneralQueryPassed() {
+ val apfFilter = getIgmpApfFilter()
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3)
+ // Using scapy to generate IGMPv1 general query packet:
+ // ether = Ether(src='00:11:22:33:44:55', dst='01:00:5e:00:00:01')
+ // ip = IP(src='10.0.0.2', dst='224.0.0.1')
+ // igmp = IGMP(type=0x11, mrcode=0)
+ // pkt = ether/ip/igmp
+ val pkt = """
+ 01005e00000100112233445508004500001c000100000102cfdc0a000002e00000011100eeff00000000
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(pkt),
+ PASSED_IPV4
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testIgmpV2ReportDropped() {
+ val apfFilter = getIgmpApfFilter()
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3)
+ // Using scapy to generate IGMPv2 report packet:
+ // ether = Ether(src='00:11:22:33:44:55', dst='01:00:5e:7f:00:01')
+ // ip = IP(src='10.0.0.2', dst='239.0.0.1')
+ // igmp = IGMP(type=0x16, gaddr='239.0.0.1')
+ // pkt = ether/ip/igmp
+ val v2ReportPkt = """
+ 01005e7f000100112233445508004500001c000100000102c0dc0a000002ef0000011614fae9ef000001
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(v2ReportPkt),
+ DROPPED_IGMP_REPORT
+ )
+
+ // Using scapy to generate IGMPv2 leave packet:
+ // ether = Ether(src='00:11:22:33:44:55', dst='01:00:5e:7f:00:01')
+ // ip = IP(src='10.0.0.2', dst='239.0.0.1')
+ // igmp = IGMP(type=0x17, gaddr='239.0.0.1')
+ // pkt = ether/ip/igmp
+ val v2LeaveReportPkt = """
+ 01005e7f000100112233445508004500001c000100000102c0dc0a000002ef0000011714f9e9ef000001
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(v2LeaveReportPkt),
+ DROPPED_IGMP_REPORT
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testIgmpV2GeneralQueryReplied() {
+ val apfFilter = getIgmpApfFilter()
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3)
+ // Using scapy to generate IGMPv2 general query packet without router alert option:
+ // ether = Ether(src='00:11:22:33:44:55', dst='01:00:5e:00:00:01')
+ // ip = IP(src='10.0.0.2', dst='224.0.0.1')
+ // igmp = IGMP(type=0x11)
+ // pkt = ether/ip/igmp
+ val pkt = """
+ 01005e00000100112233445508004500001c000100000102cfdc0a000002e00000011114eeeb00000000
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(pkt),
+ DROPPED_IGMP_V2_GENERAL_QUERY_REPLIED
+ )
+
+ val igmpv2ReportPkts = setOf(
+ // ###[ Ethernet ]###
+ // dst = 01:00:5e:00:00:01
+ // src = 02:03:04:05:06:07
+ // type = IPv4
+ // ###[ IP ]###
+ // version = 4
+ // ihl = 6
+ // tos = 0xc0
+ // len = 32
+ // id = 0
+ // flags = DF
+ // frag = 0
+ // ttl = 1
+ // proto = igmp
+ // chksum = 0xeb15
+ // src = 10.0.0.1
+ // dst = 239.0.0.1
+ // \options \
+ // |###[ IP Option Router Alert ]###
+ // | copy_flag = 1
+ // | optclass = control
+ // | option = router_alert
+ // | length = 4
+ // | alert = router_shall_examine_packet
+ // ###[ IGMP ]###
+ // type = Version 2 - Membership Report
+ // mrcode = 0
+ // chksum = 0xfafd
+ // gaddr = 239.0.0.1
+ """
+ 01005e000001020304050607080046c00020000040000102eb150a000001ef000001940400001600fafd
+ ef000001
+ """.replace("\\s+".toRegex(), "").trim().uppercase(),
+
+ // ###[ Ethernet ]###
+ // dst = 01:00:5e:00:00:02
+ // src = 02:03:04:05:06:07
+ // type = IPv4
+ // ###[ IP ]###
+ // version = 4
+ // ihl = 6
+ // tos = 0xc0
+ // len = 32
+ // id = 0
+ // flags = DF
+ // frag = 0
+ // ttl = 1
+ // proto = igmp
+ // chksum = 0xeb14
+ // src = 10.0.0.1
+ // dst = 239.0.0.2
+ // \options \
+ // |###[ IP Option Router Alert ]###
+ // | copy_flag = 1
+ // | optclass = control
+ // | option = router_alert
+ // | length = 4
+ // | alert = router_shall_examine_packet
+ // ###[ IGMP ]###
+ // type = Version 2 - Membership Report
+ // mrcode = 0
+ // chksum = 0xfafc
+ // gaddr = 239.0.0.2
+ """
+ 01005e000002020304050607080046c00020000040000102eb140a000001ef000002940400001600fafc
+ ef000002
+ """.replace("\\s+".toRegex(), "").trim().uppercase(),
+ // ###[ Ethernet ]###
+ // dst = 01:00:5e:00:00:03
+ // src = 02:03:04:05:06:07
+ // type = IPv4
+ // ###[ IP ]###
+ // version = 4
+ // ihl = 6
+ // tos = 0xc0
+ // len = 32
+ // id = 0
+ // flags = DF
+ // frag = 0
+ // ttl = 1
+ // proto = igmp
+ // chksum = 0xeb13
+ // src = 10.0.0.1
+ // dst = 239.0.0.3
+ // \options \
+ // |###[ IP Option Router Alert ]###
+ // | copy_flag = 1
+ // | optclass = control
+ // | option = router_alert
+ // | length = 4
+ // | alert = router_shall_examine_packet
+ // ###[ IGMP ]###
+ // type = Version 2 - Membership Report
+ // mrcode = 0
+ // chksum = 0xfafb
+ // gaddr = 239.0.0.3
+ """
+ 01005e000003020304050607080046c00020000040000102eb130a000001ef000003940400001600fafb
+ ef000003
+ """.replace("\\s+".toRegex(), "").trim().uppercase()
+ )
+
+ val transmitPackets = apfTestHelpers.getAllTransmittedPackets()
+ .map { HexDump.toHexString(it).uppercase() }.toSet()
+ assertEquals(igmpv2ReportPkts, transmitPackets)
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testIgmpV2GeneralQueryWithRouterAlertOptionReplied() {
+ val apfFilter = getIgmpApfFilter()
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3)
+ // Using scapy to generate IGMPv2 general query packet with router alert option:
+ // ether = Ether(src='00:11:22:33:44:55', dst='01:00:5e:00:00:01')
+ // ip = IP(src='10.0.0.2', dst='224.0.0.1', options=[IPOption_Router_Alert()])
+ // igmp = IGMP(type=0x11)
+ // pkt = ether/ip/igmp
+ val pkt = """
+ 01005e0000010011223344550800460000200001000001023ad40a000002e0000001940400001114eeeb
+ 00000000
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(pkt),
+ DROPPED_IGMP_V2_GENERAL_QUERY_REPLIED
+ )
+
+ val igmpv2ReportPkts = setOf(
+ // ###[ Ethernet ]###
+ // dst = 01:00:5e:00:00:01
+ // src = 02:03:04:05:06:07
+ // type = IPv4
+ // ###[ IP ]###
+ // version = 4
+ // ihl = 6
+ // tos = 0xc0
+ // len = 32
+ // id = 0
+ // flags = DF
+ // frag = 0
+ // ttl = 1
+ // proto = igmp
+ // chksum = 0xeb15
+ // src = 10.0.0.1
+ // dst = 239.0.0.1
+ // \options \
+ // |###[ IP Option Router Alert ]###
+ // | copy_flag = 1
+ // | optclass = control
+ // | option = router_alert
+ // | length = 4
+ // | alert = router_shall_examine_packet
+ // ###[ IGMP ]###
+ // type = Version 2 - Membership Report
+ // mrcode = 0
+ // chksum = 0xfafd
+ // gaddr = 239.0.0.1
+ """
+ 01005e000001020304050607080046c00020000040000102eb150a000001ef000001940400001600fafd
+ ef000001
+ """.replace("\\s+".toRegex(), "").trim().uppercase(),
+
+ // ###[ Ethernet ]###
+ // dst = 01:00:5e:00:00:02
+ // src = 02:03:04:05:06:07
+ // type = IPv4
+ // ###[ IP ]###
+ // version = 4
+ // ihl = 6
+ // tos = 0xc0
+ // len = 32
+ // id = 0
+ // flags = DF
+ // frag = 0
+ // ttl = 1
+ // proto = igmp
+ // chksum = 0xeb14
+ // src = 10.0.0.1
+ // dst = 239.0.0.2
+ // \options \
+ // |###[ IP Option Router Alert ]###
+ // | copy_flag = 1
+ // | optclass = control
+ // | option = router_alert
+ // | length = 4
+ // | alert = router_shall_examine_packet
+ // ###[ IGMP ]###
+ // type = Version 2 - Membership Report
+ // mrcode = 0
+ // chksum = 0xfafc
+ // gaddr = 239.0.0.2
+ """
+ 01005e000002020304050607080046c00020000040000102eb140a000001ef000002940400001600fafc
+ ef000002
+ """.replace("\\s+".toRegex(), "").trim().uppercase(),
+
+ // ###[ Ethernet ]###
+ // dst = 01:00:5e:00:00:03
+ // src = 02:03:04:05:06:07
+ // type = IPv4
+ // ###[ IP ]###
+ // version = 4
+ // ihl = 6
+ // tos = 0xc0
+ // len = 32
+ // id = 0
+ // flags = DF
+ // frag = 0
+ // ttl = 1
+ // proto = igmp
+ // chksum = 0xeb13
+ // src = 10.0.0.1
+ // dst = 239.0.0.3
+ // \options \
+ // |###[ IP Option Router Alert ]###
+ // | copy_flag = 1
+ // | optclass = control
+ // | option = router_alert
+ // | length = 4
+ // | alert = router_shall_examine_packet
+ // ###[ IGMP ]###
+ // type = Version 2 - Membership Report
+ // mrcode = 0
+ // chksum = 0xfafb
+ // gaddr = 239.0.0.3
+ """
+ 01005e000003020304050607080046c00020000040000102eb130a000001ef000003940400001600fafb
+ ef000003
+ """.replace("\\s+".toRegex(), "").trim().uppercase()
+ )
+
+ val transmitPackets = apfTestHelpers.getAllTransmittedPackets()
+ .map { HexDump.toHexString(it).uppercase() }.toSet()
+ assertEquals(igmpv2ReportPkts, transmitPackets)
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testIgmpV2GroupSpecificQueryPassed() {
+ val apfFilter = getIgmpApfFilter()
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3)
+ // Using scapy to generate IGMPv2 group specific query packet without router alert option:
+ // ether = Ether(src='00:11:22:33:44:55', dst='01:00:5e:7f:00:01')
+ // ip = IP(src='10.0.0.2', dst='239.0.0.1')
+ // igmp = IGMP(type=0x11, gaddr='239.0.0.1')
+ // pkt = ether/ip/igmp
+ val pkt = """
+ 01005e7f000100112233445508004500001c000100000102c0dc0a000002ef0000011114ffe9ef000001
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(pkt),
+ PASSED_IPV4
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testIgmpV3ReportDropped() {
+ val apfFilter = getIgmpApfFilter()
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3)
+ // Using scapy to generate IGMPv3 report packet without router alert option:
+ // ether = Ether(src='00:11:22:33:44:55', dst='01:00:5e:00:00:16')
+ // ip = IP(src='10.0.0.2', dst='224.0.0.22')
+ // igmp = IGMPv3(type=0x22)/IGMPv3mr(records=[IGMPv3gr(rtype=2, maddr='239.0.0.1')])
+ // pkt = ether/ip/igmp
+ val pkt = """
+ 01005e000001001122334455080045c00024000100000102cf140a000002e00000012200ecfc000000
+ 0102000000ef000001
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(pkt),
+ DROPPED_IGMP_REPORT
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testIgmpV3GeneralQueryReplied() {
+ val apfFilter = getIgmpApfFilter()
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3)
+ // Using scapy to generate IGMPv3 general query packet without router alert option:
+ // ether = Ether(src='00:11:22:33:44:55', dst='01:00:5e:00:00:01')
+ // ip = IP(src='10.0.0.2', dst='224.0.0.1')
+ // igmp = IGMPv3(type=0x11)/IGMPv3mq()
+ // pkt = ether/ip/igmp
+ val pkt = """
+ 01005e000001001122334455080045c00020000100000102cf180a000002e00000011114eeeb00000000
+ 00000000
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(pkt),
+ DROPPED_IGMP_V3_GENERAL_QUERY_REPLIED
+ )
+
+ val transmittedIgmpv3Reports = apfTestHelpers.consumeTransmittedPackets(1)
+
+ // ###[ Ethernet ]###
+ // dst = 01:00:5e:00:00:16
+ // src = 02:03:04:05:06:07
+ // type = IPv4
+ // ###[ IP ]###
+ // version = 4
+ // ihl = 6
+ // tos = 0xc0
+ // len = 56
+ // id = 0
+ // flags = DF
+ // frag = 0
+ // ttl = 1
+ // proto = igmp
+ // chksum = 0xf9e8
+ // src = 10.0.0.1
+ // dst = 224.0.0.22
+ // \options \
+ // |###[ IP Option Router Alert ]###
+ // | copy_flag = 1
+ // | optclass = control
+ // | option = router_alert
+ // | length = 4
+ // | alert = router_shall_examine_packet
+ // ###[ IGMPv3 ]###
+ // type = Version 3 Membership Report
+ // mrcode = 0
+ // chksum = 0xaf4
+ // ###[ IGMPv3mr ]###
+ // res2 = 0x0
+ // numgrp = 3
+ // \records \
+ // |###[ IGMPv3gr ]###
+ // | rtype = Mode Is Exclude
+ // | auxdlen = 0
+ // | numsrc = 0
+ // | maddr = 239.0.0.1
+ // | srcaddrs = []
+ // |###[ IGMPv3gr ]###
+ // | rtype = Mode Is Exclude
+ // | auxdlen = 0
+ // | numsrc = 0
+ // | maddr = 239.0.0.2
+ // | srcaddrs = []
+ // |###[ IGMPv3gr ]###
+ // | rtype = Mode Is Exclude
+ // | auxdlen = 0
+ // | numsrc = 0
+ // | maddr = 239.0.0.3
+ // | srcaddrs = []
+ val igmpv3ReportPkt = """
+ 01005e000016020304050607080046c00038000040000102f9e80a000001e00000169404000022000af40
+ 000000302000000ef00000102000000ef00000202000000ef000003
+ """.replace("\\s+".toRegex(), "").trim()
+
+ assertContentEquals(
+ HexDump.hexStringToByteArray(igmpv3ReportPkt),
+ transmittedIgmpv3Reports[0]
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testIgmpV3GeneralQueryWithRouterAlertOptionReplied() {
+ val apfFilter = getIgmpApfFilter()
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3)
+ // Using scapy to generate IGMPv3 general query packet with router alert option:
+ // ether = Ether(src='00:11:22:33:44:55', dst='01:00:5e:00:00:01')
+ // ip = IP(src='10.0.0.2', dst='224.0.0.1', options=[IPOption_Router_Alert()])
+ // igmp = IGMPv3(type=0x11)/IGMPv3mq()
+ // pkt = ether/ip/igmp
+ val pkt = """
+ 01005e000001001122334455080046c000240001000001023a100a000002e0000001940400001114eeeb0
+ 000000000000000
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(pkt),
+ DROPPED_IGMP_V3_GENERAL_QUERY_REPLIED
+ )
+
+ val transmittedIgmpv3Reports = apfTestHelpers.consumeTransmittedPackets(1)
+
+ // ###[ Ethernet ]###
+ // dst = 01:00:5e:00:00:16
+ // src = 02:03:04:05:06:07
+ // type = IPv4
+ // ###[ IP ]###
+ // version = 4
+ // ihl = 6
+ // tos = 0xc0
+ // len = 56
+ // id = 0
+ // flags = DF
+ // frag = 0
+ // ttl = 1
+ // proto = igmp
+ // chksum = 0xf9e8
+ // src = 10.0.0.1
+ // dst = 224.0.0.22
+ // \options \
+ // |###[ IP Option Router Alert ]###
+ // | copy_flag = 1
+ // | optclass = control
+ // | option = router_alert
+ // | length = 4
+ // | alert = router_shall_examine_packet
+ // ###[ IGMPv3 ]###
+ // type = Version 3 Membership Report
+ // mrcode = 0
+ // chksum = 0xaf4
+ // ###[ IGMPv3mr ]###
+ // res2 = 0x0
+ // numgrp = 3
+ // \records \
+ // |###[ IGMPv3gr ]###
+ // | rtype = Mode Is Exclude
+ // | auxdlen = 0
+ // | numsrc = 0
+ // | maddr = 239.0.0.1
+ // | srcaddrs = []
+ // |###[ IGMPv3gr ]###
+ // | rtype = Mode Is Exclude
+ // | auxdlen = 0
+ // | numsrc = 0
+ // | maddr = 239.0.0.2
+ // | srcaddrs = []
+ // |###[ IGMPv3gr ]###
+ // | rtype = Mode Is Exclude
+ // | auxdlen = 0
+ // | numsrc = 0
+ // | maddr = 239.0.0.3
+ // | srcaddrs = []
+ val igmpv3ReportPkt = """
+ 01005e000016020304050607080046c00038000040000102f9e80a000001e00000169404000022000af40
+ 000000302000000ef00000102000000ef00000202000000ef000003
+ """.replace("\\s+".toRegex(), "").trim()
+
+ assertContentEquals(
+ HexDump.hexStringToByteArray(igmpv3ReportPkt),
+ transmittedIgmpv3Reports[0]
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testIgmpV3GroupSpecificQueryPassed() {
+ val apfFilter = getIgmpApfFilter()
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3)
+ // Using scapy to generate IGMPv3 group specific query packet
+ // ether = Ether(src='00:11:22:33:44:55', dst='01:00:5e:7f:00:01')
+ // ip = IP(src='10.0.0.2', dst='239.0.0.1')
+ // igmp = IGMPv3(type=0x11)/IGMPv3mq(gaddr='239.0.0.1')
+ // pkt = ether/ip/igmp
+ val pkt = """
+ 01005e7f0001001122334455080045c00020000100000102c0180a000002ef0000011114ffe9ef000001
+ 00000000
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(pkt),
+ PASSED_IPV4
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testIgmpV3GroupAndSourceSpecificQueryPassed() {
+ val apfFilter = getIgmpApfFilter()
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3)
+ // Using scapy to generate IGMPv3 group and source specific query packet
+ // ether = Ether(src='00:11:22:33:44:55', dst='01:00:5e:7f:00:01')
+ // ip = IP(src='10.0.0.2', dst='239.0.0.1')
+ // igmp = IGMPv3(type=0x11)/IGMPv3mq(gaddr='239.0.0.1', numsrc=1, srcaddrs=['10.0.0.1'])
+ // pkt = ether/ip/igmp
+ val pkt = """
+ 01005e7f0001001122334455080045c00024000100000102c0140a000002ef0000011114f5e7ef0000010
+ 00000010a000001
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(pkt),
+ PASSED_IPV4
+ )
+ }
+
+ private fun getMldApfFilter(): ApfFilter {
+ val mcastAddrs = listOf(
+ InetAddress.getByName("ff12::1:1111:1111") as Inet6Address,
+ InetAddress.getByName("ff12::1:2222:2222") as Inet6Address,
+ InetAddress.getByName("ff12::1:3333:3333") as Inet6Address,
+ )
+ val apfConfig = getDefaultConfig()
+ apfConfig.handleMldOffload = true
+
+ // mock IPv6 multicast address from /proc/net/igmp6
+ doReturn(mcastAddrs).`when`(dependencies).getIPv6MulticastAddresses(any())
+ val apfFilter = getApfFilter(apfConfig)
+ val ipv6LinkAddress = LinkAddress(hostLinkLocalIpv6Address, 64)
+ val lp = LinkProperties()
+ lp.addLinkAddress(ipv6LinkAddress)
+ apfFilter.setLinkProperties(lp)
+ return apfFilter
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testIPv6PacketWithNonMldHopByHopPassed() {
+ val apfFilter = getMldApfFilter()
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3)
+ // Using scapy to generate MLDv1 general query with different HOPOPTS
+ // ether = Ether(src='00:11:22:33:44:55', dst='33:33:11:11:11:11')
+ // ipv6 = IPv6(src='fe80::fc01:83ff:fea6:3712', dst='ff02::1:1111:1111', hlim=1)
+ // hopOpts = IPv6ExtHdrHopByHop(options=[RouterAlert(otype=3)])
+ // mld = ICMPv6MLQuery()
+ // pkt = ether/ipv6/hopOpts/mld
+ var invalidHopOptPkt = """
+ 33331111111100112233445586dd6000000000200001fe80000000000000fc0183fffea63712ff020000
+ 0000000000000001111111113a000302000001008200813b271000000000000000000000000000000000
+ 0000
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(invalidHopOptPkt),
+ PASSED_IPV6_NON_ICMP
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testInvalidMldPacketDropped() {
+ val apfFilter = getMldApfFilter()
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3)
+ // Using scapy to generate MLDv1 general query with invalid source addr
+ // ether = Ether(src='00:11:22:33:44:55', dst='33:33:11:11:11:11')
+ // ipv6 = IPv6(src='ff02::1:4444:4444', dst='ff02::1:1111:1111', hlim=1)
+ // hopOpts = IPv6ExtHdrHopByHop(options=[RouterAlert(otype=5)])
+ // mld = ICMPv6MLQuery()
+ // pkt = ether/ipv6/hopOpts/mld
+ var invalidSrcIpPkt = """
+ 33331111111100112233445586dd6000000000200001ff020000000000000000000144444444ff02000
+ 00000000000000001111111113a000502000001008200adea2710000000000000000000000000000000
+ 000000
+ """.replace("\\s+".toRegex(), "").trim().uppercase()
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(invalidSrcIpPkt),
+ DROPPED_IPV6_MLD_INVALID
+ )
+
+ // Using scapy to generate MLDv1 general query with invalid hoplimit
+ // ether = Ether(src='00:11:22:33:44:55', dst='33:33:11:11:11:11')
+ // ipv6 = IPv6(src='fe80::fc01:83ff:fea6:3712', dst='ff02::1:1111:1111', hlim=5)
+ // hopOpts = IPv6ExtHdrHopByHop(options=[RouterAlert(otype=5)])
+ // mld = ICMPv6MLQuery()
+ // pkt = ether/ipv6/hopOpts/mld
+ var invalidHopLimitPkt = """
+ 33331111111100112233445586dd6000000000200005fe80000000000000fc0183fffea63712ff02000
+ 00000000000000001111111113a000502000001008200813b2710000000000000000000000000000000
+ 000000
+ """.replace("\\s+".toRegex(), "").trim()
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(invalidHopLimitPkt),
+ DROPPED_IPV6_MLD_INVALID
+ )
+
+ // Using scapy to generate MLDv1 general query packet with invalid destination address
+ // ether = Ether(src='00:11:22:33:44:55', dst='33:33:00:00:00:01')
+ // ipv6 = IPv6(src='fe80::fc01:83ff:fea6:3712', dst='ff03::1', hlim=1)
+ // hopOpts = IPv6ExtHdrHopByHop(options=[RouterAlert(otype=5)])
+ // mld = ICMPv6MLQuery()
+ // pkt = ether/ipv6/hopOpts/mld
+ var pkt = """
+ 33330000000100112233445586dd6000000000200001fe80000000000000fc0183fffea63712ff03000
+ 00000000000000000000000013a000502000001008200a35c2710000000000000000000000000000000
+ 000000
+ """.replace("\\s+".toRegex(), "").trim()
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(pkt),
+ DROPPED_IPV6_MLD_INVALID
+ )
+
+ // Using scapy to generate MLD message with invalid payload length 27
+ // ether = Ether(src='00:11:22:33:44:55', dst='33:33:00:00:00:01')
+ // ipv6 = IPv6(src='fe80::fc01:83ff:fea6:3712', dst='ff03::1', hlim=1)
+ // hopOpts = IPv6ExtHdrHopByHop(options=[RouterAlert(otype=5)])
+ // mld = ICMPv6MLQuery()
+ // pkt = ether/ipv6/hopOpts/mld (and drop last byte)
+ var invalidPayloadLength27Pkt = """
+ 33330000000100112233445586dd6000000000240001fe80000000000000fc0183fffea63712ff0200000
+ 000000000000000000000013a000502000001008200a35927100000000000000000000000000000000000
+ 00000000
+ """.replace("\\s+".toRegex(), "").trim()
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(invalidPayloadLength27Pkt),
+ DROPPED_IPV6_MLD_INVALID
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testMldV1ReportDropped() {
+ val apfFilter = getMldApfFilter()
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3)
+ // Using scapy to generate MLDv1 report
+ // ether = Ether(src='00:11:22:33:44:55', dst='33:33:11:11:11:11')
+ // ipv6 = IPv6(src='fe80::fc01:83ff:fea6:3712', dst='ff12::1:1111:1111', hlim=1)
+ // hopOpts = IPv6ExtHdrHopByHop(options=[RouterAlert(otype=5)])
+ // mld = ICMPv6MLReport(mladdr='ff12::1:1111:1111')
+ // pkt = ether/ipv6/hopOpts/mld
+ var pkt = """
+ 33331111111100112233445586dd6000000000200001fe80000000000000fc0183fffea63712ff12000
+ 00000000000000001111111113a000502000001008300860500000000ff120000000000000000000111
+ 111111
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(pkt),
+ DROPPED_IPV6_MLD_REPORT
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testMldV1DoneDropped() {
+ val apfFilter = getMldApfFilter()
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3)
+ // Using scapy to generate MLDv1 done
+ // ether = Ether(src='00:11:22:33:44:55', dst='33:33:00:00:00:02')
+ // ipv6 = IPv6(src='fe80::fc01:83ff:fea6:3712', dst='ff02::2', hlim=1)
+ // hopOpts = IPv6ExtHdrHopByHop(options=[RouterAlert(otype=5)])
+ // mld = ICMPv6MLDone(mladdr='ff12::1:1111:1111')
+ // pkt = ether/ipv6/hopOpts/mld
+ var pkt = """
+ 33330000000200112233445586dd6000000000200001fe80000000000000fc0183fffea63712ff020000
+ 0000000000000000000000023a000502000001008400a73600000000ff12000000000000000000011111
+ 1111
+ """.replace("\\s+".toRegex(), "").trim()
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(pkt),
+ DROPPED_IPV6_MLD_REPORT
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testMldV2ReportDropped() {
+ val apfFilter = getMldApfFilter()
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3)
+ // Using scapy to generate MLDv2 report
+ // ether = Ether(src='00:11:22:33:44:55', dst='33:33:00:00:00:16')
+ // ipv6 = IPv6(src='fe80::fc01:83ff:fea6:3712', dst='ff02::16', hlim=1)
+ // hopOpts = IPv6ExtHdrHopByHop(options=[RouterAlert(otype=5)])
+ // mld = ICMPv6MLReport2(records=[ICMPv6MLDMultAddrRec(dst='ff02::1:1111:1111')])
+ // pkt = ether/ipv6/hopOpts/mld
+ var pkt = """
+ 33330000001600112233445586dd6000000000240001fe80000000000000fc0183fffea63712ff020000
+ 0000000000000000000000163a000502000001008f00982d0000000104000000ff020000000000000000
+ 000111111111
+ """.replace("\\s+".toRegex(), "").trim()
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(pkt),
+ DROPPED_IPV6_MLD_REPORT
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testMldV1GeneralQueryReplied() {
+ val apfFilter = getMldApfFilter()
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3)
+ // Using scapy to generate MLDv1 general query
+ // ether = Ether(src='00:11:22:33:44:55', dst='33:33:00:00:00:01')
+ // ipv6 = IPv6(src='fe80::fc01:83ff:fea6:3712', dst='ff02::1', hlim=1)
+ // hopOpts = IPv6ExtHdrHopByHop(options=[RouterAlert(otype=5)])
+ // mld = ICMPv6MLQuery()
+ // pkt = ether/ipv6/hopOpts/mld
+ var pkt = """
+ 33330000000100112233445586dd6000000000200001fe80000000000000fc0183fffea63712ff02000
+ 00000000000000000000000013a000502000001008200a35d2710000000000000000000000000000000
+ 000000
+ """.replace("\\s+".toRegex(), "").trim()
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(pkt),
+ DROPPED_IPV6_MLD_V1_GENERAL_QUERY_REPLIED
+ )
+
+ val mldV1ReportPkts = setOf(
+ // ###[ Ethernet ]###
+ // dst = 33:33:11:11:11:11
+ // src = 02:03:04:05:06:07
+ // type = IPv6
+ // ###[ IPv6 ]###
+ // version = 6
+ // tc = 0
+ // fl = 0
+ // plen = None
+ // nh = Hop-by-Hop Option Header
+ // hlim = 1
+ // src = fe80::3
+ // dst = ff12::1:1111:1111
+ // ###[ IPv6 Extension Header - Hop-by-Hop Options Header ]###
+ // nh = ICMPv6
+ // len = None
+ // autopad = On
+ // \options \
+ // |###[ Router Alert ]###
+ // | otype = Router Alert [00: skip, 0: Don't change en-route]
+ // | optlen = 2
+ // | value = None
+ // ###[ MLD - Multicast Listener Report ]###
+ // type = MLD Report
+ // code = 0
+ // cksum = None
+ // mrd = 0
+ // reserved = 0
+ // mladdr = ff12::1:1111:1111
+ """
+ 33331111111102030405060786dd6000000000200001fe800000000000000000000000000003ff120000
+ 0000000000000001111111113a0005020000010083003bbd00000000ff12000000000000000000011111
+ 1111
+ """.replace("\\s+".toRegex(), "").trim().uppercase(),
+ // ###[ Ethernet ]###
+ // dst = 33:33:22:22:22:22
+ // src = 02:03:04:05:06:07
+ // type = IPv6
+ // ###[ IPv6 ]###
+ // version = 6
+ // tc = 0
+ // fl = 0
+ // plen = None
+ // nh = Hop-by-Hop Option Header
+ // hlim = 1
+ // src = fe80::3
+ // dst = ff12::1:2222:2222
+ // ###[ IPv6 Extension Header - Hop-by-Hop Options Header ]###
+ // nh = ICMPv6
+ // len = None
+ // autopad = On
+ // \options \
+ // |###[ Router Alert ]###
+ // | otype = Router Alert [00: skip, 0: Don't change en-route]
+ // | optlen = 2
+ // | value = None
+ // ###[ MLD - Multicast Listener Report ]###
+ // type = MLD Report
+ // code = 0
+ // cksum = None
+ // mrd = 0
+ // reserved = 0
+ // mladdr = ff12::1:2222:2222
+ """
+ 33332222222202030405060786dd6000000000200001fe800000000000000000000000000003ff120000
+ 0000000000000001222222223a000502000001008300f77800000000ff12000000000000000000012222
+ 2222
+ """.replace("\\s+".toRegex(), "").trim().uppercase(),
+ // ###[ Ethernet ]###
+ // dst = 33:33:33:33:33:33
+ // src = 02:03:04:05:06:07
+ // type = IPv6
+ // ###[ IPv6 ]###
+ // version = 6
+ // tc = 0
+ // fl = 0
+ // plen = None
+ // nh = Hop-by-Hop Option Header
+ // hlim = 1
+ // src = fe80::3
+ // dst = ff12::1:3333:3333
+ // ###[ IPv6 Extension Header - Hop-by-Hop Options Header ]###
+ // nh = ICMPv6
+ // len = None
+ // autopad = On
+ // \options \
+ // |###[ Router Alert ]###
+ // | otype = Router Alert [00: skip, 0: Don't change en-route]
+ // | optlen = 2
+ // | value = None
+ // ###[ MLD - Multicast Listener Report ]###
+ // type = MLD Report
+ // code = 0
+ // cksum = None
+ // mrd = 0
+ // reserved = 0
+ // mladdr = ff12::1:3333:3333
+ """
+ 33333333333302030405060786dd6000000000200001fe800000000000000000000000000003ff120000
+ 0000000000000001333333333a000502000001008300b33400000000ff12000000000000000000013333
+ 3333
+ """.replace("\\s+".toRegex(), "").trim().uppercase()
+ )
+
+ val transmitPackets = apfTestHelpers.getAllTransmittedPackets()
+ .map { HexDump.toHexString(it).uppercase() }.toSet()
+ assertEquals(mldV1ReportPkts, transmitPackets)
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testMldV2GeneralQueryReplied() {
+ val apfFilter = getMldApfFilter()
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3)
+ // Using scapy to generate MLDv2 general query
+ // ether = Ether(src='00:11:22:33:44:55', dst='33:33:00:00:00:01')
+ // ipv6 = IPv6(src='fe80::fc01:83ff:fea6:3712', dst='ff02::1', hlim=1)
+ // hopOpts = IPv6ExtHdrHopByHop(options=[RouterAlert(otype=5)])
+ // mld = ICMPv6MLQuery2()
+ // pkt = ether/ipv6/hopOpts/mld
+ var pkt = """
+ 33330000000100112233445586dd6000000000240001fe80000000000000fc0183fffea63712ff02000
+ 00000000000000000000000013a000502000001008200a3592710000000000000000000000000000000
+ 00000000000000
+ """.replace("\\s+".toRegex(), "").trim()
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(pkt),
+ DROPPED_IPV6_MLD_V2_GENERAL_QUERY_REPLIED
+ )
+
+ val transmittedMldV2Reports = apfTestHelpers.consumeTransmittedPackets(1)
+ // ###[ Ethernet ]###
+ // dst = 33:33:00:00:00:16
+ // src = 02:03:04:05:06:07
+ // type = IPv6
+ // ###[ IPv6 ]###
+ // version = 6
+ // tc = 0
+ // fl = 0
+ // plen = None
+ // nh = Hop-by-Hop Option Header
+ // hlim = 1
+ // src = fe80::3
+ // dst = ff02::16
+ // ###[ IPv6 Extension Header - Hop-by-Hop Options Header ]###
+ // nh = ICMPv6
+ // len = None
+ // autopad = On
+ // \options \
+ // |###[ Router Alert ]###
+ // | otype = Router Alert [00: skip, 0: Don't change en-route]
+ // | optlen = 2
+ // | value = None
+ // ###[ MLDv2 - Multicast Listener Report ]###
+ // type = MLD Report Version 2
+ // res = 0
+ // cksum = None
+ // reserved = 0
+ // records_number= None
+ // \records \
+ // |###[ ICMPv6 MLDv2 - Multicast Address Record ]###
+ // | rtype = 2
+ // | auxdata_len= None
+ // | sources_number= None
+ // | dst = ff12::1:1111:1111
+ // | sources = [ ]
+ // | auxdata = b''
+ // |###[ ICMPv6 MLDv2 - Multicast Address Record ]###
+ // | rtype = 2
+ // | auxdata_len= None
+ // | sources_number= None
+ // | dst = ff12::1:2222:2222
+ // | sources = [ ]
+ // | auxdata = b''
+ // |###[ ICMPv6 MLDv2 - Multicast Address Record ]###
+ // | rtype = 2
+ // | auxdata_len= None
+ // | sources_number= None
+ // | dst = ff12::1:3333:3333
+ // | sources = [ ]
+ // | auxdata = b''
+ val mldV2ReportPkt = """
+ 33330000001602030405060786dd60000000004c0001fe800000000000000000000000000003ff020000
+ 0000000000000000000000163a000502000001008f00a2d80000000302000000ff120000000000000000
+ 00011111111102000000ff12000000000000000000012222222202000000ff1200000000000000000001
+ 33333333
+ """.replace("\\s+".toRegex(), "").trim()
+ assertContentEquals(
+ HexDump.hexStringToByteArray(mldV2ReportPkt),
+ transmittedMldV2Reports[0]
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testMldV1GroupSpecificQueryPassed() {
+ val apfFilter = getMldApfFilter()
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3)
+ // Using scapy to generate MLDv1 group specific query
+ // ether = Ether(src='00:11:22:33:44:55', dst='33:33:00:00:00:01')
+ // ipv6 = IPv6(src='fe80::fc01:83ff:fea6:3712', dst='ff02::1:1111:1111', hlim=1)
+ // hopOpts = IPv6ExtHdrHopByHop(options=[RouterAlert(otype=5)])
+ // mld = ICMPv6MLQuery(mladdr='ff02::1:1111:1111')
+ // pkt = ether/ipv6/hopOpts/mld
+ var pkt = """
+ 33330000000100112233445586dd6000000000200001fe80000000000000fc0183fffea63712ff020000
+ 0000000000000001111111113a000502000001008200601527100000ff02000000000000000000011111
+ 1111
+ """.replace("\\s+".toRegex(), "").trim()
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(pkt),
+ PASSED_IPV6_ICMP
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testMldV2GroupSpecificQueryPassed() {
+ val apfFilter = getMldApfFilter()
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3)
+ // Using scapy to generate MLDv2 group specific query
+ // ether = Ether(src='00:11:22:33:44:55', dst='33:33:00:00:00:01')
+ // ipv6 = IPv6(src='fe80::fc01:83ff:fea6:3712', dst='ff02::1:1111:1111', hlim=1)
+ // hopOpts = IPv6ExtHdrHopByHop(options=[RouterAlert(otype=5)])
+ // mld = ICMPv6MLQuery2(mladdr='ff02::1:1111:1111')
+ // pkt = ether/ipv6/hopOpts/mld
+ var pkt = """
+ 33330000000100112233445586dd6000000000240001fe80000000000000fc0183fffea63712ff020000
+ 0000000000000001111111113a000502000001008200601127100000ff02000000000000000000011111
+ 111100000000
+ """.replace("\\s+".toRegex(), "").trim()
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(pkt),
+ PASSED_IPV6_ICMP
)
}
@@ -563,12 +1978,12 @@
val apfConfig = getDefaultConfig()
apfConfig.multicastFilter = true
val apfFilter = getApfFilter(apfConfig)
- consumeInstalledProgram(ipClientCallback, installCnt = 2)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24)
val lp = LinkProperties()
lp.addLinkAddress(linkAddress)
apfFilter.setLinkProperties(lp)
- val program = consumeInstalledProgram(ipClientCallback, installCnt = 1)
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
// Using scapy to generate DHCP4 offer packet:
// ether = Ether(src='00:11:22:33:44:55', dst='ff:ff:ff:ff:ff:ff')
@@ -598,7 +2013,7 @@
0000000000000000000000000000000000000000000000000000638253633501023604c0
a801010104ffffff000304c0a80101330400015180060408080808ff
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(dhcp4Pkt),
@@ -612,7 +2027,7 @@
val nonDhcpMcastPkt = """
ffffffffffff001122334455080045000014000100004015d929c0a80101e0000001
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(nonDhcpMcastPkt),
@@ -626,7 +2041,7 @@
val nonDhcpBcastPkt = """
ffffffffffff001122334455080045000014000100004015b92bc0a80101ffffffff
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(nonDhcpBcastPkt),
@@ -640,7 +2055,7 @@
val nonDhcpNetBcastPkt = """
ffffffffffff001122334455080045000014000100004015ae2cc0a801010a0000ff
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(nonDhcpNetBcastPkt),
@@ -654,7 +2069,7 @@
val nonDhcpUcastPkt = """
020304050607001122334455080045000014000100004015f780c0a80101c0a80102
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(nonDhcpUcastPkt),
@@ -668,7 +2083,7 @@
val nonDhcpUcastL2BcastPkt = """
ffffffffffff001122334455080045000014000100004015f780c0a80101c0a80102
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(nonDhcpUcastL2BcastPkt),
@@ -679,9 +2094,9 @@
@Test
fun testArpFilterDropPktsOnV6OnlyNetwork() {
val apfFilter = getApfFilter()
- consumeInstalledProgram(ipClientCallback, installCnt = 2)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
apfFilter.updateClatInterfaceState(true)
- val program = consumeInstalledProgram(ipClientCallback, installCnt = 1)
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
// Drop ARP request packet when clat is enabled
// Using scapy to generate ARP request packet:
@@ -691,8 +2106,8 @@
val arpPkt = """
010203040506000102030405080600010800060400015c857e3c74e1c0a8012200000000000000000000
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
HexDump.hexStringToByteArray(arpPkt),
DROPPED_ARP_V6_ONLY
@@ -722,9 +2137,9 @@
apfConfig.multicastFilter = true
apfConfig.ieee802_3Filter = true
val apfFilter = getApfFilter(apfConfig)
- consumeInstalledProgram(ipClientCallback, installCnt = 2)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
apfFilter.addTcpKeepalivePacketFilter(1, parcel)
- var program = consumeInstalledProgram(ipClientCallback, installCnt = 1)
+ var program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
// Drop IPv4 keepalive ack
// Using scapy to generate IPv4 TCP keepalive ack packet with seq + 1:
@@ -736,8 +2151,8 @@
01020304050600010203040508004500002800010000400666c50a0000060a000005d4313039499602d2
7e916116501020004b4f0000
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
HexDump.hexStringToByteArray(keepaliveAckPkt),
DROPPED_IPV4_KEEPALIVE_ACK
@@ -753,8 +2168,8 @@
01020304050600010203040508004500002800010000400666c50a0000060a000005d431303949960336
7e916115501020004aec0000
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
HexDump.hexStringToByteArray(nonKeepaliveAckPkt1),
PASSED_IPV4_UNICAST
@@ -771,8 +2186,8 @@
01020304050600010203040508004500003200010000400666bb0a0000060a000005d4313039499602d27
e91611650102000372c000000010203040506070809
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
HexDump.hexStringToByteArray(nonKeepaliveAckPkt2),
PASSED_IPV4_UNICAST
@@ -788,8 +2203,8 @@
01020304050600010203040508004500002800010000400666c40a0000070a0000055ba0ff987e91610c4
2f697155010200066e60000
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
HexDump.hexStringToByteArray(otherSrcKeepaliveAck),
PASSED_IPV4_UNICAST
@@ -797,16 +2212,16 @@
// test IPv4 packets when TCP keepalive filter is removed
apfFilter.removeKeepalivePacketFilter(1)
- program = consumeInstalledProgram(ipClientCallback, installCnt = 1)
- verifyProgramRun(
- APF_VERSION_6,
+ program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
HexDump.hexStringToByteArray(keepaliveAckPkt),
PASSED_IPV4_UNICAST
)
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
HexDump.hexStringToByteArray(otherSrcKeepaliveAck),
PASSED_IPV4_UNICAST
@@ -832,9 +2247,9 @@
apfConfig.multicastFilter = true
apfConfig.ieee802_3Filter = true
val apfFilter = getApfFilter(apfConfig)
- consumeInstalledProgram(ipClientCallback, installCnt = 2)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
apfFilter.addNattKeepalivePacketFilter(1, parcel)
- val program = consumeInstalledProgram(ipClientCallback, installCnt = 1)
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
// Drop IPv4 keepalive response packet
// Using scapy to generate IPv4 NAT-T keepalive ack packet with payload 0xff:
@@ -846,8 +2261,8 @@
val validNattPkt = """
01020304050600010203040508004500001d00010000401166c50a0000060a000005119404000009d73cff
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
HexDump.hexStringToByteArray(validNattPkt),
DROPPED_IPV4_NATT_KEEPALIVE
@@ -863,8 +2278,8 @@
val invalidNattPkt = """
01020304050600010203040508004500001d00010000401166c50a0000060a000005119404000009d83cfe
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
HexDump.hexStringToByteArray(invalidNattPkt),
PASSED_IPV4_UNICAST
@@ -881,8 +2296,8 @@
01020304050600010203040508004500002600010000401166bc0a0000060a000005119404000012c2120
0010203040506070809
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
HexDump.hexStringToByteArray(nonNattPkt),
PASSED_IPV4_UNICAST
@@ -899,8 +2314,8 @@
01020304050600010203040508004500002600010000401166bb0a0000070a000005119404000012c2110
0010203040506070809
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
HexDump.hexStringToByteArray(otherSrcNonNattPkt),
PASSED_IPV4_UNICAST
@@ -910,7 +2325,7 @@
@Test
fun testIPv4TcpPort7Filter() {
val apfFilter = getApfFilter()
- val program = consumeInstalledProgram(ipClientCallback, installCnt = 2)
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
// Drop IPv4 TCP port 7 packet
// Using scapy to generate IPv4 TCP port 7 packet:
@@ -922,8 +2337,8 @@
01020304050600010203040508004500002800010000400666c50a0000060a00000500140007000000000
0000000500220007bbd0000
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
HexDump.hexStringToByteArray(tcpPort7Pkt),
DROPPED_IPV4_TCP_PORT7_UNICAST
@@ -939,8 +2354,8 @@
01020304050600010203040508004500002800012000400646c50a0000060a00000500140050000000000
0000000500220007b740000
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
HexDump.hexStringToByteArray(initialFragmentTcpPkt),
PASSED_IPV4
@@ -956,8 +2371,8 @@
01020304050600010203040508004500002800012064400646610a0000060a00000500140050000000000
0000000500220007b740000
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
HexDump.hexStringToByteArray(fragmentTcpPkt),
PASSED_IPV4
@@ -969,14 +2384,14 @@
val apfConfig = getDefaultConfig()
apfConfig.multicastFilter = true
val apfFilter = getApfFilter(apfConfig)
- consumeInstalledProgram(ipClientCallback, installCnt = 2)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
val lp = LinkProperties()
for (addr in hostIpv6Addresses) {
lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64))
}
apfFilter.setLinkProperties(lp)
apfFilter.setDozeMode(true)
- val program = consumeInstalledProgram(ipClientCallback, installCnt = 2)
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
// Using scapy to generate non ICMPv6 sent to ff00::/8 (multicast prefix) packet:
// eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06")
// ip6 = IPv6(src="2001::200:1a:1122:3344", dst="ff00::1", nh=59)
@@ -985,8 +2400,8 @@
ffffffffffff00112233445586dd6000000000003b4020010000000000000200001a11223344ff00000
0000000000000000000000000
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
HexDump.hexStringToByteArray(nonIcmpv6McastPkt),
DROPPED_IPV6_NON_ICMP_MULTICAST
@@ -1001,8 +2416,8 @@
02030405060700010203040586dd6000000000083aff20010000000000000200001a11223344ff00000
000000000000000000000000180001a3a00000000
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
HexDump.hexStringToByteArray(icmpv6EchoPkt),
DROPPED_IPV6_NON_ICMP_MULTICAST
@@ -1012,13 +2427,13 @@
@Test
fun testIPv6PacketFilter() {
val apfFilter = getApfFilter()
- consumeInstalledProgram(ipClientCallback, installCnt = 2)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
val lp = LinkProperties()
for (addr in hostIpv6Addresses) {
lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64))
}
apfFilter.setLinkProperties(lp)
- val program = consumeInstalledProgram(ipClientCallback, installCnt = 1)
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
// Using scapy to generate non ICMPv6 packet:
// eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06")
// ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", nh=59)
@@ -1027,8 +2442,8 @@
ffffffffffff00112233445586dd6000000000003b4020010000000000000200001a112233442001000
0000000000200001a33441122
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
HexDump.hexStringToByteArray(nonIcmpv6Pkt),
PASSED_IPV6_NON_ICMP
@@ -1043,8 +2458,8 @@
01020304050600010203040586dd6000000000183aff20010000000000000200001a11223344ff02000
000000000000000000000000188007227a000000000000000000000000000000000000000
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
HexDump.hexStringToByteArray(icmpv6McastNaPkt),
DROPPED_IPV6_MULTICAST_NA
@@ -1058,18 +2473,63 @@
01020304050600010203040586dd600000000000004020010000000000000200001a112233442001000
0000000000200001a33441122
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
HexDump.hexStringToByteArray(ipv6WithHopByHopOptionPkt),
- PASSED_MLD
+ PASSED_IPV6_HOPOPTS
+ )
+ }
+
+ @Test
+ fun testRaFilterIgnoreReservedFieldInRdnssOption() {
+ val apfFilter = getApfFilter()
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
+ val lp = LinkProperties()
+ for (addr in hostIpv6Addresses) {
+ lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64))
+ }
+ apfFilter.setLinkProperties(lp)
+ var program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
+ val ra1 = """
+ 33330000000100c0babecafe86dd6e00000000783afffe800000000000002a0079e12e003f01ff0
+ 200000000000000000000000000018600571140000e100000000000000000010100c0babecafe05
+ 010000000023ee2602fff80064ff9b0000000000000000190500000012750020014860486000000
+ 00000000000006420014860486000000000000000006464030440c000002a3000001c2000000000
+ 2a0079e12e003f010000000000000000
+ """.replace("\\s+".toRegex(), "").trim()
+ val ra1Bytes = HexDump.hexStringToByteArray(ra1)
+ Os.write(raWriterSocket, ra1Bytes, 0, ra1Bytes.size)
+
+ program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
+
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
+ program,
+ ra1Bytes,
+ DROPPED_RA
+ )
+
+ val ra2 = """
+ 33330000000100c0babecafe86dd6e00000000783afffe800000000000002a0079e12e003f01ff0
+ 200000000000000000000000000018600dd3040000e100000000000000000010100c0babecafe05
+ 010000000023ee2602fff80064ff9b0000000000000000190579e00012750020014860486000000
+ 00000000000006420014860486000000000000000006464030440c000002a3000001c2000000000
+ 2a0079e12e003f010000000000000000
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
+ program,
+ HexDump.hexStringToByteArray(ra2),
+ DROPPED_RA
)
}
@Test
fun testArpFilterDropPktsNoIPv4() {
val apfFilter = getApfFilter()
- val program = consumeInstalledProgram(ipClientCallback, installCnt = 2)
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
// Drop ARP request packet with invalid hw type
// Using scapy to generate ARP request packet with invalid hw type :
@@ -1079,8 +2539,8 @@
val invalidHwTypePkt = """
01020304050600010203040508060003080000040001c0a8012200000000
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
HexDump.hexStringToByteArray(invalidHwTypePkt),
DROPPED_ARP_NON_IPV4
@@ -1094,8 +2554,8 @@
val invalidProtoTypePkt = """
010203040506000102030405080600010014060000015c857e3c74e1000000000000
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
HexDump.hexStringToByteArray(invalidProtoTypePkt),
DROPPED_ARP_NON_IPV4
@@ -1111,8 +2571,8 @@
0000000000000000c0a8012200000000000000000000000000000000000000000000
0000
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
HexDump.hexStringToByteArray(invalidHwLenPkt),
DROPPED_ARP_NON_IPV4
@@ -1128,8 +2588,8 @@
00000000000000000000000000000000000000000000000000000000000000000000
000000000000
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
HexDump.hexStringToByteArray(invalidProtoLenPkt),
DROPPED_ARP_NON_IPV4
@@ -1143,8 +2603,8 @@
val invalidOpPkt = """
010203040506000102030405080600010800060400055c857e3c74e1c0a8012200000000000000000000
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
HexDump.hexStringToByteArray(invalidOpPkt),
DROPPED_ARP_UNKNOWN
@@ -1158,8 +2618,8 @@
val noHostArpReplyPkt = """
010203040506000102030405080600010800060400025c857e3c74e10000000000000000000000000000
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
HexDump.hexStringToByteArray(noHostArpReplyPkt),
DROPPED_ARP_REPLY_SPA_NO_HOST
@@ -1173,8 +2633,8 @@
val garpReplyPkt = """
ffffffffffff000102030405080600010800060400025c857e3c74e1c0a8012200000000000000000000
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
HexDump.hexStringToByteArray(garpReplyPkt),
DROPPED_GARP_REPLY
@@ -1184,7 +2644,7 @@
@Test
fun testArpFilterPassPktsNoIPv4() {
val apfFilter = getApfFilter()
- val program = consumeInstalledProgram(ipClientCallback, installCnt = 2)
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
// Pass non-broadcast ARP reply packet
// Using scapy to generate unicast ARP reply packet:
// eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06")
@@ -1193,8 +2653,8 @@
val nonBcastArpReplyPkt = """
010203040506000102030405080600010800060400025c857e3c74e10102030400000000000000000000
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
HexDump.hexStringToByteArray(nonBcastArpReplyPkt),
PASSED_ARP_UNICAST_REPLY
@@ -1208,8 +2668,8 @@
val arpRequestPkt = """
ffffffffffff000102030405080600010800060400015c857e3c74e1c0a8012200000000000001020304
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
HexDump.hexStringToByteArray(arpRequestPkt),
PASSED_ARP_REQUEST
@@ -1219,12 +2679,12 @@
@Test
fun testArpFilterDropPktsWithIPv4() {
val apfFilter = getApfFilter()
- consumeInstalledProgram(ipClientCallback, installCnt = 2)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24)
val lp = LinkProperties()
lp.addLinkAddress(linkAddress)
apfFilter.setLinkProperties(lp)
- val program = consumeInstalledProgram(ipClientCallback, installCnt = 1)
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
// Drop ARP reply packet is not for the device
// Using scapy to generate ARP reply packet not for the device:
// eth = Ether(src="00:01:02:03:04:05", dst="FF:FF:FF:FF:FF:FF")
@@ -1233,8 +2693,8 @@
val otherHostArpReplyPkt = """
ffffffffffff000102030405080600010800060400025c857e3c74e1c0a8012200000000000001020304
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
HexDump.hexStringToByteArray(otherHostArpReplyPkt),
DROPPED_ARP_OTHER_HOST
@@ -1248,8 +2708,8 @@
val otherHostArpRequestPkt = """
ffffffffffff000102030405080600010800060400015c857e3c74e1c0a8012200000000000001020304
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
HexDump.hexStringToByteArray(otherHostArpRequestPkt),
DROPPED_ARP_OTHER_HOST
@@ -1259,12 +2719,12 @@
@Test
fun testArpFilterPassPktsWithIPv4() {
val apfFilter = getApfFilter()
- consumeInstalledProgram(ipClientCallback, installCnt = 2)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24)
val lp = LinkProperties()
lp.addLinkAddress(linkAddress)
apfFilter.setLinkProperties(lp)
- val program = consumeInstalledProgram(ipClientCallback, installCnt = 1)
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
// Using scapy to generate ARP broadcast reply packet:
// eth = Ether(src="00:01:02:03:04:05", dst="FF:FF:FF:FF:FF:FF")
@@ -1273,8 +2733,8 @@
val bcastArpReplyPkt = """
ffffffffffff000102030405080600010800060400025c857e3c74e1c0a801220000000000000a000001
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
HexDump.hexStringToByteArray(bcastArpReplyPkt),
PASSED_ARP_BROADCAST_REPLY
@@ -1286,12 +2746,12 @@
@Test
fun testArpTransmit() {
val apfFilter = getApfFilter()
- consumeInstalledProgram(ipClientCallback, installCnt = 2)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24)
val lp = LinkProperties()
lp.addLinkAddress(linkAddress)
apfFilter.setLinkProperties(lp)
- val program = consumeInstalledProgram(ipClientCallback, installCnt = 1)
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
val receivedArpPacketBuf = ArpPacket.buildArpPacket(
arpBroadcastMacAddress,
senderMacAddress,
@@ -1302,14 +2762,14 @@
)
val receivedArpPacket = ByteArray(ARP_ETHER_IPV4_LEN)
receivedArpPacketBuf.get(receivedArpPacket)
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
receivedArpPacket,
DROPPED_ARP_REQUEST_REPLIED
)
- val transmittedPacket = ApfJniUtils.getTransmittedPacket()
+ val transmittedPackets = apfTestHelpers.consumeTransmittedPackets(1)
val expectedArpReplyBuf = ArpPacket.buildArpPacket(
senderMacAddress,
apfFilter.mHardwareAddress,
@@ -1322,21 +2782,21 @@
expectedArpReplyBuf.get(expectedArpReplyPacket)
assertContentEquals(
expectedArpReplyPacket + ByteArray(18) { 0 },
- transmittedPacket
+ transmittedPackets[0]
)
}
@Test
fun testArpOffloadDisabled() {
val apfConfig = getDefaultConfig()
- apfConfig.shouldHandleArpOffload = false
+ apfConfig.handleArpOffload = false
val apfFilter = getApfFilter(apfConfig)
- consumeInstalledProgram(ipClientCallback, installCnt = 2)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24)
val lp = LinkProperties()
lp.addLinkAddress(linkAddress)
apfFilter.setLinkProperties(lp)
- val program = consumeInstalledProgram(ipClientCallback, installCnt = 1)
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
val receivedArpPacketBuf = ArpPacket.buildArpPacket(
arpBroadcastMacAddress,
senderMacAddress,
@@ -1347,7 +2807,7 @@
)
val receivedArpPacket = ByteArray(ARP_ETHER_IPV4_LEN)
receivedArpPacketBuf.get(receivedArpPacket)
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
receivedArpPacket,
@@ -1361,7 +2821,7 @@
doReturn(listOf<ByteArray>()).`when`(dependencies).getAnycast6Addresses(any())
val apfFilter = getApfFilter()
// validate NS packet check when there is no IPv6 address
- val program = consumeInstalledProgram(ipClientCallback, installCnt = 2)
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
// Using scapy to generate IPv6 NS packet:
// eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06")
// ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255)
@@ -1373,11 +2833,11 @@
00000020010000000000000200001A33441122
""".replace("\\s+".toRegex(), "").trim()
// when there is no IPv6 addresses -> pass NS packet
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(nsPkt),
- PASSED_IPV6_NS_NO_ADDRESS
+ PASSED_IPV6_ICMP
)
}
@@ -1385,7 +2845,7 @@
@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
fun testNsFilter() {
val apfFilter = getApfFilter()
- consumeInstalledProgram(ipClientCallback, installCnt = 2)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
val lp = LinkProperties()
for (addr in hostIpv6Addresses) {
lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64))
@@ -1403,9 +2863,9 @@
}
apfFilter.setLinkProperties(lp)
- consumeInstalledProgram(ipClientCallback, installCnt = 1)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
apfFilter.updateClatInterfaceState(true)
- val program = consumeInstalledProgram(ipClientCallback, installCnt = 1)
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
// validate Ethernet dst address check
// Using scapy to generate IPv6 NS packet:
@@ -1420,7 +2880,7 @@
000020010000000000000200001A334411220201000102030405
""".replace("\\s+".toRegex(), "").trim()
// invalid unicast ether dst -> pass
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(nonHostDstMacNsPkt),
@@ -1439,7 +2899,7 @@
0000000020010000000000000200001A334411220201000102030405
""".replace("\\s+".toRegex(), "").trim()
// mcast dst mac is not one of solicited mcast mac derived from one of device's ip -> pass
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(nonMcastDstMacNsPkt),
@@ -1459,7 +2919,7 @@
""".replace("\\s+".toRegex(), "").trim()
// mcast dst mac is one of solicited mcast mac derived from one of device's ip
// -> drop and replied
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(hostMcastDstMacNsPkt),
@@ -1478,7 +2938,7 @@
00000000000200001A334411220101000102030405
""".replace("\\s+".toRegex(), "").trim()
// mcast dst mac is broadcast address -> drop and replied
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(broadcastNsPkt),
@@ -1499,7 +2959,7 @@
00000020010000000000000200001A334411220101000102030405
""".replace("\\s+".toRegex(), "").trim()
// dst ip is one of device's ip -> drop and replied
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(validHostDstIpNsPkt),
@@ -1519,7 +2979,7 @@
0101000102030405
""".replace("\\s+".toRegex(), "").trim()
// dst ip is device's anycast address -> drop and replied
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(validHostAnycastDstIpNsPkt),
@@ -1538,7 +2998,7 @@
E30000000020010000000000000200001A334411220101000102030405
""".replace("\\s+".toRegex(), "").trim()
// unicast dst ip is not one of device's ip -> pass
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(nonHostUcastDstIpNsPkt),
@@ -1557,7 +3017,7 @@
1C0000000020010000000000000200001A334411220101000102030405
""".replace("\\s+".toRegex(), "").trim()
// mcast dst ip is not one of solicited mcast ip derived from one of device's ip -> pass
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(nonHostMcastDstIpNsPkt),
@@ -1576,7 +3036,7 @@
"000020010000000000000200001A334411220101000102030405"
// mcast dst ip is one of solicited mcast ip derived from one of device's ip
// -> drop and replied
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(hostMcastDstIpNsPkt),
@@ -1597,7 +3057,7 @@
000200001A334411220101010203040506
""".replace("\\s+".toRegex(), "").trim()
// payload len < 24 -> drop
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(shortNsPkt),
@@ -1616,7 +3076,7 @@
00000000000200001A444455550101010203040506
""".replace("\\s+".toRegex(), "").trim()
// target ip is not one of device's ip -> drop
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(otherHostNsPkt),
@@ -1635,7 +3095,7 @@
00000020010000000000000200001A334411220101010203040506
""".replace("\\s+".toRegex(), "").trim()
// hoplimit is not 255 -> drop
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(invalidHoplimitNsPkt),
@@ -1654,7 +3114,7 @@
00000020010000000000000200001A334411220101010203040506
""".replace("\\s+".toRegex(), "").trim()
// icmp6 code is not 0 -> drop
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(invalidIcmpCodeNsPkt),
@@ -1673,11 +3133,11 @@
16CE0000000020010000000000000200001A123456780101010203040506
""".replace("\\s+".toRegex(), "").trim()
// target ip is one of tentative address -> pass
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(tentativeTargetIpNsPkt),
- PASSED_IPV6_NS_TENTATIVE
+ PASSED_IPV6_ICMP
)
// Using scapy to generate IPv6 NS packet:
@@ -1692,7 +3152,7 @@
00000020010000000000000200001C225566660101010203040506
""".replace("\\s+".toRegex(), "").trim()
// target ip is none of {non-tentative, anycast} -> drop
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(invalidTargetIpNsPkt),
@@ -1711,11 +3171,11 @@
00001A334411220201020304050607
""".replace("\\s+".toRegex(), "").trim()
// DAD NS request -> pass
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(dadNsPkt),
- PASSED_IPV6_NS_DAD
+ PASSED_IPV6_ICMP
)
// Using scapy to generate IPv6 NS packet:
@@ -1729,11 +3189,11 @@
000000000200001A33441122
""".replace("\\s+".toRegex(), "").trim()
// payload len < 32 -> pass
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(noOptionNsPkt),
- PASSED_IPV6_NS_NO_SLLA_OPTION
+ PASSED_IPV6_ICMP
)
// Using scapy to generate IPv6 NS packet:
@@ -1748,7 +3208,7 @@
000020010000000000000200001A334411220101010203040506
""".replace("\\s+".toRegex(), "").trim()
// non-DAD src IPv6 is FF::/8 -> drop
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(nonDadMcastSrcIpPkt),
@@ -1767,7 +3227,7 @@
140000000020010000000000000200001A334411220101010203040506
""".replace("\\s+".toRegex(), "").trim()
// non-DAD src IPv6 is 00::/8 -> drop
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(nonDadLoopbackSrcIpPkt),
@@ -1788,11 +3248,11 @@
05060101010203040506
""".replace("\\s+".toRegex(), "").trim()
// non-DAD with multiple options, SLLA in 2nd option -> pass
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(sllaNotFirstOptionNsPkt),
- PASSED_IPV6_NS_NO_SLLA_OPTION
+ PASSED_IPV6_ICMP
)
// Using scapy to generate IPv6 NS packet:
@@ -1807,11 +3267,11 @@
20010000000000000200001A334411220201010203040506
""".replace("\\s+".toRegex(), "").trim()
// non-DAD with one option but not SLLA -> pass
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(noSllaOptionNsPkt),
- PASSED_IPV6_NS_NO_SLLA_OPTION
+ PASSED_IPV6_ICMP
)
// Using scapy to generate IPv6 NS packet:
@@ -1827,7 +3287,7 @@
0506
""".replace("\\s+".toRegex(), "").trim()
// non-DAD, SLLA is multicast MAC -> drop
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(mcastMacSllaOptionNsPkt),
@@ -1846,8 +3306,9 @@
}
apfFilter.setLinkProperties(lp)
- val program = consumeInstalledProgram(ipClientCallback, installCnt = 3)
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3)
val validIpv6Addresses = hostIpv6Addresses + hostAnycast6Addresses
+ val expectPackets = mutableListOf<ByteArray>()
for (addr in validIpv6Addresses) {
// unicast solicited NS request
val receivedUcastNsPacket = generateNsPacket(
@@ -1858,14 +3319,13 @@
addr
)
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
receivedUcastNsPacket,
DROPPED_IPV6_NS_REPLIED_NON_DAD
)
- val transmittedUcastPacket = ApfJniUtils.getTransmittedPacket()
val expectedUcastNaPacket = generateNaPacket(
apfFilter.mHardwareAddress,
senderMacAddress,
@@ -1874,11 +3334,7 @@
0xe0000000.toInt(), // R=1, S=1, O=1
addr
)
-
- assertContentEquals(
- expectedUcastNaPacket,
- transmittedUcastPacket
- )
+ expectPackets.add(expectedUcastNaPacket)
val solicitedMcastAddr = NetworkStackUtils.ipv6AddressToSolicitedNodeMulticast(
InetAddress.getByAddress(addr) as Inet6Address
@@ -1895,14 +3351,13 @@
addr
)
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
receivedMcastNsPacket,
DROPPED_IPV6_NS_REPLIED_NON_DAD
)
- val transmittedMcastPacket = ApfJniUtils.getTransmittedPacket()
val expectedMcastNaPacket = generateNaPacket(
apfFilter.mHardwareAddress,
senderMacAddress,
@@ -1911,11 +3366,12 @@
0xe0000000.toInt(), // R=1, S=1, O=1
addr
)
+ expectPackets.add(expectedMcastNaPacket)
+ }
- assertContentEquals(
- expectedMcastNaPacket,
- transmittedMcastPacket
- )
+ val transmitPackets = apfTestHelpers.consumeTransmittedPackets(expectPackets.size)
+ for (i in transmitPackets.indices) {
+ assertContentEquals(expectPackets[i], transmitPackets[i])
}
}
@@ -1931,7 +3387,7 @@
lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64))
}
apfFilter.setLinkProperties(lp)
- val program = consumeInstalledProgram(ipClientCallback, installCnt = 3)
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3)
// Using scapy to generate IPv6 NS packet:
// eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07")
// ip6 = IPv6(src="2001::200:1a:1122:3344", dst="ff02::1:ff44:1122", hlim=255, tc=20)
@@ -1943,14 +3399,14 @@
0200001A11223344FF0200000000000000000001FF4411228700952D0000
000020010000000000000200001A334411220101000102030405
""".replace("\\s+".toRegex(), "").trim()
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
HexDump.hexStringToByteArray(hostMcastDstIpNsPkt),
DROPPED_IPV6_NS_REPLIED_NON_DAD
)
- val transmitPkt = ApfJniUtils.getTransmittedPacket()
+ val transmitPkts = apfTestHelpers.consumeTransmittedPackets(1)
// Using scapy to generate IPv6 NA packet:
// eth = Ether(src="02:03:04:05:06:07", dst="00:01:02:03:04:05")
// ip6 = IPv6(src="2001::200:1a:3344:1122", dst="2001::200:1a:1122:3344", hlim=255, tc=20)
@@ -1964,14 +3420,14 @@
""".replace("\\s+".toRegex(), "").trim()
assertContentEquals(
HexDump.hexStringToByteArray(expectedNaPacket),
- transmitPkt
+ transmitPkts[0]
)
}
@Test
fun testNdOffloadDisabled() {
val apfConfig = getDefaultConfig()
- apfConfig.shouldHandleNdOffload = false
+ apfConfig.handleNdOffload = false
val apfFilter = getApfFilter(apfConfig)
val lp = LinkProperties()
for (addr in hostIpv6Addresses) {
@@ -1979,7 +3435,7 @@
}
apfFilter.setLinkProperties(lp)
- val program = consumeInstalledProgram(ipClientCallback, installCnt = 3)
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3)
val validIpv6Addresses = hostIpv6Addresses + hostAnycast6Addresses
for (addr in validIpv6Addresses) {
// unicast solicited NS request
@@ -1991,7 +3447,7 @@
addr
)
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
receivedUcastNsPacket,
@@ -2013,7 +3469,7 @@
addr
)
- verifyProgramRun(
+ apfTestHelpers.verifyProgramRun(
apfFilter.mApfVersionSupported,
program,
receivedMcastNsPacket,
@@ -2022,77 +3478,1864 @@
}
}
+ private fun getApfWithIpv6PingOffloadEnabled(
+ enableMultiCastFilter: Boolean = true,
+ inDozeMode: Boolean = false
+ ): Pair<ApfFilter, ByteArray> {
+ val apfConfig = getDefaultConfig()
+ apfConfig.multicastFilter = enableMultiCastFilter
+ apfConfig.handleIpv6PingOffload = true
+ val apfFilter = getApfFilter(apfConfig)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
+ if (inDozeMode) {
+ apfFilter.setDozeMode(inDozeMode)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
+ }
+ val lp = LinkProperties()
+ lp.addLinkAddress(LinkAddress(hostLinkLocalIpv6Address, 64))
+ apfFilter.setLinkProperties(lp)
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
+ return Pair(apfFilter, program)
+ }
+
@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Test
- fun testRegisterOffloadEngine() {
+ fun testIpv6EchoRequestReplied() {
+ doReturn(64).`when`(dependencies).getIpv6DefaultHopLimit(ifParams.name)
+ val (apfFilter, program) = getApfWithIpv6PingOffloadEnabled()
+ // Using scapy to generate IPv6 echo request packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="02:03:04:05:06:07")
+ // ip = IPv6(src="fe80::1", dst="fe80::03")
+ // icmp = ICMPv6EchoRequest(id=1, seq=123)
+ // pkt = eth/ip/icmp/b"hello"
+ val ipv6EchoRequestPkt = """
+ 02030405060701020304050686dd60000000000d3a40fe80000000000000000
+ 0000000000001fe80000000000000000000000000000380003e640001007b68
+ 656c6c6f
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(ipv6EchoRequestPkt),
+ DROPPED_IPV6_ICMP6_ECHO_REQUEST_REPLIED
+ )
+ val transmitPkt = apfTestHelpers.consumeTransmittedPackets(1)[0]
+
+ // ###[ Ethernet ]###
+ // dst = 01:02:03:04:05:06
+ // src = 02:03:04:05:06:07
+ // type = IPv6
+ // ###[ IPv6 ]###
+ // version = 6
+ // tc = 0
+ // fl = 0
+ // plen = 13
+ // nh = ICMPv6
+ // hlim = 64
+ // src = fe80::3
+ // dst = fe80::1
+ // ###[ ICMPv6 Echo Reply ]###
+ // type = Echo Reply
+ // code = 0
+ // cksum = 0x3d64
+ // id = 0x1
+ // seq = 0x7b
+ // data = b'hello'
+ val expectedReply = """
+ 01020304050602030405060786DD60000000000D3A40FE80000000000000000
+ 0000000000003FE80000000000000000000000000000181003D640001007B68
+ 656C6C6F
+ """.replace("\\s+".toRegex(), "").trim()
+ assertContentEquals(
+ HexDump.hexStringToByteArray(expectedReply),
+ transmitPkt
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testIpv6EchoRequestRepliedInDozeMode() {
+ doReturn(64).`when`(dependencies).getIpv6DefaultHopLimit(ifParams.name)
+ val (apfFilter, program) = getApfWithIpv6PingOffloadEnabled(inDozeMode = true)
+ // Using scapy to generate IPv6 echo request packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="02:03:04:05:06:07")
+ // ip = IPv6(src="fe80::1", dst="fe80::03")
+ // icmp = ICMPv6EchoRequest(id=1, seq=123)
+ // pkt = eth/ip/icmp/b"hello"
+ val ipv6EchoRequestPkt = """
+ 02030405060701020304050686dd60000000000d3a40fe80000000000000000
+ 0000000000001fe80000000000000000000000000000380003e640001007b68
+ 656c6c6f
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(ipv6EchoRequestPkt),
+ DROPPED_IPV6_ICMP6_ECHO_REQUEST_REPLIED
+ )
+ val transmitPkt = apfTestHelpers.consumeTransmittedPackets(1)[0]
+
+ // ###[ Ethernet ]###
+ // dst = 01:02:03:04:05:06
+ // src = 02:03:04:05:06:07
+ // type = IPv6
+ // ###[ IPv6 ]###
+ // version = 6
+ // tc = 0
+ // fl = 0
+ // plen = 13
+ // nh = ICMPv6
+ // hlim = 64
+ // src = fe80::3
+ // dst = fe80::1
+ // ###[ ICMPv6 Echo Reply ]###
+ // type = Echo Reply
+ // code = 0
+ // cksum = 0x3d64
+ // id = 0x1
+ // seq = 0x7b
+ // data = b'hello'
+ val expectedReply = """
+ 01020304050602030405060786DD60000000000D3A40FE80000000000000000
+ 0000000000003FE80000000000000000000000000000181003D640001007B68
+ 656C6C6F
+ """.replace("\\s+".toRegex(), "").trim()
+ assertContentEquals(
+ HexDump.hexStringToByteArray(expectedReply),
+ transmitPkt
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testCorruptedIpv6IcmpPacketDropped() {
+ val (apfFilter, program) = getApfWithIpv6PingOffloadEnabled()
+ // Using scapy to generate corrupted IPv6 ping packet
+ // eth = Ether(src="01:02:03:04:05:06", dst="02:03:04:05:06:07")
+ // ip = IPv6(src="fe80::1", dst="ff02::fb")
+ // icmp = ICMPv6EchoRequest(id=1, seq=123)
+ // pkt = eth/ip/icmp
+ // (drop the last byte in the packet)
+ val ipv6EchoRequestPkt = """
+ 02030405060701020304050686dd6000000000083a40fe80000000000000000
+ 0000000000001fe8000000000000000000000000000038000823b000100
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(ipv6EchoRequestPkt),
+ DROPPED_IPV6_ICMP6_ECHO_REQUEST_INVALID
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testIpv6EchoRequestToOtherHostPassed() {
+ val (apfFilter, program) = getApfWithIpv4PingOffloadEnabled()
+ // Using scapy to generate IPv6 echo request packet to other host:
+ // eth = Ether(src="01:02:03:04:05:06", dst="02:03:04:05:06:07")
+ // ip = IPv6(src="fe80::1", dst="fe80::02")
+ // icmp = ICMPv6EchoRequest(id=1, seq=123)
+ // pkt = eth/ip/icmp/b"hello"
+ val ipv6EchoRequestPkt = """
+ 02030405060701020304050686dd60000000000d3a40fe80000000000000000
+ 0000000000001fe80000000000000000000000000000280003e650001007b68
+ 656c6c6f
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(ipv6EchoRequestPkt),
+ PASSED_IPV6_ICMP
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testIpv6EchoReplyPassed() {
+ val (apfFilter, program) = getApfWithIpv4PingOffloadEnabled()
+ // Using scapy to generate IPv6 echo reply packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="02:03:04:05:06:07")
+ // ip = IPv6(src="fe80::1", dst="fe80::03")
+ // icmp = ICMPv6EchoReply(id=1, seq=123)
+ // pkt = eth/ip/icmp
+ val ipv6EchoReplyPkt = """
+ 02030405060701020304050686dd6000000000083a40fe80000000000000000
+ 0000000000001fe8000000000000000000000000000038100813b0001007b
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(ipv6EchoReplyPkt),
+ PASSED_IPV6_ICMP
+ )
+ }
+
+ private fun getApfWithIpv4PingOffloadEnabled(
+ enableMultiCastFilter: Boolean = true
+ ): Pair<ApfFilter, ByteArray> {
val apfConfig = getDefaultConfig()
- apfConfig.shouldHandleMdnsOffload = true
+ apfConfig.multicastFilter = enableMultiCastFilter
+ apfConfig.handleIpv4PingOffload = true
val apfFilter = getApfFilter(apfConfig)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
+ val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24)
+ val lp = LinkProperties()
+ lp.addLinkAddress(linkAddress)
+ apfFilter.setLinkProperties(lp)
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
+ return Pair(apfFilter, program)
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testIpv4EchoRequestReplied() {
+ doReturn(64).`when`(dependencies).ipv4DefaultTtl
+ val (apfFilter, program) = getApfWithIpv4PingOffloadEnabled()
+ // Using scapy to generate IPv4 echo request packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="02:03:04:05:06:07")
+ // ip = IP(src="10.0.0.2", dst="10.0.0.1")
+ // icmp = ICMP(id=1, seq=123)
+ // pkt = eth/ip/icmp/b"hello"
+ val ipv4EchoRequestPkt = """
+ 02030405060701020304050608004500002100010000400166d90a0000020a0
+ 000010800b3b10001007b68656c6c6f
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(ipv4EchoRequestPkt),
+ DROPPED_IPV4_PING_REQUEST_REPLIED
+ )
+
+ val transmitPkt = apfTestHelpers.consumeTransmittedPackets(1)[0]
+
+ // ###[ Ethernet ]###
+ // dst = 01:02:03:04:05:06
+ // src = 02:03:04:05:06:07
+ // type = IPv4
+ // ###[ IP ]###
+ // version = 4
+ // ihl = 5
+ // tos = 0x0
+ // len = 33
+ // id = 1
+ // flags =
+ // frag = 0
+ // ttl = 64
+ // proto = icmp
+ // chksum = 0x66d9
+ // src = 10.0.0.1
+ // dst = 10.0.0.2
+ // \options \
+ // ###[ ICMP ]###
+ // type = echo-reply
+ // code = 0
+ // chksum = 0xbbb1
+ // id = 0x1
+ // seq = 0x7b
+ // unused = b''
+ // ###[ Raw ]###
+ // load = b'hello'
+ val expectedReply = """
+ 01020304050602030405060708004500002100010000400166D90A0000010A0
+ 000020000BBB10001007B68656C6C6F
+ """.replace("\\s+".toRegex(), "").trim()
+ assertContentEquals(
+ HexDump.hexStringToByteArray(expectedReply),
+ transmitPkt
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testCorruptedIpv4IcmpPacketDropped() {
+ val (apfFilter, program) = getApfWithIpv4PingOffloadEnabled()
+ // Using scapy to generate corrupted icmp packet
+ // eth = Ether(src="01:02:03:04:05:06", dst="02:03:04:05:06:07")
+ // ip = IP(proto=1, src="10.0.0.2", dst="10.0.0.1")
+ // pkt = eth/ip/b"hello"
+ val ipv4EchoRequestPkt = """
+ 02030405060701020304050608004500001900010000400166e10a0000020a0
+ 0000168656c6c6f
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(ipv4EchoRequestPkt),
+ DROPPED_IPV4_ICMP_INVALID
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testIpv4EchoRequestWithOptionPassed() {
+ val (apfFilter, program) = getApfWithIpv4PingOffloadEnabled()
+ // Using scapy to generate IPv4 echo request packet with option:
+ // eth = Ether(src="01:02:03:04:05:06", dst="02:03:04:05:06:07")
+ // ip = IP(src="10.0.0.2", dst="10.0.0.1", options=IPOption(b'\x94\x04\x00\x00'))
+ // icmp = ICMP(id=1, seq=123)
+ // pkt = eth/ip/icmp/b"hello"
+ val ipv4EchoRequestPkt = """
+ 020304050607010203040506080046000025000100004001d1d00a0000020a0
+ 00001940400000800b3b10001007b68656c6c6f
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(ipv4EchoRequestPkt),
+ PASSED_IPV4_UNICAST
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testIpv4EchoRequestToOtherHostPassed() {
+ val (apfFilter, program) = getApfWithIpv4PingOffloadEnabled()
+ // Using scapy to generate IPv4 echo request packet to other host:
+ // eth = Ether(src="01:02:03:04:05:06", dst="02:03:04:05:06:07")
+ // ip = IP(src="10.0.0.2", dst="10.0.0.111")
+ // icmp = ICMP(id=1, seq=123)
+ // pkt = eth/ip/icmp/b"hello"
+ val ipv4EchoRequestPkt = """
+ 020304050607010203040506080045000021000100004001666b0a0000020a0
+ 0006f0800b3b10001007b68656c6c6f
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(ipv4EchoRequestPkt),
+ PASSED_IPV4_UNICAST
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testBroadcastIpv4EchoRequestPassed() {
+ val (apfFilter, program) = getApfWithIpv4PingOffloadEnabled(enableMultiCastFilter = false)
+ // Using scapy to generate broadcast IPv4 echo request packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="ff:ff:ff:ff:ff:ff")
+ // ip = IP(src="10.0.0.2", dst="10.0.0.255")
+ // icmp = ICMP(id=1, seq=123)
+ // pkt = eth/ip/icmp/b"hello"
+ val ipv4EchoRequestPkt = """
+ ffffffffffff01020304050608004500002100010000400165db0a0000020a0
+ 000ff0800b3b10001007b68656c6c6f
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(ipv4EchoRequestPkt),
+ PASSED_IPV4
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testIpv4EchoReplyPassed() {
+ val (apfFilter, program) = getApfWithIpv4PingOffloadEnabled()
+ // Using scapy to generate IPv4 echo reply packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="02:03:04:05:06:07")
+ // ip = IP(src="10.0.0.2", dst="10.0.0.1")
+ // icmp = ICMP(type=0, id=1, seq=123)
+ // pkt = eth/ip/icmp/b"hello"
+ val ipv4EchoReplyPkt = """
+ 02030405060701020304050608004500002100010000400166d90a0000020a0
+ 000010000bbb10001007b68656c6c6f
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(ipv4EchoReplyPkt),
+ PASSED_IPV4_UNICAST
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testOffloadServiceInfoUpdateTriggersProgramInstall() {
+ val apfConfig = getDefaultConfig()
+ apfConfig.handleMdnsOffload = true
+ val apfFilter = getApfFilter(apfConfig)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
val captor = ArgumentCaptor.forClass(OffloadEngine::class.java)
verify(nsdManager).registerOffloadEngine(
- eq(ifParams.name),
- anyLong(),
- anyLong(),
- any(),
- captor.capture()
+ eq(ifParams.name),
+ anyLong(),
+ anyLong(),
+ any(),
+ captor.capture()
)
val offloadEngine = captor.value
- val info1 = OffloadServiceInfo(
- OffloadServiceInfo.Key("TestServiceName", "_advertisertest._tcp"),
- listOf(),
- "Android_test.local",
- byteArrayOf(0x01, 0x02, 0x03, 0x04),
- 0,
- OffloadEngine.OFFLOAD_TYPE_REPLY.toLong()
- )
- val info2 = OffloadServiceInfo(
- OffloadServiceInfo.Key("TestServiceName2", "_advertisertest._tcp"),
- listOf(),
- "Android_test.local",
- byteArrayOf(0x01, 0x02, 0x03, 0x04),
- 0,
- OffloadEngine.OFFLOAD_TYPE_REPLY.toLong()
- )
- val updatedInfo1 = OffloadServiceInfo(
- OffloadServiceInfo.Key("TestServiceName", "_advertisertest._tcp"),
- listOf(),
- "Android_test.local",
- byteArrayOf(),
- 0,
- OffloadEngine.OFFLOAD_TYPE_REPLY.toLong()
- )
- handler.post { offloadEngine.onOffloadServiceUpdated(info1) }
- handlerThread.waitForIdle(TIMEOUT_MS)
- assertContentEquals(listOf(info1), apfFilter.mOffloadServiceInfos)
- handler.post { offloadEngine.onOffloadServiceUpdated(info2) }
- handlerThread.waitForIdle(TIMEOUT_MS)
- assertContentEquals(listOf(info1, info2), apfFilter.mOffloadServiceInfos)
- handler.post { offloadEngine.onOffloadServiceUpdated(updatedInfo1) }
- handlerThread.waitForIdle(TIMEOUT_MS)
- assertContentEquals(listOf(info2, updatedInfo1), apfFilter.mOffloadServiceInfos)
- handler.post { offloadEngine.onOffloadServiceRemoved(updatedInfo1) }
- handlerThread.waitForIdle(TIMEOUT_MS)
- assertContentEquals(listOf(info2), apfFilter.mOffloadServiceInfos)
+ visibleOnHandlerThread(handler) {
+ offloadEngine.onOffloadServiceUpdated(castOffloadInfo.value)
+ }
- handler.post { apfFilter.shutdown() }
- handlerThread.waitForIdle(TIMEOUT_MS)
- verify(nsdManager).unregisterOffloadEngine(any())
+ verify(apfController).installPacketFilter(any(), any())
+
+ visibleOnHandlerThread(handler) { apfFilter.shutdown() }
+ verify(nsdManager).unregisterOffloadEngine(eq(offloadEngine))
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testCorruptedOffloadServiceInfoUpdateNotTriggerNewProgramInstall() {
+ val apfConfig = getDefaultConfig()
+ apfConfig.handleMdnsOffload = true
+ val apfFilter = getApfFilter(apfConfig)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
+ val captor = ArgumentCaptor.forClass(OffloadEngine::class.java)
+ verify(nsdManager).registerOffloadEngine(
+ eq(ifParams.name),
+ anyLong(),
+ anyLong(),
+ any(),
+ captor.capture()
+ )
+ val offloadEngine = captor.value
+ visibleOnHandlerThread(handler) {
+ offloadEngine.onOffloadServiceUpdated(castOffloadInfo.value)
+ }
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
+ val corruptedOffloadInfo = OffloadServiceInfo(
+ OffloadServiceInfo.Key("gambit", "_${"a".repeat(63)}._tcp"),
+ listOf(),
+ "Android_f47ac10b58cc4b88bc3f5e7a81e59872.local",
+ byteArrayOf(0x01, 0x02, 0x03, 0x04),
+ 0,
+ OffloadEngine.OFFLOAD_TYPE_REPLY.toLong()
+ )
+ visibleOnHandlerThread(handler) {
+ offloadEngine.onOffloadServiceUpdated(corruptedOffloadInfo)
+ }
+ verify(apfController, never()).installPacketFilter(any(), any())
+ }
+
+ private fun getApfWithMdnsOffloadEnabled(
+ apfRam: Int = 4096,
+ mcFilter: Boolean = true,
+ v6Only: Boolean = false,
+ addedOffloadInfos: List<FromU<OffloadServiceInfo>> = listOf(
+ castOffloadInfo,
+ tvRemoteOffloadInfo,
+ manySubtypeOffloadInfo,
+ manySubtypeOffloadInfo
+ ),
+ removedOffloadInfos: List<FromU<OffloadServiceInfo>> = listOf(),
+ raReaderSocket: FileDescriptor = raReadSocket
+ ): Pair<ApfFilter, ByteArray> {
+ val localNsdManager = mock(NsdManager::class.java)
+ doReturn(localNsdManager).`when`(context).getSystemService(NsdManager::class.java)
+ doReturn(raReaderSocket).`when`(dependencies).createPacketReaderSocket(anyInt())
+ val apfConfig = getDefaultConfig()
+ apfConfig.apfRamSize = apfRam
+ apfConfig.handleMdnsOffload = true
+ if (mcFilter) {
+ apfConfig.multicastFilter = true
+ }
+ val apfFilter = getApfFilter(apfConfig)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
+ val captor = ArgumentCaptor.forClass(OffloadEngine::class.java)
+ verify(localNsdManager).registerOffloadEngine(
+ eq(ifParams.name),
+ anyLong(),
+ anyLong(),
+ any(),
+ captor.capture()
+ )
+ val offloadEngine = captor.value
+ val lp = LinkProperties()
+ if (v6Only) {
+ apfFilter.updateClatInterfaceState(true)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
+ } else {
+ val ipv4LinkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24)
+ lp.addLinkAddress(ipv4LinkAddress)
+ }
+ val ipv6LinkAddress = LinkAddress(hostLinkLocalIpv6Address, 64)
+ lp.addLinkAddress(ipv6LinkAddress)
+ apfFilter.setLinkProperties(lp)
+ var program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
+
+ if (addedOffloadInfos.isNotEmpty()) {
+ visibleOnHandlerThread(handler) {
+ addedOffloadInfos.forEach { offloadEngine.onOffloadServiceUpdated(it.value) }
+ }
+ program = apfTestHelpers.consumeInstalledProgram(
+ apfController,
+ installCnt = addedOffloadInfos.size
+ )
+ }
+ if (removedOffloadInfos.isNotEmpty()) {
+ visibleOnHandlerThread(handler) {
+ removedOffloadInfos.forEach { offloadEngine.onOffloadServiceRemoved(it.value) }
+ }
+ program = apfTestHelpers.consumeInstalledProgram(
+ apfController,
+ installCnt = removedOffloadInfos.size
+ )
+ }
+ return Pair(apfFilter, program)
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testIPv4MdnsQueryReplied() {
+ val (apfFilter, program) = getApfWithMdnsOffloadEnabled(mcFilter = false)
+ // Using scapy to generate packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb")
+ // ip = IP(src="10.0.0.3", dst="224.0.0.251")
+ // udp = UDP(dport=5353, sport=5353)
+ // dns = DNS(qd=DNSQR(qname="_googlecast._tcp.local", qtype="PTR"))
+ // pkt = eth/ip/udp/dns
+ val castIPv4MdnsPtrQuery = """
+ 01005e0000fb0102030405060800450000440001000040118faa0a000003e00
+ 000fb14e914e900309fa50000010000010000000000000b5f676f6f676c6563
+ 617374045f746370056c6f63616c00000c0001
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(castIPv4MdnsPtrQuery),
+ DROPPED_MDNS_REPLIED
+ )
+
+ var transmitPkt = apfTestHelpers.consumeTransmittedPackets(1)[0]
+
+ // ###[ Ethernet ]###
+ // dst = 01:00:5e:00:00:fb
+ // src = 02:03:04:05:06:07
+ // type = IPv4
+ // ###[ IP ]###
+ // version = 4
+ // ihl = 5
+ // tos = 0x0
+ // len = 514
+ // id = 0
+ // flags = DF
+ // frag = 0
+ // ttl = 255
+ // proto = udp
+ // chksum = 0x8eee
+ // src = 10.0.0.1
+ // dst = 224.0.0.251
+ // \options \
+ // ###[ UDP ]###
+ // sport = mdns
+ // dport = mdns
+ // len = 494
+ // chksum = 0x2f0d
+ // ###[ DNS ]###
+ // id = 0
+ // qr = 1
+ // opcode = QUERY
+ // aa = 1
+ // tc = 0
+ // rd = 0
+ // ra = 0
+ // z = 0
+ // ad = 0
+ // cd = 0
+ // rcode = ok
+ // qdcount = 0
+ // ancount = 7
+ // nscount = 0
+ // arcount = 0
+ // \qd \
+ // \an \
+ // |###[ DNS Resource Record ]###
+ // | rrname = b'_googlecast._tcp.local.'
+ // | type = PTR
+ // | cacheflush= 0
+ // | rclass = IN
+ // | ttl = 120
+ // | rdlen = None
+ // | rdata = b'gambit-3cb56c6253638b3641e3d289013cc0ae._googlecast._tcp.local.'
+ // |###[ DNS SRV Resource Record ]###
+ // | rrname = b'\xc0.'
+ // | type = SRV
+ // | cacheflush= 0
+ // | rclass = IN
+ // | ttl = 120
+ // | rdlen = None
+ // | priority = 12320
+ // | weight = 12320
+ // | port = 14384
+ // | target = b'9 3cb56c62-5363-8b36-41e3-d289013cc0ae.local..'
+ // |###[ DNS Resource Record ]###
+ // | rrname = b'\xc0.'
+ // | type = TXT
+ // | cacheflush= 0
+ // | rclass = IN
+ // | ttl = 120
+ // | rdlen = None
+ // | rdata = [b' "id=3cb56c6253638b3641e3d289013cc0ae cd=8ECC37F6755390D005DFC02F8EC0D4FA rm=4ABD579644ACFCCF ve=05 md=gambit ic=/setup/icon.png fn=gambit a=264709 st=0 bs=FA8FFD2242A7 nf=1 rs= ']
+ // |###[ DNS Resource Record ]###
+ // | rrname = b'Android_f47ac10b58cc4b88bc3f5e7a81e59872.local.'
+ // | type = A
+ // | cacheflush= 0
+ // | rclass = IN
+ // | ttl = 120
+ // | rdlen = None
+ // | rdata = 100.89.85.228
+ // |###[ DNS Resource Record ]###
+ // | rrname = b' (Android_f47ac10b58cc4b88bc3f5e7a81e59872\xc0\x1d\x00\x01\x00\x01\x00\x00\x00x\x00\x04dYU\xe4\xc1W\x00.\x00\x01\x00\x00\x00x\x00\x10\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xc1W\x00\x1c.'
+ // | type = AAAA
+ // | cacheflush= 0
+ // | rclass = IN
+ // | ttl = 120
+ // | rdlen = None
+ // | rdata = fe80::3
+ // |###[ DNS Resource Record ]###
+ // | rrname = b' (Android_f47ac10b58cc4b88bc3f5e7a81e59872\xc0\x1d\x00\x01\x00\x01\x00\x00\x00x\x00\x04dYU\xe4\xc1W\x00.\x00\x01\x00\x00\x00x\x00\x10\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xc1W\x00\x1c.'
+ // | type = AAAA
+ // | cacheflush= 0
+ // | rclass = IN
+ // | ttl = 120
+ // | rdlen = None
+ // | rdata = 200a::3
+ // |###[ DNS Resource Record ]###
+ // | rrname = b' (Android_f47ac10b58cc4b88bc3f5e7a81e59872\xc0\x1d\x00\x01\x00\x01\x00\x00\x00x\x00\x04dYU\xe4\xc1W\x00.\x00\x01\x00\x00\x00x\x00\x10\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xc1W\x00\x1c.'
+ // | type = AAAA
+ // | cacheflush= 0
+ // | rclass = IN
+ // | ttl = 120
+ // | rdlen = None
+ // | rdata = 200b::3
+ // \ns \
+ // \ar \
+ val expectedIPv4CastMdnsReply = """
+ 01005E0000FB02030405060708004500020200004000FF118EEE0A000001E00
+ 000FB14E914E901EE2F0D0000840000000007000000000B5F676F6F676C6563
+ 617374045F746370056C6F63616C00000C000100000078002A2767616D62697
+ 42D336362353663363235333633386233363431653364323839303133636330
+ 6165C00C01C0000021000100000078003430203020383030392033636235366
+ 336322D353336332D386233362D343165332D6432383930313363633061652E
+ 6C6F63616C2E01C000001000010000007800B3B2202269643D3363623536633
+ 6323533363338623336343165336432383930313363633061652063643D3845
+ 434333374636373535333930443030354446433032463845433044344641207
+ 26D3D344142443537393634344143464343462076653D3035206D643D67616D
+ 6269742069633D2F73657475702F69636F6E2E706E6720666E3D67616D62697
+ 420613D3236343730392073743D302062733D46413846464432323432413720
+ 6E663D312072733D2028416E64726F69645F663437616331306235386363346
+ 2383862633366356537613831653539383732C01D0001000100000078000464
+ 5955E4C157001C0001000000780010FE800000000000000000000000000003C
+ 157001C0001000000780010200A0000000000000000000000000003C157001C
+ 0001000000780010200B0000000000000000000000000003
+ """.replace("\\s+".toRegex(), "").trim()
+
+ assertContentEquals(
+ HexDump.hexStringToByteArray(expectedIPv4CastMdnsReply),
+ transmitPkt
+ )
+
+ // Using scapy to generate packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb")
+ // ip = IP(src="10.0.0.3", dst="224.0.0.251")
+ // udp = UDP(dport=5353, sport=5353)
+ // questions = [
+ // DNSQR(qname="_airplay._tcp.local", qtype="PTR"),
+ // DNSQR(qname="gambit-3cb56c6253638b3641e3d289013cc0ae._googlecast._tcp.local", qtype="TXT")
+ // ]
+ // dns = dns_compress(DNS(qd=questions))
+ // pkt = eth/ip/udp/dns
+ val castIPv4MdnsTxtQuery = """
+ 01005e0000fb01020304050608004500007b0001000040118f730a000003e00
+ 000fb14e914e900675712000001000002000000000000085f616972706c6179
+ 045f746370056c6f63616c00000c00012767616d6269742d336362353663363
+ 23533363338623336343165336432383930313363633061650b5f676f6f676c
+ 6563617374c01500100001
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(castIPv4MdnsTxtQuery),
+ DROPPED_MDNS_REPLIED
+ )
+
+ transmitPkt = apfTestHelpers.consumeTransmittedPackets(1)[0]
+
+ assertContentEquals(
+ HexDump.hexStringToByteArray(expectedIPv4CastMdnsReply),
+ transmitPkt
+ )
+
+ // Using scapy to generate packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb")
+ // ip = IP(src="10.0.0.3", dst="224.0.0.251")
+ // udp = UDP(dport=5353, sport=5353)
+ // questions = [
+ // DNSQR(qname="_airplay._tcp.local", qtype="PTR"),
+ // DNSQR(qname="gambit-3cb56c6253638b3641e3d289013cc0ae._googlecast._tcp.local", qtype="SRV")
+ // ]
+ // dns = dns_compress(DNS(qd=questions))
+ // pkt = eth/ip/udp/dns
+ val castIPv4MdnsSRVQuery = """
+ 01005e0000fb01020304050608004500007b0001000040118f730a000003e00
+ 000fb14e914e900674612000001000002000000000000085f616972706c6179
+ 045f746370056c6f63616c00000c00012767616d6269742d336362353663363
+ 23533363338623336343165336432383930313363633061650b5f676f6f676c
+ 6563617374c01500210001
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(castIPv4MdnsSRVQuery),
+ DROPPED_MDNS_REPLIED
+ )
+
+ transmitPkt = apfTestHelpers.consumeTransmittedPackets(1)[0]
+
+ assertContentEquals(
+ HexDump.hexStringToByteArray(expectedIPv4CastMdnsReply),
+ transmitPkt
+ )
+
+ // Using scapy to generate packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb")
+ // ip = IP(src="10.0.0.3", dst="224.0.0.251")
+ // udp = UDP(dport=5353, sport=5353)
+ // dns = DNS(qd=DNSQR(qname="_androidtvremote2._tcp.local", qtype="PTR"))
+ // pkt = eth/ip/udp/dns
+ val tvRemoteIPv4MdnsPtrQuery = """
+ 01005e0000fb01020304050608004500004a0001000040118fa40a000003e00
+ 000fb14e914e900366966000001000001000000000000115f616e64726f6964
+ 747672656d6f746532045f746370056c6f63616c00000c0001
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(tvRemoteIPv4MdnsPtrQuery),
+ DROPPED_MDNS_REPLIED
+ )
+
+ transmitPkt = apfTestHelpers.consumeTransmittedPackets(1)[0]
+
+ // ###[ Ethernet ]###
+ // dst = 01:00:5e:00:00:fb
+ // src = 02:03:04:05:06:07
+ // type = IPv4
+ // ###[ IP ]###
+ // version = 4
+ // ihl = 5
+ // tos = 0x0
+ // len = 332
+ // id = 0
+ // flags = DF
+ // frag = 0
+ // ttl = 255
+ // proto = udp
+ // chksum = 0x8fa4
+ // src = 10.0.0.1
+ // dst = 224.0.0.251
+ // \options \
+ // ###[ UDP ]###
+ // sport = mdns
+ // dport = mdns
+ // len = 312
+ // chksum = 0xf867
+ // ###[ DNS ]###
+ // id = 0
+ // qr = 1
+ // opcode = QUERY
+ // aa = 1
+ // tc = 0
+ // rd = 0
+ // ra = 0
+ // z = 0
+ // ad = 0
+ // cd = 0
+ // rcode = ok
+ // qdcount = 0
+ // ancount = 7
+ // nscount = 0
+ // arcount = 0
+ // \qd \
+ // \an \
+ // |###[ DNS Resource Record ]###
+ // | rrname = b'_androidtvremote2._tcp.local.'
+ // | type = PTR
+ // | cacheflush= 0
+ // | rclass = IN
+ // | ttl = 120
+ // | rdlen = None
+ // | rdata = b'gambit._androidtvremote2._tcp.local.'
+ // |###[ DNS SRV Resource Record ]###
+ // | rrname = b'gambit._androidtvremote2._tcp.local.'
+ // | type = SRV
+ // | cacheflush= 0
+ // | rclass = IN
+ // | ttl = 120
+ // | rdlen = None
+ // | priority = 12320
+ // | weight = 12320
+ // | port = 13876
+ // | target = b'6 Android_2570595cc11d4af4a4b7146b946eeb9e.local.'
+ // |###[ DNS Resource Record ]###
+ // | rrname = b'gambit._androidtvremote2._tcp.local.'
+ // | type = TXT
+ // | cacheflush= 0
+ // | rclass = IN
+ // | ttl = 120
+ // | rdlen = None
+ // | rdata = [b'"bt=3C:4E:56:76:1E:E9"']
+ // |###[ DNS Resource Record ]###
+ // | rrname = b'Android_f47ac10b58cc4b88bc3f5e7a81e59872.local.'
+ // | type = A
+ // | cacheflush= 0
+ // | rclass = IN
+ // | ttl = 120
+ // | rdlen = None
+ // | rdata = 100.89.85.228
+ // |###[ DNS Resource Record ]###
+ // | rrname = b'Android_f47ac10b58cc4b88bc3f5e7a81e59872.local.'
+ // | type = AAAA
+ // | cacheflush= 0
+ // | rclass = IN
+ // | ttl = 120
+ // | rdlen = None
+ // | rdata = fe80::3
+ // |###[ DNS Resource Record ]###
+ // | rrname = b'Android_f47ac10b58cc4b88bc3f5e7a81e59872.local.'
+ // | type = AAAA
+ // | cacheflush= 0
+ // | rclass = IN
+ // | ttl = 120
+ // | rdlen = None
+ // | rdata = 200a::3
+ // |###[ DNS Resource Record ]###
+ // | rrname = b'Android_f47ac10b58cc4b88bc3f5e7a81e59872.local.'
+ // | type = AAAA
+ // | cacheflush= 0
+ // | rclass = IN
+ // | ttl = 120
+ // | rdlen = None
+ // | rdata = 200b::3
+ // \ns \
+ // \ar \
+ val expectedIPv4tvRemoteMdnsReply = """
+ 01005E0000FB02030405060708004500014C00004000FF118FA40A000001E00
+ 000FB14E914E90138F867000084000000000700000000115F616E64726F6964
+ 747672656D6F746532045F746370056C6F63616C00000C00010000007800090
+ 667616D626974C00CC03400210001000000780037302030203634363620416E
+ 64726F69645F323537303539356363313164346166346134623731343662393
+ 43665656239652E6C6F63616CC03400100001000000780017162262743D3343
+ 3A34453A35363A37363A31453A45392228416E64726F69645F6634376163313
+ 062353863633462383862633366356537613831653539383732C02300010001
+ 000000780004645955E4C0A3001C0001000000780010FE80000000000000000
+ 0000000000003C0A3001C0001000000780010200A0000000000000000000000
+ 000003C0A3001C0001000000780010200B0000000000000000000000000003
+ """.replace("\\s+".toRegex(), "").trim()
+
+ assertContentEquals(
+ HexDump.hexStringToByteArray(expectedIPv4tvRemoteMdnsReply),
+ transmitPkt
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testIPv4MdnsQueryDropped() {
+ val (apfFilter, program) = getApfWithMdnsOffloadEnabled(
+ removedOffloadInfos = listOf(tvRemoteOffloadInfo)
+ )
+ // Using scapy to generate packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb")
+ // ip = IP(src="10.0.0.3", dst="224.0.0.251")
+ // udp = UDP(dport=5353, sport=5353)
+ // dns = DNS(qd=DNSQR(qname="_airplay._tcp.local", qtype="PTR"))
+ // pkt = eth/ip/udp/dns
+ val airplayIPv4MdnsPtrQuery = """
+ 01005e0000fb0102030405060800450000410001000040118fad0a000003e00
+ 000fb14e914e9002d8203000001000001000000000000085f616972706c6179
+ 045f746370056c6f63616c00000c0001
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(airplayIPv4MdnsPtrQuery),
+ DROPPED_MDNS
+ )
+
+ // Using scapy to generate packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb")
+ // ip = IP(src="10.0.0.3", dst="224.0.0.251")
+ // udp = UDP(dport=5353, sport=5353)
+ // dns = DNS(qd=DNSQR(qname="_androidtvremote2._tcp.local", qtype="PTR"))
+ // pkt = eth/ip/udp/dns
+ val tvRemoteIPv4MdnsPtrQuery = """
+ 01005e0000fb01020304050608004500004a0001000040118fa40a000003e00
+ 000fb14e914e900366966000001000001000000000000115f616e64726f6964
+ 747672656d6f746532045f746370056c6f63616c00000c0001
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(tvRemoteIPv4MdnsPtrQuery),
+ DROPPED_MDNS
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testIPv4MdnsQueryWithOptionPassed() {
+ val (apfFilter, program) = getApfWithMdnsOffloadEnabled(mcFilter = false)
+ // Using scapy to generate packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb")
+ // ip = IP(src="10.0.0.3", dst="224.0.0.251", options=IPOption(b'\x94\x04\x00\x00'))
+ // udp = UDP(dport=5353, sport=5353)
+ // dns = DNS(qd=DNSQR(qname="_googlecast._tcp.local", qtype="PTR"))
+ // pkt = eth/ip/udp/dns
+ val castIPv4MdnsPtrQueryWithOption = """
+ 01005e0000fb010203040506080046000048000100004011faa10a000003e00
+ 000fb9404000014e914e900309fa50000010000010000000000000b5f676f6f
+ 676c6563617374045f746370056c6f63616c00000c0001
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(castIPv4MdnsPtrQueryWithOption),
+ PASSED_IPV4
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testIPv4MdnsQueryDroppedOnV6OnlyNetwork() {
+ val (apfFilter, program) = getApfWithMdnsOffloadEnabled(mcFilter = false, v6Only = true)
+ // Using scapy to generate packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb")
+ // ip = IP(src="10.0.0.3", dst="224.0.0.251")
+ // udp = UDP(dport=5353, sport=5353)
+ // dns = DNS(qd=DNSQR(qname="_googlecast._tcp.local", qtype="PTR"))
+ // pkt = eth/ip/udp/dns
+ val castIPv4MdnsPtrQuery = """
+ 01005e0000fb0102030405060800450000440001000040118faa0a000003e00
+ 000fb14e914e900309fa50000010000010000000000000b5f676f6f676c6563
+ 617374045f746370056c6f63616c00000c0001
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(castIPv4MdnsPtrQuery),
+ DROPPED_IPV4_NON_DHCP4
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testIPv4MdnsReplyPassed() {
+ val (apfFilter, program) = getApfWithMdnsOffloadEnabled(mcFilter = false)
+ // Using scapy to generate packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb")
+ // ip = IP(src="10.0.0.3", dst="224.0.0.251")
+ // udp = UDP(dport=5353, sport=5353)
+ // dns = DNS(qr=1, aa=1, rd=0, qd=None, an=DNSRR(rrname="_androidtvremote2._tcp.local", type="PTR", rdata="gambit._androidtvremote2._tcp.local", ttl=120))
+ // pkt = eth/ip/udp/dns
+ val tvRemoteIPv4MdnsPtrAnswer = """
+ 01005e0000fb0102030405060800450000750001000040118f790a000003e00
+ 000fb14e914e9006169b4000084000000000100000000115f616e64726f6964
+ 747672656d6f746532045f746370056c6f63616c00000c00010000007800250
+ 667616d626974115f616e64726f6964747672656d6f746532045f746370056c
+ 6f63616c00
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(tvRemoteIPv4MdnsPtrAnswer),
+ PASSED_MDNS
+ )
+ }
+
+ fun testIPv6MdnsQueryReplied() {
+ val (apfFilter, program) = getApfWithMdnsOffloadEnabled(mcFilter = false)
+ // Using scapy to generate packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="33:33:00:00:00:FB")
+ // ip = IPv6(src="fe80::1", dst="ff02::fb")
+ // udp = UDP(dport=5353, sport=5353)
+ // dns = DNS(qd=DNSQR(qname="_googlecast._tcp.local", qtype="PTR"))
+ // pkt = eth/ip/udp/dns
+ val castIPv6MdnsPtrQuery = """
+ 3333000000fb01020304050686dd6000000000301140fe80000000000000000
+ 0000000000001ff0200000000000000000000000000fb14e914e900308c2400
+ 00010000010000000000000b5f676f6f676c6563617374045f746370056c6f6
+ 3616c00000c0001
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(castIPv6MdnsPtrQuery),
+ DROPPED_MDNS_REPLIED
+ )
+
+ var transmitPkt = apfTestHelpers.consumeTransmittedPackets(1)[0]
+
+ // ###[ Ethernet ]###
+ // dst = 33:33:00:00:00:fb
+ // src = 02:03:04:05:06:07
+ // type = IPv6
+ // ###[ IPv6 ]###
+ // version = 6
+ // tc = 0
+ // fl = 0
+ // plen = 494
+ // nh = UDP
+ // hlim = 255
+ // src = fe80::3
+ // dst = ff02::fb
+ // ###[ UDP ]###
+ // sport = mdns
+ // dport = mdns
+ // len = 494
+ // chksum = 0x1b88
+ // ###[ DNS ]###
+ // id = 0
+ // qr = 1
+ // opcode = QUERY
+ // aa = 1
+ // tc = 0
+ // rd = 0
+ // ra = 0
+ // z = 0
+ // ad = 0
+ // cd = 0
+ // rcode = ok
+ // qdcount = 0
+ // ancount = 7
+ // nscount = 0
+ // arcount = 0
+ // \qd \
+ // \an \
+ // |###[ DNS Resource Record ]###
+ // | rrname = b'_googlecast._tcp.local.'
+ // | type = PTR
+ // | cacheflush= 0
+ // | rclass = IN
+ // | ttl = 120
+ // | rdlen = None
+ // | rdata = b'gambit-3cb56c6253638b3641e3d289013cc0ae._googlecast._tcp.local.'
+ // |###[ DNS SRV Resource Record ]###
+ // | rrname = b'\xc0.'
+ // | type = SRV
+ // | cacheflush= 0
+ // | rclass = IN
+ // | ttl = 120
+ // | rdlen = None
+ // | priority = 12320
+ // | weight = 12320
+ // | port = 14384
+ // | target = b'9 3cb56c62-5363-8b36-41e3-d289013cc0ae.local..'
+ // |###[ DNS Resource Record ]###
+ // | rrname = b'\xc0.'
+ // | type = TXT
+ // | cacheflush= 0
+ // | rclass = IN
+ // | ttl = 120
+ // | rdlen = None
+ // | rdata = [b' "id=3cb56c6253638b3641e3d289013cc0ae cd=8ECC37F6755390D005DFC02F8EC0D4FA rm=4ABD579644ACFCCF ve=05 md=gambit ic=/setup/icon.png fn=gambit a=264709 st=0 bs=FA8FFD2242A7 nf=1 rs= ']
+ // |###[ DNS Resource Record ]###
+ // | rrname = b'Android_f47ac10b58cc4b88bc3f5e7a81e59872.local.'
+ // | type = A
+ // | cacheflush= 0
+ // | rclass = IN
+ // | ttl = 120
+ // | rdlen = None
+ // | rdata = 100.89.85.228
+ // |###[ DNS Resource Record ]###
+ // | rrname = b' (Android_f47ac10b58cc4b88bc3f5e7a81e59872\xc0\x1d\x00\x01\x00\x01\x00\x00\x00x\x00\x04dYU\xe4\xc1W\x00.\x00\x01\x00\x00\x00x\x00\x10\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xc1W\x00\x1c.'
+ // | type = AAAA
+ // | cacheflush= 0
+ // | rclass = IN
+ // | ttl = 120
+ // | rdlen = None
+ // | rdata = fe80::3
+ // |###[ DNS Resource Record ]###
+ // | rrname = b' (Android_f47ac10b58cc4b88bc3f5e7a81e59872\xc0\x1d\x00\x01\x00\x01\x00\x00\x00x\x00\x04dYU\xe4\xc1W\x00.\x00\x01\x00\x00\x00x\x00\x10\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xc1W\x00\x1c.'
+ // | type = AAAA
+ // | cacheflush= 0
+ // | rclass = IN
+ // | ttl = 120
+ // | rdlen = None
+ // | rdata = 200a::3
+ // |###[ DNS Resource Record ]###
+ // | rrname = b' (Android_f47ac10b58cc4b88bc3f5e7a81e59872\xc0\x1d\x00\x01\x00\x01\x00\x00\x00x\x00\x04dYU\xe4\xc1W\x00.\x00\x01\x00\x00\x00x\x00\x10\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xc1W\x00\x1c.'
+ // | type = AAAA
+ // | cacheflush= 0
+ // | rclass = IN
+ // | ttl = 120
+ // | rdlen = None
+ // | rdata = 200b::3
+ // \ns \
+ // \ar \
+ val expectedIPv6CastMdnsReply = """
+ 3333000000FB02030405060786DD6000000001EE11FFFE80000000000000000
+ 0000000000003FF0200000000000000000000000000FB14E914E901EE1B8800
+ 00840000000007000000000B5F676F6F676C6563617374045F746370056C6F6
+ 3616C00000C000100000078002A2767616D6269742D33636235366336323533
+ 36333862333634316533643238393031336363306165C00C01C000002100010
+ 0000078003430203020383030392033636235366336322D353336332D386233
+ 362D343165332D6432383930313363633061652E6C6F63616C2E01C00000100
+ 0010000007800B3B2202269643D336362353663363235333633386233363431
+ 65336432383930313363633061652063643D384543433337463637353533393
+ 044303035444643303246384543304434464120726D3D344142443537393634
+ 344143464343462076653D3035206D643D67616D6269742069633D2F7365747
+ 5702F69636F6E2E706E6720666E3D67616D62697420613D3236343730392073
+ 743D302062733D464138464644323234324137206E663D312072733D2028416
+ E64726F69645F66343761633130623538636334623838626333663565376138
+ 31653539383732C01D00010001000000780004645955E4C157001C000100000
+ 0780010FE800000000000000000000000000003C157001C0001000000780010
+ 200A0000000000000000000000000003C157001C0001000000780010200B000
+ 0000000000000000000000003
+ """.replace("\\s+".toRegex(), "").trim()
+
+ assertContentEquals(
+ HexDump.hexStringToByteArray(expectedIPv6CastMdnsReply),
+ transmitPkt
+ )
+
+ // Using scapy to generate packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="33:33:00:00:00:FB")
+ // ip = IPv6(src="fe80::1", dst="ff02::fb")
+ // udp = UDP(dport=5353, sport=5353)
+ // questions = [
+ // DNSQR(qname="_airplay._tcp.local", qtype="PTR"),
+ // DNSQR(qname="gambit-3cb56c6253638b3641e3d289013cc0ae._googlecast._tcp.local", qtype="TXT")
+ // ]
+ // dns = dns_compress(DNS(qd=questions))
+ // pkt = eth/ip/udp/dns
+ val castIPv6MdnsTxtQuery = """
+ 3333000000fb01020304050686dd6000000000671140fe80000000000000000
+ 0000000000001ff0200000000000000000000000000fb14e914e90067439100
+ 0001000002000000000000085f616972706c6179045f746370056c6f63616c0
+ 0000c00012767616d6269742d33636235366336323533363338623336343165
+ 336432383930313363633061650b5f676f6f676c6563617374c01500100001
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(castIPv6MdnsTxtQuery),
+ DROPPED_MDNS_REPLIED
+ )
+
+ transmitPkt = apfTestHelpers.consumeTransmittedPackets(1)[0]
+
+ assertContentEquals(
+ HexDump.hexStringToByteArray(expectedIPv6CastMdnsReply),
+ transmitPkt
+ )
+
+ // Using scapy to generate packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="33:33:00:00:00:FB")
+ // ip = IPv6(src="fe80::1", dst="ff02::fb")
+ // udp = UDP(dport=5353, sport=5353)
+ // dns = DNS(qd=DNSQR(qname="_androidtvremote2._tcp.local", qtype="PTR"))
+ // pkt = eth/ip/udp/dns
+ val tvRemoteIPv6MdnsPtrQuery = """
+ 3333000000fb01020304050686dd6000000000361140fe80000000000000000
+ 0000000000001ff0200000000000000000000000000fb14e914e9003655e500
+ 0001000001000000000000115f616e64726f6964747672656d6f746532045f7
+ 46370056c6f63616c00000c0001
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(tvRemoteIPv6MdnsPtrQuery),
+ DROPPED_MDNS_REPLIED
+ )
+
+ transmitPkt = apfTestHelpers.consumeTransmittedPackets(1)[0]
+
+ // ###[ Ethernet ]###
+ // dst = 33:33:00:00:00:fb
+ // src = 02:03:04:05:06:07
+ // type = IPv6
+ // ###[ IPv6 ]###
+ // version = 6
+ // tc = 0
+ // fl = 0
+ // plen = 312
+ // nh = UDP
+ // hlim = 255
+ // src = fe80::3
+ // dst = ff02::fb
+ // ###[ UDP ]###
+ // sport = mdns
+ // dport = mdns
+ // len = 312
+ // chksum = 0xf867
+ // ###[ DNS ]###
+ // id = 0
+ // qr = 1
+ // opcode = QUERY
+ // aa = 1
+ // tc = 0
+ // rd = 0
+ // ra = 0
+ // z = 0
+ // ad = 0
+ // cd = 0
+ // rcode = ok
+ // qdcount = 0
+ // ancount = 7
+ // nscount = 0
+ // arcount = 0
+ // \qd \
+ // \an \
+ // |###[ DNS Resource Record ]###
+ // | rrname = b'_androidtvremote2._tcp.local.'
+ // | type = PTR
+ // | cacheflush= 0
+ // | rclass = IN
+ // | ttl = 120
+ // | rdlen = None
+ // | rdata = b'gambit._androidtvremote2._tcp.local.'
+ // |###[ DNS SRV Resource Record ]###
+ // | rrname = b'gambit._androidtvremote2._tcp.local.'
+ // | type = SRV
+ // | cacheflush= 0
+ // | rclass = IN
+ // | ttl = 120
+ // | rdlen = None
+ // | priority = 12320
+ // | weight = 12320
+ // | port = 13876
+ // | target = b'6 Android_2570595cc11d4af4a4b7146b946eeb9e.local.'
+ // |###[ DNS Resource Record ]###
+ // | rrname = b'gambit._androidtvremote2._tcp.local.'
+ // | type = TXT
+ // | cacheflush= 0
+ // | rclass = IN
+ // | ttl = 120
+ // | rdlen = None
+ // | rdata = [b'"bt=3C:4E:56:76:1E:E9"']
+ // |###[ DNS Resource Record ]###
+ // | rrname = b'Android_f47ac10b58cc4b88bc3f5e7a81e59872.local.'
+ // | type = A
+ // | cacheflush= 0
+ // | rclass = IN
+ // | ttl = 120
+ // | rdlen = None
+ // | rdata = 100.89.85.228
+ // |###[ DNS Resource Record ]###
+ // | rrname = b'Android_f47ac10b58cc4b88bc3f5e7a81e59872.local.'
+ // | type = AAAA
+ // | cacheflush= 0
+ // | rclass = IN
+ // | ttl = 120
+ // | rdlen = None
+ // | rdata = fe80::3
+ // |###[ DNS Resource Record ]###
+ // | rrname = b'Android_f47ac10b58cc4b88bc3f5e7a81e59872.local.'
+ // | type = AAAA
+ // | cacheflush= 0
+ // | rclass = IN
+ // | ttl = 120
+ // | rdlen = None
+ // | rdata = 200a::3
+ // |###[ DNS Resource Record ]###
+ // | rrname = b'Android_f47ac10b58cc4b88bc3f5e7a81e59872.local.'
+ // | type = AAAA
+ // | cacheflush= 0
+ // | rclass = IN
+ // | ttl = 120
+ // | rdlen = None
+ // | rdata = 200b::3
+ // \ns \
+ // \ar \
+ val expectedIPv6tvRemoteMdnsReply = """
+ 3333000000FB02030405060786DD60000000013811FFFE80000000000000000
+ 0000000000003FF0200000000000000000000000000FB14E914E90138E4E200
+ 0084000000000700000000115F616E64726F6964747672656D6F746532045F7
+ 46370056C6F63616C00000C00010000007800090667616D626974C00CC03400
+ 210001000000780037302030203634363620416E64726F69645F32353730353
+ 935636331316434616634613462373134366239343665656239652E6C6F6361
+ 6CC03400100001000000780017162262743D33433A34453A35363A37363A314
+ 53A45392228416E64726F69645F663437616331306235386363346238386263
+ 3366356537613831653539383732C02300010001000000780004645955E4C0A
+ 3001C0001000000780010FE800000000000000000000000000003C0A3001C00
+ 01000000780010200A0000000000000000000000000003C0A3001C000100000
+ 0780010200B0000000000000000000000000003
+ """.replace("\\s+".toRegex(), "").trim()
+
+ assertContentEquals(
+ HexDump.hexStringToByteArray(expectedIPv6tvRemoteMdnsReply),
+ transmitPkt
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testIPv6MdnsQueryDropped() {
+ val (apfFilter, program) = getApfWithMdnsOffloadEnabled(
+ removedOffloadInfos = listOf(tvRemoteOffloadInfo)
+ )
+ // Using scapy to generate packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="33:33:00:00:00:FB")
+ // ip = IPv6(src="fe80::1", dst="ff02::fb")
+ // udp = UDP(dport=5353, sport=5353)
+ // dns = DNS(qd=DNSQR(qname="_airplay._tcp.local", qtype="PTR"))
+ // pkt = eth/ip/udp/dns
+ val airplayIPv6MdnsPtrQuery = """
+ 3333000000fb01020304050686dd60000000002d1140fe80000000000000000
+ 0000000000001ff0200000000000000000000000000fb14e914e9002d6e8200
+ 0001000001000000000000085f616972706c6179045f746370056c6f63616c0
+ 0000c0001
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(airplayIPv6MdnsPtrQuery),
+ DROPPED_MDNS
+ )
+
+ // Using scapy to generate packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="33:33:00:00:00:FB")
+ // ip = IPv6(src="fe80::1", dst="ff02::fb")
+ // udp = UDP(dport=5353, sport=5353)
+ // dns = DNS(qd=DNSQR(qname="_androidtvremote2._tcp.local", qtype="PTR"))
+ // pkt = eth/ip/udp/dns
+ val tvRemoteIPv6MdnsPtrQuery = """
+ 3333000000fb01020304050686dd6000000000361140fe80000000000000000
+ 0000000000001ff0200000000000000000000000000fb14e914e9003655e500
+ 0001000001000000000000115f616e64726f6964747672656d6f746532045f7
+ 46370056c6f63616c00000c0001
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(tvRemoteIPv6MdnsPtrQuery),
+ DROPPED_MDNS
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testIPv6MdnsReplyPassed() {
+ val (apfFilter, program) = getApfWithMdnsOffloadEnabled(mcFilter = false)
+ // Using scapy to generate packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="33:33:00:00:00:FB")
+ // ip = IPv6(src="fe80::1", dst="ff02::fb")
+ // udp = UDP(dport=5353, sport=5353)
+ // dns = DNS(qr=1, aa=1, rd=0, qd=None, an=DNSRR(rrname="_androidtvremote2._tcp.local", type="PTR", rdata="gambit._androidtvremote2._tcp.local", ttl=120))
+ // pkt = eth/ip/udp/dns
+ val tvRemoteIPv6MdnsPtrAnswer = """
+ 3333000000fb01020304050686dd6000000000611140fe80000000000000000
+ 0000000000001ff0200000000000000000000000000fb14e914e90061563300
+ 0084000000000100000000115f616e64726f6964747672656d6f746532045f7
+ 46370056c6f63616c00000c00010000007800250667616d626974115f616e64
+ 726f6964747672656d6f746532045f746370056c6f63616c00
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(tvRemoteIPv6MdnsPtrAnswer),
+ PASSED_MDNS
+ )
+ }
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testRaFilterWorksWhenMdnsOffloadEnabled() {
+ var (apfFilter, program) = getApfWithMdnsOffloadEnabled()
+ // ###[ Ethernet ]###
+ // dst = 33:33:00:00:00:01
+ // src = f4:34:f0:64:52:fe
+ // type = IPv6
+ // ###[ IPv6 ]###
+ // version = 6
+ // tc = 0
+ // fl = 68608
+ // plen = 80
+ // nh = ICMPv6
+ // hlim = 255
+ // src = fe80::1cb6:b5bc:353b:7cfd
+ // dst = ff02::1
+ // ###[ ICMPv6 Neighbor Discovery - Router Advertisement ]###
+ // type = Router Advertisement
+ // code = 0
+ // cksum = 0xfab
+ // chlim = 0
+ // M = 0
+ // O = 0
+ // H = 0
+ // prf = Medium (default)
+ // P = 0
+ // res = 0
+ // routerlifetime= 0
+ // reachabletime= 0
+ // retranstimer= 0
+ // ###[ ICMPv6 Neighbor Discovery Option - Prefix Information ]###
+ // type = 3
+ // len = 4
+ // prefixlen = 64
+ // L = 1
+ // A = 1
+ // R = 0
+ // res1 = 0
+ // validlifetime= 0x708
+ // preferredlifetime= 0x708
+ // res2 = 0x0
+ // prefix = fdee:d0c4:7546:5344::
+ // ###[ ICMPv6 Neighbor Discovery Option - Route Information Option ]###
+ // type = 24
+ // len = 2
+ // plen = 64
+ // res1 = 0
+ // prf = Medium (default)
+ // res2 = 0
+ // rtlifetime= 1800
+ // prefix = fd0c:8be6:43ee::
+ // ###[ ICMPv6 Neighbor Discovery Option - Expanded Flags Option ]###
+ // type = 26
+ // len = 1
+ // res = 140737488355328
+ // ###[ ICMPv6 Neighbor Discovery Option - Source Link-Layer Address ]###
+ // type = 1
+ // len = 1
+ // lladdr = f4:34:f0:64:52:fe
+ val ra = """
+ 333300000001f434f06452fe86dd60010c0000503afffe800000000000001cb6b5bc353b7cfdff0
+ 2000000000000000000000000000186000fab000000000000000000000000030440c00000070800
+ 00070800000000fdeed0c47546534400000000000000001802400000000708fd0c8be643ee00001
+ a018000000000000101f434f06452fe
+ """.replace("\\s+".toRegex(), "").trim()
+ val raBytes = HexDump.hexStringToByteArray(ra)
+ Os.write(raWriterSocket, raBytes, 0, raBytes.size)
+
+ program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ raBytes,
+ DROPPED_RA
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testMdnsOffloadFailOpenForTooManySubtype() {
+ val (apfFilter, program) = getApfWithMdnsOffloadEnabled()
+ // Using scapy to generate packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb")
+ // ip = IP(src="10.0.0.3", dst="224.0.0.251")
+ // udp = UDP(dport=5353, sport=5353)
+ // dns = DNS(qd=DNSQR(qname="_testsubtype._tcp.local", qtype="PTR"))
+ // pkt = eth/ip/udp/dns
+ val typePtrQuery = """
+ 01005e0000fb0102030405060800450000450001000040118fa90a000003e00
+ 000fb14e914e900319b020000010000010000000000000c5f74657374737562
+ 74797065045f746370056c6f63616c00000c0001
+ """.replace("\\s+".toRegex(), "").trim()
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(typePtrQuery),
+ PASSED_MDNS
+ )
+
+ // Using scapy to generate packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb")
+ // ip = IP(src="10.0.0.3", dst="224.0.0.251")
+ // udp = UDP(dport=5353, sport=5353)
+ // dns = DNS(qd=DNSQR(qname="sub1._sub._testsubtype._tcp.local", qtype="PTR"))
+ // pkt = eth/ip/udp/dns
+ val subTypePtrQuery = """
+ 01005e0000fb01020304050608004500004f0001000040118f9f0a000003e00
+ 000fb14e914e9003b1b3f0000010000010000000000000473756231045f7375
+ 620c5f7465737473756274797065045f746370056c6f63616c00000c0001
+ """.replace("\\s+".toRegex(), "").trim()
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(subTypePtrQuery),
+ PASSED_MDNS
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testMdnsOffloadRulePrioritizedOverRaFilter() {
+ val (apfFilterForEstimation, _) = getApfWithMdnsOffloadEnabled(
+ apfRam = 4096,
+ addedOffloadInfos = listOf(
+ castOffloadInfo,
+ tvRemoteOffloadInfo,
+ manySubtypeOffloadInfo
+ ),
+ raReaderSocket = FileDescriptor(),
+ )
+
+ val apfRam = apfFilterForEstimation.overEstimatedProgramSize + counterTotalSize
+
+ val (apfFilter, _) = getApfWithMdnsOffloadEnabled(
+ apfRam = apfRam,
+ addedOffloadInfos = listOf(
+ castOffloadInfo,
+ tvRemoteOffloadInfo,
+ manySubtypeOffloadInfo
+ ),
+ )
+ // ###[ Ethernet ]###
+ // dst = 33:33:00:00:00:01
+ // src = f4:34:f0:64:52:fe
+ // type = IPv6
+ // ###[ IPv6 ]###
+ // version = 6
+ // tc = 0
+ // fl = 68608
+ // plen = 80
+ // nh = ICMPv6
+ // hlim = 255
+ // src = fe80::1cb6:b5bc:353b:7cfd
+ // dst = ff02::1
+ // ###[ ICMPv6 Neighbor Discovery - Router Advertisement ]###
+ // type = Router Advertisement
+ // code = 0
+ // cksum = 0xfab
+ // chlim = 0
+ // M = 0
+ // O = 0
+ // H = 0
+ // prf = Medium (default)
+ // P = 0
+ // res = 0
+ // routerlifetime= 0
+ // reachabletime= 0
+ // retranstimer= 0
+ // ###[ ICMPv6 Neighbor Discovery Option - Prefix Information ]###
+ // type = 3
+ // len = 4
+ // prefixlen = 64
+ // L = 1
+ // A = 1
+ // R = 0
+ // res1 = 0
+ // validlifetime= 0x708
+ // preferredlifetime= 0x708
+ // res2 = 0x0
+ // prefix = fdee:d0c4:7546:5344::
+ // ###[ ICMPv6 Neighbor Discovery Option - Route Information Option ]###
+ // type = 24
+ // len = 2
+ // plen = 64
+ // res1 = 0
+ // prf = Medium (default)
+ // res2 = 0
+ // rtlifetime= 1800
+ // prefix = fd0c:8be6:43ee::
+ // ###[ ICMPv6 Neighbor Discovery Option - Expanded Flags Option ]###
+ // type = 26
+ // len = 1
+ // res = 140737488355328
+ // ###[ ICMPv6 Neighbor Discovery Option - Source Link-Layer Address ]###
+ // type = 1
+ // len = 1
+ // lladdr = f4:34:f0:64:52:fe
+ val ra = """
+ 333300000001f434f06452fe86dd60010c0000503afffe800000000000001cb6b5bc353b7cfdff0
+ 2000000000000000000000000000186000fab000000000000000000000000030440c00000070800
+ 00070800000000fdeed0c47546534400000000000000001802400000000708fd0c8be643ee00001
+ a018000000000000101f434f06452fe
+ """.replace("\\s+".toRegex(), "").trim()
+ val raBytes = HexDump.hexStringToByteArray(ra)
+ Os.write(raWriterSocket, raBytes, 0, raBytes.size)
+
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
+ assertThat(program.size).isLessThan(apfRam + 1)
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ raBytes,
+ PASSED_IPV6_ICMP
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testMdnsOffloadRulePrioritizationAllRulesOffloaded() {
+ val (apfFilter, program) = getApfWithMdnsOffloadEnabled(
+ apfRam = 4096,
+ addedOffloadInfos = listOf(
+ castOffloadInfo,
+ tvRemoteOffloadInfo,
+ manySubtypeOffloadInfo
+ ),
+ )
+ assertThat(program.size).isLessThan(4097)
+
+ // Using scapy to generate packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb")
+ // ip = IP(src="10.0.0.3", dst="224.0.0.251")
+ // udp = UDP(dport=5353, sport=5353)
+ // dns = DNS(qd=DNSQR(qname="_googlecast._tcp.local", qtype="PTR"))
+ // pkt = eth/ip/udp/dns
+ val castIPv4MdnsPtrQueryForOffload = """
+ 01005e0000fb0102030405060800450000440001000040118faa0a000003e00
+ 000fb14e914e900309fa50000010000010000000000000b5f676f6f676c6563
+ 617374045f746370056c6f63616c00000c0001
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(castIPv4MdnsPtrQueryForOffload),
+ DROPPED_MDNS_REPLIED
+ )
+
+ // Using scapy to generate packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb")
+ // ip = IP(src="10.0.0.3", dst="224.0.0.251")
+ // udp = UDP(dport=5353, sport=5353)
+ // dns = DNS(qd=DNSQR(qname="_androidtvremote2._tcp.local", qtype="PTR"))
+ // pkt = eth/ip/udp/dns
+ val tvRemoteIPv4MdnsPtrQueryForOffload = """
+ 01005e0000fb01020304050608004500004a0001000040118fa40a000003e00
+ 000fb14e914e900366966000001000001000000000000115f616e64726f6964
+ 747672656d6f746532045f746370056c6f63616c00000c0001
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(tvRemoteIPv4MdnsPtrQueryForOffload),
+ DROPPED_MDNS_REPLIED
+ )
+
+ // Using scapy to generate packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb")
+ // ip = IP(src="10.0.0.3", dst="224.0.0.251")
+ // udp = UDP(dport=5353, sport=5353)
+ // dns = DNS(qd=DNSQR(qname="sub1._sub._testsubtype._tcp.local", qtype="PTR"))
+ // pkt = eth/ip/udp/dns
+ val subTypePtrQueryForPassthrough = """
+ 01005e0000fb01020304050608004500004f0001000040118f9f0a000003e00
+ 000fb14e914e9003b1b3f0000010000010000000000000473756231045f7375
+ 620c5f7465737473756274797065045f746370056c6f63616c00000c0001
+ """.replace("\\s+".toRegex(), "").trim()
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(subTypePtrQueryForPassthrough),
+ PASSED_MDNS
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testMdnsOffloadRulePrioritizationSomeRulesFailOpened() {
+ val (apfFilterForEstimation, _) = getApfWithMdnsOffloadEnabled(
+ apfRam = 4096,
+ addedOffloadInfos = listOf(
+ castOffloadInfo,
+ tvRemoteOffloadInfo,
+ manySubtypeOffloadInfo
+ ),
+ )
+
+ val apfRam = apfFilterForEstimation.overEstimatedProgramSize + counterTotalSize - 1
+
+ val (apfFilter, program) = getApfWithMdnsOffloadEnabled(
+ apfRam = apfRam,
+ addedOffloadInfos = listOf(
+ castOffloadInfo,
+ tvRemoteOffloadInfo,
+ manySubtypeOffloadInfo
+ ),
+ )
+ assertThat(program.size).isLessThan(apfRam + 1)
+
+ // Using scapy to generate packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb")
+ // ip = IP(src="10.0.0.3", dst="224.0.0.251")
+ // udp = UDP(dport=5353, sport=5353)
+ // dns = DNS(qd=DNSQR(qname="_googlecast._tcp.local", qtype="PTR"))
+ // pkt = eth/ip/udp/dns
+ val castIPv4MdnsPtrQueryForOffload = """
+ 01005e0000fb0102030405060800450000440001000040118faa0a000003e00
+ 000fb14e914e900309fa50000010000010000000000000b5f676f6f676c6563
+ 617374045f746370056c6f63616c00000c0001
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(castIPv4MdnsPtrQueryForOffload),
+ DROPPED_MDNS_REPLIED
+ )
+
+ // Using scapy to generate packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb")
+ // ip = IP(src="10.0.0.3", dst="224.0.0.251")
+ // udp = UDP(dport=5353, sport=5353)
+ // dns = DNS(qd=DNSQR(qname="_androidtvremote2._tcp.local", qtype="PTR"))
+ // pkt = eth/ip/udp/dns
+ val tvRemoteIPv4MdnsPtrQueryForPassthrough = """
+ 01005e0000fb01020304050608004500004a0001000040118fa40a000003e00
+ 000fb14e914e900366966000001000001000000000000115f616e64726f6964
+ 747672656d6f746532045f746370056c6f63616c00000c0001
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(tvRemoteIPv4MdnsPtrQueryForPassthrough),
+ PASSED_MDNS
+ )
+
+ // Using scapy to generate packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb")
+ // ip = IP(src="10.0.0.3", dst="224.0.0.251")
+ // udp = UDP(dport=5353, sport=5353)
+ // dns = DNS(qd=DNSQR(qname="sub1._sub._testsubtype._tcp.local", qtype="PTR"))
+ // pkt = eth/ip/udp/dns
+ val subTypePtrQueryForPassthrough = """
+ 01005e0000fb01020304050608004500004f0001000040118f9f0a000003e00
+ 000fb14e914e9003b1b3f0000010000010000000000000473756231045f7375
+ 620c5f7465737473756274797065045f746370056c6f63616c00000c0001
+ """.replace("\\s+".toRegex(), "").trim()
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(subTypePtrQueryForPassthrough),
+ PASSED_MDNS
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testMdnsOffloadRulePrioritizationAllRulesFailOpened() {
+ val (apfFilterForEstimation, _) = getApfWithMdnsOffloadEnabled(
+ apfRam = 4096,
+ addedOffloadInfos = listOf(passthroughCastOffloadInfo),
+ )
+
+ val apfRam = apfFilterForEstimation.overEstimatedProgramSize + counterTotalSize
+ val (apfFilter, program) = getApfWithMdnsOffloadEnabled(
+ apfRam = apfRam,
+ addedOffloadInfos = listOf(
+ castOffloadInfo,
+ tvRemoteOffloadInfo,
+ manySubtypeOffloadInfo
+ ),
+ )
+ assertThat(program.size).isLessThan(apfRam + 1)
+
+ // Using scapy to generate packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb")
+ // ip = IP(src="10.0.0.3", dst="224.0.0.251")
+ // udp = UDP(dport=5353, sport=5353)
+ // dns = DNS(qd=DNSQR(qname="_googlecast._tcp.local", qtype="PTR"))
+ // pkt = eth/ip/udp/dns
+ val castIPv4MdnsPtrQueryForPassthrough = """
+ 01005e0000fb0102030405060800450000440001000040118faa0a000003e00
+ 000fb14e914e900309fa50000010000010000000000000b5f676f6f676c6563
+ 617374045f746370056c6f63616c00000c0001
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(castIPv4MdnsPtrQueryForPassthrough),
+ PASSED_MDNS
+ )
+
+ // Using scapy to generate packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb")
+ // ip = IP(src="10.0.0.3", dst="224.0.0.251")
+ // udp = UDP(dport=5353, sport=5353)
+ // dns = DNS(qd=DNSQR(qname="_androidtvremote2._tcp.local", qtype="PTR"))
+ // pkt = eth/ip/udp/dns
+ val tvRemoteIPv4MdnsPtrQueryForPassthrough = """
+ 01005e0000fb01020304050608004500004a0001000040118fa40a000003e00
+ 000fb14e914e900366966000001000001000000000000115f616e64726f6964
+ 747672656d6f746532045f746370056c6f63616c00000c0001
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(tvRemoteIPv4MdnsPtrQueryForPassthrough),
+ PASSED_MDNS
+ )
+
+ // Using scapy to generate packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb")
+ // ip = IP(src="10.0.0.3", dst="224.0.0.251")
+ // udp = UDP(dport=5353, sport=5353)
+ // dns = DNS(qd=DNSQR(qname="sub1._sub._testsubtype._tcp.local", qtype="PTR"))
+ // pkt = eth/ip/udp/dns
+ val subTypePtrQueryForPassthrough = """
+ 01005e0000fb01020304050608004500004f0001000040118f9f0a000003e00
+ 000fb14e914e9003b1b3f0000010000010000000000000473756231045f7375
+ 620c5f7465737473756274797065045f746370056c6f63616c00000c0001
+ """.replace("\\s+".toRegex(), "").trim()
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(subTypePtrQueryForPassthrough),
+ PASSED_MDNS
+ )
+
+ // Using scapy to generate packet:
+ // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb")
+ // ip = IP(src="10.0.0.3", dst="224.0.0.251")
+ // udp = UDP(dport=5353, sport=5353)
+ // dns = DNS(qd=DNSQR(qname="_airplay._tcp.local", qtype="PTR"))
+ // pkt = eth/ip/udp/dns
+ val airplayIPv4MdnsPtrQueryForPassthrough = """
+ 01005e0000fb0102030405060800450000410001000040118fad0a000003e00
+ 000fb14e914e9002d8203000001000001000000000000085f616972706c6179
+ 045f746370056c6f63616c00000c0001
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(airplayIPv4MdnsPtrQueryForPassthrough),
+ PASSED_MDNS
+ )
}
@Test
fun testApfProgramUpdate() {
val apfFilter = getApfFilter()
- consumeInstalledProgram(ipClientCallback, installCnt = 2)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
// add IPv4 address, expect to have apf program update
val lp = LinkProperties()
val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24)
lp.addLinkAddress(linkAddress)
apfFilter.setLinkProperties(lp)
- consumeInstalledProgram(ipClientCallback, installCnt = 1)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
// add the same IPv4 address, expect to have no apf program update
apfFilter.setLinkProperties(lp)
- verify(ipClientCallback, never()).installPacketFilter(any())
+ verify(apfController, never()).installPacketFilter(any(), any())
// add IPv6 addresses, expect to have apf program update
for (addr in hostIpv6Addresses) {
@@ -2100,11 +5343,11 @@
}
apfFilter.setLinkProperties(lp)
- consumeInstalledProgram(ipClientCallback, installCnt = 1)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
// add the same IPv6 addresses, expect to have no apf program update
apfFilter.setLinkProperties(lp)
- verify(ipClientCallback, never()).installPacketFilter(any())
+ verify(apfController, never()).installPacketFilter(any(), any())
// add more tentative IPv6 addresses, expect to have apf program update
for (addr in hostIpv6TentativeAddresses) {
@@ -2119,18 +5362,86 @@
}
apfFilter.setLinkProperties(lp)
- consumeInstalledProgram(ipClientCallback, installCnt = 1)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
// add the same IPv6 addresses, expect to have no apf program update
apfFilter.setLinkProperties(lp)
- verify(ipClientCallback, never()).installPacketFilter(any())
+ verify(apfController, never()).installPacketFilter(any(), any())
+ }
+
+ // The APFv6 code path is only turned on in V+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testApfProgramUpdateWithMulticastAddressChange() {
+ val mcastAddrs = mutableListOf(
+ InetAddress.getByName("224.0.0.1") as Inet4Address
+ )
+ doReturn(mcastAddrs).`when`(dependencies).getIPv4MulticastAddresses(any())
+ val apfConfig = getDefaultConfig()
+ apfConfig.handleIgmpOffload = true
+ val apfFilter = getApfFilter(apfConfig)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
+ val addr = InetAddress.getByName("239.0.0.1") as Inet4Address
+ mcastAddrs.add(addr)
+ doReturn(mcastAddrs).`when`(dependencies).getIPv4MulticastAddresses(any())
+ val testPacket = HexDump.hexStringToByteArray("000000")
+ Os.write(mcastWriteSocket, testPacket, 0, testPacket.size)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
+
+ Os.write(mcastWriteSocket, testPacket, 0, testPacket.size)
+ Thread.sleep(NO_CALLBACK_TIMEOUT_MS)
+ verify(apfController, never()).installPacketFilter(any(), any())
+
+ mcastAddrs.remove(addr)
+ doReturn(mcastAddrs).`when`(dependencies).getIPv4MulticastAddresses(any())
+ Os.write(mcastWriteSocket, testPacket, 0, testPacket.size)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testApfProgramUpdateWithIPv6MulticastAddressChange() {
+ val mcastAddrs = mutableListOf(
+ IPV6_ADDR_ALL_NODES_MULTICAST,
+ IPV6_ADDR_NODE_LOCAL_ALL_NODES_MULTICAST
+ )
+ doReturn(mcastAddrs).`when`(dependencies).getIPv6MulticastAddresses(any())
+ val apfConfig = getDefaultConfig()
+ apfConfig.handleMldOffload = true
+ val apfFilter = getApfFilter(apfConfig)
+ val ipv6LinkAddress = LinkAddress(hostLinkLocalIpv6Address, 64)
+ val lp = LinkProperties()
+ lp.addLinkAddress(ipv6LinkAddress)
+ apfFilter.setLinkProperties(lp)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3)
+ val addr = InetAddress.getByName("ff0e::1") as Inet6Address
+ mcastAddrs.add(addr)
+ updateIPv6MulticastAddrs(apfFilter, mcastAddrs)
+ val testPacket = HexDump.hexStringToByteArray("000000")
+ Os.write(mcastWriteSocket, testPacket, 0, testPacket.size)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
+
+ var solicitedNodeMcastAddr = InetAddress.getByName("ff02::1:ff12:3456") as Inet6Address
+ mcastAddrs.add(solicitedNodeMcastAddr)
+ Os.write(mcastWriteSocket, testPacket, 0, testPacket.size)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
+
+ Os.write(mcastWriteSocket, testPacket, 0, testPacket.size)
+ Thread.sleep(NO_CALLBACK_TIMEOUT_MS)
+ verify(apfController, never()).installPacketFilter(any(), any())
+
+ mcastAddrs.remove(addr)
+ updateIPv6MulticastAddrs(apfFilter, mcastAddrs)
+ Os.write(mcastWriteSocket, testPacket, 0, testPacket.size)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
}
@Test
fun testApfFilterInitializationCleanUpTheApfMemoryRegion() {
val apfFilter = getApfFilter()
val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java)
- verify(ipClientCallback, times(2)).installPacketFilter(programCaptor.capture())
+ verify(apfController, times(2))
+ .installPacketFilter(programCaptor.capture(), any())
val program = programCaptor.allValues.first()
assertContentEquals(ByteArray(4096) { 0 }, program)
}
@@ -2138,9 +5449,524 @@
@Test
fun testApfFilterResumeWillCleanUpTheApfMemoryRegion() {
val apfFilter = getApfFilter()
- consumeInstalledProgram(ipClientCallback, installCnt = 2)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
apfFilter.resume()
- val program = consumeInstalledProgram(ipClientCallback, installCnt = 1)
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
assertContentEquals(ByteArray(4096) { 0 }, program)
}
+
+ @Test
+ fun testApfIPv4MulticastAddrsUpdate() {
+ // mock IPv4 multicast address from /proc/net/igmp
+ val mcastAddrs = mutableListOf(
+ InetAddress.getByName("224.0.0.1") as Inet4Address,
+ InetAddress.getByName("239.0.0.1") as Inet4Address
+ )
+ val mcastAddrsExcludeAllHost = mutableListOf(
+ InetAddress.getByName("239.0.0.1") as Inet4Address
+ )
+ doReturn(mcastAddrs).`when`(dependencies).getIPv4MulticastAddresses(any())
+ val apfFilter = getApfFilter()
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
+ assertEquals(mcastAddrs.toSet(), apfFilter.mIPv4MulticastAddresses)
+ assertEquals(mcastAddrsExcludeAllHost.toSet(), apfFilter.mIPv4McastAddrsExcludeAllHost)
+
+ val addr = InetAddress.getByName("239.0.0.2") as Inet4Address
+ mcastAddrs.add(addr)
+ mcastAddrsExcludeAllHost.add(addr)
+ updateIPv4MulticastAddrs(apfFilter, mcastAddrs)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
+ assertEquals(mcastAddrs.toSet(), apfFilter.mIPv4MulticastAddresses)
+ assertEquals(mcastAddrsExcludeAllHost.toSet(), apfFilter.mIPv4McastAddrsExcludeAllHost)
+
+ updateIPv4MulticastAddrs(apfFilter, mcastAddrs)
+ verify(apfController, never()).installPacketFilter(any(), any())
+ }
+
+ @Test
+ fun testApfFailOpenOnLimitedRAM() {
+ val apfConfig = getDefaultConfig()
+ apfConfig.apfRamSize = 512
+ val apfFilter = getApfFilter(apfConfig)
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
+ assertContentEquals(
+ ByteArray(apfConfig.apfRamSize - ApfCounterTracker.Counter.totalSize()) { 0 },
+ program
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testCreateEgressReportReaderSocket() {
+ var apfFilter = getApfFilter()
+ verify(dependencies, never()).createEgressIgmpReportsReaderSocket(anyInt())
+ verify(dependencies, never()).createEgressMulticastReportsReaderSocket(anyInt())
+ clearInvocations(dependencies)
+
+ val apfConfig = getDefaultConfig()
+ apfConfig.handleMldOffload = true
+ apfFilter = getApfFilter(apfConfig)
+
+ verify(dependencies, never()).createEgressIgmpReportsReaderSocket(anyInt())
+ verify(dependencies, times(1)).createEgressMulticastReportsReaderSocket(anyInt())
+ clearInvocations(dependencies)
+
+ apfConfig.handleIgmpOffload = true
+ apfConfig.handleMldOffload = false
+ apfFilter = getApfFilter(apfConfig)
+
+ verify(dependencies, never()).createEgressMulticastReportsReaderSocket(anyInt())
+ verify(dependencies, times(1)).createEgressIgmpReportsReaderSocket(anyInt())
+ clearInvocations(dependencies)
+
+ apfConfig.handleIgmpOffload = true
+ apfConfig.handleMldOffload = true
+ apfFilter = getApfFilter(apfConfig)
+ verify(dependencies, never()).createEgressIgmpReportsReaderSocket(anyInt())
+ verify(dependencies, times(1)).createEgressMulticastReportsReaderSocket(anyInt())
+ }
+
+ fun getProgramWithAllFeatureEnabled(
+ apfRamSize: Int = 8192,
+ apfVersion: Int = apfInterpreterVersion
+ ): Pair<ByteArray, Long> {
+ val localNsdManager = mock(NsdManager::class.java)
+ doReturn(localNsdManager).`when`(context).getSystemService(NsdManager::class.java)
+ val localRaWriterSocket = FileDescriptor()
+ val localRaReaderSocket = FileDescriptor()
+ Os.socketpair(AF_UNIX, SOCK_STREAM, 0, localRaWriterSocket, localRaReaderSocket)
+ doReturn(localRaReaderSocket).`when`(dependencies).createPacketReaderSocket(anyInt())
+ var program = byteArrayOf(0)
+ var generationTime = 0L
+ val ipv4McastAddrs = listOf(
+ InetAddress.getByName("224.0.0.1") as Inet4Address,
+ InetAddress.getByName("224.0.0.251") as Inet4Address,
+ InetAddress.getByName("239.255.255.250") as Inet4Address
+ )
+ doReturn(ipv4McastAddrs).`when`(dependencies).getIPv4MulticastAddresses(any())
+ val ipv6McastAddrs = listOf(
+ InetAddress.getByName("ff02::1:ff11:33e1") as Inet6Address,
+ InetAddress.getByName("ff02::1:ff11:33e2") as Inet6Address,
+ InetAddress.getByName("ff02::fb") as Inet6Address,
+ InetAddress.getByName("ff02::c") as Inet6Address,
+ InetAddress.getByName("ff05::c") as Inet6Address,
+ InetAddress.getByName("ff02::1") as Inet6Address,
+ InetAddress.getByName("ff01::1") as Inet6Address,
+ )
+ // mock IPv6 multicast address from /proc/net/igmp6
+ doReturn(ipv6McastAddrs).`when`(dependencies).getIPv6MulticastAddresses(any())
+ tryTest {
+ val apfConfig = getDefaultConfig()
+ apfConfig.apfRamSize = apfRamSize
+ apfConfig.apfVersionSupported = apfVersion
+ apfConfig.multicastFilter = true
+ apfConfig.handleArpOffload = true
+ apfConfig.handleNdOffload = true
+ apfConfig.handleIgmpOffload = true
+ apfConfig.handleMldOffload = true
+ apfConfig.handleIpv4PingOffload = true
+ apfConfig.handleIpv6PingOffload = true
+ apfConfig.handleMdnsOffload = true
+ val apfFilter = getApfFilter(apfConfig)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
+
+ val srcAddr = byteArrayOf(10, 0, 0, 5)
+ val dstAddr = byteArrayOf(10, 0, 0, 6)
+ val srcPort = 1024
+ val dstPort = 4500
+ val parcel = NattKeepalivePacketDataParcelable()
+ parcel.srcAddress = InetAddress.getByAddress(srcAddr).address
+ parcel.srcPort = srcPort
+ parcel.dstAddress = InetAddress.getByAddress(dstAddr).address
+ parcel.dstPort = dstPort
+ apfFilter.addNattKeepalivePacketFilter(1, parcel)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
+
+ val captor = ArgumentCaptor.forClass(OffloadEngine::class.java)
+ verify(localNsdManager).registerOffloadEngine(
+ eq(ifParams.name),
+ anyLong(),
+ anyLong(),
+ any(),
+ captor.capture()
+ )
+ val offloadEngine = captor.value
+
+ val lp = LinkProperties()
+ val ipv4LinkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24)
+ lp.addLinkAddress(ipv4LinkAddress)
+ val ipv6LinkAddress = LinkAddress(hostLinkLocalIpv6Address, 64)
+ lp.addLinkAddress(ipv6LinkAddress)
+ for (addr in hostIpv6Addresses) {
+ lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64))
+ }
+ apfFilter.setLinkProperties(lp)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
+
+ visibleOnHandlerThread(handler) {
+ offloadEngine.onOffloadServiceUpdated(castOffloadInfo.value)
+ offloadEngine.onOffloadServiceUpdated(tvRemoteOffloadInfo.value)
+ offloadEngine.onOffloadServiceUpdated(airplayOffloadInfo.value)
+ offloadEngine.onOffloadServiceUpdated(raopOffloadInfo.value)
+ }
+
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 4)
+
+ val ra1 = """
+ 333300000001f434f06452fe86dd60010c0000503afffe800000000000001cb6b5bc353b7cfdff0
+ 2000000000000000000000000000186000fab000000000000000000000000030440c00000070800
+ 00070800000000fdeed0c47546534400000000000000001802400000000708fd0c8be643ee00001
+ a018000000000000101f434f06452fe
+ """.replace("\\s+".toRegex(), "").trim()
+ val ra1Bytes = HexDump.hexStringToByteArray(ra1)
+ Os.write(localRaWriterSocket, ra1Bytes, 0, ra1Bytes.size)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
+
+ // Using scapy to generate packet:
+ // eth = Ether(src="E8:9F:80:66:60:BC", dst="f2:9c:70:2c:39:5a")
+ // ip6 = IPv6(src="fe80::2", dst="ff02::1")
+ // icmpra = ICMPv6ND_RA(routerlifetime=360, retranstimer=360)
+ // pio1 = ICMPv6NDOptPrefixInfo(prefixlen=64, prefix="2002:db8::")
+ // rio = ICMPv6NDOptRouteInfo(prefix="2002:db8:cafe::")
+ // ra = eth/ip6/icmpra/pio1/rio
+ val ra2 = """
+ f29c702c395ae89f806660bc86dd6000000000483afffe800000000000000000000000000002ff0
+ 200000000000000000000000000018600f6e3000801680000000000000168030440c0ffffffffff
+ ffffff0000000020020db800000000000000000000000018030000ffffffff20020db8cafe00000
+ 000000000000000
+ """.replace("\\s+".toRegex(), "").trim()
+ val ra2Bytes = HexDump.hexStringToByteArray(ra2)
+ val beforeNs = SystemClock.elapsedRealtimeNanos()
+ Os.write(localRaWriterSocket, ra2Bytes, 0, ra2Bytes.size)
+ program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
+ val afterNs = SystemClock.elapsedRealtimeNanos()
+ generationTime = (afterNs - beforeNs) / 1000000
+ } cleanup {
+ IoUtils.closeQuietly(localRaWriterSocket)
+ }
+ return Pair(program, generationTime)
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testAllOffloadFeatureEnabled() {
+ val (program, generationTimeMs) = getProgramWithAllFeatureEnabled()
+ val programSize = program.size
+ val counterSize = ApfCounterTracker.Counter.totalSize()
+ val totalSize = programSize + counterSize
+ Log.i(
+ TAG,
+ "all feature on, program size: $programSize, counter size: $counterSize," +
+ " total size:$totalSize, program:"
+ )
+ val programChunk = program.toList().chunked(2000)
+ programChunk.forEach {
+ Log.i(TAG, HexDump.toHexString(it.toByteArray()))
+ }
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testAllOffloadFeatureEnabledPerformanceEstimation() {
+ val (program, generationTimeMs) = getProgramWithAllFeatureEnabled()
+ // Ignore the first iteration as it may take longer time for JVM warm up
+ if (apfInterpreterVersion == ApfJniUtils.APF_INTERPRETER_VERSION_NEXT) {
+ Log.i(
+ TAG,
+ "all offload on: program size ${program.size}, " +
+ "generation time: $generationTimeMs ms"
+ )
+ }
+ }
+
+ fun getProgramWithAllFeatureOff(
+ apfRamSize: Int = 8192,
+ apfVersion: Int = apfInterpreterVersion
+ ): Pair<ByteArray, Long> {
+ val localRaWriterSocket = FileDescriptor()
+ val localRaReaderSocket = FileDescriptor()
+ Os.socketpair(AF_UNIX, SOCK_STREAM, 0, localRaWriterSocket, localRaReaderSocket)
+ doReturn(localRaReaderSocket).`when`(dependencies).createPacketReaderSocket(anyInt())
+ var program = byteArrayOf(0)
+ var generationTime = 0L
+ tryTest {
+ val ipv4McastAddrs = listOf(
+ InetAddress.getByName("224.0.0.1") as Inet4Address,
+ InetAddress.getByName("224.0.0.251") as Inet4Address,
+ InetAddress.getByName("239.255.255.250") as Inet4Address
+ )
+ doReturn(ipv4McastAddrs).`when`(dependencies).getIPv4MulticastAddresses(any())
+ val ipv6McastAddrs = listOf(
+ InetAddress.getByName("ff02::1:ff11:33e1") as Inet6Address,
+ InetAddress.getByName("ff02::1:ff11:33e2") as Inet6Address,
+ InetAddress.getByName("ff02::fb") as Inet6Address,
+ InetAddress.getByName("ff02::c") as Inet6Address,
+ InetAddress.getByName("ff05::c") as Inet6Address,
+ InetAddress.getByName("ff02::1") as Inet6Address,
+ InetAddress.getByName("ff01::1") as Inet6Address,
+ )
+ // mock IPv6 multicast address from /proc/net/igmp6
+ doReturn(ipv6McastAddrs).`when`(dependencies).getIPv6MulticastAddresses(any())
+ val apfConfig = getDefaultConfig()
+ apfConfig.apfRamSize = apfRamSize
+ apfConfig.apfVersionSupported = apfVersion
+ apfConfig.multicastFilter = true
+ apfConfig.handleArpOffload = false
+ apfConfig.handleNdOffload = false
+ apfConfig.handleIgmpOffload = false
+ apfConfig.handleMldOffload = false
+ apfConfig.handleIpv4PingOffload = false
+ apfConfig.handleIpv6PingOffload = false
+ apfConfig.handleMdnsOffload = false
+ val apfFilter = getApfFilter(apfConfig)
+ if (apfVersion > 2) {
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
+ } else {
+ // If the APF version is less than 3, only one program will be installed because
+ // APFv2 lacks counter support, and therefore, counter region cleanup is unnecessary
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
+ }
+
+ val lp = LinkProperties()
+ val ipv4LinkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24)
+ lp.addLinkAddress(ipv4LinkAddress)
+ val ipv6LinkAddress = LinkAddress(hostLinkLocalIpv6Address, 64)
+ lp.addLinkAddress(ipv6LinkAddress)
+ for (addr in hostIpv6Addresses) {
+ lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64))
+ }
+ apfFilter.setLinkProperties(lp)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
+
+ val ra1 = """
+ 333300000001f434f06452fe86dd60010c0000503afffe800000000000001cb6b5bc353b7cfdff0
+ 2000000000000000000000000000186000fab000000000000000000000000030440c00000070800
+ 00070800000000fdeed0c47546534400000000000000001802400000000708fd0c8be643ee00001
+ a018000000000000101f434f06452fe
+ """.replace("\\s+".toRegex(), "").trim()
+ val ra1Bytes = HexDump.hexStringToByteArray(ra1)
+ Os.write(localRaWriterSocket, ra1Bytes, 0, ra1Bytes.size)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
+
+ // Using scapy to generate packet:
+ // eth = Ether(src="E8:9F:80:66:60:BC", dst="f2:9c:70:2c:39:5a")
+ // ip6 = IPv6(src="fe80::2", dst="ff02::1")
+ // icmpra = ICMPv6ND_RA(routerlifetime=360, retranstimer=360)
+ // pio1 = ICMPv6NDOptPrefixInfo(prefixlen=64, prefix="2002:db8::")
+ // rio = ICMPv6NDOptRouteInfo(prefix="2002:db8:cafe::")
+ // ra = eth/ip6/icmpra/pio1/rio
+ val ra2 = """
+ f29c702c395ae89f806660bc86dd6000000000483afffe800000000000000000000000000002ff0
+ 200000000000000000000000000018600f6e3000801680000000000000168030440c0ffffffffff
+ ffffff0000000020020db800000000000000000000000018030000ffffffff20020db8cafe00000
+ 000000000000000
+ """.replace("\\s+".toRegex(), "").trim()
+ val ra2Bytes = HexDump.hexStringToByteArray(ra2)
+ val beforeNs = SystemClock.elapsedRealtimeNanos()
+ Os.write(localRaWriterSocket, ra2Bytes, 0, ra2Bytes.size)
+ program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
+ val afterNs = SystemClock.elapsedRealtimeNanos()
+ generationTime = (afterNs - beforeNs) / 1000000
+ } cleanup {
+ IoUtils.closeQuietly(localRaWriterSocket)
+ }
+ return Pair(program, generationTime)
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testAllOffloadFeatureDisabledPerformanceEstimation() {
+ val (program, generationTimeMs) = getProgramWithAllFeatureOff()
+ // Ignore the first iteration as it may take longer time for JVM warm up
+ if (apfInterpreterVersion == ApfJniUtils.APF_INTERPRETER_VERSION_NEXT) {
+ Log.i(
+ TAG,
+ "all offload off: program size ${program.size}, " +
+ "generation time: $generationTimeMs ms"
+ )
+ }
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testAPFv2GenerateValidProgram() {
+ assumeTrue(apfInterpreterVersion == ApfJniUtils.APF_INTERPRETER_VERSION_NEXT)
+ var apfRamSize = 600
+ val maxApfRamSize = 2048
+
+ while (apfRamSize <= maxApfRamSize) {
+ val (program, _) = getProgramWithAllFeatureOff(
+ apfRamSize = apfRamSize,
+ apfVersion = 2
+ )
+ assertThat(program.size).isLessThan(apfRamSize + 1)
+ assertThat(program).isNotEqualTo(ByteArray(apfRamSize) { 0 })
+ // TODO: reduce after fixing 'Failed to receive adb shell test output within 66000 ms'
+ val step = Random.nextInt(1, 64)
+ apfRamSize += step
+ }
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testAPFv4GenerateValidProgram() {
+ assumeTrue(apfInterpreterVersion == ApfJniUtils.APF_INTERPRETER_VERSION_NEXT)
+ var apfRamSize = 1024
+ val maxApfRamSize = 4096
+
+ while (apfRamSize <= maxApfRamSize) {
+ val (program, _) = getProgramWithAllFeatureOff(
+ apfRamSize = apfRamSize,
+ apfVersion = 4
+ )
+ val availableRam = apfRamSize - ApfCounterTracker.Counter.totalSize()
+ assertThat(program.size).isLessThan(availableRam + 1)
+ assertThat(program).isNotEqualTo(ByteArray(availableRam) { 0 })
+ // TODO: reduce after fixing 'Failed to receive adb shell test output within 66000 ms'
+ val step = Random.nextInt(1, 64)
+ apfRamSize += step
+ }
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testAPFv6GenerateValidProgram() {
+ var apfRamSize = 3000
+ val maxApfRamSize = 6000
+
+ while (apfRamSize <= maxApfRamSize) {
+ val (program, _) = getProgramWithAllFeatureEnabled(
+ apfRamSize = apfRamSize,
+ apfVersion = apfInterpreterVersion
+ )
+ val availableRam = apfRamSize - ApfCounterTracker.Counter.totalSize()
+ assertThat(program.size).isLessThan(availableRam + 1)
+ assertThat(program).isNotEqualTo(ByteArray(availableRam) { 0 })
+ // TODO: reduce after fixing 'Failed to receive adb shell test output within 66000 ms'
+ val step = Random.nextInt(1, 64)
+ apfRamSize += step
+ }
+ }
+
+ private fun getProgramForRaSizeEstimation(
+ apfRamSize: Int,
+ ): Pair<Int, ByteArray> {
+ val localRaWriterSocket = FileDescriptor()
+ val localRaReaderSocket = FileDescriptor()
+ Os.socketpair(AF_UNIX, SOCK_STREAM, 0, localRaWriterSocket, localRaReaderSocket)
+ doReturn(localRaReaderSocket).`when`(dependencies).createPacketReaderSocket(anyInt())
+ var overEstimatedProgramSize = 0
+ var program = byteArrayOf(0)
+ tryTest {
+ val apfConfig = getDefaultConfig()
+ apfConfig.apfRamSize = apfRamSize
+ val apfFilter = getApfFilter(apfConfig)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
+
+ val lp = LinkProperties()
+ val ipv4LinkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24)
+ lp.addLinkAddress(ipv4LinkAddress)
+ val ipv6LinkAddress = LinkAddress(hostLinkLocalIpv6Address, 64)
+ lp.addLinkAddress(ipv6LinkAddress)
+ for (addr in hostIpv6Addresses) {
+ lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64))
+ }
+ apfFilter.setLinkProperties(lp)
+ program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
+
+ val ra1 = """
+ 333300000001f434f06452fe86dd60010c0000503afffe800000000000001cb6b5bc353b7cfdff0
+ 2000000000000000000000000000186000fab000000000000000000000000030440c00000070800
+ 00070800000000fdeed0c47546534400000000000000001802400000000708fd0c8be643ee00001
+ a018000000000000101f434f06452fe
+ """.replace("\\s+".toRegex(), "").trim()
+ val ra1Bytes = HexDump.hexStringToByteArray(ra1)
+ Os.write(localRaWriterSocket, ra1Bytes, 0, ra1Bytes.size)
+ apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
+
+ // Using scapy to generate packet:
+ // eth = Ether(src="E8:9F:80:66:60:BC", dst="f2:9c:70:2c:39:5a")
+ // ip6 = IPv6(src="fe80::2", dst="ff02::1")
+ // icmpra = ICMPv6ND_RA(routerlifetime=360, retranstimer=360)
+ // pio1 = ICMPv6NDOptPrefixInfo(prefixlen=64, prefix="2002:db8::")
+ // rio = ICMPv6NDOptRouteInfo(prefix="2002:db8:cafe::")
+ // ra = eth/ip6/icmpra/pio1/rio
+ val ra2 = """
+ f29c702c395ae89f806660bc86dd6000000000483afffe800000000000000000000000000002ff0
+ 200000000000000000000000000018600f6e3000801680000000000000168030440c0ffffffffff
+ ffffff0000000020020db800000000000000000000000018030000ffffffff20020db8cafe00000
+ 000000000000000
+ """.replace("\\s+".toRegex(), "").trim()
+ val ra2Bytes = HexDump.hexStringToByteArray(ra2)
+ Os.write(localRaWriterSocket, ra2Bytes, 0, ra2Bytes.size)
+ program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1)
+ overEstimatedProgramSize = apfFilter.overEstimatedProgramSize
+ } cleanup {
+ IoUtils.closeQuietly(localRaWriterSocket)
+ }
+ return Pair(overEstimatedProgramSize, program)
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testRaFilterSizeEstimation() {
+ val (overEstimatedProgramSize, _) = getProgramForRaSizeEstimation(apfRamSize = 8096)
+ val apfRam = overEstimatedProgramSize - 1
+ val (_, program) = getProgramForRaSizeEstimation(apfRamSize = apfRam)
+
+ val ra1 = """
+ 333300000001f434f06452fe86dd60010c0000503afffe800000000000001cb6b5bc353b7cfdff0
+ 2000000000000000000000000000186000fab000000000000000000000000030440c00000070800
+ 00070800000000fdeed0c47546534400000000000000001802400000000708fd0c8be643ee00001
+ a018000000000000101f434f06452fe
+ """.replace("\\s+".toRegex(), "").trim()
+
+ val ra2 = """
+ f29c702c395ae89f806660bc86dd6000000000483afffe800000000000000000000000000002ff0
+ 200000000000000000000000000018600f6e3000801680000000000000168030440c0ffffffffff
+ ffffff0000000020020db800000000000000000000000018030000ffffffff20020db8cafe00000
+ 000000000000000
+ """.replace("\\s+".toRegex(), "").trim()
+
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
+ program,
+ HexDump.hexStringToByteArray(ra2),
+ DROPPED_RA
+ )
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
+ program,
+ HexDump.hexStringToByteArray(ra1),
+ PASSED_IPV6_ICMP
+ )
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testFilteringNonUnicastTDLSPacket() {
+ val apfConfig = getDefaultConfig()
+ apfConfig.apfRamSize = 1500
+ val apfFilter = getApfFilter(apfConfig)
+ val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2)
+ // Using scapy to generate packet:
+ // pkt = Ether(dst="ff:ff:ff:ff:ff:ff", type=0x890D)/Raw(load="01")
+ val bcastTDLSPkt = "ffffffffffff000000000000890d3031"
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(bcastTDLSPkt),
+ DROPPED_NON_UNICAST_TDLS
+ )
+
+ // Using scapy to generate packet:
+ // pkt = Ether(dst="02:03:04:05:06:07", type=0x890D)/Raw(load="01")
+ val ucastTDLSPkt = "020304050607000000000000890d3031"
+ apfTestHelpers.verifyProgramRun(
+ apfFilter.mApfVersionSupported,
+ program,
+ HexDump.hexStringToByteArray(ucastTDLSPkt),
+ PASSED_NON_IP_UNICAST
+ )
+ }
}
diff --git a/tests/unit/src/android/net/apf/ApfGeneratorTest.kt b/tests/unit/src/android/net/apf/ApfGeneratorTest.kt
index 98b2a42..1c383bc 100644
--- a/tests/unit/src/android/net/apf/ApfGeneratorTest.kt
+++ b/tests/unit/src/android/net/apf/ApfGeneratorTest.kt
@@ -19,21 +19,18 @@
import android.net.apf.ApfCounterTracker.Counter.CORRUPT_DNS_PACKET
import android.net.apf.ApfCounterTracker.Counter.DROPPED_ETHERTYPE_NOT_ALLOWED
import android.net.apf.ApfCounterTracker.Counter.DROPPED_ETH_BROADCAST
+import android.net.apf.ApfCounterTracker.Counter.DROPPED_RA
import android.net.apf.ApfCounterTracker.Counter.PASSED_ALLOCATE_FAILURE
-import android.net.apf.ApfCounterTracker.Counter.PASSED_ARP
+import android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_REQUEST
+import android.net.apf.ApfCounterTracker.Counter.PASSED_MDNS
import android.net.apf.ApfCounterTracker.Counter.PASSED_TRANSMIT_FAILURE
import android.net.apf.ApfCounterTracker.Counter.TOTAL_PACKETS
import android.net.apf.ApfTestHelpers.Companion.DROP
import android.net.apf.ApfTestHelpers.Companion.MIN_PKT_SIZE
import android.net.apf.ApfTestHelpers.Companion.PASS
-import android.net.apf.ApfTestHelpers.Companion.assertDrop
-import android.net.apf.ApfTestHelpers.Companion.assertPass
-import android.net.apf.ApfTestHelpers.Companion.assertVerdict
import android.net.apf.ApfTestHelpers.Companion.decodeCountersIntoMap
-import android.net.apf.ApfTestHelpers.Companion.verifyProgramRun
import android.net.apf.BaseApfGenerator.APF_VERSION_2
import android.net.apf.BaseApfGenerator.APF_VERSION_3
-import android.net.apf.BaseApfGenerator.APF_VERSION_6
import android.net.apf.BaseApfGenerator.DROP_LABEL
import android.net.apf.BaseApfGenerator.IllegalInstructionException
import android.net.apf.BaseApfGenerator.MemorySlot
@@ -52,10 +49,13 @@
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
+import org.junit.After
+import org.junit.Assume.assumeTrue
+import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito.times
+import org.junit.runners.Parameterized
const val ETH_HLEN = 14
const val IPV4_HLEN = 20
@@ -68,25 +68,52 @@
@SmallTest
class ApfGeneratorTest {
+ companion object {
+ @Parameterized.Parameters
+ @JvmStatic
+ fun data(): Iterable<Any?> {
+ return mutableListOf<Int?>(
+ ApfJniUtils.APF_INTERPRETER_VERSION_V6,
+ ApfJniUtils.APF_INTERPRETER_VERSION_NEXT
+ )
+ }
+ }
+
@get:Rule val ignoreRule = DevSdkIgnoreRule()
+ // Indicates which apfInterpreter to load.
+ @Parameterized.Parameter(0)
+ @JvmField
+ var apfInterpreterVersion: Int = ApfJniUtils.APF_INTERPRETER_VERSION_NEXT
+
private val ramSize = 2048
private val clampSize = 2048
private val testPacket = byteArrayOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
+ private lateinit var apfTestHelpers: ApfTestHelpers
+
+ @Before
+ fun setUp() {
+ apfTestHelpers = ApfTestHelpers(apfInterpreterVersion)
+ }
+
+ @After
+ fun tearDown() {
+ apfTestHelpers.resetTransmittedPacketMemory()
+ }
@Test
fun testDataInstructionMustComeFirst() {
- var gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ var gen = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
gen.addAllocateR0()
assertFailsWith<IllegalInstructionException> { gen.addData(ByteArray(3) { 0x01 }) }
}
@Test
fun testApfInstructionEncodingSizeCheck() {
- var gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ var gen = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
assertFailsWith<IllegalArgumentException> {
- ApfV6Generator(ByteArray(65536) { 0x01 }, APF_VERSION_6, ramSize, clampSize)
+ ApfV6Generator(ByteArray(65536) { 0x01 }, apfInterpreterVersion, ramSize, clampSize)
}
assertFailsWith<IllegalArgumentException> { gen.addAllocate(65536) }
assertFailsWith<IllegalArgumentException> { gen.addAllocate(-1) }
@@ -242,67 +269,67 @@
assertFailsWith<IllegalArgumentException> {
gen.addJumpIfBytesAtR0NotEqual(ByteArray(2048) { 1 }, DROP_LABEL)
}
- assertFailsWith<IllegalArgumentException> { gen.addCountAndDrop(PASSED_ARP) }
+ assertFailsWith<IllegalArgumentException> { gen.addCountAndDrop(PASSED_ARP_REQUEST) }
assertFailsWith<IllegalArgumentException> { gen.addCountAndPass(DROPPED_ETH_BROADCAST) }
assertFailsWith<IllegalArgumentException> {
- gen.addCountAndDropIfR0Equals(3, PASSED_ARP)
+ gen.addCountAndDropIfR0Equals(3, PASSED_ARP_REQUEST)
}
assertFailsWith<IllegalArgumentException> {
gen.addCountAndPassIfR0Equals(3, DROPPED_ETH_BROADCAST)
}
assertFailsWith<IllegalArgumentException> {
- gen.addCountAndDropIfR0NotEquals(3, PASSED_ARP)
+ gen.addCountAndDropIfR0NotEquals(3, PASSED_ARP_REQUEST)
}
assertFailsWith<IllegalArgumentException> {
gen.addCountAndPassIfR0NotEquals(3, DROPPED_ETH_BROADCAST)
}
assertFailsWith<IllegalArgumentException> {
- gen.addCountAndDropIfR0LessThan(3, PASSED_ARP)
+ gen.addCountAndDropIfR0LessThan(3, PASSED_ARP_REQUEST)
}
assertFailsWith<IllegalArgumentException> {
gen.addCountAndPassIfR0LessThan(3, DROPPED_ETH_BROADCAST)
}
assertFailsWith<IllegalArgumentException> {
- gen.addCountAndDropIfR0GreaterThan(3, PASSED_ARP)
+ gen.addCountAndDropIfR0GreaterThan(3, PASSED_ARP_REQUEST)
}
assertFailsWith<IllegalArgumentException> {
gen.addCountAndPassIfR0GreaterThan(3, DROPPED_ETH_BROADCAST)
}
assertFailsWith<IllegalArgumentException> {
- gen.addCountAndDropIfBytesAtR0NotEqual(byteArrayOf(1), PASSED_ARP)
+ gen.addCountAndDropIfBytesAtR0NotEqual(byteArrayOf(1), PASSED_ARP_REQUEST)
}
assertFailsWith<IllegalArgumentException> {
- gen.addCountAndDropIfBytesAtR0Equal(byteArrayOf(1), PASSED_ARP)
+ gen.addCountAndDropIfBytesAtR0Equal(byteArrayOf(1), PASSED_ARP_REQUEST)
}
assertFailsWith<IllegalArgumentException> {
gen.addCountAndPassIfBytesAtR0Equal(byteArrayOf(1), DROPPED_ETH_BROADCAST)
}
assertFailsWith<IllegalArgumentException> {
- gen.addCountAndDropIfR0AnyBitsSet(3, PASSED_ARP)
+ gen.addCountAndDropIfR0AnyBitsSet(3, PASSED_ARP_REQUEST)
}
assertFailsWith<IllegalArgumentException> {
gen.addCountAndPassIfR0AnyBitsSet(3, DROPPED_ETH_BROADCAST)
}
assertFailsWith<IllegalArgumentException> {
- gen.addCountAndDropIfR0IsOneOf(setOf(3), PASSED_ARP)
+ gen.addCountAndDropIfR0IsOneOf(setOf(3), PASSED_ARP_REQUEST)
}
assertFailsWith<IllegalArgumentException> {
gen.addCountAndPassIfR0IsOneOf(setOf(3), DROPPED_ETH_BROADCAST)
}
assertFailsWith<IllegalArgumentException> {
- gen.addCountAndDropIfR0IsNoneOf(setOf(3), PASSED_ARP)
+ gen.addCountAndDropIfR0IsNoneOf(setOf(3), PASSED_ARP_REQUEST)
}
assertFailsWith<IllegalArgumentException> {
gen.addCountAndPassIfR0IsNoneOf(setOf(3), DROPPED_ETH_BROADCAST)
}
assertFailsWith<IllegalArgumentException> {
- gen.addCountAndDropIfBytesAtR0EqualsAnyOf(listOf(byteArrayOf(1)), PASSED_ARP)
+ gen.addCountAndDropIfBytesAtR0EqualsAnyOf(listOf(byteArrayOf(1)), PASSED_ARP_REQUEST)
}
assertFailsWith<IllegalArgumentException> {
gen.addCountAndPassIfBytesAtR0EqualsAnyOf(listOf(byteArrayOf(1)), DROPPED_ETH_BROADCAST)
}
assertFailsWith<IllegalArgumentException> {
- gen.addCountAndDropIfBytesAtR0EqualsNoneOf(listOf(byteArrayOf(1)), PASSED_ARP)
+ gen.addCountAndDropIfBytesAtR0EqualsNoneOf(listOf(byteArrayOf(1)), PASSED_ARP_REQUEST)
}
assertFailsWith<IllegalArgumentException> {
gen.addCountAndPassIfBytesAtR0EqualsNoneOf(
@@ -336,61 +363,61 @@
}
val v4gen = ApfV4Generator(APF_VERSION_3, ramSize, clampSize)
- assertFailsWith<IllegalArgumentException> { v4gen.addCountAndDrop(PASSED_ARP) }
+ assertFailsWith<IllegalArgumentException> { v4gen.addCountAndDrop(PASSED_ARP_REQUEST) }
assertFailsWith<IllegalArgumentException> { v4gen.addCountAndPass(DROPPED_ETH_BROADCAST) }
assertFailsWith<IllegalArgumentException> {
- v4gen.addCountAndDropIfR0Equals(3, PASSED_ARP)
+ v4gen.addCountAndDropIfR0Equals(3, PASSED_ARP_REQUEST)
}
assertFailsWith<IllegalArgumentException> {
v4gen.addCountAndPassIfR0Equals(3, DROPPED_ETH_BROADCAST)
}
assertFailsWith<IllegalArgumentException> {
- v4gen.addCountAndDropIfR0NotEquals(3, PASSED_ARP)
+ v4gen.addCountAndDropIfR0NotEquals(3, PASSED_ARP_REQUEST)
}
assertFailsWith<IllegalArgumentException> {
v4gen.addCountAndPassIfR0NotEquals(3, DROPPED_ETH_BROADCAST)
}
assertFailsWith<IllegalArgumentException> {
- v4gen.addCountAndDropIfBytesAtR0Equal(byteArrayOf(1), PASSED_ARP)
+ v4gen.addCountAndDropIfBytesAtR0Equal(byteArrayOf(1), PASSED_ARP_REQUEST)
}
assertFailsWith<IllegalArgumentException> {
v4gen.addCountAndPassIfBytesAtR0Equal(byteArrayOf(1), DROPPED_ETH_BROADCAST)
}
assertFailsWith<IllegalArgumentException> {
- v4gen.addCountAndDropIfR0LessThan(3, PASSED_ARP)
+ v4gen.addCountAndDropIfR0LessThan(3, PASSED_ARP_REQUEST)
}
assertFailsWith<IllegalArgumentException> {
v4gen.addCountAndPassIfR0LessThan(3, DROPPED_ETH_BROADCAST)
}
assertFailsWith<IllegalArgumentException> {
- v4gen.addCountAndDropIfR0GreaterThan(3, PASSED_ARP)
+ v4gen.addCountAndDropIfR0GreaterThan(3, PASSED_ARP_REQUEST)
}
assertFailsWith<IllegalArgumentException> {
v4gen.addCountAndPassIfR0GreaterThan(3, DROPPED_ETH_BROADCAST)
}
assertFailsWith<IllegalArgumentException> {
- v4gen.addCountAndDropIfBytesAtR0NotEqual(byteArrayOf(1), PASSED_ARP)
+ v4gen.addCountAndDropIfBytesAtR0NotEqual(byteArrayOf(1), PASSED_ARP_REQUEST)
}
assertFailsWith<IllegalArgumentException> {
- v4gen.addCountAndDropIfR0AnyBitsSet(3, PASSED_ARP)
+ v4gen.addCountAndDropIfR0AnyBitsSet(3, PASSED_ARP_REQUEST)
}
assertFailsWith<IllegalArgumentException> {
v4gen.addCountAndPassIfR0AnyBitsSet(3, DROPPED_ETH_BROADCAST)
}
assertFailsWith<IllegalArgumentException> {
- v4gen.addCountAndDropIfR0IsOneOf(setOf(3), PASSED_ARP)
+ v4gen.addCountAndDropIfR0IsOneOf(setOf(3), PASSED_ARP_REQUEST)
}
assertFailsWith<IllegalArgumentException> {
v4gen.addCountAndPassIfR0IsOneOf(setOf(3), DROPPED_ETH_BROADCAST)
}
assertFailsWith<IllegalArgumentException> {
- v4gen.addCountAndDropIfR0IsNoneOf(setOf(3), PASSED_ARP)
+ v4gen.addCountAndDropIfR0IsNoneOf(setOf(3), PASSED_ARP_REQUEST)
}
assertFailsWith<IllegalArgumentException> {
v4gen.addCountAndPassIfR0IsNoneOf(setOf(3), DROPPED_ETH_BROADCAST)
}
assertFailsWith<IllegalArgumentException> {
- v4gen.addCountAndDropIfBytesAtR0EqualsAnyOf(listOf(byteArrayOf(1)), PASSED_ARP)
+ v4gen.addCountAndDropIfBytesAtR0EqualsAnyOf(listOf(byteArrayOf(1)), PASSED_ARP_REQUEST)
}
assertFailsWith<IllegalArgumentException> {
v4gen.addCountAndPassIfBytesAtR0EqualsAnyOf(
@@ -399,7 +426,7 @@
)
}
assertFailsWith<IllegalArgumentException> {
- v4gen.addCountAndDropIfBytesAtR0EqualsNoneOf(listOf(byteArrayOf(1)), PASSED_ARP)
+ v4gen.addCountAndDropIfBytesAtR0EqualsNoneOf(listOf(byteArrayOf(1)), PASSED_ARP_REQUEST)
}
assertFailsWith<IllegalArgumentException> {
v4gen.addCountAndPassIfBytesAtR0EqualsNoneOf(
@@ -435,10 +462,10 @@
)
assertContentEquals(
listOf("0: pass"),
- ApfJniUtils.disassembleApf(program).map { it.trim() }
+ apfTestHelpers.disassembleApf(program).map { it.trim() }
)
- var gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ var gen = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
gen.addDrop()
program = gen.generate().skipDataAndDebug()
// encoding DROP opcode: opcode=0, imm_len=0, R=1
@@ -448,10 +475,10 @@
)
assertContentEquals(
listOf("0: drop"),
- ApfJniUtils.disassembleApf(program).map { it.trim() }
+ apfTestHelpers.disassembleApf(program).map { it.trim() }
)
- gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ gen = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
gen.addCountAndPass(129)
program = gen.generate().skipDataAndDebug()
// encoding COUNT(PASS) opcode: opcode=0, imm_len=size_of(imm), R=0, imm=counterNumber
@@ -464,10 +491,10 @@
)
assertContentEquals(
listOf("0: pass counter=129"),
- ApfJniUtils.disassembleApf(program).map { it.trim() }
+ apfTestHelpers.disassembleApf(program).map { it.trim() }
)
- gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ gen = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
gen.addCountAndDrop(1000)
program = gen.generate().skipDataAndDebug()
// encoding COUNT(DROP) opcode: opcode=0, imm_len=size_of(imm), R=1, imm=counterNumber
@@ -481,26 +508,27 @@
)
assertContentEquals(
listOf("0: drop counter=1000"),
- ApfJniUtils.disassembleApf(program).map { it.trim() }
+ apfTestHelpers.disassembleApf(program).map { it.trim() }
)
- gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
- gen.addCountAndPass(PASSED_ARP)
+ gen = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
+ gen.addCountAndPass(PASSED_ARP_REQUEST)
program = gen.generate().skipDataAndDebug()
// encoding COUNT(PASS) opcode: opcode=0, imm_len=size_of(imm), R=0, imm=counterNumber
assertContentEquals(
byteArrayOf(
encodeInstruction(opcode = 0, immLength = 1, register = 0),
- PASSED_ARP.value().toByte()
+ PASSED_ARP_REQUEST.value().toByte()
),
program
)
+ val expectedCounterValue1 = PASSED_ARP_REQUEST.value()
assertContentEquals(
- listOf("0: pass counter=10"),
- ApfJniUtils.disassembleApf(program).map { it.trim() }
+ listOf("0: pass counter=$expectedCounterValue1"),
+ apfTestHelpers.disassembleApf(program).map { it.trim() }
)
- gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ gen = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
gen.addCountAndDrop(DROPPED_ETHERTYPE_NOT_ALLOWED)
program = gen.generate().skipDataAndDebug()
// encoding COUNT(DROP) opcode: opcode=0, imm_len=size_of(imm), R=1, imm=counterNumber
@@ -511,12 +539,13 @@
),
program
)
+ val expectedCounterValue2 = DROPPED_ETHERTYPE_NOT_ALLOWED.value()
assertContentEquals(
- listOf("0: drop counter=47"),
- ApfJniUtils.disassembleApf(program).map { it.trim() }
+ listOf("0: drop counter=$expectedCounterValue2"),
+ apfTestHelpers.disassembleApf(program).map { it.trim() }
)
- gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ gen = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
gen.addAllocateR0()
gen.addAllocate(1500)
program = gen.generate().skipDataAndDebug()
@@ -536,9 +565,9 @@
assertContentEquals(listOf(
"0: allocate r0",
"2: allocate 1500"
- ), ApfJniUtils.disassembleApf(program).map { it.trim() })
+ ), apfTestHelpers.disassembleApf(program).map { it.trim() })
- gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ gen = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
gen.addTransmitWithoutChecksum()
gen.addTransmitL4(30, 40, 50, 256, true)
program = gen.generate().skipDataAndDebug()
@@ -552,28 +581,30 @@
assertContentEquals(listOf(
"0: transmit ip_ofs=255",
"4: transmitudp ip_ofs=30, csum_ofs=40, csum_start=50, partial_csum=0x0100",
- ), ApfJniUtils.disassembleApf(program).map { it.trim() })
+ ), apfTestHelpers.disassembleApf(program).map { it.trim() })
val largeByteArray = ByteArray(256) { 0x01 }
- gen = ApfV6Generator(largeByteArray, APF_VERSION_6, ramSize, clampSize)
+ gen = ApfV6Generator(largeByteArray, apfInterpreterVersion, ramSize, clampSize)
program = gen.generate()
+ val debugBufferSize = ramSize - program.size - Counter.totalSize()
assertContentEquals(
byteArrayOf(
encodeInstruction(opcode = 14, immLength = 2, register = 1), 1, 0
) + largeByteArray + byteArrayOf(
- encodeInstruction(opcode = 21, immLength = 1, register = 0), 48, 6, 9
+ encodeInstruction(opcode = 21, immLength = 1, register = 0),
+ 48, (debugBufferSize shr 8).toByte(), debugBufferSize.toByte()
),
program
)
assertContentEquals(
listOf(
"0: data 256, " + "01".repeat(256),
- "259: debugbuf size=1545"
+ "259: debugbuf size=$debugBufferSize"
),
- ApfJniUtils.disassembleApf(program).map { it.trim() }
+ apfTestHelpers.disassembleApf(program).map { it.trim() }
)
- gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ gen = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
gen.addWriteU8(0x01)
gen.addWriteU16(0x0102)
gen.addWriteU32(0x01020304)
@@ -612,9 +643,9 @@
"25: write 0x80000000",
"30: write 0xfffffffe",
"35: write 0xfffefdfc"
- ), ApfJniUtils.disassembleApf(program).map { it.trim() })
+ ), apfTestHelpers.disassembleApf(program).map { it.trim() })
- gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ gen = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
gen.addWriteU8(R0)
gen.addWriteU16(R0)
gen.addWriteU32(R0)
@@ -637,26 +668,26 @@
"6: ewrite1 r1",
"8: ewrite2 r1",
"10: ewrite4 r1"
- ), ApfJniUtils.disassembleApf(program).map { it.trim() })
+ ), apfTestHelpers.disassembleApf(program).map { it.trim() })
- gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
- gen.addDataCopy(0, 10)
- gen.addDataCopy(1, 5)
+ gen = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
+ gen.addDataCopy(0, 2)
+ gen.addDataCopy(1, 1)
gen.addPacketCopy(1000, 255)
program = gen.generate().skipDataAndDebug()
assertContentEquals(byteArrayOf(
- encodeInstruction(25, 0, 1), 10,
- encodeInstruction(25, 1, 1), 1, 5,
+ encodeInstruction(25, 0, 1), 2,
+ encodeInstruction(25, 1, 1), 1, 1,
encodeInstruction(25, 2, 0),
0x03.toByte(), 0xe8.toByte(), 0xff.toByte(),
), program)
assertContentEquals(listOf(
- "0: datacopy src=0, len=10",
- "2: datacopy src=1, len=5",
+ "0: datacopy src=0, (2)c902",
+ "2: datacopy src=1, (1)02",
"5: pktcopy src=1000, len=255"
- ), ApfJniUtils.disassembleApf(program).map { it.trim() })
+ ), apfTestHelpers.disassembleApf(program).map { it.trim() })
- gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ gen = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
gen.addDataCopyFromR0(5)
gen.addPacketCopyFromR0(5)
gen.addDataCopyFromR0LenR1()
@@ -669,13 +700,13 @@
encodeInstruction(21, 1, 0), 42,
), program)
assertContentEquals(listOf(
- "0: edatacopy src=r0, len=5",
- "3: epktcopy src=r0, len=5",
- "6: edatacopy src=r0, len=r1",
- "8: epktcopy src=r0, len=r1"
- ), ApfJniUtils.disassembleApf(program).map{ it.trim() })
+ "0: edatacopy src=r0, len=5",
+ "3: epktcopy src=r0, len=5",
+ "6: edatacopy src=r0, len=r1",
+ "8: epktcopy src=r0, len=r1"
+ ), apfTestHelpers.disassembleApf(program).map{ it.trim() })
- gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ gen = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
gen.addJumpIfBytesAtR0Equal(byteArrayOf('a'.code.toByte()), ApfV4Generator.DROP_LABEL)
program = gen.generate().skipDataAndDebug()
assertContentEquals(byteArrayOf(
@@ -685,11 +716,11 @@
'a'.code.toByte()
), program)
assertContentEquals(listOf(
- "0: jbseq r0, 0x1, DROP, 61"
- ), ApfJniUtils.disassembleApf(program).map{ it.trim() })
+ "0: jbseq r0, (1), DROP, 61"
+ ), apfTestHelpers.disassembleApf(program).map{ it.trim() })
val qnames = byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte(), 0, 0)
- gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ gen = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
gen.addJumpIfPktAtR0DoesNotContainDnsQ(qnames, 0x0c, ApfV4Generator.DROP_LABEL)
gen.addJumpIfPktAtR0ContainDnsQ(qnames, 0x0c, ApfV4Generator.DROP_LABEL)
program = gen.generate().skipDataAndDebug()
@@ -699,11 +730,11 @@
encodeInstruction(21, 1, 1), 43, 1, 0x0c.toByte(),
) + qnames, program)
assertContentEquals(listOf(
- "0: jdnsqne r0, DROP, 12, (1)A(1)B(0)(0)",
- "10: jdnsqeq r0, DROP, 12, (1)A(1)B(0)(0)"
- ), ApfJniUtils.disassembleApf(program).map{ it.trim() })
+ "0: jdnsqne r0, DROP, PTR, (1)A(1)B(0)(0)",
+ "10: jdnsqeq r0, DROP, PTR, (1)A(1)B(0)(0)"
+ ), apfTestHelpers.disassembleApf(program).map{ it.trim() })
- gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ gen = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
gen.addJumpIfPktAtR0DoesNotContainDnsQSafe(qnames, 0x0c, ApfV4Generator.DROP_LABEL)
gen.addJumpIfPktAtR0ContainDnsQSafe(qnames, 0x0c, ApfV4Generator.DROP_LABEL)
program = gen.generate().skipDataAndDebug()
@@ -713,11 +744,11 @@
encodeInstruction(21, 1, 1), 45, 1, 0x0c.toByte(),
) + qnames, program)
assertContentEquals(listOf(
- "0: jdnsqnesafe r0, DROP, 12, (1)A(1)B(0)(0)",
- "10: jdnsqeqsafe r0, DROP, 12, (1)A(1)B(0)(0)"
- ), ApfJniUtils.disassembleApf(program).map{ it.trim() })
+ "0: jdnsqnesafe r0, DROP, PTR, (1)A(1)B(0)(0)",
+ "10: jdnsqeqsafe r0, DROP, PTR, (1)A(1)B(0)(0)"
+ ), apfTestHelpers.disassembleApf(program).map{ it.trim() })
- gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ gen = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
gen.addJumpIfPktAtR0DoesNotContainDnsA(qnames, ApfV4Generator.DROP_LABEL)
gen.addJumpIfPktAtR0ContainDnsA(qnames, ApfV4Generator.DROP_LABEL)
program = gen.generate().skipDataAndDebug()
@@ -729,9 +760,9 @@
assertContentEquals(listOf(
"0: jdnsane r0, DROP, (1)A(1)B(0)(0)",
"9: jdnsaeq r0, DROP, (1)A(1)B(0)(0)"
- ), ApfJniUtils.disassembleApf(program).map{ it.trim() })
+ ), apfTestHelpers.disassembleApf(program).map{ it.trim() })
- gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ gen = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
gen.addJumpIfPktAtR0DoesNotContainDnsASafe(qnames, ApfV4Generator.DROP_LABEL)
gen.addJumpIfPktAtR0ContainDnsASafe(qnames, ApfV4Generator.DROP_LABEL)
program = gen.generate().skipDataAndDebug()
@@ -743,9 +774,9 @@
assertContentEquals(listOf(
"0: jdnsanesafe r0, DROP, (1)A(1)B(0)(0)",
"9: jdnsaeqsafe r0, DROP, (1)A(1)B(0)(0)"
- ), ApfJniUtils.disassembleApf(program).map{ it.trim() })
+ ), apfTestHelpers.disassembleApf(program).map{ it.trim() })
- gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ gen = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
gen.addJumpIfOneOf(R1, List(32) { (it + 1).toLong() }.toSet(), DROP_LABEL)
gen.addJumpIfOneOf(R0, setOf(0, 257, 65536), DROP_LABEL)
gen.addJumpIfNoneOf(R0, setOf(1, 2, 3), DROP_LABEL)
@@ -759,19 +790,19 @@
encodeInstruction(21, 1, 0), 47, 1, 9, 1, 2, 3
), program)
- gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ gen = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
gen.addJumpIfOneOf(R0, setOf(0, 128, 256, 65536), DROP_LABEL)
gen.addJumpIfNoneOf(R1, setOf(0, 128, 256, 65536), DROP_LABEL)
program = gen.generate().skipDataAndDebug()
assertContentEquals(listOf(
"0: joneof r0, DROP, { 0, 128, 256, 65536 }",
"20: jnoneof r1, DROP, { 0, 128, 256, 65536 }"
- ), ApfJniUtils.disassembleApf(program).map{ it.trim() })
+ ), apfTestHelpers.disassembleApf(program).map{ it.trim() })
- gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ gen = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
gen.addJumpIfBytesAtR0EqualsAnyOf(listOf(byteArrayOf(1, 2), byteArrayOf(3, 4)), DROP_LABEL)
- gen.addJumpIfBytesAtR0EqualNoneOf(listOf(byteArrayOf(1, 2), byteArrayOf(3, 4)), DROP_LABEL)
- gen.addJumpIfBytesAtR0EqualNoneOf(listOf(byteArrayOf(1, 1), byteArrayOf(1, 1)), DROP_LABEL)
+ gen.addJumpIfBytesAtR0EqualsNoneOf(listOf(byteArrayOf(1, 2), byteArrayOf(3, 4)), DROP_LABEL)
+ gen.addJumpIfBytesAtR0EqualsNoneOf(listOf(byteArrayOf(1, 1), byteArrayOf(1, 1)), DROP_LABEL)
program = gen.generate().skipDataAndDebug()
assertContentEquals(byteArrayOf(
encodeInstruction(opcode = 20, immLength = 2, register = 1),
@@ -782,15 +813,36 @@
1, 2, 1, 1
), program)
assertContentEquals(listOf(
- "0: jbseq r0, 0x2, DROP, { 0102, 0304 }",
- "9: jbsne r0, 0x2, DROP, { 0102, 0304 }",
- "18: jbsne r0, 0x2, DROP, 0101"
- ), ApfJniUtils.disassembleApf(program).map{ it.trim() })
+ "0: jbseq r0, (2), DROP, { 0102, 0304 }[2]",
+ "9: jbsne r0, (2), DROP, { 0102, 0304 }[2]",
+ "18: jbsne r0, (2), DROP, 0101"
+ ), apfTestHelpers.disassembleApf(program).map{ it.trim() })
+ }
+
+ @Test
+ fun testApf61InstructionEncoding() {
+ var gen = ApfV61Generator(apfInterpreterVersion, ramSize, clampSize)
+ gen.addCountAndDropIfR0Equals(1, DROPPED_RA)
+ var program = gen.generate().skipDataAndDebug()
+ assertContentEquals(byteArrayOf(
+ encodeInstruction(15, 1, 0),
+ DROPPED_RA.jumpDropLabel.toByte(),
+ 1,
+ ), program)
+
+ gen = ApfV61Generator(apfInterpreterVersion, ramSize, clampSize)
+ gen.addCountAndPassIfR0Equals(1, PASSED_MDNS)
+ program = gen.generate().skipDataAndDebug()
+ assertContentEquals(byteArrayOf(
+ encodeInstruction(15, 1, 0),
+ PASSED_MDNS.jumpPassLabel.toByte(),
+ 1,
+ ), program)
}
@Test
fun testWriteToTxBuffer() {
- var program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ var program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
.addAllocate(14)
.addWriteU8(0x01)
.addWriteU16(0x0203)
@@ -805,19 +857,25 @@
.addWriteU32(R1)
.addTransmitWithoutChecksum()
.generate()
- assertPass(APF_VERSION_6, program, ByteArray(MIN_PKT_SIZE))
+ apfTestHelpers.assertPass(apfInterpreterVersion, program, ByteArray(MIN_PKT_SIZE))
+ val transmitPackets = apfTestHelpers.consumeTransmittedPackets(1)
assertContentEquals(
byteArrayOf(
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0xff.toByte(),
0xff.toByte(), 0xff.toByte(), 0xfe.toByte(), 0xff.toByte(), 0xfe.toByte(),
0xfd.toByte(), 0xfc.toByte(), 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07),
- ApfJniUtils.getTransmittedPacket()
+ transmitPackets[0]
)
}
@Test
fun testCopyToTxBuffer() {
- var program = ApfV6Generator(byteArrayOf(33, 34, 35), APF_VERSION_6, ramSize, clampSize)
+ var program = ApfV6Generator(
+ byteArrayOf(33, 34, 35),
+ apfInterpreterVersion,
+ ramSize,
+ clampSize
+ )
.addAllocate(14)
.addDataCopy(3, 2) // arg1=src, arg2=len
.addDataCopy(5, 1) // arg1=src, arg2=len
@@ -835,16 +893,17 @@
.addPacketCopyFromR0LenR1()
.addTransmitWithoutChecksum()
.generate()
- assertPass(APF_VERSION_6, program, testPacket)
+ apfTestHelpers.assertPass(apfInterpreterVersion, program, testPacket)
+ val transmitPackets = apfTestHelpers.consumeTransmittedPackets(1)
assertContentEquals(
byteArrayOf(33, 34, 35, 1, 2, 3, 4, 33, 34, 35, 1, 2, 3, 4),
- ApfJniUtils.getTransmittedPacket()
+ transmitPackets[0]
)
}
@Test
fun testCopyContentToTxBuffer() {
- val program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ val program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
.addAllocate(18)
.addDataCopy(HexDump.hexStringToByteArray("112233445566"))
.addDataCopy(HexDump.hexStringToByteArray("223344"))
@@ -854,36 +913,195 @@
.generate()
assertContentEquals(listOf(
"0: data 9, 112233445566778899",
- "12: debugbuf size=1772",
+ "12: debugbuf size=${ramSize - program.size - Counter.totalSize()}",
"16: allocate 18",
- "20: datacopy src=3, len=6",
- "23: datacopy src=4, len=3",
- "26: datacopy src=9, len=3",
- "29: datacopy src=3, len=6",
+ "20: datacopy src=3, (6)112233445566",
+ "23: datacopy src=4, (3)223344",
+ "26: datacopy src=9, (3)778899",
+ "29: datacopy src=3, (6)112233445566",
"32: transmit ip_ofs=255"
- ), ApfJniUtils.disassembleApf(program).map{ it.trim() })
- assertPass(APF_VERSION_6, program, testPacket)
- val transmitPkt = HexDump.toHexString(ApfJniUtils.getTransmittedPacket())
+ ), apfTestHelpers.disassembleApf(program).map{ it.trim() })
+ apfTestHelpers.assertPass(apfInterpreterVersion, program, testPacket)
+ val transmitPackets = apfTestHelpers.consumeTransmittedPackets(1)
+ val transmitPkt = HexDump.toHexString(transmitPackets[0])
assertEquals("112233445566223344778899112233445566", transmitPkt)
}
@Test
+ fun testCopyLargeContentToTxBufferWithCompression() {
+ val program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
+ .addAllocate(300)
+ // Chunked into 255 and 35 bytes.
+ // 255 bytes appended to data region.
+ // 35 bytes compressed by reusing the duplicated chunk from the data region.
+ .addDataCopy(ByteArray(290) { 1 })
+ // Appended to the data region.
+ .addDataCopy(ByteArray(5) { 2 })
+ // Compressed by reusing the duplicated chunk from the data region.
+ .addDataCopy(ByteArray(3) { 1 } + ByteArray(2) { 2 })
+ .addTransmitWithoutChecksum()
+ .generate()
+
+ val byteHexString = "01".repeat(255) + "02".repeat(5)
+ assertContentEquals(listOf(
+ "0: data 260, $byteHexString",
+ "263: debugbuf size=${ramSize - program.size - Counter.totalSize()}",
+ "267: allocate 300",
+ "271: datacopy src=3, (255)" + "01".repeat(255),
+ "274: datacopy src=3, (35)" + "01".repeat(35),
+ "277: datacopy src=258, (5)" + "02".repeat(5),
+ "281: datacopy src=255, (5)" + "01".repeat(3) + "02".repeat(2),
+ "284: transmit ip_ofs=255"
+ ), apfTestHelpers.disassembleApf(program).map{ it.trim() })
+ apfTestHelpers.assertPass(apfInterpreterVersion, program, testPacket)
+ val transmitPackets = apfTestHelpers.consumeTransmittedPackets(1)
+ val transmitPkt = HexDump.toHexString(transmitPackets[0])
+ assertEquals(
+ "01".repeat(290) + "02".repeat(5) + "01".repeat(3) + "02".repeat(2),
+ transmitPkt
+ )
+ }
+
+ @Test
+ fun testCopyLargeContentToTxBufferWithoutCompression() {
+ val program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
+ .addAllocate(300)
+ // Chunked into 255 and 45 bytes and then appended to the data region.
+ .addDataCopy(ByteArray(255) { 3 } + ByteArray(45) { 4 })
+ .addTransmitWithoutChecksum()
+ .generate()
+
+ val byteHexString = "03".repeat(255) + "04".repeat(45)
+ assertContentEquals(listOf(
+ "0: data 300, $byteHexString",
+ "303: debugbuf size=${ramSize - program.size - Counter.totalSize()}",
+ "307: allocate 300",
+ "311: datacopy src=3, (255)" + "03".repeat(255),
+ "314: datacopy src=258, (45)" + "04".repeat(45),
+ "318: transmit ip_ofs=255"
+ ), apfTestHelpers.disassembleApf(program).map{ it.trim() })
+ apfTestHelpers.assertPass(apfInterpreterVersion, program, testPacket)
+ val transmitPackets = apfTestHelpers.consumeTransmittedPackets(1)
+ val transmitPkt = HexDump.toHexString(transmitPackets[0])
+ assertEquals( "03".repeat(255) + "04".repeat(45), transmitPkt)
+ }
+
+ @Test
+ fun testJBSPTRMATCHOpcodeEncoding() {
+ assumeTrue(apfInterpreterVersion != ApfJniUtils.APF_INTERPRETER_VERSION_V6)
+ val dataBytes = HexDump.hexStringToByteArray(
+ "01020304050607080910111213141516171819202122232425262728293031323334353637383940"
+ )
+ val bytes1 = HexDump.hexStringToByteArray("0102")
+ val bytes2 = HexDump.hexStringToByteArray("0304")
+ val bytes3 = HexDump.hexStringToByteArray("0506")
+ val bytes4 = HexDump.hexStringToByteArray("0708")
+ val bytes5 = HexDump.hexStringToByteArray("0910")
+ val bytes6 = HexDump.hexStringToByteArray("1112")
+ val bytes7 = HexDump.hexStringToByteArray("1314")
+ val bytes8 = HexDump.hexStringToByteArray("1516")
+ val bytes9 = HexDump.hexStringToByteArray("1718")
+ val bytes10 = HexDump.hexStringToByteArray("1920")
+ val bytes11 = HexDump.hexStringToByteArray("2122")
+ val bytes12 = HexDump.hexStringToByteArray("2324")
+ val bytes13 = HexDump.hexStringToByteArray("2526")
+ val bytes14 = HexDump.hexStringToByteArray("2728")
+ val bytes15 = HexDump.hexStringToByteArray("2930")
+ val bytes16 = HexDump.hexStringToByteArray("3132")
+ val bytes17 = HexDump.hexStringToByteArray("3334")
+ val bytesAtOddIndex = HexDump.hexStringToByteArray("0203")
+ val notExistBytes = HexDump.hexStringToByteArray("ffff")
+ val total17BytesList = listOf(
+ bytes1,
+ bytes2,
+ bytes3,
+ bytes4,
+ bytes5,
+ bytes6,
+ bytes7,
+ bytes8,
+ bytes9,
+ bytes10,
+ bytes11,
+ bytes12,
+ bytes13,
+ bytes14,
+ bytes15,
+ bytes16,
+ bytes17,
+ )
+ val joinedBytes: ByteArray = total17BytesList.flatMap { it.toList() }.toByteArray()
+ var program = ApfV61Generator(apfInterpreterVersion, ramSize, clampSize)
+ .addPreloadData(dataBytes)
+ .addJumpIfBytesAtOffsetEqualsNoneOf(0, listOf(bytes1, notExistBytes), PASS_LABEL)
+ .addJumpIfBytesAtOffsetEqualsAnyOf(1, listOf(bytes1, bytes2), PASS_LABEL)
+ .addJumpIfBytesAtOffsetEqualsNoneOf(2, listOf(notExistBytes), PASS_LABEL)
+ .addJumpIfBytesAtOffsetEqualsAnyOf(3, total17BytesList, PASS_LABEL)
+ .addJumpIfBytesAtOffsetEqualsNoneOf(4, listOf(bytesAtOddIndex), PASS_LABEL)
+ .addJumpIfBytesAtOffsetEqualsNoneOf(6, listOf(joinedBytes), PASS_LABEL)
+ .generate()
+ var debugBufferSize = ramSize - program.size - Counter.totalSize()
+ assertContentEquals(listOf(
+ "0: data 40, ${HexDump.toHexString(dataBytes)}",
+ "43: debugbuf size=$debugBufferSize",
+ "47: jbsptrne pktofs=0, (2), PASS, @0[0102]",
+ "52: li r0, 0",
+ "53: jbsne r0, (2), PASS, ffff",
+ "58: jbsptreq pktofs=1, (2), PASS, { @0[0102], @2[0304] }[2]",
+ "64: li r0, 2",
+ "66: jbsne r0, (2), PASS, ffff",
+ "71: jbsptreq pktofs=3, (2), PASS, { @0[0102], @2[0304], @4[0506], @6[0708], " +
+ "@8[0910], @10[1112], @12[1314], @14[1516], @16[1718], @18[1920], @20[2122], " +
+ "@22[2324], @24[2526], @26[2728], @28[2930], @30[3132] }[16]",
+ "91: jbsptreq pktofs=3, (2), PASS, @32[3334]",
+ "96: li r0, 4",
+ "98: jbsne r0, (2), PASS, 0203",
+ "103: li r0, 6",
+ "105: jbsne r0, (34), PASS, ${HexDump.toHexString(joinedBytes)}",
+ ), apfTestHelpers.disassembleApf(program).map{ it.trim() })
+
+ val largePrefix = ByteArray(510) { 0 }
+ program = ApfV61Generator(apfInterpreterVersion, ramSize, clampSize)
+ .addPreloadData(largePrefix + dataBytes)
+ .addJumpIfBytesAtOffsetEqualsAnyOf(1, listOf(bytes1, bytes2), PASS_LABEL)
+ .generate()
+ debugBufferSize = ramSize - program.size - Counter.totalSize()
+ assertContentEquals(listOf(
+ "0: data 550, ${HexDump.toHexString(largePrefix + dataBytes)}",
+ "553: debugbuf size=$debugBufferSize",
+ "557: jbsptreq pktofs=1, (2), PASS, @510[0102]",
+ "562: li r0, 1",
+ "564: jbseq r0, (2), PASS, 0304",
+ ), apfTestHelpers.disassembleApf(program).map{ it.trim() })
+ }
+
+ @Test
fun testPassDrop() {
- var program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ var program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
.addDrop()
.addPass()
.generate()
- assertDrop(APF_VERSION_6, program, testPacket)
+ apfTestHelpers.assertDrop(apfInterpreterVersion, program, testPacket)
- program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
.addCountAndDrop(Counter.DROPPED_ETH_BROADCAST)
.generate()
- verifyProgramRun(APF_VERSION_6, program, testPacket, DROPPED_ETH_BROADCAST)
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
+ program,
+ testPacket,
+ DROPPED_ETH_BROADCAST
+ )
- program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
- .addCountAndPass(Counter.PASSED_ARP)
+ program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
+ .addCountAndPass(Counter.PASSED_ARP_REQUEST)
.generate()
- verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP)
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
+ program,
+ testPacket,
+ PASSED_ARP_REQUEST
+ )
}
@Test
@@ -894,7 +1112,7 @@
)
doTestLoadStoreCounter (
{ mutableMapOf(TOTAL_PACKETS to 1) },
- { ApfV6Generator(APF_VERSION_6, ramSize, clampSize) }
+ { ApfV6Generator(apfInterpreterVersion, ramSize, clampSize) }
)
}
@@ -903,14 +1121,14 @@
getGenerator: () -> ApfV4GeneratorBase<*>
) {
val program = getGenerator()
- .addIncrementCounter(PASSED_ARP, 2)
+ .addIncrementCounter(PASSED_ARP_REQUEST, 2)
.addPass()
.generate()
var dataRegion = ByteArray(Counter.totalSize()) { 0 }
- assertVerdict(APF_VERSION_6, PASS, program, testPacket, dataRegion)
+ apfTestHelpers.assertVerdict(apfInterpreterVersion, PASS, program, testPacket, dataRegion)
var counterMap = decodeCountersIntoMap(dataRegion)
var expectedMap = getInitialMap()
- expectedMap[PASSED_ARP] = 2
+ expectedMap[PASSED_ARP_REQUEST] = 2
assertEquals(expectedMap, counterMap)
}
@@ -925,11 +1143,20 @@
@Test
fun testV6CountAndPassDropCompareR0() {
doTestCountAndPassDropCompareR0(
- getGenerator = { ApfV6Generator(APF_VERSION_6, ramSize, clampSize) },
+ getGenerator = { ApfV6Generator(apfInterpreterVersion, ramSize, clampSize) },
incTotal = true
)
}
+ @Test
+ fun testV61CountAndPassDropCompareR0() {
+ assumeTrue(apfInterpreterVersion > ApfJniUtils.APF_INTERPRETER_VERSION_V6)
+ doTestCountAndPassDropCompareR0(
+ getGenerator = { ApfV61Generator(apfInterpreterVersion, ramSize, clampSize) },
+ incTotal = true
+ )
+ }
+
private fun doTestCountAndPassDropCompareR0(
getGenerator: () -> ApfV4GeneratorBase<*>,
incTotal: Boolean
@@ -940,8 +1167,8 @@
.addPass()
.addCountTrampoline()
.generate()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
testPacket,
DROPPED_ETH_BROADCAST,
@@ -950,11 +1177,17 @@
program = getGenerator()
.addLoadImmediate(R0, 123)
- .addCountAndPassIfR0Equals(123, Counter.PASSED_ARP)
+ .addCountAndPassIfR0Equals(123, Counter.PASSED_ARP_REQUEST)
.addPass()
.addCountTrampoline()
.generate()
- verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal)
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
+ program,
+ testPacket,
+ PASSED_ARP_REQUEST,
+ incTotal = incTotal
+ )
program = getGenerator()
.addLoadImmediate(R0, 123)
@@ -962,8 +1195,8 @@
.addPass()
.addCountTrampoline()
.generate()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
testPacket,
DROPPED_ETH_BROADCAST,
@@ -972,11 +1205,17 @@
program = getGenerator()
.addLoadImmediate(R0, 123)
- .addCountAndPassIfR0NotEquals(124, Counter.PASSED_ARP)
+ .addCountAndPassIfR0NotEquals(124, Counter.PASSED_ARP_REQUEST)
.addPass()
.addCountTrampoline()
.generate()
- verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal)
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
+ program,
+ testPacket,
+ PASSED_ARP_REQUEST,
+ incTotal = incTotal
+ )
program = getGenerator()
.addLoadImmediate(R0, 123)
@@ -984,8 +1223,8 @@
.addPass()
.addCountTrampoline()
.generate()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
testPacket,
DROPPED_ETH_BROADCAST,
@@ -994,11 +1233,17 @@
program = getGenerator()
.addLoadImmediate(R0, 123)
- .addCountAndPassIfR0LessThan(124, Counter.PASSED_ARP)
+ .addCountAndPassIfR0LessThan(124, Counter.PASSED_ARP_REQUEST)
.addPass()
.addCountTrampoline()
.generate()
- verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal)
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
+ program,
+ testPacket,
+ PASSED_ARP_REQUEST,
+ incTotal = incTotal
+ )
program = getGenerator()
.addLoadImmediate(R0, 123)
@@ -1006,8 +1251,8 @@
.addPass()
.addCountTrampoline()
.generate()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
testPacket,
DROPPED_ETH_BROADCAST,
@@ -1016,11 +1261,17 @@
program = getGenerator()
.addLoadImmediate(R0, 123)
- .addCountAndPassIfR0GreaterThan(122, Counter.PASSED_ARP)
+ .addCountAndPassIfR0GreaterThan(122, Counter.PASSED_ARP_REQUEST)
.addPass()
.addCountTrampoline()
.generate()
- verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal)
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
+ program,
+ testPacket,
+ PASSED_ARP_REQUEST,
+ incTotal = incTotal
+ )
program = getGenerator()
.addLoadImmediate(R0, 1)
@@ -1029,8 +1280,8 @@
.addPass()
.addCountTrampoline()
.generate()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
testPacket,
DROPPED_ETH_BROADCAST,
@@ -1040,11 +1291,17 @@
program = getGenerator()
.addLoadImmediate(R0, 1)
.addCountAndPassIfBytesAtR0NotEqual(
- byteArrayOf(5, 5), PASSED_ARP)
+ byteArrayOf(5, 5), PASSED_ARP_REQUEST)
.addPass()
.addCountTrampoline()
.generate()
- verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal)
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
+ program,
+ testPacket,
+ PASSED_ARP_REQUEST,
+ incTotal = incTotal
+ )
program = getGenerator()
.addLoadImmediate(R0, 1)
@@ -1052,8 +1309,8 @@
.addPass()
.addCountTrampoline()
.generate()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
testPacket,
DROPPED_ETH_BROADCAST,
@@ -1062,11 +1319,17 @@
program = getGenerator()
.addLoadImmediate(R0, 1)
- .addCountAndPassIfR0AnyBitsSet(0xffff, PASSED_ARP)
+ .addCountAndPassIfR0AnyBitsSet(0xffff, PASSED_ARP_REQUEST)
.addPass()
.addCountTrampoline()
.generate()
- verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal)
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
+ program,
+ testPacket,
+ PASSED_ARP_REQUEST,
+ incTotal = incTotal
+ )
program = getGenerator()
.addLoadImmediate(R0, 123)
@@ -1074,8 +1337,8 @@
.addPass()
.addCountTrampoline()
.generate()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
testPacket,
DROPPED_ETH_BROADCAST,
@@ -1084,11 +1347,17 @@
program = getGenerator()
.addLoadImmediate(R0, 123)
- .addCountAndPassIfR0IsOneOf(setOf(123), PASSED_ARP)
+ .addCountAndPassIfR0IsOneOf(setOf(123), PASSED_ARP_REQUEST)
.addPass()
.addCountTrampoline()
.generate()
- verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal)
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
+ program,
+ testPacket,
+ PASSED_ARP_REQUEST,
+ incTotal = incTotal
+ )
program = getGenerator()
.addLoadImmediate(R0, 123)
@@ -1096,8 +1365,8 @@
.addPass()
.addCountTrampoline()
.generate()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
testPacket,
DROPPED_ETH_BROADCAST,
@@ -1106,11 +1375,17 @@
program = getGenerator()
.addLoadImmediate(R0, 123)
- .addCountAndPassIfR0IsNoneOf(setOf(124), PASSED_ARP)
+ .addCountAndPassIfR0IsNoneOf(setOf(124), PASSED_ARP_REQUEST)
.addPass()
.addCountTrampoline()
.generate()
- verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal)
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
+ program,
+ testPacket,
+ PASSED_ARP_REQUEST,
+ incTotal = incTotal
+ )
program = getGenerator()
.addLoadImmediate(R0, 123)
@@ -1118,8 +1393,8 @@
.addPass()
.addCountTrampoline()
.generate()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
testPacket,
DROPPED_ETH_BROADCAST,
@@ -1128,11 +1403,17 @@
program = getGenerator()
.addLoadImmediate(R0, 123)
- .addCountAndPassIfR0IsOneOf(setOf(123, 124), PASSED_ARP)
+ .addCountAndPassIfR0IsOneOf(setOf(123, 124), PASSED_ARP_REQUEST)
.addPass()
.addCountTrampoline()
.generate()
- verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal)
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
+ program,
+ testPacket,
+ PASSED_ARP_REQUEST,
+ incTotal = incTotal
+ )
program = getGenerator()
.addLoadImmediate(R0, 123)
@@ -1140,8 +1421,8 @@
.addPass()
.addCountTrampoline()
.generate()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
testPacket,
DROPPED_ETH_BROADCAST,
@@ -1150,11 +1431,17 @@
program = getGenerator()
.addLoadImmediate(R0, 123)
- .addCountAndPassIfR0IsNoneOf(setOf(122, 124), PASSED_ARP)
+ .addCountAndPassIfR0IsNoneOf(setOf(122, 124), PASSED_ARP_REQUEST)
.addPass()
.addCountTrampoline()
.generate()
- verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal)
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
+ program,
+ testPacket,
+ PASSED_ARP_REQUEST,
+ incTotal = incTotal
+ )
program = getGenerator()
.addLoadImmediate(R0, 0)
@@ -1165,8 +1452,8 @@
.addPass()
.addCountTrampoline()
.generate()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
testPacket,
DROPPED_ETH_BROADCAST,
@@ -1177,12 +1464,18 @@
.addLoadImmediate(R0, 0)
.addCountAndPassIfBytesAtR0EqualsAnyOf(
listOf(byteArrayOf(1, 2), byteArrayOf(3, 4)),
- PASSED_ARP
+ PASSED_ARP_REQUEST
)
.addPass()
.addCountTrampoline()
.generate()
- verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal)
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
+ program,
+ testPacket,
+ PASSED_ARP_REQUEST,
+ incTotal = incTotal
+ )
program = getGenerator()
.addLoadImmediate(R0, 0)
@@ -1193,8 +1486,8 @@
.addPass()
.addCountTrampoline()
.generate()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
testPacket,
DROPPED_ETH_BROADCAST,
@@ -1205,12 +1498,18 @@
.addLoadImmediate(R0, 0)
.addCountAndPassIfBytesAtR0EqualsNoneOf(
listOf(byteArrayOf(1, 3), byteArrayOf(3, 4)),
- PASSED_ARP
+ PASSED_ARP_REQUEST
)
.addPass()
.addCountTrampoline()
.generate()
- verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal)
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
+ program,
+ testPacket,
+ PASSED_ARP_REQUEST,
+ incTotal = incTotal
+ )
program = getGenerator()
.addLoadImmediate(R0, 1)
@@ -1219,8 +1518,8 @@
.addPass()
.addCountTrampoline()
.generate()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
testPacket,
DROPPED_ETH_BROADCAST,
@@ -1230,11 +1529,17 @@
program = getGenerator()
.addLoadImmediate(R0, 1)
.addCountAndPassIfBytesAtR0Equal(
- byteArrayOf(2, 3), PASSED_ARP)
+ byteArrayOf(2, 3), PASSED_ARP_REQUEST)
.addPass()
.addCountTrampoline()
.generate()
- verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal)
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
+ program,
+ testPacket,
+ PASSED_ARP_REQUEST,
+ incTotal = incTotal
+ )
}
@Test
@@ -1243,8 +1548,8 @@
.addCountAndDrop(Counter.DROPPED_ETH_BROADCAST)
.addCountTrampoline()
.generate()
- verifyProgramRun(
- APF_VERSION_6,
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
program,
testPacket,
DROPPED_ETH_BROADCAST,
@@ -1252,10 +1557,16 @@
)
program = ApfV4Generator(APF_VERSION_3, ramSize, clampSize)
- .addCountAndPass(Counter.PASSED_ARP)
+ .addCountAndPass(Counter.PASSED_ARP_REQUEST)
.addCountTrampoline()
.generate()
- verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = false)
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
+ program,
+ testPacket,
+ PASSED_ARP_REQUEST,
+ incTotal = false
+ )
}
@Test
@@ -1265,31 +1576,36 @@
.addCountTrampoline()
.generate()
var dataRegion = ByteArray(Counter.totalSize()) { 0 }
- assertVerdict(APF_VERSION_6, DROP, program, testPacket, dataRegion)
+ apfTestHelpers.assertVerdict(apfInterpreterVersion, DROP, program, testPacket, dataRegion)
assertContentEquals(ByteArray(Counter.totalSize()) { 0 }, dataRegion)
program = ApfV4Generator(APF_VERSION_2, ramSize, clampSize)
- .addCountAndPass(PASSED_ARP)
+ .addCountAndPass(PASSED_ARP_REQUEST)
.addCountTrampoline()
.generate()
dataRegion = ByteArray(Counter.totalSize()) { 0 }
- assertVerdict(APF_VERSION_6, PASS, program, testPacket, dataRegion)
+ apfTestHelpers.assertVerdict(apfInterpreterVersion, PASS, program, testPacket, dataRegion)
assertContentEquals(ByteArray(Counter.totalSize()) { 0 }, dataRegion)
}
@Test
fun testAllocateFailure() {
- val program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ val program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
// allocate size: 65535 > sizeof(apf_test_buffer): 1514, trigger allocate failure.
.addAllocate(65535)
.addDrop()
.generate()
- verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ALLOCATE_FAILURE)
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
+ program,
+ testPacket,
+ PASSED_ALLOCATE_FAILURE
+ )
}
@Test
fun testTransmitFailure() {
- val program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ val program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
.addAllocate(14)
// len: 13 is less than ETH_HLEN, trigger transmit failure.
.addLoadImmediate(R0, 13)
@@ -1297,7 +1613,12 @@
.addTransmitWithoutChecksum()
.addDrop()
.generate()
- verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_TRANSMIT_FAILURE)
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
+ program,
+ testPacket,
+ PASSED_TRANSMIT_FAILURE
+ )
}
@Test
@@ -1325,7 +1646,7 @@
0x00, 0x00, 0x01, 0x80, 0x01, 0x00, 0x00, 0x00, 0x78, 0x00, 0x04, 0xc0, 0xa8, 0x01,
0x09,
).map { it.toByte() }.toByteArray()
- val program = ApfV6Generator(etherIpv4UdpPacket, APF_VERSION_6, ramSize, clampSize)
+ val program = ApfV6Generator(etherIpv4UdpPacket, apfInterpreterVersion, ramSize, clampSize)
.addAllocate(etherIpv4UdpPacket.size)
.addDataCopy(3, etherIpv4UdpPacket.size) // arg1=src, arg2=len
.addTransmitL4(
@@ -1336,8 +1657,9 @@
true // isUdp
)
.generate()
- assertPass(APF_VERSION_6, program, testPacket)
- val txBuf = ByteBuffer.wrap(ApfJniUtils.getTransmittedPacket())
+ apfTestHelpers.assertPass(apfInterpreterVersion, program, testPacket)
+ val transmitPackets = apfTestHelpers.consumeTransmittedPackets(1)
+ val txBuf = ByteBuffer.wrap(transmitPackets[0])
Struct.parse(EthernetHeader::class.java, txBuf)
val ipv4Hdr = Struct.parse(Ipv4Header::class.java, txBuf)
val udpHdr = Struct.parse(UdpHeader::class.java, txBuf)
@@ -1371,33 +1693,33 @@
0x00, 0x01, 0x00, 0x01 // type = A, class = 0x0001
).map { it.toByte() }.toByteArray()
- var program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ var program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
.addLoadImmediate(R0, 0)
.addJumpIfPktAtR0ContainDnsQ(needlesMatch, 0x01, DROP_LABEL) // arg2=qtype
.addPass()
.generate()
- assertDrop(APF_VERSION_6, program, udpPayload)
+ apfTestHelpers.assertDrop(apfInterpreterVersion, program, udpPayload)
- program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
.addLoadImmediate(R0, 0)
.addJumpIfPktAtR0ContainDnsQSafe(needlesMatch, 0x01, DROP_LABEL)
.addPass()
.generate()
- assertDrop(APF_VERSION_6, program, udpPayload)
+ apfTestHelpers.assertDrop(apfInterpreterVersion, program, udpPayload)
- program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
.addLoadImmediate(R0, 0)
.addJumpIfPktAtR0DoesNotContainDnsQ(needlesMatch, 0x01, DROP_LABEL) // arg2=qtype
.addPass()
.generate()
- assertPass(APF_VERSION_6, program, udpPayload)
+ apfTestHelpers.assertPass(apfInterpreterVersion, program, udpPayload)
- program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
.addLoadImmediate(R0, 0)
.addJumpIfPktAtR0DoesNotContainDnsQSafe(needlesMatch, 0x01, DROP_LABEL) // arg2=qtype
.addPass()
.generate()
- assertPass(APF_VERSION_6, program, udpPayload)
+ apfTestHelpers.assertPass(apfInterpreterVersion, program, udpPayload)
val badUdpPayload = intArrayOf(
0x00, 0x00, 0x00, 0x00, // tid = 0x00, flags = 0x00,
@@ -1414,19 +1736,31 @@
0x00, 0x01, 0x00, 0x01 // type = A, class = 0x0001
).map { it.toByte() }.toByteArray()
- program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
.addLoadImmediate(R0, 0)
.addJumpIfPktAtR0ContainDnsQ(needlesMatch, 0x01, DROP_LABEL) // arg2=qtype
.addPass()
.generate()
- verifyProgramRun(APF_VERSION_6, program, badUdpPayload, CORRUPT_DNS_PACKET, result = DROP)
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
+ program,
+ badUdpPayload,
+ CORRUPT_DNS_PACKET,
+ result = DROP
+ )
- program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
.addLoadImmediate(R0, 0)
.addJumpIfPktAtR0ContainDnsQSafe(needlesMatch, 0x01, DROP_LABEL) // arg2=qtype
.addPass()
.generate()
- verifyProgramRun(APF_VERSION_6, program, badUdpPayload, CORRUPT_DNS_PACKET, result = PASS)
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
+ program,
+ badUdpPayload,
+ CORRUPT_DNS_PACKET,
+ result = PASS
+ )
}
@Test
@@ -1460,33 +1794,33 @@
0x00, 0x04, 0xc0, 0xa8, 0x01, 0x09 // rdlengh = 4, rdata = 192.168.1.9
).map { it.toByte() }.toByteArray()
- var program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ var program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
.addLoadImmediate(R0, 0)
.addJumpIfPktAtR0ContainDnsA(needlesMatch, DROP_LABEL)
.addPass()
.generate()
- assertDrop(APF_VERSION_6, program, udpPayload)
+ apfTestHelpers.assertDrop(apfInterpreterVersion, program, udpPayload)
- program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
.addLoadImmediate(R0, 0)
.addJumpIfPktAtR0ContainDnsASafe(needlesMatch, DROP_LABEL)
.addPass()
.generate()
- assertDrop(APF_VERSION_6, program, udpPayload)
+ apfTestHelpers.assertDrop(apfInterpreterVersion, program, udpPayload)
- program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
.addLoadImmediate(R0, 0)
.addJumpIfPktAtR0DoesNotContainDnsA(needlesMatch, DROP_LABEL)
.addPass()
.generate()
- assertPass(APF_VERSION_6, program, udpPayload)
+ apfTestHelpers.assertPass(apfInterpreterVersion, program, udpPayload)
- program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
.addLoadImmediate(R0, 0)
.addJumpIfPktAtR0DoesNotContainDnsASafe(needlesMatch, DROP_LABEL)
.addPass()
.generate()
- assertPass(APF_VERSION_6, program, udpPayload)
+ apfTestHelpers.assertPass(apfInterpreterVersion, program, udpPayload)
val badUdpPayload = intArrayOf(
0x00, 0x00, 0x84, 0x00, // tid = 0x00, flags = 0x8400,
@@ -1507,19 +1841,31 @@
0x00, 0x04, 0xc0, 0xa8, 0x01, 0x09 // rdlengh = 4, rdata = 192.168.1.9
).map { it.toByte() }.toByteArray()
- program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
.addLoadImmediate(R0, 0)
.addJumpIfPktAtR0ContainDnsA(needlesMatch, DROP_LABEL)
.addPass()
.generate()
- verifyProgramRun(APF_VERSION_6, program, badUdpPayload, CORRUPT_DNS_PACKET, result = DROP)
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
+ program,
+ badUdpPayload,
+ CORRUPT_DNS_PACKET,
+ result = DROP
+ )
- program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
.addLoadImmediate(R0, 0)
.addJumpIfPktAtR0ContainDnsASafe(needlesMatch, DROP_LABEL)
.addPass()
.generate()
- verifyProgramRun(APF_VERSION_6, program, badUdpPayload, CORRUPT_DNS_PACKET, result = PASS)
+ apfTestHelpers.verifyProgramRun(
+ apfInterpreterVersion,
+ program,
+ badUdpPayload,
+ CORRUPT_DNS_PACKET,
+ result = PASS
+ )
}
@Test
@@ -1531,7 +1877,7 @@
@Test
fun testJumpMultipleByteSequencesMatch() {
- var program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ var program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
.addLoadImmediate(R0, 0)
.addJumpIfBytesAtR0EqualsAnyOf(
listOf(byteArrayOf(1, 2, 3), byteArrayOf(6, 5, 4)),
@@ -1539,9 +1885,9 @@
)
.addPass()
.generate()
- assertDrop(APF_VERSION_6, program, testPacket)
+ apfTestHelpers.assertDrop(apfInterpreterVersion, program, testPacket)
- program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
.addLoadImmediate(R0, 2)
.addJumpIfBytesAtR0EqualsAnyOf(
listOf(byteArrayOf(1, 2, 3), byteArrayOf(6, 5, 4)),
@@ -1549,72 +1895,104 @@
)
.addPass()
.generate()
- assertPass(APF_VERSION_6, program, testPacket)
+ apfTestHelpers.assertPass(apfInterpreterVersion, program, testPacket)
- program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
.addLoadImmediate(R0, 1)
- .addJumpIfBytesAtR0EqualNoneOf(
+ .addJumpIfBytesAtR0EqualsNoneOf(
listOf(byteArrayOf(1, 2, 3), byteArrayOf(6, 5, 4)),
DROP_LABEL
)
.addPass()
.generate()
- assertDrop(APF_VERSION_6, program, testPacket)
+ apfTestHelpers.assertDrop(apfInterpreterVersion, program, testPacket)
- program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
.addLoadImmediate(R0, 0)
- .addJumpIfBytesAtR0EqualNoneOf(
+ .addJumpIfBytesAtR0EqualsNoneOf(
listOf(byteArrayOf(1, 2, 3), byteArrayOf(6, 5, 4)),
DROP_LABEL
)
.addPass()
.generate()
- assertPass(APF_VERSION_6, program, testPacket)
+ apfTestHelpers.assertPass(apfInterpreterVersion, program, testPacket)
}
@Test
fun testJumpOneOf() {
- var program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ var program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
.addLoadImmediate(R0, 255)
.addJumpIfOneOf(R0, setOf(1, 2, 3, 128, 255), DROP_LABEL)
.addPass()
.generate()
- assertDrop(APF_VERSION_6, program, testPacket)
+ apfTestHelpers.assertDrop(apfInterpreterVersion, program, testPacket)
- program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
.addLoadImmediate(R0, 254)
.addJumpIfOneOf(R0, setOf(1, 2, 3, 128, 255), DROP_LABEL)
.addPass()
.generate()
- assertPass(APF_VERSION_6, program, testPacket)
+ apfTestHelpers.assertPass(apfInterpreterVersion, program, testPacket)
- program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
.addLoadImmediate(R0, 254)
.addJumpIfNoneOf(R0, setOf(1, 2, 3, 128, 255), DROP_LABEL)
.addPass()
.generate()
- assertDrop(APF_VERSION_6, program, testPacket)
+ apfTestHelpers.assertDrop(apfInterpreterVersion, program, testPacket)
- program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
+ program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
.addLoadImmediate(R0, 255)
.addJumpIfNoneOf(R0, setOf(1, 2, 3, 128, 255), DROP_LABEL)
.addPass()
.generate()
- assertPass(APF_VERSION_6, program, testPacket)
+ apfTestHelpers.assertPass(apfInterpreterVersion, program, testPacket)
}
@Test
fun testDebugBuffer() {
- val program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize)
- .addLoad8(R0, 255)
+ val program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
+ .addLoad8intoR0(255)
.generate()
val dataRegion = ByteArray(ramSize - program.size) { 0 }
- assertVerdict(APF_VERSION_6, PASS, program, testPacket, dataRegion)
+ apfTestHelpers.assertVerdict(apfInterpreterVersion, PASS, program, testPacket, dataRegion)
// offset 3 in the data region should contain if the interpreter is APFv6 mode or not
assertEquals(1, dataRegion[3])
}
+ @Test
+ fun testGetApfV6BaseProgramSize() {
+ val gen = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
+ assertEquals(gen.baseProgramSize, gen.generate().size)
+ assertEquals(7, gen.baseProgramSize)
+ }
+
+ @Test
+ fun testGetApfV4BaseProgramSize() {
+ val gen = ApfV4Generator(apfInterpreterVersion, ramSize, clampSize)
+ assertEquals(gen.baseProgramSize, gen.generate().size)
+ assertEquals(0, gen.baseProgramSize)
+ }
+
+ @Test
+ fun testGetApfV6DefaultPacketHandlingSizeOverEstimate() {
+ val gen = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
+ gen.addDefaultPacketHandling()
+ val size = gen.programLengthOverEstimate() - gen.baseProgramSize
+ assertEquals(2, size)
+ assertEquals(size, gen.defaultPacketHandlingSizeOverEstimate)
+ }
+
+ @Test
+ fun testGetApfV4DefaultPacketHandlingSizeOverEstimate() {
+ val gen = ApfV4Generator(apfInterpreterVersion, ramSize, clampSize)
+ gen.addDefaultPacketHandling()
+ val size = gen.programLengthOverEstimate() - gen.baseProgramSize
+ assertEquals(25, size)
+ assertEquals(size, gen.defaultPacketHandlingSizeOverEstimate)
+ }
+
private fun encodeInstruction(opcode: Int, immLength: Int, register: Int): Byte {
val immLengthEncoding = if (immLength == 4) 3 else immLength
return opcode.shl(3).or(immLengthEncoding.shl(1)).or(register).toByte()
diff --git a/tests/unit/src/android/net/apf/ApfJniUtils.java b/tests/unit/src/android/net/apf/ApfJniUtils.java
index e6a7ad7..85f76b9 100644
--- a/tests/unit/src/android/net/apf/ApfJniUtils.java
+++ b/tests/unit/src/android/net/apf/ApfJniUtils.java
@@ -15,28 +15,40 @@
*/
package android.net.apf;
+import java.util.List;
+
/**
* The class contains the helper method for interacting with native apf code.
*/
public class ApfJniUtils {
-
- static {
+ static final int APF_INTERPRETER_VERSION_V6 = 6000;
+ static final int APF_INTERPRETER_VERSION_NEXT = 99999999;
+ public ApfJniUtils(int apfInterpreterVersion) {
// Load up native shared library containing APF interpreter exposed via JNI.
- System.loadLibrary("networkstacktestsjni");
+ if (apfInterpreterVersion == APF_INTERPRETER_VERSION_V6) {
+ System.loadLibrary("apfjniv6");
+ } else if (apfInterpreterVersion == APF_INTERPRETER_VERSION_NEXT) {
+ System.loadLibrary("apfjninext");
+ } else {
+ throw new IllegalArgumentException(
+ "apfInterpreterVersion must be "
+ + APF_INTERPRETER_VERSION_V6 + " or "
+ + APF_INTERPRETER_VERSION_NEXT);
+ }
}
/**
* Call the APF interpreter to run {@code program} on {@code packet} with persistent memory
* segment {@data} pretending the filter was installed {@code filter_age} seconds ago.
*/
- public static native int apfSimulate(int apfVersion, byte[] program, byte[] packet,
+ public native int apfSimulate(int apfVersion, byte[] program, byte[] packet,
byte[] data, int filterAge);
/**
* Compile a tcpdump human-readable filter (e.g. "icmp" or "tcp port 54") into a BPF
* prorgam and return a human-readable dump of the BPF program identical to "tcpdump -d".
*/
- public static native String compileToBpf(String filter);
+ public native String compileToBpf(String filter);
/**
* Open packet capture file {@code pcap_filename} and filter the packets using tcpdump
@@ -44,7 +56,7 @@
* at the same time using APF program {@code apf_program}. Return {@code true} if
* both APF and BPF programs filter out exactly the same packets.
*/
- public static native boolean compareBpfApf(int apfVersion, String filter,
+ public native boolean compareBpfApf(int apfVersion, String filter,
String pcapFilename, byte[] apfProgram);
/**
@@ -52,21 +64,21 @@
* checks whether all the packets are dropped and populates data[] {@code data} with
* the APF counters.
*/
- public static native boolean dropsAllPackets(int apfVersion, byte[] program, byte[] data,
+ public native boolean dropsAllPackets(int apfVersion, byte[] program, byte[] data,
String pcapFilename);
/**
* Disassemble the Apf program into human-readable text.
*/
- public static native String[] disassembleApf(byte[] program);
+ public native String[] disassembleApf(byte[] program);
/**
- * Get the transmitted packet.
+ * Get all transmitted packets.
*/
- public static native byte[] getTransmittedPacket();
+ public native List<byte[]> getAllTransmittedPackets();
/**
* Reset the memory region that stored the transmitted packet.
*/
- public static native void resetTransmittedPacketMemory();
+ public native void resetTransmittedPacketMemory();
}
diff --git a/tests/unit/src/android/net/apf/ApfMdnsOffloadEngineTest.kt b/tests/unit/src/android/net/apf/ApfMdnsOffloadEngineTest.kt
new file mode 100644
index 0000000..d2841ba
--- /dev/null
+++ b/tests/unit/src/android/net/apf/ApfMdnsOffloadEngineTest.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.apf
+
+import android.net.apf.ApfMdnsOffloadEngine.Callback
+import android.net.apf.ApfMdnsUtils.extractOffloadReplyRule
+import android.net.nsd.NsdManager
+import android.net.nsd.OffloadEngine
+import android.net.nsd.OffloadServiceInfo
+import android.os.Build
+import android.os.Handler
+import android.os.HandlerThread
+import androidx.test.filters.SmallTest
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.visibleOnHandlerThread
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyLong
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+private const val TIMEOUT_MS: Long = 1000
+
+/**
+ * Tests for ApfMdnsOffloadEngine.
+ */
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+class ApfMdnsOffloadEngineTest {
+
+ @get:Rule
+ val ignoreRule = DevSdkIgnoreRule()
+
+ private val TAG = ApfMdnsOffloadEngineTest::class.java.simpleName
+
+ private val handlerThread by lazy {
+ HandlerThread("$TAG handler thread").apply { start() }
+ }
+ private val handler by lazy { Handler(handlerThread.looper) }
+
+ private val interfaceName = "test_interface"
+
+ @Mock
+ private lateinit var nsdManager: NsdManager
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @After
+ fun tearDown() {
+ handlerThread.quitSafely()
+ handlerThread.join()
+ Mockito.framework().clearInlineMocks()
+ }
+
+ @Test
+ fun testOffloadEngineRegistration() {
+ val callback = mock(Callback::class.java)
+ val apfOffloadEngine = ApfMdnsOffloadEngine(interfaceName, handler, nsdManager, callback)
+ apfOffloadEngine.registerOffloadEngine()
+ verify(nsdManager).registerOffloadEngine(
+ eq(interfaceName),
+ anyLong(),
+ anyLong(),
+ any(),
+ eq(apfOffloadEngine)
+ )
+ val info1 = OffloadServiceInfo(
+ OffloadServiceInfo.Key("TestServiceName", "_advertisertest._tcp"),
+ listOf(),
+ "Android_test.local",
+ byteArrayOf(0x01, 0x02, 0x03, 0x04),
+ 0,
+ OffloadEngine.OFFLOAD_TYPE_REPLY.toLong()
+ )
+ val info2 = OffloadServiceInfo(
+ OffloadServiceInfo.Key("TestServiceName2", "_advertisertest._tcp"),
+ listOf(),
+ "Android_test.local",
+ byteArrayOf(0x01, 0x02, 0x03, 0x04),
+ 0,
+ OffloadEngine.OFFLOAD_TYPE_REPLY.toLong()
+ )
+ val updatedInfo1 = OffloadServiceInfo(
+ OffloadServiceInfo.Key("TestServiceName", "_advertisertest._tcp"),
+ listOf(),
+ "Android_test.local",
+ byteArrayOf(),
+ 0,
+ OffloadEngine.OFFLOAD_TYPE_REPLY.toLong()
+ )
+ visibleOnHandlerThread(handler) { apfOffloadEngine.onOffloadServiceUpdated(info1) }
+ verify(callback).onOffloadRulesUpdated(eq(extractOffloadReplyRule(listOf(info1))))
+ visibleOnHandlerThread(handler) { apfOffloadEngine.onOffloadServiceUpdated(info2) }
+ verify(callback).onOffloadRulesUpdated(eq(extractOffloadReplyRule(listOf(info1, info2))))
+ visibleOnHandlerThread(handler) { apfOffloadEngine.onOffloadServiceUpdated(updatedInfo1) }
+ verify(callback).onOffloadRulesUpdated(
+ eq(extractOffloadReplyRule(listOf(info2, updatedInfo1)))
+ )
+ visibleOnHandlerThread(handler) { apfOffloadEngine.onOffloadServiceRemoved(updatedInfo1) }
+ verify(callback).onOffloadRulesUpdated(eq(extractOffloadReplyRule(listOf(info2))))
+
+ visibleOnHandlerThread(handler) { apfOffloadEngine.unregisterOffloadEngine() }
+ verify(nsdManager).unregisterOffloadEngine(eq(apfOffloadEngine))
+ }
+
+ @Test
+ fun testCorruptedOffloadServiceInfoUpdateNotTriggerUpdate() {
+ val callback = mock(Callback::class.java)
+ val apfOffloadEngine = ApfMdnsOffloadEngine(interfaceName, handler, nsdManager, callback)
+ apfOffloadEngine.registerOffloadEngine()
+ val corruptedOffloadInfo = OffloadServiceInfo(
+ OffloadServiceInfo.Key("gambit", "_${"a".repeat(63)}._tcp"),
+ listOf(),
+ "Android_f47ac10b58cc4b88bc3f5e7a81e59872.local",
+ byteArrayOf(0x01, 0x02, 0x03, 0x04),
+ 0,
+ OffloadEngine.OFFLOAD_TYPE_REPLY.toLong()
+ )
+ visibleOnHandlerThread(handler) {
+ apfOffloadEngine.onOffloadServiceUpdated(corruptedOffloadInfo)
+ }
+ verify(callback, never()).onOffloadRulesUpdated(any())
+ }
+}
diff --git a/tests/unit/src/android/net/apf/ApfMdnsUtilsTest.kt b/tests/unit/src/android/net/apf/ApfMdnsUtilsTest.kt
index edf4f43..e8bbac1 100644
--- a/tests/unit/src/android/net/apf/ApfMdnsUtilsTest.kt
+++ b/tests/unit/src/android/net/apf/ApfMdnsUtilsTest.kt
@@ -32,7 +32,6 @@
import java.io.IOException
import kotlin.test.assertContentEquals
import kotlin.test.assertFailsWith
-import kotlin.test.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -49,7 +48,7 @@
private val testServiceName1 = "NsdChat"
private val testServiceName2 = "NsdCall"
- private val testServiceType = "_http._tcp.local"
+ private val testServiceType = "_http._tcp"
private val testSubType = "tsub"
private val testHostName = "Android.local"
private val testRawPacket1 = byteArrayOf(1, 2, 3, 4, 5)
@@ -91,17 +90,10 @@
0, 0).map { it.toByte() }.toByteArray()
@Test
- fun testExtractOffloadReplyRule_noPriorityReturnsEmptySet() {
- val info = createOffloadServiceInfo(Int.MAX_VALUE)
- val rules = extractOffloadReplyRule(listOf(info))
- assertTrue(rules.isEmpty())
- }
-
- @Test
- fun testExtractOffloadReplyRule_extractRulesWithValidPriority() {
+ fun testExtractOffloadReplyRule_extractRules() {
val info1 = createOffloadServiceInfo(10)
val info2 = createOffloadServiceInfo(
- 11,
+ Integer.MAX_VALUE,
testServiceName2,
listOf("a", "b", "c", "d"),
testRawPacket2
@@ -109,22 +101,36 @@
val rules = extractOffloadReplyRule(listOf(info2, info1))
val expectedResult = listOf(
MdnsOffloadRule(
+ "${info1.key.serviceName}.${info1.key.serviceType}",
listOf(
- MdnsOffloadRule.Matcher(encodedServiceType, TYPE_PTR),
- MdnsOffloadRule.Matcher(encodedServiceTypeWithSub1, TYPE_PTR),
- MdnsOffloadRule.Matcher(encodedFullServiceName1, TYPE_SRV),
- MdnsOffloadRule.Matcher(encodedFullServiceName1, TYPE_TXT),
- MdnsOffloadRule.Matcher(encodedTestHostName, TYPE_A),
- MdnsOffloadRule.Matcher(encodedTestHostName, TYPE_AAAA),
+ MdnsOffloadRule.Matcher(encodedServiceType, intArrayOf(TYPE_PTR)),
+ MdnsOffloadRule.Matcher(
+ encodedServiceTypeWithSub1,
+ intArrayOf(TYPE_PTR)
+ ),
+ MdnsOffloadRule.Matcher(
+ encodedFullServiceName1,
+ intArrayOf(TYPE_SRV, TYPE_TXT)
+ ),
+ MdnsOffloadRule.Matcher(
+ encodedTestHostName,
+ intArrayOf(TYPE_A, TYPE_AAAA)
+ ),
),
testRawPacket1,
),
MdnsOffloadRule(
+ "${info2.key.serviceName}.${info2.key.serviceType}",
listOf(
- MdnsOffloadRule.Matcher(encodedServiceTypeWithWildCard, TYPE_PTR),
- MdnsOffloadRule.Matcher(encodedFullServiceName2, TYPE_SRV),
- MdnsOffloadRule.Matcher(encodedFullServiceName2, TYPE_TXT),
+ MdnsOffloadRule.Matcher(
+ encodedServiceTypeWithWildCard,
+ intArrayOf(TYPE_PTR)
+ ),
+ MdnsOffloadRule.Matcher(
+ encodedFullServiceName2,
+ intArrayOf(TYPE_SRV, TYPE_TXT)
+ ),
),
null,
diff --git a/tests/unit/src/android/net/apf/ApfStandaloneTest.kt b/tests/unit/src/android/net/apf/ApfStandaloneTest.kt
index 2a918f8..21dc8fb 100644
--- a/tests/unit/src/android/net/apf/ApfStandaloneTest.kt
+++ b/tests/unit/src/android/net/apf/ApfStandaloneTest.kt
@@ -38,8 +38,10 @@
import com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION
import com.android.testutils.DevSdkIgnoreRunner
import kotlin.test.assertEquals
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
/**
* This class generate ApfStandaloneTest programs for side-loading into firmware without needing the
@@ -53,9 +55,20 @@
@SmallTest
class ApfStandaloneTest {
+ // Indicates which apfInterpreter to load.
+ @Parameterized.Parameter(0)
+ @JvmField
+ var apfInterpreterVersion: Int = ApfJniUtils.APF_INTERPRETER_VERSION_NEXT
+
private val etherTypeDenyList = listOf(0x88A2, 0x88A4, 0x88B8, 0x88CD, 0x88E1, 0x88E3)
private val ramSize = 1024
private val clampSize = 1024
+ private lateinit var apfTestHelpers: ApfTestHelpers
+
+ @Before
+ fun setUp() {
+ apfTestHelpers = ApfTestHelpers(apfInterpreterVersion)
+ }
fun runApfTest(isSuspendMode: Boolean) {
val program = generateApfV4Program(isSuspendMode)
@@ -78,7 +91,7 @@
val packetBadEtherType =
HexDump.hexStringToByteArray("ffffffffffff047bcb463fb588a201")
val dataRegion = ByteArray(Counter.totalSize()) { 0 }
- ApfTestHelpers.assertVerdict(
+ apfTestHelpers.assertVerdict(
APF_VERSION_4,
ApfTestHelpers.DROP,
program,
@@ -154,7 +167,7 @@
c0a801013204c0a80164ff
""".replace("\\s+".toRegex(), "").trim()
val dhcpRequestPkt = HexDump.hexStringToByteArray(dhcpRequestPktRawBytes)
- ApfTestHelpers.assertVerdict(
+ apfTestHelpers.assertVerdict(
APF_VERSION_4,
ApfTestHelpers.DROP,
program,
@@ -195,7 +208,7 @@
0000000000000000000000028500c81d00000000
""".replace("\\s+".toRegex(), "").trim()
val rsPkt = HexDump.hexStringToByteArray(rsPktRawBytes)
- ApfTestHelpers.assertVerdict(APF_VERSION_4, ApfTestHelpers.DROP, program, rsPkt, dataRegion)
+ apfTestHelpers.assertVerdict(APF_VERSION_4, ApfTestHelpers.DROP, program, rsPkt, dataRegion)
assertEquals(mapOf<Counter, Long>(
Counter.TOTAL_PACKETS to 3,
Counter.DROPPED_RS to 1,
@@ -238,7 +251,7 @@
00000000
""".replace("\\s+".toRegex(), "").trim()
val pingRequestPkt = HexDump.hexStringToByteArray(pingRequestPktRawBytes)
- ApfTestHelpers.assertVerdict(
+ apfTestHelpers.assertVerdict(
APF_VERSION_4,
ApfTestHelpers.DROP,
program,
@@ -266,12 +279,12 @@
}
private fun generateApfV4Program(isDeviceIdle: Boolean): ByteArray {
- val countAndPassLabel = "countAndPass"
- val countAndDropLabel = "countAndDrop"
- val endOfDhcpFilter = "endOfDhcpFilter"
- val endOfRsFilter = "endOfRsFiler"
- val endOfPingFilter = "endOfPingFilter"
val gen = ApfV4Generator(APF_VERSION_4, ramSize, clampSize)
+ val countAndPassLabel = gen.uniqueLabel
+ val countAndDropLabel = gen.uniqueLabel
+ val endOfDhcpFilter = gen.uniqueLabel
+ val endOfRsFilter = gen.uniqueLabel
+ val endOfPingFilter = gen.uniqueLabel
maybeSetupCounter(gen, Counter.TOTAL_PACKETS)
gen.addLoadData(R0, 0)
@@ -287,7 +300,7 @@
gen.addStoreData(R0, 0)
// ethtype filter
- gen.addLoad16(R0, ETHER_TYPE_OFFSET)
+ gen.addLoad16intoR0(ETHER_TYPE_OFFSET)
maybeSetupCounter(gen, Counter.DROPPED_ETHERTYPE_DENYLISTED)
for (p in etherTypeDenyList) {
gen.addJumpIfR0Equals(p.toLong(), countAndDropLabel)
@@ -296,22 +309,22 @@
// dhcp request filters
// Check IPv4
- gen.addLoad16(R0, ETHER_TYPE_OFFSET)
+ gen.addLoad16intoR0(ETHER_TYPE_OFFSET)
gen.addJumpIfR0NotEquals(ETH_P_IP.toLong(), endOfDhcpFilter)
// Pass DHCP addressed to us.
// Check src is IP is 0.0.0.0
- gen.addLoad32(R0, IPV4_SRC_ADDR_OFFSET)
+ gen.addLoad32intoR0(IPV4_SRC_ADDR_OFFSET)
gen.addJumpIfR0NotEquals(0, endOfDhcpFilter)
// Check dst ip is 255.255.255.255
- gen.addLoad32(R0, IPV4_DEST_ADDR_OFFSET)
+ gen.addLoad32intoR0(IPV4_DEST_ADDR_OFFSET)
gen.addJumpIfR0NotEquals(IPV4_BROADCAST_ADDRESS.toLong(), endOfDhcpFilter)
// Check it's UDP.
- gen.addLoad8(R0, IPV4_PROTOCOL_OFFSET)
+ gen.addLoad8intoR0(IPV4_PROTOCOL_OFFSET)
gen.addJumpIfR0NotEquals(OsConstants.IPPROTO_UDP.toLong(), endOfDhcpFilter)
// Check it's addressed to DHCP client port.
gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE)
- gen.addLoad16Indexed(R0, TCP_UDP_DESTINATION_PORT_OFFSET)
+ gen.addLoad16R1IndexedIntoR0(TCP_UDP_DESTINATION_PORT_OFFSET)
gen.addJumpIfR0NotEquals(DHCP_SERVER_PORT.toLong(), endOfDhcpFilter)
// drop dhcp the discovery and request
maybeSetupCounter(gen, Counter.DROPPED_DHCP_REQUEST_DISCOVERY)
@@ -322,13 +335,13 @@
// rs filters
// check IPv6
- gen.addLoad16(R0, ETHER_TYPE_OFFSET)
+ gen.addLoad16intoR0(ETHER_TYPE_OFFSET)
gen.addJumpIfR0NotEquals(OsConstants.ETH_P_IPV6.toLong(), endOfRsFilter)
// check ICMP6 packet
- gen.addLoad8(R0, IPV6_NEXT_HEADER_OFFSET)
+ gen.addLoad8intoR0(IPV6_NEXT_HEADER_OFFSET)
gen.addJumpIfR0NotEquals(IPPROTO_ICMPV6.toLong(), endOfRsFilter)
// check type it is RS
- gen.addLoad8(R0, ICMP6_TYPE_OFFSET)
+ gen.addLoad8intoR0(ICMP6_TYPE_OFFSET)
gen.addJumpIfR0NotEquals(ICMPV6_ROUTER_SOLICITATION.toLong(), endOfRsFilter)
// drop rs packet
maybeSetupCounter(gen, Counter.DROPPED_RS)
@@ -340,14 +353,14 @@
// ping filter
// Check IPv4
- gen.addLoad16(R0, ETHER_TYPE_OFFSET)
+ gen.addLoad16intoR0(ETHER_TYPE_OFFSET)
gen.addJumpIfR0NotEquals(ETH_P_IP.toLong(), endOfPingFilter)
// Check it's ICMP.
- gen.addLoad8(R0, IPV4_PROTOCOL_OFFSET)
+ gen.addLoad8intoR0(IPV4_PROTOCOL_OFFSET)
gen.addJumpIfR0NotEquals(OsConstants.IPPROTO_ICMP.toLong(), endOfPingFilter)
// Check if it is echo request
gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE)
- gen.addLoad8Indexed(R0, ETH_HEADER_LEN)
+ gen.addLoad8R1IndexedIntoR0(ETH_HEADER_LEN)
gen.addJumpIfR0NotEquals(8, endOfPingFilter)
// drop ping request
maybeSetupCounter(gen, Counter.DROPPED_ICMP4_ECHO_REQUEST)
@@ -438,5 +451,13 @@
companion object {
const val TAG = "ApfStandaloneTest"
+ @Parameterized.Parameters
+ @JvmStatic
+ fun data(): Iterable<Any?> {
+ return mutableListOf<Int?>(
+ ApfJniUtils.APF_INTERPRETER_VERSION_V6,
+ ApfJniUtils.APF_INTERPRETER_VERSION_NEXT
+ )
+ }
}
}
diff --git a/tests/unit/src/android/net/apf/ApfTest.java b/tests/unit/src/android/net/apf/ApfTest.java
index 9a4a224..64f3b11 100644
--- a/tests/unit/src/android/net/apf/ApfTest.java
+++ b/tests/unit/src/android/net/apf/ApfTest.java
@@ -18,7 +18,6 @@
import static android.net.apf.ApfCounterTracker.Counter.getCounterEnumFromOffset;
import static android.net.apf.ApfTestHelpers.TIMEOUT_MS;
-import static android.net.apf.ApfTestHelpers.consumeInstalledProgram;
import static android.net.apf.ApfTestHelpers.DROP;
import static android.net.apf.ApfTestHelpers.MIN_PKT_SIZE;
import static android.net.apf.ApfTestHelpers.PASS;
@@ -31,9 +30,6 @@
import static android.net.apf.BaseApfGenerator.PASS_LABEL;
import static android.net.apf.BaseApfGenerator.Register.R0;
import static android.net.apf.BaseApfGenerator.Register.R1;
-import static android.net.apf.ApfJniUtils.compareBpfApf;
-import static android.net.apf.ApfJniUtils.compileToBpf;
-import static android.net.apf.ApfJniUtils.dropsAllPackets;
import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
import static android.os.PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED;
import static android.system.OsConstants.AF_UNIX;
@@ -57,7 +53,6 @@
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
@@ -74,7 +69,6 @@
import android.net.apf.ApfCounterTracker.Counter;
import android.net.apf.ApfFilter.ApfConfiguration;
import android.net.apf.BaseApfGenerator.IllegalInstructionException;
-import android.net.ip.IpClient;
import android.net.metrics.IpConnectivityLog;
import android.os.Build;
import android.os.Handler;
@@ -177,13 +171,14 @@
@Mock private NetworkQuirkMetrics mNetworkQuirkMetrics;
@Mock private ApfSessionInfoMetrics mApfSessionInfoMetrics;
@Mock private IpClientRaInfoMetrics mIpClientRaInfoMetrics;
- @Mock private IpClient.IpClientCallbacksWrapper mIpClientCb;
+ @Mock private ApfFilter.IApfController mApfController;
@GuardedBy("mApfFilterCreated")
- private final ArrayList<AndroidPacketFilter> mApfFilterCreated = new ArrayList<>();
+ private final ArrayList<ApfFilter> mApfFilterCreated = new ArrayList<>();
private FileDescriptor mWriteSocket;
private HandlerThread mHandlerThread;
private Handler mHandler;
private long mCurrentTimeMs;
+ private ApfTestHelpers mApfTestHelpers;
@Before
public void setUp() throws Exception {
@@ -197,7 +192,7 @@
doReturn(readSocket).when(mDependencies).createPacketReaderSocket(anyInt());
mCurrentTimeMs = SystemClock.elapsedRealtime();
doReturn(mCurrentTimeMs).when(mDependencies).elapsedRealtime();
- doReturn(true).when(mIpClientCb).installPacketFilter(any());
+ doReturn(true).when(mApfController).installPacketFilter(any(), any());
doAnswer((invocation) -> {
synchronized (mApfFilterCreated) {
mApfFilterCreated.add(invocation.getArgument(0));
@@ -207,12 +202,13 @@
mHandlerThread = new HandlerThread("ApfTestThread");
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
+ mApfTestHelpers = new ApfTestHelpers(ApfJniUtils.APF_INTERPRETER_VERSION_V6);
}
private void shutdownApfFilters() throws Exception {
ConcurrentUtils.quitResources(THREAD_QUIT_MAX_RETRY_COUNT, () -> {
synchronized (mApfFilterCreated) {
- final ArrayList<AndroidPacketFilter> ret =
+ final ArrayList<ApfFilter> ret =
new ArrayList<>(mApfFilterCreated);
mApfFilterCreated.clear();
return ret;
@@ -279,58 +275,58 @@
}
private void assertPass(ApfV4Generator gen) throws ApfV4Generator.IllegalInstructionException {
- ApfTestHelpers.assertPass(mApfVersion, gen);
+ mApfTestHelpers.assertPass(mApfVersion, gen);
}
private void assertDrop(ApfV4Generator gen) throws ApfV4Generator.IllegalInstructionException {
- ApfTestHelpers.assertDrop(mApfVersion, gen);
+ mApfTestHelpers.assertDrop(mApfVersion, gen);
}
private void assertPass(byte[] program, byte[] packet) {
- ApfTestHelpers.assertPass(mApfVersion, program, packet);
+ mApfTestHelpers.assertPass(mApfVersion, program, packet);
}
private void assertDrop(byte[] program, byte[] packet) {
- ApfTestHelpers.assertDrop(mApfVersion, program, packet);
+ mApfTestHelpers.assertDrop(mApfVersion, program, packet);
}
private void assertPass(byte[] program, byte[] packet, int filterAge) {
- ApfTestHelpers.assertPass(mApfVersion, program, packet, filterAge);
+ mApfTestHelpers.assertPass(mApfVersion, program, packet, filterAge);
}
private void assertDrop(byte[] program, byte[] packet, int filterAge) {
- ApfTestHelpers.assertDrop(mApfVersion, program, packet, filterAge);
+ mApfTestHelpers.assertDrop(mApfVersion, program, packet, filterAge);
}
private void assertPass(ApfV4Generator gen, byte[] packet, int filterAge)
throws ApfV4Generator.IllegalInstructionException {
- ApfTestHelpers.assertPass(mApfVersion, gen, packet, filterAge);
+ mApfTestHelpers.assertPass(mApfVersion, gen, packet, filterAge);
}
private void assertDrop(ApfV4Generator gen, byte[] packet, int filterAge)
throws ApfV4Generator.IllegalInstructionException {
- ApfTestHelpers.assertDrop(mApfVersion, gen, packet, filterAge);
+ mApfTestHelpers.assertDrop(mApfVersion, gen, packet, filterAge);
}
private void assertDataMemoryContents(int expected, byte[] program, byte[] packet,
byte[] data, byte[] expectedData) throws Exception {
- ApfTestHelpers.assertDataMemoryContents(mApfVersion, expected, program, packet, data,
+ mApfTestHelpers.assertDataMemoryContents(mApfVersion, expected, program, packet, data,
expectedData, false /* ignoreInterpreterVersion */);
}
private void assertDataMemoryContentsIgnoreVersion(int expected, byte[] program,
byte[] packet, byte[] data, byte[] expectedData) throws Exception {
- ApfTestHelpers.assertDataMemoryContents(mApfVersion, expected, program, packet, data,
+ mApfTestHelpers.assertDataMemoryContents(mApfVersion, expected, program, packet, data,
expectedData, true /* ignoreInterpreterVersion */);
}
private void assertVerdict(String msg, int expected, byte[] program,
byte[] packet, int filterAge) {
- ApfTestHelpers.assertVerdict(mApfVersion, msg, expected, program, packet, filterAge);
+ mApfTestHelpers.assertVerdict(mApfVersion, msg, expected, program, packet, filterAge);
}
private void assertVerdict(int expected, byte[] program, byte[] packet) {
- ApfTestHelpers.assertVerdict(mApfVersion, expected, program, packet);
+ mApfTestHelpers.assertVerdict(mApfVersion, expected, program, packet);
}
/**
@@ -537,53 +533,53 @@
// Test byte load.
gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize);
- gen.addLoad8(R0, 1);
+ gen.addLoad8intoR0(1);
gen.addJumpIfR0Equals(45, DROP_LABEL);
assertDrop(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
// Test out of bounds load.
gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize);
- gen.addLoad8(R0, 16);
+ gen.addLoad8intoR0(16);
gen.addJumpIfR0Equals(0, DROP_LABEL);
assertPass(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
// Test half-word load.
gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize);
- gen.addLoad16(R0, 1);
+ gen.addLoad16intoR0(1);
gen.addJumpIfR0Equals((45 << 8) | 67, DROP_LABEL);
assertDrop(gen, new byte[]{123,45,67,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
// Test word load.
gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize);
- gen.addLoad32(R0, 1);
+ gen.addLoad32intoR0(1);
gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, DROP_LABEL);
assertDrop(gen, new byte[]{123,45,67,89,12,0,0,0,0,0,0,0,0,0,0}, 0);
// Test byte indexed load.
gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize);
gen.addLoadImmediate(R1, 1);
- gen.addLoad8Indexed(R0, 0);
+ gen.addLoad8R1IndexedIntoR0(0);
gen.addJumpIfR0Equals(45, DROP_LABEL);
assertDrop(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
// Test out of bounds indexed load.
gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize);
gen.addLoadImmediate(R1, 8);
- gen.addLoad8Indexed(R0, 8);
+ gen.addLoad8R1IndexedIntoR0(8);
gen.addJumpIfR0Equals(0, DROP_LABEL);
assertPass(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
// Test half-word indexed load.
gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize);
gen.addLoadImmediate(R1, 1);
- gen.addLoad16Indexed(R0, 0);
+ gen.addLoad16R1IndexedIntoR0(0);
gen.addJumpIfR0Equals((45 << 8) | 67, DROP_LABEL);
assertDrop(gen, new byte[]{123,45,67,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
// Test word indexed load.
gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize);
gen.addLoadImmediate(R1, 1);
- gen.addLoad32Indexed(R0, 0);
+ gen.addLoad32R1IndexedIntoR0(0);
gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, DROP_LABEL);
assertDrop(gen, new byte[]{123,45,67,89,12,0,0,0,0,0,0,0,0,0,0}, 0);
@@ -995,24 +991,6 @@
assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data);
}
- /**
- * Generate some BPF programs, translate them to APF, then run APF and BPF programs
- * over packet traces and verify both programs filter out the same packets.
- */
- @Test
- public void testApfAgainstBpf() throws Exception {
- String[] tcpdump_filters = new String[]{ "udp", "tcp", "icmp", "icmp6", "udp port 53",
- "arp", "dst 239.255.255.250", "arp or tcp or udp port 53", "net 192.168.1.0/24",
- "arp or icmp6 or portrange 53-54", "portrange 53-54 or portrange 100-50000",
- "tcp[tcpflags] & (tcp-ack|tcp-fin) != 0 and (ip[2:2] > 57 or icmp)" };
- String pcap_filename = stageFile(R.raw.apf);
- for (String tcpdump_filter : tcpdump_filters) {
- byte[] apf_program = Bpf2Apf.convert(compileToBpf(tcpdump_filter));
- assertTrue("Failed to match for filter: " + tcpdump_filter,
- compareBpfApf(mApfVersion, tcpdump_filter, pcap_filename, apf_program));
- }
- }
-
private void pretendPacketReceived(byte[] packet)
throws InterruptedIOException, ErrnoException {
Os.write(mWriteSocket, packet, 0, packet.length);
@@ -1022,7 +1000,7 @@
AtomicReference<ApfFilter> apfFilter = new AtomicReference<>();
mHandler.post(() ->
apfFilter.set(new ApfFilter(mHandler, mContext, config, TEST_PARAMS,
- mIpClientCb, mNetworkQuirkMetrics, mDependencies)));
+ mApfController, mNetworkQuirkMetrics, mDependencies)));
HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS);
return apfFilter.get();
}
@@ -1045,13 +1023,15 @@
config.multicastFilter = DROP_MULTICAST;
config.ieee802_3Filter = DROP_802_3_FRAMES;
final ApfFilter apfFilter = getApfFilter(config);
- consumeInstalledProgram(mIpClientCb, 2 /* installCnt */);
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 2 /* installCnt */);
apfFilter.setLinkProperties(lp);
- byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ byte[] program =
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
byte[] data = new byte[Counter.totalSize()];
final boolean result;
- result = dropsAllPackets(mApfVersion, program, data, pcapFilename);
+ result = mApfTestHelpers.dropsAllPackets(
+ mApfVersion, program, data, pcapFilename);
Log.i(TAG, "testApfFilterPcapFile(): Data counters: " + HexDump.toHexString(data, false));
assertTrue("Failed to drop all packets by filter. \nAPF counters:" +
@@ -1193,10 +1173,11 @@
ApfConfiguration config = getDefaultConfig();
config.multicastFilter = DROP_MULTICAST;
final ApfFilter apfFilter = getApfFilter(config);
- consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
apfFilter.setLinkProperties(lp);
- byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ byte[] program =
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
if (SdkLevel.isAtLeastV()) {
@@ -1248,7 +1229,8 @@
public void testApfFilterIPv6() throws Exception {
ApfConfiguration config = getDefaultConfig();
ApfFilter apfFilter = getApfFilter(config);
- byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ byte[] program =
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
// Verify empty IPv6 packet is passed
ByteBuffer packet = makeIpv6Packet(IPPROTO_UDP);
@@ -1475,178 +1457,6 @@
assertEquals(count, gen.generate().length);
}
- private ApfV4Generator generateDnsFilter(boolean ipv6, String... labels) throws Exception {
- ApfV4Generator gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize);
- gen.addLoadImmediate(R1, ipv6 ? IPV6_HEADER_LEN : IPV4_HEADER_LEN);
- DnsUtils.generateFilter(gen, labels);
- return gen;
- }
-
- private void doTestDnsParsing(boolean expectPass, boolean ipv6, String filterName,
- byte[] pkt) throws Exception {
- final String[] labels = filterName.split(/*regex=*/ "[.]");
- ApfV4Generator gen = generateDnsFilter(ipv6, labels);
-
- // Hack to prevent the APF instruction limit triggering.
- for (int i = 0; i < 500; i++) {
- gen.addNop();
- }
-
- byte[] program = gen.generate();
- Log.d(TAG, "prog_len=" + program.length);
- if (expectPass) {
- assertPass(program, pkt, 0);
- } else {
- assertDrop(program, pkt, 0);
- }
- }
-
- private void doTestDnsParsing(boolean expectPass, boolean ipv6, String filterName,
- String... packetNames) throws Exception {
- final byte[] pkt = ipv6 ? makeMdnsV6Packet(packetNames) : makeMdnsV4Packet(packetNames);
- doTestDnsParsing(expectPass, ipv6, filterName, pkt);
- }
-
- @Test
- public void testDnsParsing() throws Exception {
- final boolean ipv4 = false, ipv6 = true;
-
- // Packets with one question.
- // Names don't start with _ because DnsPacket thinks such names are invalid.
- doTestDnsParsing(true, ipv6, "googlecast.tcp.local", "googlecast.tcp.local");
- doTestDnsParsing(true, ipv4, "googlecast.tcp.local", "googlecast.tcp.local");
- doTestDnsParsing(false, ipv6, "googlecast.tcp.lozal", "googlecast.tcp.local");
- doTestDnsParsing(false, ipv4, "googlecast.tcp.lozal", "googlecast.tcp.local");
- doTestDnsParsing(false, ipv6, "googlecast.udp.local", "googlecast.tcp.local");
- doTestDnsParsing(false, ipv4, "googlecast.udp.local", "googlecast.tcp.local");
-
- // Packets with multiple questions that can't be compressed. Not realistic for MDNS since
- // everything ends in .local, but useful to ensure only the non-compression code is tested.
- doTestDnsParsing(true, ipv6, "googlecast.tcp.local",
- "googlecast.tcp.local", "developer.android.com");
- doTestDnsParsing(true, ipv4, "googlecast.tcp.local",
- "developer.android.com", "googlecast.tcp.local");
- doTestDnsParsing(false, ipv4, "googlecast.tcp.local",
- "developer.android.com", "googlecast.tcp.invalid");
- doTestDnsParsing(true, ipv6, "googlecast.tcp.local",
- "developer.android.com", "www.google.co.jp", "googlecast.tcp.local");
- doTestDnsParsing(false, ipv4, "veryverylongservicename.tcp.local",
- "www.google.co.jp", "veryverylongservicename.tcp.invalid");
- doTestDnsParsing(true, ipv6, "googlecast.tcp.local",
- "www.google.co.jp", "googlecast.tcp.local", "developer.android.com");
-
- // Name with duplicate labels.
- doTestDnsParsing(true, ipv6, "local.tcp.local", "local.tcp.local");
-
- final byte[] pkt = makeMdnsCompressedV6Packet();
- doTestDnsParsing(true, ipv6, "googlecast.tcp.local", pkt);
- doTestDnsParsing(true, ipv6, "matter.tcp.local", pkt);
- doTestDnsParsing(true, ipv6, "myservice.tcp.local", pkt);
- doTestDnsParsing(false, ipv6, "otherservice.tcp.local", pkt);
- }
-
- private void doTestDnsParsingProgramLength(int expectedLength,
- String filterName) throws Exception {
- final String[] labels = filterName.split(/*regex=*/ "[.]");
-
- ApfV4Generator gen = generateDnsFilter(/*ipv6=*/ true, labels);
- assertEquals("Program for " + filterName + " had unexpected length:",
- expectedLength, gen.generate().length);
- }
-
- /**
- * Rough metric of code size. Checks how large the generated filter is in various scenarios.
- * Helps ensure any changes to the code do not substantially increase APF code size.
- */
- @Test
- public void testDnsParsingProgramLength() throws Exception {
- doTestDnsParsingProgramLength(237, "MyDevice.local");
- doTestDnsParsingProgramLength(285, "_googlecast.tcp.local");
- doTestDnsParsingProgramLength(291, "_googlecast12345.tcp.local");
- doTestDnsParsingProgramLength(244, "_googlecastZtcp.local");
- doTestDnsParsingProgramLength(249, "_googlecastZtcp12345.local");
- }
-
- private void doTestDnsParsingNecessaryOverhead(int expectedNecessaryOverhead,
- String filterName, byte[] pkt, String description) throws Exception {
- final String[] labels = filterName.split(/*regex=*/ "[.]");
-
- // Check that the generated code, when the program contains the specified number of extra
- // bytes, is capable of dropping the packet.
- ApfV4Generator gen = generateDnsFilter(/*ipv6=*/ true, labels);
- for (int i = 0; i < expectedNecessaryOverhead; i++) {
- gen.addNop();
- }
- final byte[] programWithJustEnoughOverhead = gen.generate();
- assertVerdict(
- "Overhead too low: filter for " + filterName + " with " + expectedNecessaryOverhead
- + " extra instructions unexpectedly passed " + description,
- DROP, programWithJustEnoughOverhead, pkt, 0);
-
- if (expectedNecessaryOverhead == 0) return;
-
- // Check that the generated code, without the specified number of extra program bytes,
- // cannot correctly drop the packet because it hits the interpreter instruction limit.
- gen = generateDnsFilter(/*ipv6=*/ true, labels);
- for (int i = 0; i < expectedNecessaryOverhead - 1; i++) {
- gen.addNop();
- }
- final byte[] programWithNotEnoughOverhead = gen.generate();
-
- assertVerdict(
- "Overhead too high: filter for " + filterName + " with " + expectedNecessaryOverhead
- + " extra instructions unexpectedly dropped " + description,
- PASS, programWithNotEnoughOverhead, pkt, 0);
- }
-
- private void doTestDnsParsingNecessaryOverhead(int expectedNecessaryOverhead,
- String filterName, String... packetNames) throws Exception {
- doTestDnsParsingNecessaryOverhead(expectedNecessaryOverhead, filterName,
- makeMdnsV6Packet(packetNames),
- "IPv6 MDNS packet containing: " + Arrays.toString(packetNames));
- }
-
- /**
- * Rough metric of filter efficiency. Because the filter uses backwards jumps, on complex
- * packets it will not finish running before the interpreter hits the maximum number of allowed
- * instructions (== number of bytes in the program) and unconditionally accepts the packet.
- * This test checks much extra code the program must contain in order for the generated filter
- * to successfully drop the packet. It helps ensure any changes to the code do not reduce the
- * complexity of packets that the APF code can drop.
- */
- @Test
- public void testDnsParsingNecessaryOverhead() throws Exception {
- // Simple packets can be parsed with zero extra code.
- doTestDnsParsingNecessaryOverhead(0, "googlecast.tcp.local",
- "matter.tcp.local", "developer.android.com");
-
- doTestDnsParsingNecessaryOverhead(0, "googlecast.tcp.local",
- "developer.android.com", "matter.tcp.local");
-
- doTestDnsParsingNecessaryOverhead(0, "googlecast.tcp.local",
- "developer.android.com", "matter.tcp.local", "www.google.co.jp");
-
- doTestDnsParsingNecessaryOverhead(0, "googlecast.tcp.local",
- "developer.android.com", "matter.tcp.local", "www.google.co.jp",
- "example.org");
-
- // More complicated packets cause more instructions to be run and can only be dropped if
- // the program contains lots of extra code.
- doTestDnsParsingNecessaryOverhead(57, "googlecast.tcp.local",
- "developer.android.com", "matter.tcp.local", "www.google.co.jp",
- "example.org", "otherexample.net");
-
- doTestDnsParsingNecessaryOverhead(115, "googlecast.tcp.local",
- "developer.android.com", "matter.tcp.local", "www.google.co.jp",
- "example.org", "otherexample.net", "docs.new");
-
- doTestDnsParsingNecessaryOverhead(0, "foo.tcp.local",
- makeMdnsCompressedV6Packet(), "compressed packet");
-
- doTestDnsParsingNecessaryOverhead(235, "foo.tcp.local",
- makeMdnsCompressedV6PacketWithManyNames(), "compressed packet with many names");
- }
-
@Test
public void testApfFilterMulticast() throws Exception {
final byte[] unicastIpv4Addr = {(byte)192,0,2,63};
@@ -1661,10 +1471,11 @@
ApfConfiguration config = getDefaultConfig();
config.ieee802_3Filter = DROP_802_3_FRAMES;
final ApfFilter apfFilter = getApfFilter(config);
- consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
apfFilter.setLinkProperties(lp);
- byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ byte[] program =
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
// Construct IPv4 and IPv6 multicast packets.
ByteBuffer mcastv4packet = makeIpv4Packet(IPPROTO_UDP);
@@ -1699,7 +1510,7 @@
// Turn on multicast filter and verify it works
apfFilter.setMulticastFilter(true);
- program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
assertDrop(program, mcastv4packet.array());
assertDrop(program, mcastv6packet.array());
assertDrop(program, bcastv4packet1.array());
@@ -1708,7 +1519,7 @@
// Turn off multicast filter and verify it's off
apfFilter.setMulticastFilter(false);
- program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
assertPass(program, mcastv4packet.array());
assertPass(program, mcastv6packet.array());
assertPass(program, bcastv4packet1.array());
@@ -1718,11 +1529,11 @@
// Verify it can be initialized to on
config.multicastFilter = DROP_MULTICAST;
config.ieee802_3Filter = DROP_802_3_FRAMES;
- clearInvocations(mIpClientCb);
+ clearInvocations(mApfController);
final ApfFilter apfFilter2 = getApfFilter(config);
- consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
apfFilter2.setLinkProperties(lp);
- program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
assertDrop(program, mcastv4packet.array());
assertDrop(program, mcastv6packet.array());
assertDrop(program, bcastv4packet1.array());
@@ -1747,7 +1558,8 @@
private void doTestApfFilterMulticastPingWhileDozing(boolean isLightDozing) throws Exception {
final ApfConfiguration configuration = getDefaultConfig();
final ApfFilter apfFilter = getApfFilter(configuration);
- byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ byte[] program =
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
final ArgumentCaptor<BroadcastReceiver> receiverCaptor =
ArgumentCaptor.forClass(BroadcastReceiver.class);
verify(mDependencies).addDeviceIdleReceiver(receiverCaptor.capture());
@@ -1769,13 +1581,13 @@
doReturn(true).when(mPowerManager).isDeviceIdleMode();
receiver.onReceive(mContext, new Intent(ACTION_DEVICE_IDLE_MODE_CHANGED));
}
- program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
// ...and even while dozing...
assertPass(program, packet.array());
// ...but when the multicast filter is also enabled, drop the multicast pings to save power.
apfFilter.setMulticastFilter(true);
- program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
assertDrop(program, packet.array());
// However, we should still let through all other ICMPv6 types.
@@ -1794,7 +1606,7 @@
doReturn(false).when(mPowerManager).isDeviceIdleMode();
receiver.onReceive(mContext, new Intent(ACTION_DEVICE_IDLE_MODE_CHANGED));
}
- program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
assertPass(program, packet.array());
}
@@ -1803,7 +1615,8 @@
public void testApfFilter802_3() throws Exception {
ApfConfiguration config = getDefaultConfig();
ApfFilter apfFilter = getApfFilter(config);
- byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ byte[] program =
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
// Verify empty packet of 100 zero bytes is passed
// Note that eth-type = 0 makes it an IEEE802.3 frame
@@ -1821,7 +1634,7 @@
// Now turn on the filter
config.ieee802_3Filter = DROP_802_3_FRAMES;
apfFilter = getApfFilter(config);
- program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
// Verify that IEEE802.3 frame is dropped
// In this case ethtype is used for payload length
@@ -1846,7 +1659,8 @@
ApfConfiguration config = getDefaultConfig();
ApfFilter apfFilter = getApfFilter(config);
- byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ byte[] program =
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
// Verify empty packet of 100 zero bytes is passed
// Note that eth-type = 0 makes it an IEEE802.3 frame
@@ -1864,7 +1678,7 @@
// Now add IPv4 to the black list
config.ethTypeBlackList = ipv4BlackList;
apfFilter = getApfFilter(config);
- program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
// Verify that IPv4 frame will be dropped
setIpv4VersionFields(packet);
@@ -1877,7 +1691,7 @@
// Now let us have both IPv4 and IPv6 in the black list
config.ethTypeBlackList = ipv4Ipv6BlackList;
apfFilter = getApfFilter(config);
- program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
// Verify that IPv4 frame will be dropped
setIpv4VersionFields(packet);
@@ -1915,7 +1729,8 @@
config.multicastFilter = DROP_MULTICAST;
config.ieee802_3Filter = DROP_802_3_FRAMES;
ApfFilter apfFilter = getApfFilter(config);
- byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ byte[] program =
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
// Verify initially ARP request filter is off, and GARP filter is on.
verifyArpFilter(program, PASS);
@@ -1925,11 +1740,11 @@
LinkProperties lp = new LinkProperties();
assertTrue(lp.addLinkAddress(linkAddress));
apfFilter.setLinkProperties(lp);
- program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
verifyArpFilter(program, DROP);
apfFilter.setLinkProperties(new LinkProperties());
- program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
// Inform ApfFilter of loss of IP and verify ARP filtering is off
verifyArpFilter(program, PASS);
}
@@ -2197,19 +2012,20 @@
private byte[] verifyRaLifetime(ByteBuffer packet, int lifetime)
throws IOException, ErrnoException {
// Verify new program generated if ApfFilter witnesses RA
- clearInvocations(mIpClientCb);
+ clearInvocations(mApfController);
pretendPacketReceived(packet.array());
- byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ byte[] program =
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
verifyRaLifetime(program, packet, lifetime);
return program;
}
private void assertInvalidRa(ByteBuffer packet)
throws IOException, ErrnoException, InterruptedException {
- clearInvocations(mIpClientCb);
+ clearInvocations(mApfController);
pretendPacketReceived(packet.array());
Thread.sleep(NO_CALLBACK_TIMEOUT_MS);
- verify(mIpClientCb, never()).installPacketFilter(any());
+ verify(mApfController, never()).installPacketFilter(any(), any());
}
@Test
@@ -2218,7 +2034,8 @@
config.multicastFilter = DROP_MULTICAST;
config.ieee802_3Filter = DROP_802_3_FRAMES;
ApfFilter apfFilter = getApfFilter(config);
- byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ byte[] program =
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
final int ROUTER_LIFETIME = 1000;
final int PREFIX_VALID_LIFETIME = 200;
@@ -2302,7 +2119,8 @@
config.multicastFilter = DROP_MULTICAST;
config.ieee802_3Filter = DROP_802_3_FRAMES;
final ApfFilter apfFilter = getApfFilter(config);
- byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ byte[] program =
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
final int RA_REACHABLE_TIME = 1800;
final int RA_RETRANSMISSION_TIMER = 1234;
@@ -2317,7 +2135,7 @@
// Assume apf is shown the given RA, it generates program to filter it.
pretendPacketReceived(raPacket);
- program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
assertDrop(program, raPacket);
// A packet with different reachable time should be passed.
@@ -2342,7 +2160,7 @@
config.multicastFilter = DROP_MULTICAST;
config.ieee802_3Filter = DROP_802_3_FRAMES;
final ApfFilter apfFilter = getApfFilter(config);
- consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
final int routerLifetime = 1000;
final int timePassedSeconds = 12;
@@ -2357,7 +2175,8 @@
synchronized (apfFilter) {
apfFilter.installNewProgram();
}
- byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ byte[] program =
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
verifyRaLifetime(program, basePacket, routerLifetime, timePassedSeconds);
// Packet should be passed if the program is installed after 1/6 * lifetime from last seen
@@ -2367,7 +2186,7 @@
synchronized (apfFilter) {
apfFilter.installNewProgram();
}
- program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
assertDrop(program, basePacket.array());
mCurrentTimeMs += DateUtils.SECOND_IN_MILLIS;
@@ -2375,7 +2194,7 @@
synchronized (apfFilter) {
apfFilter.installNewProgram();
}
- program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
assertPass(program, basePacket.array());
}
@@ -2448,12 +2267,13 @@
@Test
public void testMatchedRaUpdatesLifetime() throws Exception {
final ApfFilter apfFilter = getApfFilter(getDefaultConfig());
- consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
// Create an RA and build an APF program
byte[] ra = new RaPacketBuilder(1800 /* router lifetime */).build();
pretendPacketReceived(ra);
- byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ byte[] program =
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
// lifetime dropped significantly, assert pass
ra = new RaPacketBuilder(200 /* router lifetime */).build();
@@ -2461,7 +2281,7 @@
// update program with the new RA
pretendPacketReceived(ra);
- program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
// assert program was updated and new lifetimes were taken into account.
assertDrop(program, ra);
@@ -2472,7 +2292,7 @@
final ApfConfiguration config = getDefaultConfig();
config.acceptRaMinLft = 180;
ApfFilter apfFilter = getApfFilter(config);
- consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
// Template packet:
// Frame 1: 150 bytes on wire (1200 bits), 150 bytes captured (1200 bits)
// Ethernet II, Src: Netgear_23:67:2c (28:c6:8e:23:67:2c), Dst: IPv6mcast_01 (33:33:00:00:00:01)
@@ -2524,7 +2344,7 @@
String.format(packetStringFmt, lifetime + lifetime));
// feed the RA into APF and generate the filter, the filter shouldn't crash.
pretendPacketReceived(ra);
- consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
}
}
@@ -2536,7 +2356,7 @@
final ApfConfiguration config = getDefaultConfig();
config.acceptRaMinLft = 180;
final ApfFilter apfFilter = getApfFilter(config);
- consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
// Create an initial RA and build an APF program
byte[] ra = new RaPacketBuilder(1800 /* router lifetime */)
@@ -2544,7 +2364,8 @@
.build();
pretendPacketReceived(ra);
- byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ byte[] program =
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
// repeated RA is dropped
assertDrop(program, ra);
@@ -2564,7 +2385,7 @@
final ApfConfiguration config = getDefaultConfig();
config.acceptRaMinLft = 180;
final ApfFilter apfFilter = getApfFilter(config);
- consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
// Create an initial RA and build an APF program
byte[] ra = new RaPacketBuilder(1800 /* router lifetime */)
@@ -2572,7 +2393,8 @@
.build();
pretendPacketReceived(ra);
- byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ byte[] program =
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
// repeated RA is dropped
assertDrop(program, ra);
@@ -2599,13 +2421,14 @@
final ApfConfiguration config = getDefaultConfig();
config.acceptRaMinLft = 180;
final ApfFilter apfFilter = getApfFilter(config);
- consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
// Create an initial RA and build an APF program
byte[] ra = new RaPacketBuilder(0 /* router lifetime */).build();
pretendPacketReceived(ra);
- byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ byte[] program =
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
// repeated RA is dropped
assertDrop(program, ra);
@@ -2627,13 +2450,14 @@
final ApfConfiguration config = getDefaultConfig();
config.acceptRaMinLft = 180;
final ApfFilter apfFilter = getApfFilter(config);
- consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
// Create an initial RA and build an APF program
byte[] ra = new RaPacketBuilder(100 /* router lifetime */).build();
pretendPacketReceived(ra);
- byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ byte[] program =
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
// repeated RA is dropped
assertDrop(program, ra);
@@ -2663,13 +2487,14 @@
final ApfConfiguration config = getDefaultConfig();
config.acceptRaMinLft = 180;
final ApfFilter apfFilter = getApfFilter(config);
- consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
// Create an initial RA and build an APF program
byte[] ra = new RaPacketBuilder(200 /* router lifetime */).build();
pretendPacketReceived(ra);
- byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ byte[] program =
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
// repeated RA is dropped
assertDrop(program, ra);
@@ -2695,13 +2520,14 @@
final ApfConfiguration config = getDefaultConfig();
config.acceptRaMinLft = 180;
final ApfFilter apfFilter = getApfFilter(config);
- consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
// Create an initial RA and build an APF program
byte[] ra = new RaPacketBuilder(1800 /* router lifetime */).build();
pretendPacketReceived(ra);
- byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ byte[] program =
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
// repeated RA is dropped
assertDrop(program, ra);
@@ -2733,12 +2559,13 @@
final ApfConfiguration config = getDefaultConfig();
config.acceptRaMinLft = 180;
final ApfFilter apfFilter = getApfFilter(config);
- consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
// Create an initial RA and build an APF program
byte[] ra = new RaPacketBuilder(1800 /* router lifetime */).build();
pretendPacketReceived(ra);
- byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ byte[] program =
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
// repeated RA is dropped.
assertDrop(program, ra);
@@ -2747,37 +2574,37 @@
ra = new RaPacketBuilder(599 /* router lifetime */).build();
assertPass(program, ra);
pretendPacketReceived(ra);
- program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
assertDrop(program, ra);
ra = new RaPacketBuilder(180 /* router lifetime */).build();
assertPass(program, ra);
pretendPacketReceived(ra);
- program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
assertDrop(program, ra);
ra = new RaPacketBuilder(0 /* router lifetime */).build();
assertPass(program, ra);
pretendPacketReceived(ra);
- program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
assertDrop(program, ra);
ra = new RaPacketBuilder(180 /* router lifetime */).build();
assertPass(program, ra);
pretendPacketReceived(ra);
- program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
assertDrop(program, ra);
ra = new RaPacketBuilder(599 /* router lifetime */).build();
assertPass(program, ra);
pretendPacketReceived(ra);
- program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
assertDrop(program, ra);
ra = new RaPacketBuilder(1800 /* router lifetime */).build();
assertPass(program, ra);
pretendPacketReceived(ra);
- program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
assertDrop(program, ra);
}
@@ -2802,7 +2629,7 @@
@Test
public void testInstallPacketFilterFailure() throws Exception {
- doReturn(false).when(mIpClientCb).installPacketFilter(any());
+ doReturn(false).when(mApfController).installPacketFilter(any(), any());
final ApfConfiguration config = getDefaultConfig();
final ApfFilter apfFilter = getApfFilter(config);
@@ -2821,33 +2648,14 @@
public void testApfProgramOverSize() throws Exception {
final ApfConfiguration config = getDefaultConfig();
config.apfVersionSupported = 2;
- config.apfRamSize = 512;
+ config.apfRamSize = 256;
final ApfFilter apfFilter = getApfFilter(config);
- consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
- final byte[] ra = buildLargeRa();
- pretendPacketReceived(ra);
- // The generated program size will be 529, which is larger than 512
- consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_OVER_SIZE_FAILURE);
verify(mNetworkQuirkMetrics).statsWrite();
}
@Test
- public void testGenerateApfProgramException() {
- final ApfConfiguration config = getDefaultConfig();
- ApfFilter apfFilter = getApfFilter(config);
- // Simulate exception during installNewProgram() by mocking
- // mDependencies.elapsedRealtime() to throw an exception (this method doesn't throw in
- // real-world scenarios).
- doThrow(new IllegalStateException("test exception")).when(mDependencies).elapsedRealtime();
- synchronized (apfFilter) {
- apfFilter.installNewProgram();
- }
- verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_GENERATE_FILTER_EXCEPTION);
- verify(mNetworkQuirkMetrics).statsWrite();
- }
-
- @Test
public void testApfSessionInfoMetrics() throws Exception {
final ApfConfiguration config = getDefaultConfig();
config.apfVersionSupported = 4;
@@ -2856,7 +2664,8 @@
final long durationTimeMs = config.minMetricsSessionDurationMs;
doReturn(startTimeMs).when(mDependencies).elapsedRealtime();
final ApfFilter apfFilter = getApfFilter(config);
- byte[] program = consumeInstalledProgram(mIpClientCb, 2 /* installCnt */);
+ byte[] program =
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 2 /* installCnt */);
int maxProgramSize = 0;
int numProgramUpdated = 0;
maxProgramSize = Math.max(maxProgramSize, program.length);
@@ -2876,13 +2685,13 @@
expectedData[passedIpv6IcmpCounterIdx + 3] += 1;
assertDataMemoryContentsIgnoreVersion(PASS, program, ra, data, expectedData);
pretendPacketReceived(ra);
- program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
maxProgramSize = Math.max(maxProgramSize, program.length);
numProgramUpdated++;
apfFilter.setMulticastFilter(true);
// setMulticastFilter will trigger program installation.
- program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ program = mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
maxProgramSize = Math.max(maxProgramSize, program.length);
numProgramUpdated++;
@@ -2935,7 +2744,7 @@
final long durationTimeMs = config.minMetricsSessionDurationMs;
doReturn(startTimeMs).when(mDependencies).elapsedRealtime();
final ApfFilter apfFilter = getApfFilter(config);
- consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
final int routerLifetime = 1000;
final int prefixValidLifetime = 200;
@@ -2973,20 +2782,20 @@
// Inject RA packets. Calling assertProgramUpdateAndGet()/assertNoProgramUpdate() is to make
// sure that the RA packet has been processed.
pretendPacketReceived(ra1.build());
- consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
pretendPacketReceived(ra2.build());
- consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
pretendPacketReceived(raInvalid.build());
Thread.sleep(NO_CALLBACK_TIMEOUT_MS);
- verify(mIpClientCb, never()).installPacketFilter(any());
+ verify(mApfController, never()).installPacketFilter(any(), any());
pretendPacketReceived(raZeroRouterLifetime.build());
- consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
pretendPacketReceived(raZeroPioValidLifetime.build());
- consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
pretendPacketReceived(raZeroRdnssLifetime.build());
- consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
pretendPacketReceived(raZeroRioRouteLifetime.build());
- consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+ mApfTestHelpers.consumeInstalledProgram(mApfController, 1 /* installCnt */);
// Write metrics data to statsd pipeline when shutdown.
doReturn(startTimeMs + durationTimeMs).when(mDependencies).elapsedRealtime();
@@ -3058,141 +2867,141 @@
gen.addLoadData(R0, 0);
gen.addAdd(1);
gen.addStoreData(R0, 0);
- gen.addLoad16(R0, 12);
+ gen.addLoad16intoR0(12);
gen.addLoadImmediate(R1, -108);
- gen.addJumpIfR0LessThan(0x600, "LABEL_504");
+ gen.addJumpIfR0LessThan(0x600, (short) -504);
gen.addLoadImmediate(R1, -112);
- gen.addJumpIfR0Equals(0x88a2, "LABEL_504");
- gen.addJumpIfR0Equals(0x88a4, "LABEL_504");
- gen.addJumpIfR0Equals(0x88b8, "LABEL_504");
- gen.addJumpIfR0Equals(0x88cd, "LABEL_504");
- gen.addJumpIfR0Equals(0x88e1, "LABEL_504");
- gen.addJumpIfR0Equals(0x88e3, "LABEL_504");
- gen.addJumpIfR0NotEquals(0x806, "LABEL_116");
+ gen.addJumpIfR0Equals(0x88a2, (short) -504);
+ gen.addJumpIfR0Equals(0x88a4, (short) -504);
+ gen.addJumpIfR0Equals(0x88b8, (short) -504);
+ gen.addJumpIfR0Equals(0x88cd, (short) -504);
+ gen.addJumpIfR0Equals(0x88e1, (short) -504);
+ gen.addJumpIfR0Equals(0x88e3, (short) -504);
+ gen.addJumpIfR0NotEquals(0x806, (short) -116);
gen.addLoadImmediate(R0, 14);
gen.addLoadImmediate(R1, -36);
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("000108000604"), "LABEL_498");
- gen.addLoad16(R0, 20);
- gen.addJumpIfR0Equals(0x1, "LABEL_102");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("000108000604"), (short) -498);
+ gen.addLoad16intoR0(20);
+ gen.addJumpIfR0Equals(0x1, (short) -102);
gen.addLoadImmediate(R1, -40);
- gen.addJumpIfR0NotEquals(0x2, "LABEL_498");
- gen.addLoad32(R0, 28);
+ gen.addJumpIfR0NotEquals(0x2, (short) -498);
+ gen.addLoad32intoR0(28);
gen.addLoadImmediate(R1, -116);
- gen.addJumpIfR0Equals(0x0, "LABEL_504");
+ gen.addJumpIfR0Equals(0x0, (short) -504);
gen.addLoadImmediate(R0, 0);
gen.addLoadImmediate(R1, -44);
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), "LABEL_498");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), (short) -498);
- gen.defineLabel("LABEL_102");
- gen.addLoad32(R0, 38);
+ gen.defineLabel((short) -102);
+ gen.addLoad32intoR0(38);
gen.addLoadImmediate(R1, -64);
- gen.addJumpIfR0Equals(0x0, "LABEL_504");
+ gen.addJumpIfR0Equals(0x0, (short) -504);
gen.addLoadImmediate(R1, -8);
- gen.addJump("LABEL_498");
+ gen.addJump((short) -498);
- gen.defineLabel("LABEL_116");
- gen.addLoad16(R0, 12);
- gen.addJumpIfR0NotEquals(0x800, "LABEL_207");
- gen.addLoad8(R0, 23);
- gen.addJumpIfR0NotEquals(0x11, "LABEL_159");
- gen.addLoad16(R0, 20);
- gen.addJumpIfR0AnyBitsSet(0x1fff, "LABEL_159");
+ gen.defineLabel((short) -116);
+ gen.addLoad16intoR0(12);
+ gen.addJumpIfR0NotEquals(0x800, (short) -207);
+ gen.addLoad8intoR0(23);
+ gen.addJumpIfR0NotEquals(0x11, (short) -159);
+ gen.addLoad16intoR0(20);
+ gen.addJumpIfR0AnyBitsSet(0x1fff, (short) -159);
gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE);
- gen.addLoad16Indexed(R0, 16);
- gen.addJumpIfR0NotEquals(0x44, "LABEL_159");
+ gen.addLoad16R1IndexedIntoR0(16);
+ gen.addJumpIfR0NotEquals(0x44, (short) -159);
gen.addLoadImmediate(R0, 50);
gen.addAddR1ToR0();
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("e212507c6345"), "LABEL_159");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("e212507c6345"), (short) -159);
gen.addLoadImmediate(R1, -12);
- gen.addJump("LABEL_498");
+ gen.addJump((short) -498);
- gen.defineLabel("LABEL_159");
- gen.addLoad8(R0, 30);
+ gen.defineLabel((short) -159);
+ gen.addLoad8intoR0(30);
gen.addAnd(240);
gen.addLoadImmediate(R1, -84);
- gen.addJumpIfR0Equals(0xe0, "LABEL_504");
+ gen.addJumpIfR0Equals(0xe0, (short) -504);
gen.addLoadImmediate(R1, -76);
- gen.addLoad32(R0, 30);
- gen.addJumpIfR0Equals(0xffffffff, "LABEL_504");
+ gen.addLoad32intoR0(30);
+ gen.addJumpIfR0Equals(0xffffffff, (short) -504);
gen.addLoadImmediate(R1, -24);
gen.addLoadImmediate(R0, 0);
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), "LABEL_498");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), (short) -498);
gen.addLoadImmediate(R1, -72);
- gen.addJump("LABEL_504");
+ gen.addJump((short) -504);
gen.addLoadImmediate(R1, -16);
- gen.addJump("LABEL_498");
+ gen.addJump((short) -498);
- gen.defineLabel("LABEL_207");
- gen.addJumpIfR0Equals(0x86dd, "LABEL_231");
+ gen.defineLabel((short) -207);
+ gen.addJumpIfR0Equals(0x86dd, (short) -231);
gen.addLoadImmediate(R0, 0);
gen.addLoadImmediate(R1, -48);
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), "LABEL_498");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), (short) -498);
gen.addLoadImmediate(R1, -56);
- gen.addJump("LABEL_504");
+ gen.addJump((short) -504);
- gen.defineLabel("LABEL_231");
- gen.addLoad8(R0, 20);
- gen.addJumpIfR0Equals(0x3a, "LABEL_249");
+ gen.defineLabel((short) -231);
+ gen.addLoad8intoR0(20);
+ gen.addJumpIfR0Equals(0x3a, (short) -249);
gen.addLoadImmediate(R1, -104);
- gen.addLoad8(R0, 38);
- gen.addJumpIfR0Equals(0xff, "LABEL_504");
+ gen.addLoad8intoR0(38);
+ gen.addJumpIfR0Equals(0xff, (short) -504);
gen.addLoadImmediate(R1, -32);
- gen.addJump("LABEL_498");
+ gen.addJump((short) -498);
- gen.defineLabel("LABEL_249");
- gen.addLoad8(R0, 54);
+ gen.defineLabel((short) -249);
+ gen.addLoad8intoR0(54);
gen.addLoadImmediate(R1, -88);
- gen.addJumpIfR0Equals(0x85, "LABEL_504");
- gen.addJumpIfR0NotEquals(0x88, "LABEL_283");
+ gen.addJumpIfR0Equals(0x85, (short) -504);
+ gen.addJumpIfR0NotEquals(0x88, (short) -283);
gen.addLoadImmediate(R0, 38);
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ff0200000000000000000000000000"), "LABEL_283");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ff0200000000000000000000000000"), (short) -283);
gen.addLoadImmediate(R1, -92);
- gen.addJump("LABEL_504");
+ gen.addJump((short) -504);
- gen.defineLabel("LABEL_283");
+ gen.defineLabel((short) -283);
gen.addLoadFromMemory(R0, MemorySlot.PACKET_SIZE);
- gen.addJumpIfR0NotEquals(0xa6, "LABEL_496");
+ gen.addJumpIfR0NotEquals(0xa6, (short) -496);
gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_SECONDS);
- gen.addJumpIfR0GreaterThan(0x254, "LABEL_496");
+ gen.addJumpIfR0GreaterThan(0x254, (short) -496);
gen.addLoadImmediate(R0, 0);
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("e212507c6345648788fd6df086dd68"), "LABEL_496");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("e212507c6345648788fd6df086dd68"), (short) -496);
gen.addLoadImmediate(R0, 18);
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("00703afffe800000000000002a0079e10abc1539fe80000000000000e01250fffe7c63458600"), "LABEL_496");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("00703afffe800000000000002a0079e10abc1539fe80000000000000e01250fffe7c63458600"), (short) -496);
gen.addLoadImmediate(R0, 58);
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("4000"), "LABEL_496");
- gen.addLoad16(R0, 60);
- gen.addJumpIfR0LessThan(0x254, "LABEL_496");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("4000"), (short) -496);
+ gen.addLoad16intoR0(60);
+ gen.addJumpIfR0LessThan(0x254, (short) -496);
gen.addLoadImmediate(R0, 62);
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("0000000000000000"), "LABEL_496");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("0000000000000000"), (short) -496);
gen.addLoadImmediate(R0, 78);
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("19050000"), "LABEL_496");
- gen.addLoad32(R0, 82);
- gen.addJumpIfR0LessThan(0x254, "LABEL_496");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("19050000"), (short) -496);
+ gen.addLoad32intoR0(82);
+ gen.addJumpIfR0LessThan(0x254, (short) -496);
gen.addLoadImmediate(R0, 86);
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("2001486048600000000000000000646420014860486000000000000000000064"), "LABEL_496");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("2001486048600000000000000000646420014860486000000000000000000064"), (short) -496);
gen.addLoadImmediate(R0, 118);
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("030440c0"), "LABEL_496");
- gen.addLoad32(R0, 122);
- gen.addJumpIfR0LessThan(0x254, "LABEL_496");
- gen.addLoad32(R0, 126);
- gen.addJumpIfR0LessThan(0x254, "LABEL_496");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("030440c0"), (short) -496);
+ gen.addLoad32intoR0(122);
+ gen.addJumpIfR0LessThan(0x254, (short) -496);
+ gen.addLoad32intoR0(126);
+ gen.addJumpIfR0LessThan(0x254, (short) -496);
gen.addLoadImmediate(R0, 130);
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("00000000"), "LABEL_496");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("00000000"), (short) -496);
gen.addLoadImmediate(R0, 134);
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("2a0079e10abc15390000000000000000"), "LABEL_496");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("2a0079e10abc15390000000000000000"), (short) -496);
gen.addLoadImmediate(R1, -60);
- gen.addJump("LABEL_504");
+ gen.addJump((short) -504);
- gen.defineLabel("LABEL_496");
+ gen.defineLabel((short) -496);
gen.addLoadImmediate(R1, -28);
- gen.defineLabel("LABEL_498");
+ gen.defineLabel((short) -498);
gen.addLoadData(R0, 0);
gen.addAdd(1);
gen.addStoreData(R0, 0);
gen.addJump(PASS_LABEL);
- gen.defineLabel("LABEL_504");
+ gen.defineLabel((short) -504);
gen.addLoadData(R0, 0);
gen.addAdd(1);
gen.addStoreData(R0, 0);
@@ -3212,109 +3021,109 @@
gen.addLoadData(R0, 0);
gen.addAdd(1);
gen.addStoreData(R0, 0);
- gen.addLoad16(R0, 12);
+ gen.addLoad16intoR0(12);
gen.addLoadImmediate(R1, -108);
- gen.addJumpIfR0LessThan(0x600, "LABEL_283");
+ gen.addJumpIfR0LessThan(0x600, (short) -283);
gen.addLoadImmediate(R1, -112);
- gen.addJumpIfR0Equals(0x88a2, "LABEL_283");
- gen.addJumpIfR0Equals(0x88a4, "LABEL_283");
- gen.addJumpIfR0Equals(0x88b8, "LABEL_283");
- gen.addJumpIfR0Equals(0x88cd, "LABEL_283");
- gen.addJumpIfR0Equals(0x88e1, "LABEL_283");
- gen.addJumpIfR0Equals(0x88e3, "LABEL_283");
- gen.addJumpIfR0NotEquals(0x806, "LABEL_109");
+ gen.addJumpIfR0Equals(0x88a2, (short) -283);
+ gen.addJumpIfR0Equals(0x88a4, (short) -283);
+ gen.addJumpIfR0Equals(0x88b8, (short) -283);
+ gen.addJumpIfR0Equals(0x88cd, (short) -283);
+ gen.addJumpIfR0Equals(0x88e1, (short) -283);
+ gen.addJumpIfR0Equals(0x88e3, (short) -283);
+ gen.addJumpIfR0NotEquals(0x806, (short) -109);
gen.addLoadImmediate(R0, 14);
gen.addLoadImmediate(R1, -36);
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("000108000604"), "LABEL_277");
- gen.addLoad16(R0, 20);
- gen.addJumpIfR0Equals(0x1, "LABEL_94");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("000108000604"), (short) -277);
+ gen.addLoad16intoR0(20);
+ gen.addJumpIfR0Equals(0x1, (short) -94);
gen.addLoadImmediate(R1, -40);
- gen.addJumpIfR0NotEquals(0x2, "LABEL_277");
- gen.addLoad32(R0, 28);
+ gen.addJumpIfR0NotEquals(0x2, (short) -277);
+ gen.addLoad32intoR0(28);
gen.addLoadImmediate(R1, -116);
- gen.addJumpIfR0Equals(0x0, "LABEL_283");
+ gen.addJumpIfR0Equals(0x0, (short) -283);
gen.addLoadImmediate(R0, 0);
gen.addLoadImmediate(R1, -44);
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), "LABEL_277");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), (short) -277);
- gen.defineLabel("LABEL_94");
+ gen.defineLabel((short) -94);
gen.addLoadImmediate(R0, 38);
gen.addLoadImmediate(R1, -68);
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("c0a801b3"), "LABEL_283");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("c0a801b3"), (short) -283);
gen.addLoadImmediate(R1, -8);
- gen.addJump("LABEL_277");
+ gen.addJump((short) -277);
- gen.defineLabel("LABEL_109");
- gen.addLoad16(R0, 12);
- gen.addJumpIfR0NotEquals(0x800, "LABEL_204");
- gen.addLoad8(R0, 23);
- gen.addJumpIfR0NotEquals(0x11, "LABEL_151");
- gen.addLoad16(R0, 20);
- gen.addJumpIfR0AnyBitsSet(0x1fff, "LABEL_151");
+ gen.defineLabel((short) -109);
+ gen.addLoad16intoR0(12);
+ gen.addJumpIfR0NotEquals(0x800, (short) -204);
+ gen.addLoad8intoR0(23);
+ gen.addJumpIfR0NotEquals(0x11, (short) -151);
+ gen.addLoad16intoR0(20);
+ gen.addJumpIfR0AnyBitsSet(0x1fff, (short) -151);
gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE);
- gen.addLoad16Indexed(R0, 16);
- gen.addJumpIfR0NotEquals(0x44, "LABEL_151");
+ gen.addLoad16R1IndexedIntoR0(16);
+ gen.addJumpIfR0NotEquals(0x44, (short) -151);
gen.addLoadImmediate(R0, 50);
gen.addAddR1ToR0();
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("f683d58f832b"), "LABEL_151");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("f683d58f832b"), (short) -151);
gen.addLoadImmediate(R1, -12);
- gen.addJump("LABEL_277");
+ gen.addJump((short) -277);
- gen.defineLabel("LABEL_151");
- gen.addLoad8(R0, 30);
+ gen.defineLabel((short) -151);
+ gen.addLoad8intoR0(30);
gen.addAnd(240);
gen.addLoadImmediate(R1, -84);
- gen.addJumpIfR0Equals(0xe0, "LABEL_283");
+ gen.addJumpIfR0Equals(0xe0, (short) -283);
gen.addLoadImmediate(R1, -76);
- gen.addLoad32(R0, 30);
- gen.addJumpIfR0Equals(0xffffffff, "LABEL_283");
+ gen.addLoad32intoR0(30);
+ gen.addJumpIfR0Equals(0xffffffff, (short) -283);
gen.addLoadImmediate(R1, -80);
- gen.addJumpIfR0Equals(0xc0a801ff, "LABEL_283");
+ gen.addJumpIfR0Equals(0xc0a801ff, (short) -283);
gen.addLoadImmediate(R1, -24);
gen.addLoadImmediate(R0, 0);
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), "LABEL_277");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), (short) -277);
gen.addLoadImmediate(R1, -72);
- gen.addJump("LABEL_283");
+ gen.addJump((short) -283);
gen.addLoadImmediate(R1, -16);
- gen.addJump("LABEL_277");
+ gen.addJump((short) -277);
- gen.defineLabel("LABEL_204");
- gen.addJumpIfR0Equals(0x86dd, "LABEL_225");
+ gen.defineLabel((short) -204);
+ gen.addJumpIfR0Equals(0x86dd, (short) -225);
gen.addLoadImmediate(R0, 0);
gen.addLoadImmediate(R1, -48);
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), "LABEL_277");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), (short) -277);
gen.addLoadImmediate(R1, -56);
- gen.addJump("LABEL_283");
+ gen.addJump((short) -283);
- gen.defineLabel("LABEL_225");
- gen.addLoad8(R0, 20);
- gen.addJumpIfR0Equals(0x3a, "LABEL_241");
+ gen.defineLabel((short) -225);
+ gen.addLoad8intoR0(20);
+ gen.addJumpIfR0Equals(0x3a, (short) -241);
gen.addLoadImmediate(R1, -104);
- gen.addLoad8(R0, 38);
- gen.addJumpIfR0Equals(0xff, "LABEL_283");
+ gen.addLoad8intoR0(38);
+ gen.addJumpIfR0Equals(0xff, (short) -283);
gen.addLoadImmediate(R1, -32);
- gen.addJump("LABEL_277");
+ gen.addJump((short) -277);
- gen.defineLabel("LABEL_241");
- gen.addLoad8(R0, 54);
+ gen.defineLabel((short) -241);
+ gen.addLoad8intoR0(54);
gen.addLoadImmediate(R1, -88);
- gen.addJumpIfR0Equals(0x85, "LABEL_283");
- gen.addJumpIfR0NotEquals(0x88, "LABEL_275");
+ gen.addJumpIfR0Equals(0x85, (short) -283);
+ gen.addJumpIfR0NotEquals(0x88, (short) -275);
gen.addLoadImmediate(R0, 38);
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ff0200000000000000000000000000"), "LABEL_275");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ff0200000000000000000000000000"), (short) -275);
gen.addLoadImmediate(R1, -92);
- gen.addJump("LABEL_283");
+ gen.addJump((short) -283);
- gen.defineLabel("LABEL_275");
+ gen.defineLabel((short) -275);
gen.addLoadImmediate(R1, -28);
- gen.defineLabel("LABEL_277");
+ gen.defineLabel((short) -277);
gen.addLoadData(R0, 0);
gen.addAdd(1);
gen.addStoreData(R0, 0);
gen.addJump(PASS_LABEL);
- gen.defineLabel("LABEL_283");
+ gen.defineLabel((short) -283);
gen.addLoadData(R0, 0);
gen.addAdd(1);
gen.addStoreData(R0, 0);
@@ -3333,7 +3142,7 @@
gen.addLoadData(R0, 0);
gen.addAdd(1);
gen.addStoreData(R0, 0);
- gen.addLoad16(R0, 12);
+ gen.addLoad16intoR0(12);
gen.addCountAndDropIfR0LessThan(0x600, getCounterEnumFromOffset(-108));
gen.addLoadImmediate(R1, -112);
gen.addJumpIfR0Equals(0x88a2, gen.mCountAndDropLabel);
@@ -3342,95 +3151,95 @@
gen.addJumpIfR0Equals(0x88cd, gen.mCountAndDropLabel);
gen.addJumpIfR0Equals(0x88e1, gen.mCountAndDropLabel);
gen.addJumpIfR0Equals(0x88e3, gen.mCountAndDropLabel);
- gen.addJumpIfR0NotEquals(0x806, "LABEL_115");
+ gen.addJumpIfR0NotEquals(0x806, (short) -115);
gen.addLoadImmediate(R0, 14);
gen.addCountAndPassIfBytesAtR0NotEqual(hexStringToByteArray("000108000604"), getCounterEnumFromOffset(-36));
- gen.addLoad16(R0, 20);
- gen.addJumpIfR0Equals(0x1, "LABEL_100");
+ gen.addLoad16intoR0(20);
+ gen.addJumpIfR0Equals(0x1, (short) -100);
gen.addCountAndPassIfR0NotEquals(0x2, getCounterEnumFromOffset(-40));
- gen.addLoad32(R0, 28);
+ gen.addLoad32intoR0(28);
gen.addCountAndDropIfR0Equals(0x0, getCounterEnumFromOffset(-116));
gen.addLoadImmediate(R0, 0);
gen.addCountAndPassIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), getCounterEnumFromOffset(-44));
- gen.defineLabel("LABEL_100");
+ gen.defineLabel((short) -100);
gen.addLoadImmediate(R0, 38);
gen.addCountAndDropIfBytesAtR0NotEqual(hexStringToByteArray("c0a801be"), getCounterEnumFromOffset(-68));
gen.addCountAndPass(getCounterEnumFromOffset(-8));
- gen.defineLabel("LABEL_115");
- gen.addLoad16(R0, 12);
- gen.addJumpIfR0NotEquals(0x800, "LABEL_263");
- gen.addLoad8(R0, 23);
- gen.addJumpIfR0NotEquals(0x11, "LABEL_157");
- gen.addLoad16(R0, 20);
- gen.addJumpIfR0AnyBitsSet(0x1fff, "LABEL_157");
+ gen.defineLabel((short) -115);
+ gen.addLoad16intoR0(12);
+ gen.addJumpIfR0NotEquals(0x800, (short) -263);
+ gen.addLoad8intoR0(23);
+ gen.addJumpIfR0NotEquals(0x11, (short) -157);
+ gen.addLoad16intoR0(20);
+ gen.addJumpIfR0AnyBitsSet(0x1fff, (short) -157);
gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE);
- gen.addLoad16Indexed(R0, 16);
- gen.addJumpIfR0NotEquals(0x44, "LABEL_157");
+ gen.addLoad16R1IndexedIntoR0(16);
+ gen.addJumpIfR0NotEquals(0x44, (short) -157);
gen.addLoadImmediate(R0, 50);
gen.addAddR1ToR0();
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ea42226789c0"), "LABEL_157");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ea42226789c0"), (short) -157);
gen.addCountAndPass(getCounterEnumFromOffset(-12));
- gen.defineLabel("LABEL_157");
- gen.addLoad8(R0, 30);
+ gen.defineLabel((short) -157);
+ gen.addLoad8intoR0(30);
gen.addAnd(240);
gen.addCountAndDropIfR0Equals(0xe0, getCounterEnumFromOffset(-84));
gen.addLoadImmediate(R1, -76);
- gen.addLoad32(R0, 30);
+ gen.addLoad32intoR0(30);
gen.addJumpIfR0Equals(0xffffffff, gen.mCountAndDropLabel);
gen.addCountAndDropIfR0Equals(0xc0a801ff, getCounterEnumFromOffset(-80));
- gen.addLoad8(R0, 23);
- gen.addJumpIfR0NotEquals(0x11, "LABEL_243");
+ gen.addLoad8intoR0(23);
+ gen.addJumpIfR0NotEquals(0x11, (short) -243);
gen.addLoadImmediate(R0, 26);
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("6b7a1f1fc0a801be"), "LABEL_243");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("6b7a1f1fc0a801be"), (short) -243);
gen.addLoadFromMemory(R0, MemorySlot.IPV4_HEADER_SIZE);
gen.addAdd(8);
gen.addSwap();
- gen.addLoad16(R0, 16);
+ gen.addLoad16intoR0(16);
gen.addNeg(R1);
gen.addAddR1ToR0();
- gen.addJumpIfR0NotEquals(0x1, "LABEL_243");
+ gen.addJumpIfR0NotEquals(0x1, (short) -243);
gen.addLoadFromMemory(R0, MemorySlot.IPV4_HEADER_SIZE);
gen.addAdd(14);
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("1194ceca"), "LABEL_243");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("1194ceca"), (short) -243);
gen.addAdd(8);
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ff"), "LABEL_243");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ff"), (short) -243);
gen.addCountAndDrop(getCounterEnumFromOffset(-128));
- gen.defineLabel("LABEL_243");
+ gen.defineLabel((short) -243);
gen.addLoadImmediate(R1, -24);
gen.addLoadImmediate(R0, 0);
gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), gen.mCountAndPassLabel);
gen.addCountAndDrop(getCounterEnumFromOffset(-72));
gen.addCountAndPass(getCounterEnumFromOffset(-16));
- gen.defineLabel("LABEL_263");
- gen.addJumpIfR0Equals(0x86dd, "LABEL_284");
+ gen.defineLabel((short) -263);
+ gen.addJumpIfR0Equals(0x86dd, (short) -284);
gen.addLoadImmediate(R0, 0);
gen.addCountAndPassIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), getCounterEnumFromOffset(-48));
gen.addCountAndDrop(getCounterEnumFromOffset(-56));
- gen.defineLabel("LABEL_284");
- gen.addLoad8(R0, 20);
+ gen.defineLabel((short) -284);
+ gen.addLoad8intoR0(20);
gen.addJumpIfR0Equals(0x0, gen.mCountAndPassLabel);
- gen.addJumpIfR0Equals(0x3a, "LABEL_303");
+ gen.addJumpIfR0Equals(0x3a, (short) -303);
gen.addLoadImmediate(R1, -104);
- gen.addLoad8(R0, 38);
+ gen.addLoad8intoR0(38);
gen.addJumpIfR0Equals(0xff, gen.mCountAndDropLabel);
gen.addCountAndPass(getCounterEnumFromOffset(-32));
- gen.defineLabel("LABEL_303");
- gen.addLoad8(R0, 54);
+ gen.defineLabel((short) -303);
+ gen.addLoad8intoR0(54);
gen.addLoadImmediate(R1, -88);
gen.addJumpIfR0Equals(0x85, gen.mCountAndDropLabel);
- gen.addJumpIfR0NotEquals(0x88, "LABEL_337");
+ gen.addJumpIfR0NotEquals(0x88, (short) -337);
gen.addLoadImmediate(R0, 38);
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ff0200000000000000000000000000"), "LABEL_337");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ff0200000000000000000000000000"), (short) -337);
gen.addCountAndDrop(getCounterEnumFromOffset(-92));
- gen.defineLabel("LABEL_337");
+ gen.defineLabel((short) -337);
gen.addLoadImmediate(R1, -28);
gen.addCountTrampoline();
@@ -3448,7 +3257,7 @@
gen.addLoadCounter(R0, getCounterEnumFromOffset(-8));
gen.addAdd(1);
gen.addStoreData(R0, 0);
- gen.addLoad16(R0, 12);
+ gen.addLoad16intoR0(12);
gen.addCountAndDropIfR0LessThan(0x600, getCounterEnumFromOffset(-120));
gen.addLoadImmediate(R1, -124);
gen.addJumpIfR0Equals(0x88a2, gen.mCountAndDropLabel);
@@ -3457,130 +3266,130 @@
gen.addJumpIfR0Equals(0x88cd, gen.mCountAndDropLabel);
gen.addJumpIfR0Equals(0x88e1, gen.mCountAndDropLabel);
gen.addJumpIfR0Equals(0x88e3, gen.mCountAndDropLabel);
- gen.addJumpIfR0NotEquals(0x806, "LABEL_122");
+ gen.addJumpIfR0NotEquals(0x806, (short) -122);
gen.addLoadImmediate(R0, 14);
gen.addCountAndDropIfBytesAtR0NotEqual(hexStringToByteArray("000108000604"), getCounterEnumFromOffset(-152));
- gen.addLoad16(R0, 20);
- gen.addJumpIfR0Equals(0x1, "LABEL_104");
+ gen.addLoad16intoR0(20);
+ gen.addJumpIfR0Equals(0x1, (short) -104);
gen.addCountAndDropIfR0NotEquals(0x2, getCounterEnumFromOffset(-156));
- gen.addLoad32(R0, 28);
+ gen.addLoad32intoR0(28);
gen.addCountAndDropIfR0Equals(0x0, getCounterEnumFromOffset(-128));
gen.addLoadImmediate(R0, 0);
gen.addCountAndPassIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), getCounterEnumFromOffset(-56));
- gen.defineLabel("LABEL_104");
+ gen.defineLabel((short) -104);
gen.addLoadImmediate(R0, 38);
gen.addCountAndDropIfBytesAtR0NotEqual(hexStringToByteArray("c0a801ec"), getCounterEnumFromOffset(-80));
gen.addCountAndPass(getCounterEnumFromOffset(-20));
- gen.defineLabel("LABEL_122");
- gen.addLoad16(R0, 12);
- gen.addJumpIfR0NotEquals(0x800, "LABEL_249");
- gen.addLoad8(R0, 23);
- gen.addJumpIfR0NotEquals(0x11, "LABEL_165");
- gen.addLoad16(R0, 20);
- gen.addJumpIfR0AnyBitsSet(0x1fff, "LABEL_165");
+ gen.defineLabel((short) -122);
+ gen.addLoad16intoR0(12);
+ gen.addJumpIfR0NotEquals(0x800, (short) -249);
+ gen.addLoad8intoR0(23);
+ gen.addJumpIfR0NotEquals(0x11, (short) -165);
+ gen.addLoad16intoR0(20);
+ gen.addJumpIfR0AnyBitsSet(0x1fff, (short) -165);
gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE);
- gen.addLoad16Indexed(R0, 16);
- gen.addJumpIfR0NotEquals(0x44, "LABEL_165");
+ gen.addLoad16R1IndexedIntoR0(16);
+ gen.addJumpIfR0NotEquals(0x44, (short) -165);
gen.addLoadImmediate(R0, 50);
gen.addAddR1ToR0();
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("7e9046bc7008"), "LABEL_165");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("7e9046bc7008"), (short) -165);
gen.addCountAndPass(getCounterEnumFromOffset(-24));
- gen.defineLabel("LABEL_165");
- gen.addLoad8(R0, 30);
+ gen.defineLabel((short) -165);
+ gen.addLoad8intoR0(30);
gen.addAnd(240);
gen.addCountAndDropIfR0Equals(0xe0, getCounterEnumFromOffset(-96));
gen.addLoadImmediate(R1, -88);
- gen.addLoad32(R0, 30);
+ gen.addLoad32intoR0(30);
gen.addJumpIfR0Equals(0xffffffff, gen.mCountAndDropLabel);
gen.addCountAndDropIfR0Equals(0xc0a801ff, getCounterEnumFromOffset(-92));
- gen.addLoad8(R0, 23);
- gen.addJumpIfR0NotEquals(0x6, "LABEL_225");
- gen.addLoad16(R0, 20);
- gen.addJumpIfR0AnyBitsSet(0x1fff, "LABEL_225");
+ gen.addLoad8intoR0(23);
+ gen.addJumpIfR0NotEquals(0x6, (short) -225);
+ gen.addLoad16intoR0(20);
+ gen.addJumpIfR0AnyBitsSet(0x1fff, (short) -225);
gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE);
- gen.addLoad16Indexed(R0, 16);
- gen.addJumpIfR0NotEquals(0x7, "LABEL_225");
+ gen.addLoad16R1IndexedIntoR0(16);
+ gen.addJumpIfR0NotEquals(0x7, (short) -225);
gen.addCountAndDrop(getCounterEnumFromOffset(-148));
- gen.defineLabel("LABEL_225");
+ gen.defineLabel((short) -225);
gen.addLoadImmediate(R1, -36);
gen.addLoadImmediate(R0, 0);
gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), gen.mCountAndPassLabel);
gen.addCountAndDrop(getCounterEnumFromOffset(-84));
gen.addCountAndPass(getCounterEnumFromOffset(-28));
- gen.defineLabel("LABEL_249");
- gen.addJumpIfR0Equals(0x86dd, "LABEL_273");
+ gen.defineLabel((short) -249);
+ gen.addJumpIfR0Equals(0x86dd, (short) -273);
gen.addLoadImmediate(R0, 0);
gen.addCountAndPassIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), getCounterEnumFromOffset(-60));
gen.addCountAndDrop(getCounterEnumFromOffset(-68));
- gen.defineLabel("LABEL_273");
- gen.addLoad8(R0, 20);
+ gen.defineLabel((short) -273);
+ gen.addLoad8intoR0(20);
gen.addJumpIfR0Equals(0x0, gen.mCountAndPassLabel);
- gen.addJumpIfR0Equals(0x3a, "LABEL_297");
+ gen.addJumpIfR0Equals(0x3a, (short) -297);
gen.addLoadImmediate(R1, -116);
- gen.addLoad8(R0, 38);
+ gen.addLoad8intoR0(38);
gen.addJumpIfR0Equals(0xff, gen.mCountAndDropLabel);
gen.addCountAndPass(getCounterEnumFromOffset(-44));
- gen.defineLabel("LABEL_297");
- gen.addLoad8(R0, 54);
+ gen.defineLabel((short) -297);
+ gen.addLoad8intoR0(54);
gen.addCountAndDropIfR0Equals(0x85, getCounterEnumFromOffset(-100));
- gen.addJumpIfR0NotEquals(0x88, "LABEL_333");
+ gen.addJumpIfR0NotEquals(0x88, (short) -333);
gen.addLoadImmediate(R0, 38);
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ff0200000000000000000000000000"), "LABEL_333");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ff0200000000000000000000000000"), (short) -333);
gen.addCountAndDrop(getCounterEnumFromOffset(-104));
- gen.defineLabel("LABEL_333");
+ gen.defineLabel((short) -333);
gen.addLoadFromMemory(R0, MemorySlot.PACKET_SIZE);
- gen.addJumpIfR0NotEquals(0x96, "LABEL_574");
+ gen.addJumpIfR0NotEquals(0x96, (short) -574);
gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_SECONDS);
- gen.addJumpIfR0GreaterThan(0x48e, "LABEL_574");
+ gen.addJumpIfR0GreaterThan(0x48e, (short) -574);
gen.addLoadImmediate(R0, 0);
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("7e9046bc700828c68e23672c86dd60"), "LABEL_574");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("7e9046bc700828c68e23672c86dd60"), (short) -574);
gen.addLoadImmediate(R0, 18);
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("00603afffe800000000000002ac68efffe23672c"), "LABEL_574");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("00603afffe800000000000002ac68efffe23672c"), (short) -574);
gen.addLoadImmediate(R0, 54);
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("8600"), "LABEL_574");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("8600"), (short) -574);
gen.addLoadImmediate(R0, 58);
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("40c0"), "LABEL_574");
- gen.addLoad16(R0, 60);
- gen.addJumpIfR0Equals(0x0, "LABEL_574");
- gen.addJumpIfR0LessThan(0xb4, "LABEL_421");
- gen.addJumpIfR0LessThan(0x91e, "LABEL_574");
- gen.addJumpIfR0GreaterThan(0x1b58, "LABEL_574");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("40c0"), (short) -574);
+ gen.addLoad16intoR0(60);
+ gen.addJumpIfR0Equals(0x0, (short) -574);
+ gen.addJumpIfR0LessThan(0xb4, (short) -421);
+ gen.addJumpIfR0LessThan(0x91e, (short) -574);
+ gen.addJumpIfR0GreaterThan(0x1b58, (short) -574);
- gen.defineLabel("LABEL_421");
+ gen.defineLabel((short) -421);
gen.addLoadImmediate(R0, 62);
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("0000000000000000010128c68e23672c05010000000005dc030440c0"), "LABEL_574");
- gen.addLoad32(R0, 90);
- gen.addJumpIfR0Equals(0x0, "LABEL_574");
- gen.addJumpIfR0LessThan(0xb4, "LABEL_480");
- gen.addJumpIfR0LessThan(0x55555555, "LABEL_574");
- gen.addJumpIfR0GreaterThan(0xffffffffL, "LABEL_574");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("0000000000000000010128c68e23672c05010000000005dc030440c0"), (short) -574);
+ gen.addLoad32intoR0(90);
+ gen.addJumpIfR0Equals(0x0, (short) -574);
+ gen.addJumpIfR0LessThan(0xb4, (short) -480);
+ gen.addJumpIfR0LessThan(0x55555555, (short) -574);
+ gen.addJumpIfR0GreaterThan(0xffffffffL, (short) -574);
- gen.defineLabel("LABEL_480");
- gen.addLoad32(R0, 94);
- gen.addJumpIfR0LessThan(0x55555555, "LABEL_574");
- gen.addJumpIfR0GreaterThan(0xffffffffL, "LABEL_574");
+ gen.defineLabel((short) -480);
+ gen.addLoad32intoR0(94);
+ gen.addJumpIfR0LessThan(0x55555555, (short) -574);
+ gen.addJumpIfR0GreaterThan(0xffffffffL, (short) -574);
gen.addLoadImmediate(R0, 98);
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("000000002401fa000480f000000000000000000019030000"), "LABEL_574");
- gen.addLoad32(R0, 122);
- gen.addJumpIfR0Equals(0x0, "LABEL_574");
- gen.addJumpIfR0LessThan(0x78, "LABEL_547");
- gen.addJumpIfR0LessThan(0x91e, "LABEL_574");
- gen.addJumpIfR0GreaterThan(0x1b58, "LABEL_574");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("000000002401fa000480f000000000000000000019030000"), (short) -574);
+ gen.addLoad32intoR0(122);
+ gen.addJumpIfR0Equals(0x0, (short) -574);
+ gen.addJumpIfR0LessThan(0x78, (short) -547);
+ gen.addJumpIfR0LessThan(0x91e, (short) -574);
+ gen.addJumpIfR0GreaterThan(0x1b58, (short) -574);
- gen.defineLabel("LABEL_547");
+ gen.defineLabel((short) -547);
gen.addLoadImmediate(R0, 126);
- gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("2401fa000480f00000000000000000010701"), "LABEL_574");
+ gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("2401fa000480f00000000000000000010701"), (short) -574);
gen.addCountAndDrop(getCounterEnumFromOffset(-72));
- gen.defineLabel("LABEL_574");
+ gen.defineLabel((short) -574);
gen.addLoadImmediate(R1, -40);
gen.addCountTrampoline();
diff --git a/tests/unit/src/android/net/apf/ApfTestHelpers.kt b/tests/unit/src/android/net/apf/ApfTestHelpers.kt
index 6a5688e..ae225eb 100644
--- a/tests/unit/src/android/net/apf/ApfTestHelpers.kt
+++ b/tests/unit/src/android/net/apf/ApfTestHelpers.kt
@@ -20,15 +20,16 @@
import android.net.apf.ApfCounterTracker.Counter.APF_VERSION
import android.net.apf.ApfCounterTracker.Counter.TOTAL_PACKETS
import android.net.apf.BaseApfGenerator.APF_VERSION_6
-import android.net.ip.IpClient
import com.android.net.module.util.HexDump
import kotlin.test.assertEquals
import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.timeout
import org.mockito.Mockito.verify
-class ApfTestHelpers private constructor() {
+class ApfTestHelpers(apfInterpreterVersion: Int){
+ private val apfJniUtils = ApfJniUtils(apfInterpreterVersion)
companion object {
const val TIMEOUT_MS: Long = 1000
const val PASS: Int = 1
@@ -53,172 +54,6 @@
assertEquals(label(expected), label(got))
}
- private fun assertVerdict(
- apfVersion: Int,
- expected: Int,
- program: ByteArray,
- packet: ByteArray,
- filterAge: Int
- ) {
- val msg = """Unexpected APF verdict. To debug:
- apf_run
- --program ${HexDump.toHexString(program)}
- --packet ${HexDump.toHexString(packet)}
- --age $filterAge
- ${if (apfVersion > 4) " --v6" else ""}
- --trace " + " | less\n
- """
- assertReturnCodesEqual(
- msg,
- expected,
- ApfJniUtils.apfSimulate(apfVersion, program, packet, null, filterAge)
- )
- }
-
- @Throws(BaseApfGenerator.IllegalInstructionException::class)
- private fun assertVerdict(
- apfVersion: Int,
- expected: Int,
- gen: ApfV4Generator,
- packet: ByteArray,
- filterAge: Int
- ) {
- assertVerdict(apfVersion, expected, gen.generate(), packet, null, filterAge)
- }
-
- private fun assertVerdict(
- apfVersion: Int,
- expected: Int,
- program: ByteArray,
- packet: ByteArray,
- data: ByteArray?,
- filterAge: Int
- ) {
- val msg = """Unexpected APF verdict. To debug:
- apf_run
- --program ${HexDump.toHexString(program)}
- --packet ${HexDump.toHexString(packet)}
- ${if (data != null) "--data ${HexDump.toHexString(data)}" else ""}
- --age $filterAge
- ${if (apfVersion > 4) "--v6" else ""}
- --trace | less
- """
- assertReturnCodesEqual(
- msg,
- expected,
- ApfJniUtils.apfSimulate(apfVersion, program, packet, data, filterAge)
- )
- }
-
- /**
- * Runs the APF program with customized data region and checks the return code.
- */
- fun assertVerdict(
- apfVersion: Int,
- expected: Int,
- program: ByteArray,
- packet: ByteArray,
- data: ByteArray?
- ) {
- assertVerdict(apfVersion, expected, program, packet, data, filterAge = 0)
- }
-
- /**
- * Runs the APF program and checks the return code is equals to expected value. If not, the
- * customized message is printed.
- */
- @JvmStatic
- fun assertVerdict(
- apfVersion: Int,
- msg: String,
- expected: Int,
- program: ByteArray?,
- packet: ByteArray?,
- filterAge: Int
- ) {
- assertReturnCodesEqual(
- msg,
- expected,
- ApfJniUtils.apfSimulate(apfVersion, program, packet, null, filterAge)
- )
- }
-
- /**
- * Runs the APF program and checks the return code is equals to expected value.
- */
- @JvmStatic
- fun assertVerdict(apfVersion: Int, expected: Int, program: ByteArray, packet: ByteArray) {
- assertVerdict(apfVersion, expected, program, packet, 0)
- }
-
- /**
- * Runs the APF program and checks the return code is PASS.
- */
- @JvmStatic
- fun assertPass(apfVersion: Int, program: ByteArray, packet: ByteArray, filterAge: Int) {
- assertVerdict(apfVersion, PASS, program, packet, filterAge)
- }
-
- /**
- * Runs the APF program and checks the return code is PASS.
- */
- @JvmStatic
- fun assertPass(apfVersion: Int, program: ByteArray, packet: ByteArray) {
- assertVerdict(apfVersion, PASS, program, packet)
- }
-
- /**
- * Runs the APF program and checks the return code is DROP.
- */
- @JvmStatic
- fun assertDrop(apfVersion: Int, program: ByteArray, packet: ByteArray, filterAge: Int) {
- assertVerdict(apfVersion, DROP, program, packet, filterAge)
- }
-
- /**
- * Runs the APF program and checks the return code is DROP.
- */
- @JvmStatic
- fun assertDrop(apfVersion: Int, program: ByteArray, packet: ByteArray) {
- assertVerdict(apfVersion, DROP, program, packet)
- }
-
- /**
- * Runs the APF program and checks the return code is PASS.
- */
- @Throws(BaseApfGenerator.IllegalInstructionException::class)
- @JvmStatic
- fun assertPass(apfVersion: Int, gen: ApfV4Generator, packet: ByteArray, filterAge: Int) {
- assertVerdict(apfVersion, PASS, gen, packet, filterAge)
- }
-
- /**
- * Runs the APF program and checks the return code is DROP.
- */
- @Throws(BaseApfGenerator.IllegalInstructionException::class)
- @JvmStatic
- fun assertDrop(apfVersion: Int, gen: ApfV4Generator, packet: ByteArray, filterAge: Int) {
- assertVerdict(apfVersion, DROP, gen, packet, filterAge)
- }
-
- /**
- * Runs the APF program and checks the return code is PASS.
- */
- @Throws(BaseApfGenerator.IllegalInstructionException::class)
- @JvmStatic
- fun assertPass(apfVersion: Int, gen: ApfV4Generator) {
- assertVerdict(apfVersion, PASS, gen, ByteArray(MIN_PKT_SIZE), 0)
- }
-
- /**
- * Runs the APF program and checks the return code is DROP.
- */
- @Throws(BaseApfGenerator.IllegalInstructionException::class)
- @JvmStatic
- fun assertDrop(apfVersion: Int, gen: ApfV4Generator) {
- assertVerdict(apfVersion, DROP, gen, ByteArray(MIN_PKT_SIZE), 0)
- }
-
/**
* Checks the generated APF program equals to the expected value.
*/
@@ -234,69 +69,6 @@
}
}
- /**
- * Runs the APF program and checks the return code and data regions
- * equals to expected value.
- */
- @Throws(BaseApfGenerator.IllegalInstructionException::class, Exception::class)
- @JvmStatic
- fun assertDataMemoryContents(
- apfVersion: Int,
- expected: Int,
- program: ByteArray?,
- packet: ByteArray?,
- data: ByteArray,
- expectedData: ByteArray,
- ignoreInterpreterVersion: Boolean
- ) {
- assertReturnCodesEqual(
- expected,
- ApfJniUtils.apfSimulate(apfVersion, program, packet, data, 0)
- )
-
- if (ignoreInterpreterVersion) {
- val apfVersionIdx = (Counter.totalSize() +
- APF_VERSION.offset())
- val apfProgramIdIdx = (Counter.totalSize() +
- APF_PROGRAM_ID.offset())
- for (i in 0..3) {
- data[apfVersionIdx + i] = 0
- data[apfProgramIdIdx + i] = 0
- }
- }
- // assertArrayEquals() would only print one byte, making debugging difficult.
- if (!expectedData.contentEquals(data)) {
- throw Exception(
- ("\nprogram: " + HexDump.toHexString(program) +
- "\ndata memory: " + HexDump.toHexString(data) +
- "\nexpected: " + HexDump.toHexString(expectedData))
- )
- }
- }
-
- fun verifyProgramRun(
- version: Int,
- program: ByteArray,
- pkt: ByteArray,
- targetCnt: Counter,
- cntMap: MutableMap<Counter, Long> = mutableMapOf(),
- dataRegion: ByteArray = ByteArray(Counter.totalSize()) { 0 },
- incTotal: Boolean = true,
- result: Int = if (targetCnt.name.startsWith("PASSED")) PASS else DROP
- ) {
- assertVerdict(version, result, program, pkt, dataRegion)
- cntMap[targetCnt] = cntMap.getOrDefault(targetCnt, 0) + 1
- if (incTotal) {
- cntMap[TOTAL_PACKETS] = cntMap.getOrDefault(TOTAL_PACKETS, 0) + 1
- }
- val errMsg = "Counter is not increased properly. To debug: \n" +
- " apf_run --program ${HexDump.toHexString(program)} " +
- "--packet ${HexDump.toHexString(pkt)} " +
- "--data ${HexDump.toHexString(dataRegion)} --age 0 " +
- "${if (version == APF_VERSION_6) "--v6" else "" } --trace | less \n"
- assertEquals(cntMap, decodeCountersIntoMap(dataRegion), errMsg)
- }
-
fun decodeCountersIntoMap(counterBytes: ByteArray): Map<Counter, Long> {
val counters = Counter::class.java.enumConstants
val ret = HashMap<Counter, Long>()
@@ -313,22 +85,283 @@
}
return ret
}
+ }
- @JvmStatic
- fun consumeInstalledProgram(
- ipClientCb: IpClient.IpClientCallbacksWrapper,
- installCnt: Int
- ): ByteArray {
- val programCaptor = ArgumentCaptor.forClass(
- ByteArray::class.java
- )
+ private fun assertVerdict(
+ apfVersion: Int,
+ expected: Int,
+ program: ByteArray,
+ packet: ByteArray,
+ filterAge: Int
+ ) {
+ val msg = """Unexpected APF verdict. To debug:
+ apf_run
+ --program ${HexDump.toHexString(program)}
+ --packet ${HexDump.toHexString(packet)}
+ --age $filterAge
+ ${if (apfVersion > 4) " --v6" else ""}
+ --trace " + " | less\n
+ """
+ assertReturnCodesEqual(
+ msg,
+ expected,
+ apfJniUtils.apfSimulate(apfVersion, program, packet, null, filterAge)
+ )
+ }
- verify(ipClientCb, timeout(TIMEOUT_MS).times(installCnt)).installPacketFilter(
- programCaptor.capture()
- )
+ @Throws(BaseApfGenerator.IllegalInstructionException::class)
+ private fun assertVerdict(
+ apfVersion: Int,
+ expected: Int,
+ gen: ApfV4Generator,
+ packet: ByteArray,
+ filterAge: Int
+ ) {
+ assertVerdict(apfVersion, expected, gen.generate(), packet, null, filterAge)
+ }
- clearInvocations<Any>(ipClientCb)
- return programCaptor.value
+ private fun assertVerdict(
+ apfVersion: Int,
+ expected: Int,
+ program: ByteArray,
+ packet: ByteArray,
+ data: ByteArray?,
+ filterAge: Int
+ ) {
+ val msg = "Unexpected APF verdict. To debug: \n" + """
+ apf_run
+ --program ${HexDump.toHexString(program)}
+ --packet ${HexDump.toHexString(packet)}
+ ${if (data != null) "--data ${HexDump.toHexString(data)}" else ""}
+ --age $filterAge
+ ${if (apfVersion > 4) "--v6" else ""}
+ --trace | less
+ """.replace("\n", " ").replace("\\s+".toRegex(), " ") + "\n"
+ assertReturnCodesEqual(
+ msg,
+ expected,
+ apfJniUtils.apfSimulate(apfVersion, program, packet, data, filterAge)
+ )
+ }
+
+ /**
+ * Runs the APF program with customized data region and checks the return code.
+ */
+ fun assertVerdict(
+ apfVersion: Int,
+ expected: Int,
+ program: ByteArray,
+ packet: ByteArray,
+ data: ByteArray?
+ ) {
+ assertVerdict(apfVersion, expected, program, packet, data, filterAge = 0)
+ }
+
+ /**
+ * Runs the APF program and checks the return code is equals to expected value. If not, the
+ * customized message is printed.
+ */
+ fun assertVerdict(
+ apfVersion: Int,
+ msg: String,
+ expected: Int,
+ program: ByteArray?,
+ packet: ByteArray?,
+ filterAge: Int
+ ) {
+ assertReturnCodesEqual(
+ msg,
+ expected,
+ apfJniUtils.apfSimulate(apfVersion, program, packet, null, filterAge)
+ )
+ }
+
+ /**
+ * Runs the APF program and checks the return code is equals to expected value.
+ */
+ fun assertVerdict(apfVersion: Int, expected: Int, program: ByteArray, packet: ByteArray) {
+ assertVerdict(apfVersion, expected, program, packet, 0)
+ }
+
+ /**
+ * Runs the APF program and checks the return code is PASS.
+ */
+ fun assertPass(apfVersion: Int, program: ByteArray, packet: ByteArray, filterAge: Int) {
+ assertVerdict(apfVersion, PASS, program, packet, filterAge)
+ }
+
+ /**
+ * Runs the APF program and checks the return code is PASS.
+ */
+ fun assertPass(apfVersion: Int, program: ByteArray, packet: ByteArray) {
+ assertVerdict(apfVersion, PASS, program, packet)
+ }
+
+ /**
+ * Runs the APF program and checks the return code is DROP.
+ */
+ fun assertDrop(apfVersion: Int, program: ByteArray, packet: ByteArray, filterAge: Int) {
+ assertVerdict(apfVersion, DROP, program, packet, filterAge)
+ }
+
+ /**
+ * Runs the APF program and checks the return code is DROP.
+ */
+ fun assertDrop(apfVersion: Int, program: ByteArray, packet: ByteArray) {
+ assertVerdict(apfVersion, DROP, program, packet)
+ }
+
+ /**
+ * Runs the APF program and checks the return code is PASS.
+ */
+ @Throws(BaseApfGenerator.IllegalInstructionException::class)
+ fun assertPass(apfVersion: Int, gen: ApfV4Generator, packet: ByteArray, filterAge: Int) {
+ assertVerdict(apfVersion, PASS, gen, packet, filterAge)
+ }
+
+ /**
+ * Runs the APF program and checks the return code is DROP.
+ */
+ @Throws(BaseApfGenerator.IllegalInstructionException::class)
+ fun assertDrop(apfVersion: Int, gen: ApfV4Generator, packet: ByteArray, filterAge: Int) {
+ assertVerdict(apfVersion, DROP, gen, packet, filterAge)
+ }
+
+ /**
+ * Runs the APF program and checks the return code is PASS.
+ */
+ @Throws(BaseApfGenerator.IllegalInstructionException::class)
+ fun assertPass(apfVersion: Int, gen: ApfV4Generator) {
+ assertVerdict(apfVersion, PASS, gen, ByteArray(MIN_PKT_SIZE), 0)
+ }
+
+ /**
+ * Runs the APF program and checks the return code is DROP.
+ */
+ @Throws(BaseApfGenerator.IllegalInstructionException::class)
+ fun assertDrop(apfVersion: Int, gen: ApfV4Generator) {
+ assertVerdict(apfVersion, DROP, gen, ByteArray(MIN_PKT_SIZE), 0)
+ }
+
+ /**
+ * Runs the APF program and checks the return code and data regions
+ * equals to expected value.
+ */
+ @Throws(BaseApfGenerator.IllegalInstructionException::class, Exception::class)
+ fun assertDataMemoryContents(
+ apfVersion: Int,
+ expected: Int,
+ program: ByteArray?,
+ packet: ByteArray?,
+ data: ByteArray,
+ expectedData: ByteArray,
+ ignoreInterpreterVersion: Boolean
+ ) {
+ assertReturnCodesEqual(
+ expected,
+ apfJniUtils.apfSimulate(apfVersion, program, packet, data, 0)
+ )
+
+ if (ignoreInterpreterVersion) {
+ val apfVersionIdx = (Counter.totalSize() +
+ APF_VERSION.offset())
+ val apfProgramIdIdx = (Counter.totalSize() +
+ APF_PROGRAM_ID.offset())
+ for (i in 0..3) {
+ data[apfVersionIdx + i] = 0
+ data[apfProgramIdIdx + i] = 0
+ }
}
+ // assertArrayEquals() would only print one byte, making debugging difficult.
+ if (!expectedData.contentEquals(data)) {
+ throw Exception(
+ ("\nprogram: " + HexDump.toHexString(program) +
+ "\ndata memory: " + HexDump.toHexString(data) +
+ "\nexpected: " + HexDump.toHexString(expectedData))
+ )
+ }
+ }
+
+ fun verifyProgramRun(
+ version: Int,
+ program: ByteArray,
+ pkt: ByteArray,
+ targetCnt: Counter,
+ cntMap: MutableMap<Counter, Long> = mutableMapOf(),
+ dataRegion: ByteArray = ByteArray(Counter.totalSize()) { 0 },
+ incTotal: Boolean = true,
+ result: Int = if (targetCnt.name.startsWith("PASSED")) PASS else DROP
+ ) {
+ assertVerdict(version, result, program, pkt, dataRegion)
+ cntMap[targetCnt] = cntMap.getOrDefault(targetCnt, 0) + 1
+ if (incTotal) {
+ cntMap[TOTAL_PACKETS] = cntMap.getOrDefault(TOTAL_PACKETS, 0) + 1
+ }
+ val errMsg = "Counter is not increased properly. To debug: \n" +
+ " apf_run --program ${HexDump.toHexString(program)} " +
+ "--packet ${HexDump.toHexString(pkt)} " +
+ "--data ${HexDump.toHexString(dataRegion)} --age 0 " +
+ "${if (version == APF_VERSION_6) "--v6" else "" } --trace | less \n"
+ assertEquals(cntMap, decodeCountersIntoMap(dataRegion), errMsg)
+ }
+
+ fun consumeInstalledProgram(
+ apfController: ApfFilter.IApfController,
+ installCnt: Int
+ ): ByteArray {
+ val programCaptor = ArgumentCaptor.forClass(
+ ByteArray::class.java
+ )
+
+ verify(apfController, timeout(TIMEOUT_MS).times(installCnt)).installPacketFilter(
+ programCaptor.capture(),
+ any()
+ )
+
+ clearInvocations<Any>(apfController)
+ return programCaptor.value
+ }
+
+ fun consumeTransmittedPackets(
+ expectCnt: Int
+ ): List<ByteArray> {
+ val transmittedPackets = apfJniUtils.getAllTransmittedPackets()
+ assertEquals(expectCnt, transmittedPackets.size)
+ resetTransmittedPacketMemory()
+ return transmittedPackets
+ }
+
+ fun resetTransmittedPacketMemory() {
+ apfJniUtils.resetTransmittedPacketMemory()
+ }
+
+ fun disassembleApf(program: ByteArray): Array<String> {
+ return apfJniUtils.disassembleApf(program)
+ }
+
+ fun getAllTransmittedPackets(): List<ByteArray> {
+ return apfJniUtils.allTransmittedPackets
+ }
+
+ fun compareBpfApf(
+ apfVersion: Int,
+ filter: String,
+ pcapFilename: String,
+ apfProgram: ByteArray
+ ): Boolean {
+ return apfJniUtils.compareBpfApf(apfVersion, filter, pcapFilename, apfProgram)
+ }
+
+ fun compileToBpf(filter: String): String {
+ return apfJniUtils.compileToBpf(filter)
+ }
+
+ fun dropsAllPackets(
+ apfVersion: Int,
+ program: ByteArray,
+ data: ByteArray,
+ pcapFilename: String
+ ): Boolean {
+ return apfJniUtils.dropsAllPackets(apfVersion, program, data, pcapFilename)
}
}
diff --git a/tests/unit/src/android/net/apf/Bpf2Apf.java b/tests/unit/src/android/net/apf/Bpf2Apf.java
deleted file mode 100644
index 4dee2f6..0000000
--- a/tests/unit/src/android/net/apf/Bpf2Apf.java
+++ /dev/null
@@ -1,339 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.apf;
-
-import static android.net.apf.BaseApfGenerator.APF_VERSION_3;
-import static android.net.apf.BaseApfGenerator.MemorySlot;
-import static android.net.apf.BaseApfGenerator.Register.R0;
-import static android.net.apf.BaseApfGenerator.Register.R1;
-
-import android.net.apf.BaseApfGenerator.IllegalInstructionException;
-import android.net.apf.BaseApfGenerator.Register;
-
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
-
-/**
- * BPF to APF translator.
- *
- * Note: This is for testing purposes only and is not guaranteed to support
- * translation of all BPF programs.
- *
- * Example usage:
- * javac net/java/android/net/apf/ApfV4Generator.java \
- * tests/servicestests/src/android/net/apf/Bpf2Apf.java
- * sudo tcpdump -i em1 -d icmp | java -classpath tests/servicestests/src:net/java \
- * android.net.apf.Bpf2Apf
- */
-public class Bpf2Apf {
- private static int sRamSize = 1024;
- private static int sClampSize = 1024;
- private static int parseImm(String line, String arg) {
- if (!arg.startsWith("#0x")) {
- throw new IllegalArgumentException("Unhandled instruction: " + line);
- }
- final long val_long = Long.parseLong(arg.substring(3), 16);
- if (val_long < 0 || val_long > Long.parseLong("ffffffff", 16)) {
- throw new IllegalArgumentException("Unhandled instruction: " + line);
- }
- return new Long((val_long << 32) >> 32).intValue();
- }
-
- private static MemorySlot byIndex(int value) {
- switch (value) {
- case 0: return MemorySlot.SLOT_0;
- case 1: return MemorySlot.SLOT_1;
- case 2: return MemorySlot.SLOT_2;
- case 3: return MemorySlot.SLOT_3;
- case 4: return MemorySlot.SLOT_4;
- case 5: return MemorySlot.SLOT_5;
- case 6: return MemorySlot.SLOT_6;
- case 7: return MemorySlot.SLOT_7;
- }
- // Either < 0 or > 15 which aren't valid slot numbers,
- // or >= Memory.FIRST_PREFILLED (ie. 8),
- // but we don't need to check that,
- // since we only handle valid SLOT_X numbers.
- throw new IllegalArgumentException(
- String.format("Memory slot %d not in range 0..7", value));
- }
-
- /**
- * Convert a single line of "tcpdump -d" (human readable BPF program dump) {@code line} into
- * APF instruction(s) and append them to {@code gen}. Here's an example line:
- * (001) jeq #0x86dd jt 2 jf 7
- */
- private static void convertLine(String line, ApfV4Generator gen)
- throws IllegalInstructionException {
- if (line.indexOf("(") != 0 || line.indexOf(")") != 4 || line.indexOf(" ") != 5) {
- throw new IllegalArgumentException("Unhandled instruction: " + line);
- }
- int label = Integer.parseInt(line.substring(1, 4));
- gen.defineLabel(Integer.toString(label));
- String opcode = line.substring(6, 10).trim();
- String arg = line.substring(15, Math.min(32, line.length())).trim();
- switch (opcode) {
- case "ld":
- case "ldh":
- case "ldb":
- case "ldx":
- case "ldxb":
- case "ldxh":
- Register dest = opcode.contains("x") ? R1 : R0;
- if (arg.equals("4*([14]&0xf)")) {
- if (!opcode.equals("ldxb")) {
- throw new IllegalArgumentException("Unhandled instruction: " + line);
- }
- gen.addLoadFromMemory(dest, MemorySlot.IPV4_HEADER_SIZE);
- break;
- }
- if (arg.equals("#pktlen")) {
- if (!opcode.equals("ld")) {
- throw new IllegalArgumentException("Unhandled instruction: " + line);
- }
- gen.addLoadFromMemory(dest, MemorySlot.PACKET_SIZE);
- break;
- }
- if (arg.startsWith("#0x")) {
- if (!opcode.equals("ld")) {
- throw new IllegalArgumentException("Unhandled instruction: " + line);
- }
- gen.addLoadImmediate(dest, parseImm(line, arg));
- break;
- }
- if (arg.startsWith("M[")) {
- if (!opcode.startsWith("ld")) {
- throw new IllegalArgumentException("Unhandled instruction: " + line);
- }
- int memory_slot = Integer.parseInt(arg.substring(2, arg.length() - 1));
- gen.addLoadFromMemory(dest, byIndex(memory_slot));
- break;
- }
- if (arg.startsWith("[x + ")) {
- int offset = Integer.parseInt(arg.substring(5, arg.length() - 1));
- switch (opcode) {
- case "ld":
- case "ldx":
- gen.addLoad32Indexed(dest, offset);
- break;
- case "ldh":
- case "ldxh":
- gen.addLoad16Indexed(dest, offset);
- break;
- case "ldb":
- case "ldxb":
- gen.addLoad8Indexed(dest, offset);
- break;
- }
- } else {
- int offset = Integer.parseInt(arg.substring(1, arg.length() - 1));
- switch (opcode) {
- case "ld":
- case "ldx":
- gen.addLoad32(dest, offset);
- break;
- case "ldh":
- case "ldxh":
- gen.addLoad16(dest, offset);
- break;
- case "ldb":
- case "ldxb":
- gen.addLoad8(dest, offset);
- break;
- }
- }
- break;
- case "st":
- case "stx":
- Register src = opcode.contains("x") ? R1 : R0;
- if (!arg.startsWith("M[")) {
- throw new IllegalArgumentException("Unhandled instruction: " + line);
- }
- int memory_slot = Integer.parseInt(arg.substring(2, arg.length() - 1));
- gen.addStoreToMemory(byIndex(memory_slot), src);
- break;
- case "add":
- case "and":
- case "or":
- case "sub":
- if (arg.equals("x")) {
- switch(opcode) {
- case "add":
- gen.addAddR1ToR0();
- break;
- case "and":
- gen.addAndR0WithR1();
- break;
- case "or":
- gen.addOrR0WithR1();
- break;
- case "sub":
- gen.addNeg(R1);
- gen.addAddR1ToR0();
- gen.addNeg(R1);
- break;
- }
- } else {
- int imm = parseImm(line, arg);
- switch(opcode) {
- case "add":
- gen.addAdd(imm);
- break;
- case "and":
- gen.addAnd(imm);
- break;
- case "or":
- gen.addOr(imm);
- break;
- case "sub":
- gen.addAdd(-imm);
- break;
- }
- }
- break;
- case "jeq":
- case "jset":
- case "jgt":
- case "jge":
- int val = 0;
- boolean reg_compare;
- if (arg.startsWith("x")) {
- reg_compare = true;
- } else {
- reg_compare = false;
- val = parseImm(line, arg);
- }
- int jt_offset = line.indexOf("jt");
- int jf_offset = line.indexOf("jf");
- String true_label = line.substring(jt_offset + 2, jf_offset).trim();
- String false_label = line.substring(jf_offset + 2).trim();
- boolean true_label_is_fallthrough = Integer.parseInt(true_label) == label + 1;
- boolean false_label_is_fallthrough = Integer.parseInt(false_label) == label + 1;
- if (true_label_is_fallthrough && false_label_is_fallthrough)
- break;
- switch (opcode) {
- case "jeq":
- if (!true_label_is_fallthrough) {
- if (reg_compare) {
- gen.addJumpIfR0EqualsR1(true_label);
- } else {
- gen.addJumpIfR0Equals(val, true_label);
- }
- }
- if (!false_label_is_fallthrough) {
- if (!true_label_is_fallthrough) {
- gen.addJump(false_label);
- } else if (reg_compare) {
- gen.addJumpIfR0NotEqualsR1(false_label);
- } else {
- gen.addJumpIfR0NotEquals(val, false_label);
- }
- }
- break;
- case "jset":
- if (reg_compare) {
- gen.addJumpIfR0AnyBitsSetR1(true_label);
- } else {
- gen.addJumpIfR0AnyBitsSet(val, true_label);
- }
- if (!false_label_is_fallthrough) {
- gen.addJump(false_label);
- }
- break;
- case "jgt":
- if (!true_label_is_fallthrough ||
- // We have no less-than-or-equal-to register to register
- // comparison instruction, so in this case we'll jump
- // around an unconditional jump.
- (!false_label_is_fallthrough && reg_compare)) {
- if (reg_compare) {
- gen.addJumpIfR0GreaterThanR1(true_label);
- } else {
- gen.addJumpIfR0GreaterThan(val, true_label);
- }
- }
- if (!false_label_is_fallthrough) {
- if (!true_label_is_fallthrough || reg_compare) {
- gen.addJump(false_label);
- } else {
- gen.addJumpIfR0LessThan(val + 1, false_label);
- }
- }
- break;
- case "jge":
- if (!false_label_is_fallthrough ||
- // We have no greater-than-or-equal-to register to register
- // comparison instruction, so in this case we'll jump
- // around an unconditional jump.
- (!true_label_is_fallthrough && reg_compare)) {
- if (reg_compare) {
- gen.addJumpIfR0LessThanR1(false_label);
- } else {
- gen.addJumpIfR0LessThan(val, false_label);
- }
- }
- if (!true_label_is_fallthrough) {
- if (!false_label_is_fallthrough || reg_compare) {
- gen.addJump(true_label);
- } else {
- gen.addJumpIfR0GreaterThan(val - 1, true_label);
- }
- }
- break;
- }
- break;
- case "ret":
- if (arg.equals("#0")) {
- gen.addJump(gen.DROP_LABEL);
- } else {
- gen.addJump(gen.PASS_LABEL);
- }
- break;
- case "tax":
- gen.addMove(R1);
- break;
- case "txa":
- gen.addMove(R0);
- break;
- default:
- throw new IllegalArgumentException("Unhandled instruction: " + line);
- }
- }
-
- /**
- * Convert the output of "tcpdump -d" (human readable BPF program dump) {@code bpf} into an APF
- * program and return it.
- */
- public static byte[] convert(String bpf) throws IllegalInstructionException {
- ApfV4Generator gen = new ApfV4Generator(APF_VERSION_3, sRamSize, sClampSize);
- for (String line : bpf.split("\\n")) convertLine(line, gen);
- return gen.generate();
- }
-
- /**
- * Convert the output of "tcpdump -d" (human readable BPF program dump) piped in stdin into an
- * APF program and output it via stdout.
- */
- public static void main(String[] args) throws Exception {
- BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
- String line = null;
- StringBuilder responseData = new StringBuilder();
- ApfV4Generator gen = new ApfV4Generator(APF_VERSION_3, sRamSize, sClampSize);
- while ((line = in.readLine()) != null) convertLine(line, gen);
- System.out.write(gen.generate());
- }
-}
diff --git a/tests/unit/src/android/net/apf/JumpTableTest.kt b/tests/unit/src/android/net/apf/JumpTableTest.kt
deleted file mode 100644
index 066c34a..0000000
--- a/tests/unit/src/android/net/apf/JumpTableTest.kt
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.apf
-
-import android.net.apf.BaseApfGenerator.MemorySlot
-import android.net.apf.BaseApfGenerator.Register.R0
-import androidx.test.filters.SmallTest
-import androidx.test.runner.AndroidJUnit4
-import com.android.testutils.assertThrows
-import java.util.NoSuchElementException
-import java.util.concurrent.atomic.AtomicReference
-import kotlin.test.assertEquals
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.inOrder
-import org.mockito.MockitoAnnotations
-
-@RunWith(AndroidJUnit4::class)
-@SmallTest
-class JumpTableTest {
-
- @Mock
- lateinit var gen: ApfV4Generator
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- }
-
- @Test(expected = NullPointerException::class)
- fun testNullStartLabel() {
- // Can't use "null" because the method is @NonNull.
- JumpTable(AtomicReference<String>(null).get(), MemorySlot.SLOT_0)
- }
-
- @Test(expected = IllegalArgumentException::class)
- fun testSlotTooLarge() {
- JumpTable("my_jump_table", MemorySlot.IPV4_HEADER_SIZE)
- }
-
- @Test
- fun testValidSlotNumbers() {
- JumpTable("my_jump_table", MemorySlot.SLOT_1)
- JumpTable("my_jump_table", MemorySlot.SLOT_4)
- JumpTable("my_jump_table", MemorySlot.SLOT_6)
- }
-
- @Test
- fun testGetStartLabel() {
- assertEquals("xyz", JumpTable("xyz", MemorySlot.SLOT_3).startLabel)
- assertEquals("abc", JumpTable("abc", MemorySlot.SLOT_5).startLabel)
- }
-
- @Test
- fun testCodeGeneration() {
- val name = "my_jump_table"
- val slot = MemorySlot.SLOT_7
-
- val j = JumpTable(name, slot)
- j.addLabel("foo")
- j.addLabel("bar")
- j.addLabel("bar")
- j.addLabel("baz")
-
- assertEquals(0, j.getIndex("foo"))
- assertEquals(1, j.getIndex("bar"))
- assertEquals(2, j.getIndex("baz"))
-
- assertThrows(NoSuchElementException::class.java) {
- j.getIndex("nonexistent")
- }
-
- val inOrder = inOrder(gen)
-
- j.generate(gen)
-
- inOrder.verify(gen).defineLabel(name)
- inOrder.verify(gen).addLoadFromMemory(R0, slot)
- inOrder.verify(gen).addJumpIfR0Equals(0, "foo")
- inOrder.verify(gen).addJumpIfR0Equals(1, "bar")
- inOrder.verify(gen).addJumpIfR0Equals(2, "baz")
- inOrder.verify(gen).addJump(ApfV4Generator.PASS_LABEL)
- inOrder.verifyNoMoreInteractions()
- }
-}
diff --git a/tests/unit/src/android/net/apf/LegacyApfTest.java b/tests/unit/src/android/net/apf/LegacyApfTest.java
deleted file mode 100644
index 319a997..0000000
--- a/tests/unit/src/android/net/apf/LegacyApfTest.java
+++ /dev/null
@@ -1,2045 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.apf;
-
-import static android.net.apf.ApfJniUtils.dropsAllPackets;
-import static android.net.apf.ApfTestHelpers.TIMEOUT_MS;
-import static android.system.OsConstants.AF_UNIX;
-import static android.net.apf.ApfTestHelpers.DROP;
-import static android.net.apf.ApfTestHelpers.PASS;
-import static android.system.OsConstants.ETH_P_ARP;
-import static android.system.OsConstants.ETH_P_IP;
-import static android.system.OsConstants.ETH_P_IPV6;
-import static android.system.OsConstants.IPPROTO_ICMPV6;
-import static android.system.OsConstants.IPPROTO_TCP;
-import static android.system.OsConstants.IPPROTO_UDP;
-import static android.system.OsConstants.SOCK_STREAM;
-
-import static com.android.net.module.util.HexDump.hexStringToByteArray;
-import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.doAnswer;
-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.Context;
-import android.net.IpPrefix;
-import android.net.LinkAddress;
-import android.net.LinkProperties;
-import android.net.NattKeepalivePacketDataParcelable;
-import android.net.TcpKeepalivePacketDataParcelable;
-import android.net.apf.ApfCounterTracker.Counter;
-import android.net.apf.ApfFilter.ApfConfiguration;
-import android.net.ip.IIpClientCallbacks;
-import android.net.ip.IpClient;
-import android.net.metrics.IpConnectivityLog;
-import android.os.Build;
-import android.os.ConditionVariable;
-import android.os.PowerManager;
-import android.stats.connectivity.NetworkQuirkEvent;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.text.format.DateUtils;
-import android.util.ArrayMap;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-
-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.NetworkStackConstants;
-import com.android.net.module.util.SharedLog;
-import com.android.networkstack.apishim.NetworkInformationShimImpl;
-import com.android.networkstack.metrics.ApfSessionInfoMetrics;
-import com.android.networkstack.metrics.IpClientRaInfoMetrics;
-import com.android.networkstack.metrics.NetworkQuirkMetrics;
-import com.android.server.networkstack.tests.R;
-import com.android.testutils.ConcurrentUtils;
-import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.DevSdkIgnoreRunner;
-
-import libcore.io.IoUtils;
-import libcore.io.Streams;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.InetAddress;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
-
-/**
- * Tests for APF program generator and interpreter.
- *
- * The test cases will be executed by both APFv4 and APFv6 interpreter.
- */
-@DevSdkIgnoreRunner.MonitorThreadLeak
-@RunWith(DevSdkIgnoreRunner.class)
-@SmallTest
-public class LegacyApfTest {
- private static final int APF_VERSION_2 = 2;
-
- @Rule
- public DevSdkIgnoreRule mDevSdkIgnoreRule = new DevSdkIgnoreRule();
- // Indicates which apf interpreter to run.
- @Parameterized.Parameter()
- public int mApfVersion;
-
- @Parameterized.Parameters
- public static Iterable<? extends Object> data() {
- return Arrays.asList(4, 6);
- }
-
- @Mock private Context mContext;
- @Mock
- private ApfFilter.Dependencies mDependencies;
- @Mock private PowerManager mPowerManager;
- @Mock private IpConnectivityLog mIpConnectivityLog;
- @Mock private NetworkQuirkMetrics mNetworkQuirkMetrics;
- @Mock private ApfSessionInfoMetrics mApfSessionInfoMetrics;
- @Mock private IpClientRaInfoMetrics mIpClientRaInfoMetrics;
- @Mock private LegacyApfFilter.Clock mClock;
- @GuardedBy("mApfFilterCreated")
- private final ArrayList<AndroidPacketFilter> mApfFilterCreated = new ArrayList<>();
- @GuardedBy("mThreadsToBeCleared")
- private final ArrayList<Thread> mThreadsToBeCleared = new ArrayList<>();
-
- @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();
- doAnswer((invocation) -> {
- synchronized (mApfFilterCreated) {
- mApfFilterCreated.add(invocation.getArgument(0));
- }
- return null;
- }).when(mDependencies).onApfFilterCreated(any());
- doAnswer((invocation) -> {
- synchronized (mThreadsToBeCleared) {
- mThreadsToBeCleared.add(invocation.getArgument(0));
- }
- return null;
- }).when(mDependencies).onThreadCreated(any());
- }
-
- private void quitThreads() throws Exception {
- ConcurrentUtils.quitThreads(
- THREAD_QUIT_MAX_RETRY_COUNT,
- false /* interrupt */,
- HANDLER_TIMEOUT_MS,
- () -> {
- synchronized (mThreadsToBeCleared) {
- final ArrayList<Thread> ret = new ArrayList<>(mThreadsToBeCleared);
- mThreadsToBeCleared.clear();
- return ret;
- }
- });
- }
-
- private void shutdownApfFilters() throws Exception {
- ConcurrentUtils.quitResources(THREAD_QUIT_MAX_RETRY_COUNT, () -> {
- synchronized (mApfFilterCreated) {
- final ArrayList<AndroidPacketFilter> ret =
- new ArrayList<>(mApfFilterCreated);
- mApfFilterCreated.clear();
- return ret;
- }
- }, (apf) -> {
- apf.shutdown();
- });
- synchronized (mApfFilterCreated) {
- assertEquals("ApfFilters did not fully shutdown.",
- 0, mApfFilterCreated.size());
- }
- // It's necessary to wait until all ReceiveThreads have finished running because
- // clearInlineMocks clears all Mock objects, including some privilege frameworks
- // required by logStats, at the end of ReceiveThread#run.
- quitThreads();
- }
-
- @After
- public void tearDown() throws Exception {
- shutdownApfFilters();
- // Clear mocks to prevent from stubs holding instances and cause memory leaks.
- Mockito.framework().clearInlineMocks();
- }
-
- private static final String TAG = "ApfTest";
-
- private static final boolean DROP_MULTICAST = true;
- private static final boolean ALLOW_MULTICAST = false;
-
- private static final boolean DROP_802_3_FRAMES = true;
- 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;
-
- private static final int HANDLER_TIMEOUT_MS = 1000;
- private static final int THREAD_QUIT_MAX_RETRY_COUNT = 3;
-
- // Constants for opcode encoding
- private static final byte LI_OP = (byte)(13 << 3);
- private static final byte LDDW_OP = (byte)(22 << 3);
- private static final byte STDW_OP = (byte)(23 << 3);
- private static final byte SIZE0 = (byte)(0 << 1);
- private static final byte SIZE8 = (byte)(1 << 1);
- private static final byte SIZE16 = (byte)(2 << 1);
- private static final byte SIZE32 = (byte)(3 << 1);
- private static final byte R1_REG = 1;
-
- private static ApfConfiguration getDefaultConfig() {
- ApfFilter.ApfConfiguration config = new ApfConfiguration();
- config.apfVersionSupported = 2;
- config.apfRamSize = 4096;
- config.multicastFilter = ALLOW_MULTICAST;
- config.ieee802_3Filter = ALLOW_802_3_FRAMES;
- config.ethTypeBlackList = new int[0];
- config.minRdnssLifetimeSec = MIN_RDNSS_LIFETIME_SEC;
- config.minRdnssLifetimeSec = 67;
- config.minMetricsSessionDurationMs = MIN_METRICS_SESSION_DURATIONS_MS;
- return config;
- }
-
- private void assertPass(ApfV4Generator gen) throws ApfV4Generator.IllegalInstructionException {
- ApfTestHelpers.assertPass(mApfVersion, gen);
- }
-
- private void assertDrop(ApfV4Generator gen) throws ApfV4Generator.IllegalInstructionException {
- ApfTestHelpers.assertDrop(mApfVersion, gen);
- }
-
- private void assertPass(byte[] program, byte[] packet) {
- ApfTestHelpers.assertPass(mApfVersion, program, packet);
- }
-
- private void assertDrop(byte[] program, byte[] packet) {
- ApfTestHelpers.assertDrop(mApfVersion, program, packet);
- }
-
- private void assertPass(byte[] program, byte[] packet, int filterAge) {
- ApfTestHelpers.assertPass(mApfVersion, program, packet, filterAge);
- }
-
- private void assertDrop(byte[] program, byte[] packet, int filterAge) {
- ApfTestHelpers.assertDrop(mApfVersion, program, packet, filterAge);
- }
-
- private void assertPass(ApfV4Generator gen, byte[] packet, int filterAge)
- throws ApfV4Generator.IllegalInstructionException {
- ApfTestHelpers.assertPass(mApfVersion, gen, packet, filterAge);
- }
-
- private void assertDrop(ApfV4Generator gen, byte[] packet, int filterAge)
- throws ApfV4Generator.IllegalInstructionException {
- ApfTestHelpers.assertDrop(mApfVersion, gen, packet, filterAge);
- }
-
- private void assertDataMemoryContents(int expected, byte[] program, byte[] packet,
- byte[] data, byte[] expectedData) throws Exception {
- ApfTestHelpers.assertDataMemoryContents(mApfVersion, expected, program, packet, data,
- expectedData, false /* ignoreInterpreterVersion */);
- }
-
- private void assertDataMemoryContentsIgnoreVersion(int expected, byte[] program,
- byte[] packet, byte[] data, byte[] expectedData) throws Exception {
- ApfTestHelpers.assertDataMemoryContents(mApfVersion, expected, program, packet, data,
- expectedData, true /* ignoreInterpreterVersion */);
- }
-
- private void assertVerdict(String msg, int expected, byte[] program,
- byte[] packet, int filterAge) {
- ApfTestHelpers.assertVerdict(mApfVersion, msg, expected, program, packet, filterAge);
- }
-
- private void assertVerdict(int expected, byte[] program, byte[] packet) {
- ApfTestHelpers.assertVerdict(mApfVersion, expected, program, packet);
- }
-
- /**
- * Generate APF program, run pcap file though APF filter, then check all the packets in the file
- * should be dropped.
- */
- @Test
- public void testApfFilterPcapFile() throws Exception {
- final byte[] MOCK_PCAP_IPV4_ADDR = {(byte) 172, 16, 7, (byte) 151};
- String pcapFilename = stageFile(R.raw.apfPcap);
- MockIpClientCallback ipClientCallback = new MockIpClientCallback();
- LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_PCAP_IPV4_ADDR), 16);
- LinkProperties lp = new LinkProperties();
- lp.addLinkAddress(link);
-
- ApfConfiguration config = getDefaultConfig();
- config.apfVersionSupported = 4;
- config.apfRamSize = 1700;
- config.multicastFilter = DROP_MULTICAST;
- config.ieee802_3Filter = DROP_802_3_FRAMES;
- TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback,
- mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
- apfFilter.setLinkProperties(lp);
- byte[] program = ipClientCallback.assertProgramUpdateAndGet();
- byte[] data = new byte[Counter.totalSize()];
- final boolean result;
-
- result = dropsAllPackets(mApfVersion, program, data, pcapFilename);
- Log.i(TAG, "testApfFilterPcapFile(): Data counters: " + HexDump.toHexString(data, false));
-
- assertTrue("Failed to drop all packets by filter. \nAPF counters:" +
- HexDump.toHexString(data, false), result);
- }
-
- private static final int ETH_HEADER_LEN = 14;
- private static final int ETH_DEST_ADDR_OFFSET = 0;
- private static final int ETH_ETHERTYPE_OFFSET = 12;
- private static final byte[] ETH_BROADCAST_MAC_ADDRESS =
- {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
- private static final byte[] ETH_MULTICAST_MDNS_v4_MAC_ADDRESS =
- {(byte) 0x01, (byte) 0x00, (byte) 0x5e, (byte) 0x00, (byte) 0x00, (byte) 0xfb};
- private static final byte[] ETH_MULTICAST_MDNS_V6_MAC_ADDRESS =
- {(byte) 0x33, (byte) 0x33, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xfb};
-
- private static final int IP_HEADER_OFFSET = ETH_HEADER_LEN;
-
- private static final int IPV4_HEADER_LEN = 20;
- private static final int IPV4_TOTAL_LENGTH_OFFSET = IP_HEADER_OFFSET + 2;
- private static final int IPV4_PROTOCOL_OFFSET = IP_HEADER_OFFSET + 9;
- private static final int IPV4_SRC_ADDR_OFFSET = IP_HEADER_OFFSET + 12;
- private static final int IPV4_DEST_ADDR_OFFSET = IP_HEADER_OFFSET + 16;
-
- private static final int IPV4_TCP_HEADER_LEN = 20;
- private static final int IPV4_TCP_HEADER_OFFSET = IP_HEADER_OFFSET + IPV4_HEADER_LEN;
- private static final int IPV4_TCP_SRC_PORT_OFFSET = IPV4_TCP_HEADER_OFFSET + 0;
- private static final int IPV4_TCP_DEST_PORT_OFFSET = IPV4_TCP_HEADER_OFFSET + 2;
- private static final int IPV4_TCP_SEQ_NUM_OFFSET = IPV4_TCP_HEADER_OFFSET + 4;
- private static final int IPV4_TCP_ACK_NUM_OFFSET = IPV4_TCP_HEADER_OFFSET + 8;
- private static final int IPV4_TCP_HEADER_LENGTH_OFFSET = IPV4_TCP_HEADER_OFFSET + 12;
- private static final int IPV4_TCP_HEADER_FLAG_OFFSET = IPV4_TCP_HEADER_OFFSET + 13;
-
- private static final int IPV4_UDP_HEADER_OFFSET = IP_HEADER_OFFSET + IPV4_HEADER_LEN;
- private static final int IPV4_UDP_SRC_PORT_OFFSET = IPV4_UDP_HEADER_OFFSET + 0;
- private static final int IPV4_UDP_DEST_PORT_OFFSET = IPV4_UDP_HEADER_OFFSET + 2;
- private static final int IPV4_UDP_LENGTH_OFFSET = IPV4_UDP_HEADER_OFFSET + 4;
- private static final int IPV4_UDP_PAYLOAD_OFFSET = IPV4_UDP_HEADER_OFFSET + 8;
- private static final byte[] IPV4_BROADCAST_ADDRESS =
- {(byte) 255, (byte) 255, (byte) 255, (byte) 255};
-
- private static final int IPV6_HEADER_LEN = 40;
- private static final int IPV6_PAYLOAD_LENGTH_OFFSET = IP_HEADER_OFFSET + 4;
- private static final int IPV6_NEXT_HEADER_OFFSET = IP_HEADER_OFFSET + 6;
- private static final int IPV6_SRC_ADDR_OFFSET = IP_HEADER_OFFSET + 8;
- private static final int IPV6_DEST_ADDR_OFFSET = IP_HEADER_OFFSET + 24;
- private static final int IPV6_PAYLOAD_OFFSET = IP_HEADER_OFFSET + IPV6_HEADER_LEN;
- private static final int IPV6_TCP_SRC_PORT_OFFSET = IPV6_PAYLOAD_OFFSET + 0;
- private static final int IPV6_TCP_DEST_PORT_OFFSET = IPV6_PAYLOAD_OFFSET + 2;
- private static final int IPV6_TCP_SEQ_NUM_OFFSET = IPV6_PAYLOAD_OFFSET + 4;
- private static final int IPV6_TCP_ACK_NUM_OFFSET = IPV6_PAYLOAD_OFFSET + 8;
- // The IPv6 all nodes address ff02::1
- private static final byte[] IPV6_ALL_NODES_ADDRESS =
- { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
- private static final byte[] IPV6_ALL_ROUTERS_ADDRESS =
- { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 };
- private static final byte[] IPV6_SOLICITED_NODE_MULTICAST_ADDRESS = {
- (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
- (byte) 0xff, (byte) 0xab, (byte) 0xcd, (byte) 0xef,
- };
-
- private static final int ICMP6_TYPE_OFFSET = IP_HEADER_OFFSET + IPV6_HEADER_LEN;
- private static final int ICMP6_ROUTER_SOLICITATION = 133;
- private static final int ICMP6_ROUTER_ADVERTISEMENT = 134;
- private static final int ICMP6_NEIGHBOR_SOLICITATION = 135;
- private static final int ICMP6_NEIGHBOR_ANNOUNCEMENT = 136;
-
- private static final int ICMP6_RA_HEADER_LEN = 16;
- private static final int ICMP6_RA_CHECKSUM_OFFSET =
- IP_HEADER_OFFSET + IPV6_HEADER_LEN + 2;
- private static final int ICMP6_RA_ROUTER_LIFETIME_OFFSET =
- IP_HEADER_OFFSET + IPV6_HEADER_LEN + 6;
- private static final int ICMP6_RA_REACHABLE_TIME_OFFSET =
- IP_HEADER_OFFSET + IPV6_HEADER_LEN + 8;
- private static final int ICMP6_RA_RETRANSMISSION_TIMER_OFFSET =
- IP_HEADER_OFFSET + IPV6_HEADER_LEN + 12;
- private static final int ICMP6_RA_OPTION_OFFSET =
- IP_HEADER_OFFSET + IPV6_HEADER_LEN + ICMP6_RA_HEADER_LEN;
-
- private static final int ICMP6_PREFIX_OPTION_TYPE = 3;
- private static final int ICMP6_PREFIX_OPTION_LEN = 32;
- private static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET = 4;
- private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET = 8;
-
- // From RFC6106: Recursive DNS Server option
- private static final int ICMP6_RDNSS_OPTION_TYPE = 25;
- // From RFC6106: DNS Search List option
- private static final int ICMP6_DNSSL_OPTION_TYPE = 31;
-
- // From RFC4191: Route Information option
- private static final int ICMP6_ROUTE_INFO_OPTION_TYPE = 24;
- // Above three options all have the same format:
- private static final int ICMP6_4_BYTE_OPTION_LEN = 8;
- private static final int ICMP6_4_BYTE_LIFETIME_OFFSET = 4;
- private static final int ICMP6_4_BYTE_LIFETIME_LEN = 4;
-
- private static final int UDP_HEADER_LEN = 8;
- private static final int UDP_DESTINATION_PORT_OFFSET = ETH_HEADER_LEN + 22;
-
- private static final int DHCP_CLIENT_PORT = 68;
- private static final int DHCP_CLIENT_MAC_OFFSET = ETH_HEADER_LEN + UDP_HEADER_LEN + 48;
-
- private static final int ARP_HEADER_OFFSET = ETH_HEADER_LEN;
- private static final byte[] ARP_IPV4_REQUEST_HEADER = {
- 0, 1, // Hardware type: Ethernet (1)
- 8, 0, // Protocol type: IP (0x0800)
- 6, // Hardware size: 6
- 4, // Protocol size: 4
- 0, 1 // Opcode: request (1)
- };
- private static final byte[] ARP_IPV4_REPLY_HEADER = {
- 0, 1, // Hardware type: Ethernet (1)
- 8, 0, // Protocol type: IP (0x0800)
- 6, // Hardware size: 6
- 4, // Protocol size: 4
- 0, 2 // Opcode: reply (2)
- };
- private static final int ARP_SOURCE_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 14;
- private static final int ARP_TARGET_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 24;
-
- private static final byte[] MOCK_IPV4_ADDR = {10, 0, 0, 1};
- private static final byte[] MOCK_BROADCAST_IPV4_ADDR = {10, 0, 31, (byte) 255}; // prefix = 19
- private static final byte[] MOCK_MULTICAST_IPV4_ADDR = {(byte) 224, 0, 0, 1};
- private static final byte[] ANOTHER_IPV4_ADDR = {10, 0, 0, 2};
- private static final byte[] IPV4_SOURCE_ADDR = {10, 0, 0, 3};
- private static final byte[] ANOTHER_IPV4_SOURCE_ADDR = {(byte) 192, 0, 2, 1};
- private static final byte[] BUG_PROBE_SOURCE_ADDR1 = {0, 0, 1, 2};
- private static final byte[] BUG_PROBE_SOURCE_ADDR2 = {3, 4, 0, 0};
- private static final byte[] IPV4_ANY_HOST_ADDR = {0, 0, 0, 0};
- private static final byte[] IPV4_MDNS_MULTICAST_ADDR = {(byte) 224, 0, 0, (byte) 251};
- private static final byte[] IPV6_MDNS_MULTICAST_ADDR =
- {(byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xfb};
- private static final int IPV6_UDP_DEST_PORT_OFFSET = IPV6_PAYLOAD_OFFSET + 2;
- private static final int MDNS_UDP_PORT = 5353;
-
- private static void setIpv4VersionFields(ByteBuffer packet) {
- packet.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IP);
- packet.put(IP_HEADER_OFFSET, (byte) 0x45);
- }
-
- private static void setIpv6VersionFields(ByteBuffer packet) {
- packet.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IPV6);
- packet.put(IP_HEADER_OFFSET, (byte) 0x60);
- }
-
- private static ByteBuffer makeIpv4Packet(int proto) {
- ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
- setIpv4VersionFields(packet);
- packet.put(IPV4_PROTOCOL_OFFSET, (byte) proto);
- return packet;
- }
-
- private static ByteBuffer makeIpv6Packet(int nextHeader) {
- ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
- setIpv6VersionFields(packet);
- packet.put(IPV6_NEXT_HEADER_OFFSET, (byte) nextHeader);
- return packet;
- }
-
- @Test
- public void testApfFilterIPv4() throws Exception {
- MockIpClientCallback ipClientCallback = new MockIpClientCallback();
- LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
- LinkProperties lp = new LinkProperties();
- lp.addLinkAddress(link);
-
- ApfConfiguration config = getDefaultConfig();
- config.multicastFilter = DROP_MULTICAST;
- TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback,
- mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
- apfFilter.setLinkProperties(lp);
-
- byte[] program = ipClientCallback.assertProgramUpdateAndGet();
-
- ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
- // Verify empty packet of 100 zero bytes is passed
- assertPass(program, packet.array());
-
- // Verify unicast IPv4 packet is passed
- put(packet, ETH_DEST_ADDR_OFFSET, TestLegacyApfFilter.MOCK_MAC_ADDR);
- packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
- put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_IPV4_ADDR);
- assertPass(program, packet.array());
-
- // Verify L2 unicast to IPv4 broadcast addresses is dropped (b/30231088)
- put(packet, IPV4_DEST_ADDR_OFFSET, IPV4_BROADCAST_ADDRESS);
- assertDrop(program, packet.array());
- put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_BROADCAST_IPV4_ADDR);
- assertDrop(program, packet.array());
-
- // Verify multicast/broadcast IPv4, not DHCP to us, is dropped
- put(packet, ETH_DEST_ADDR_OFFSET, ETH_BROADCAST_MAC_ADDRESS);
- assertDrop(program, packet.array());
- packet.put(IP_HEADER_OFFSET, (byte) 0x45);
- assertDrop(program, packet.array());
- packet.put(IPV4_PROTOCOL_OFFSET, (byte)IPPROTO_UDP);
- assertDrop(program, packet.array());
- packet.putShort(UDP_DESTINATION_PORT_OFFSET, (short)DHCP_CLIENT_PORT);
- assertDrop(program, packet.array());
- put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_MULTICAST_IPV4_ADDR);
- assertDrop(program, packet.array());
- put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_BROADCAST_IPV4_ADDR);
- assertDrop(program, packet.array());
- put(packet, IPV4_DEST_ADDR_OFFSET, IPV4_BROADCAST_ADDRESS);
- assertDrop(program, packet.array());
-
- // Verify broadcast IPv4 DHCP to us is passed
- put(packet, DHCP_CLIENT_MAC_OFFSET, TestLegacyApfFilter.MOCK_MAC_ADDR);
- assertPass(program, packet.array());
-
- // Verify unicast IPv4 DHCP to us is passed
- put(packet, ETH_DEST_ADDR_OFFSET, TestLegacyApfFilter.MOCK_MAC_ADDR);
- assertPass(program, packet.array());
- }
-
- @Test
- public void testApfFilterIPv6() throws Exception {
- MockIpClientCallback ipClientCallback = new MockIpClientCallback();
- ApfConfiguration config = getDefaultConfig();
- TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback,
- mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
- byte[] program = ipClientCallback.assertProgramUpdateAndGet();
-
- // Verify empty IPv6 packet is passed
- ByteBuffer packet = makeIpv6Packet(IPPROTO_UDP);
- assertPass(program, packet.array());
-
- // Verify empty ICMPv6 packet is passed
- packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
- assertPass(program, packet.array());
-
- // Verify empty ICMPv6 NA packet is passed
- packet.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_NEIGHBOR_ANNOUNCEMENT);
- assertPass(program, packet.array());
-
- // Verify ICMPv6 NA to ff02::1 is dropped
- put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_ALL_NODES_ADDRESS);
- assertDrop(program, packet.array());
-
- // Verify ICMPv6 NA to ff02::2 is dropped
- put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_ALL_ROUTERS_ADDRESS);
- assertDrop(program, packet.array());
-
- // Verify ICMPv6 NA to Solicited-Node Multicast is passed
- put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_SOLICITED_NODE_MULTICAST_ADDRESS);
- assertPass(program, packet.array());
-
- // Verify ICMPv6 RS to any is dropped
- packet.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_ROUTER_SOLICITATION);
- assertDrop(program, packet.array());
- put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_ALL_ROUTERS_ADDRESS);
- assertDrop(program, packet.array());
- }
-
- @Test
- public void testApfFilterMulticast() throws Exception {
- final byte[] unicastIpv4Addr = {(byte)192,0,2,63};
- final byte[] broadcastIpv4Addr = {(byte)192,0,2,(byte)255};
- final byte[] multicastIpv4Addr = {(byte)224,0,0,1};
- final byte[] multicastIpv6Addr = {(byte)0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,(byte)0xfb};
-
- MockIpClientCallback ipClientCallback = new MockIpClientCallback();
- LinkAddress link = new LinkAddress(InetAddress.getByAddress(unicastIpv4Addr), 24);
- LinkProperties lp = new LinkProperties();
- lp.addLinkAddress(link);
-
- ApfConfiguration config = getDefaultConfig();
- config.ieee802_3Filter = DROP_802_3_FRAMES;
- TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback,
- mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
- apfFilter.setLinkProperties(lp);
-
- byte[] program = ipClientCallback.assertProgramUpdateAndGet();
-
- // Construct IPv4 and IPv6 multicast packets.
- ByteBuffer mcastv4packet = makeIpv4Packet(IPPROTO_UDP);
- put(mcastv4packet, IPV4_DEST_ADDR_OFFSET, multicastIpv4Addr);
-
- ByteBuffer mcastv6packet = makeIpv6Packet(IPPROTO_UDP);
- put(mcastv6packet, IPV6_DEST_ADDR_OFFSET, multicastIpv6Addr);
-
- // Construct IPv4 broadcast packet.
- ByteBuffer bcastv4packet1 = makeIpv4Packet(IPPROTO_UDP);
- bcastv4packet1.put(ETH_BROADCAST_MAC_ADDRESS);
- bcastv4packet1.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
- put(bcastv4packet1, IPV4_DEST_ADDR_OFFSET, multicastIpv4Addr);
-
- ByteBuffer bcastv4packet2 = makeIpv4Packet(IPPROTO_UDP);
- bcastv4packet2.put(ETH_BROADCAST_MAC_ADDRESS);
- bcastv4packet2.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
- put(bcastv4packet2, IPV4_DEST_ADDR_OFFSET, IPV4_BROADCAST_ADDRESS);
-
- // Construct IPv4 broadcast with L2 unicast address packet (b/30231088).
- ByteBuffer bcastv4unicastl2packet = makeIpv4Packet(IPPROTO_UDP);
- bcastv4unicastl2packet.put(TestLegacyApfFilter.MOCK_MAC_ADDR);
- bcastv4unicastl2packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
- put(bcastv4unicastl2packet, IPV4_DEST_ADDR_OFFSET, broadcastIpv4Addr);
-
- // Verify initially disabled multicast filter is off
- assertPass(program, mcastv4packet.array());
- assertPass(program, mcastv6packet.array());
- assertPass(program, bcastv4packet1.array());
- assertPass(program, bcastv4packet2.array());
- assertPass(program, bcastv4unicastl2packet.array());
-
- // Turn on multicast filter and verify it works
- ipClientCallback.resetApfProgramWait();
- apfFilter.setMulticastFilter(true);
- program = ipClientCallback.assertProgramUpdateAndGet();
- assertDrop(program, mcastv4packet.array());
- assertDrop(program, mcastv6packet.array());
- assertDrop(program, bcastv4packet1.array());
- assertDrop(program, bcastv4packet2.array());
- assertDrop(program, bcastv4unicastl2packet.array());
-
- // Turn off multicast filter and verify it's off
- ipClientCallback.resetApfProgramWait();
- apfFilter.setMulticastFilter(false);
- program = ipClientCallback.assertProgramUpdateAndGet();
- assertPass(program, mcastv4packet.array());
- assertPass(program, mcastv6packet.array());
- assertPass(program, bcastv4packet1.array());
- assertPass(program, bcastv4packet2.array());
- assertPass(program, bcastv4unicastl2packet.array());
-
- // Verify it can be initialized to on
- ipClientCallback.resetApfProgramWait();
- config.multicastFilter = DROP_MULTICAST;
- config.ieee802_3Filter = DROP_802_3_FRAMES;
- apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback,
- mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
- apfFilter.setLinkProperties(lp);
- program = ipClientCallback.assertProgramUpdateAndGet();
- assertDrop(program, mcastv4packet.array());
- assertDrop(program, mcastv6packet.array());
- assertDrop(program, bcastv4packet1.array());
- assertDrop(program, bcastv4unicastl2packet.array());
-
- // Verify that ICMPv6 multicast is not dropped.
- mcastv6packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
- assertPass(program, mcastv6packet.array());
- }
-
- @Test
- public void testApfFilterMulticastPingWhileDozing() throws Exception {
- MockIpClientCallback ipClientCallback = new MockIpClientCallback();
- final ApfConfiguration configuration = getDefaultConfig();
- final LegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, configuration,
- ipClientCallback, mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
-
- // Construct a multicast ICMPv6 ECHO request.
- final byte[] multicastIpv6Addr = {(byte)0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,(byte)0xfb};
- ByteBuffer packet = makeIpv6Packet(IPPROTO_ICMPV6);
- packet.put(ICMP6_TYPE_OFFSET, (byte)ICMPV6_ECHO_REQUEST_TYPE);
- put(packet, IPV6_DEST_ADDR_OFFSET, multicastIpv6Addr);
-
- // Normally, we let multicast pings alone...
- assertPass(ipClientCallback.assertProgramUpdateAndGet(), packet.array());
-
- // ...and even while dozing...
- apfFilter.setDozeMode(true);
- assertPass(ipClientCallback.assertProgramUpdateAndGet(), packet.array());
-
- // ...but when the multicast filter is also enabled, drop the multicast pings to save power.
- apfFilter.setMulticastFilter(true);
- assertDrop(ipClientCallback.assertProgramUpdateAndGet(), packet.array());
-
- // However, we should still let through all other ICMPv6 types.
- ByteBuffer raPacket = ByteBuffer.wrap(packet.array().clone());
- setIpv6VersionFields(packet);
- packet.put(IPV6_NEXT_HEADER_OFFSET, (byte) IPPROTO_ICMPV6);
- raPacket.put(ICMP6_TYPE_OFFSET, (byte) NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT);
- assertPass(ipClientCallback.assertProgramUpdateAndGet(), raPacket.array());
-
- // Now wake up from doze mode to ensure that we no longer drop the packets.
- // (The multicast filter is still enabled at this point).
- apfFilter.setDozeMode(false);
- assertPass(ipClientCallback.assertProgramUpdateAndGet(), packet.array());
-
- apfFilter.shutdown();
- }
-
- @Test
- @DevSdkIgnoreRule.IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- public void testApfFilter802_3() throws Exception {
- MockIpClientCallback ipClientCallback = new MockIpClientCallback();
- ApfConfiguration config = getDefaultConfig();
- TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback,
- mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
- byte[] program = ipClientCallback.assertProgramUpdateAndGet();
-
- // Verify empty packet of 100 zero bytes is passed
- // Note that eth-type = 0 makes it an IEEE802.3 frame
- ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
- assertPass(program, packet.array());
-
- // Verify empty packet with IPv4 is passed
- setIpv4VersionFields(packet);
- assertPass(program, packet.array());
-
- // Verify empty IPv6 packet is passed
- setIpv6VersionFields(packet);
- assertPass(program, packet.array());
-
- // Now turn on the filter
- ipClientCallback.resetApfProgramWait();
- config.ieee802_3Filter = DROP_802_3_FRAMES;
- apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback,
- mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
- program = ipClientCallback.assertProgramUpdateAndGet();
-
- // Verify that IEEE802.3 frame is dropped
- // In this case ethtype is used for payload length
- packet.putShort(ETH_ETHERTYPE_OFFSET, (short)(100 - 14));
- assertDrop(program, packet.array());
-
- // Verify that IPv4 (as example of Ethernet II) frame will pass
- setIpv4VersionFields(packet);
- assertPass(program, packet.array());
-
- // Verify that IPv6 (as example of Ethernet II) frame will pass
- setIpv6VersionFields(packet);
- assertPass(program, packet.array());
- }
-
- @Test
- @DevSdkIgnoreRule.IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- public void testApfFilterEthTypeBL() throws Exception {
- final int[] emptyBlackList = {};
- final int[] ipv4BlackList = {ETH_P_IP};
- final int[] ipv4Ipv6BlackList = {ETH_P_IP, ETH_P_IPV6};
-
- MockIpClientCallback ipClientCallback = new MockIpClientCallback();
- ApfConfiguration config = getDefaultConfig();
- TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback,
- mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
- byte[] program = ipClientCallback.assertProgramUpdateAndGet();
-
- // Verify empty packet of 100 zero bytes is passed
- // Note that eth-type = 0 makes it an IEEE802.3 frame
- ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
- assertPass(program, packet.array());
-
- // Verify empty packet with IPv4 is passed
- setIpv4VersionFields(packet);
- assertPass(program, packet.array());
-
- // Verify empty IPv6 packet is passed
- setIpv6VersionFields(packet);
- assertPass(program, packet.array());
-
- // Now add IPv4 to the black list
- ipClientCallback.resetApfProgramWait();
- config.ethTypeBlackList = ipv4BlackList;
- apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback,
- mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
- program = ipClientCallback.assertProgramUpdateAndGet();
-
- // Verify that IPv4 frame will be dropped
- setIpv4VersionFields(packet);
- assertDrop(program, packet.array());
-
- // Verify that IPv6 frame will pass
- setIpv6VersionFields(packet);
- assertPass(program, packet.array());
-
- // Now let us have both IPv4 and IPv6 in the black list
- ipClientCallback.resetApfProgramWait();
- config.ethTypeBlackList = ipv4Ipv6BlackList;
- apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback,
- mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
- program = ipClientCallback.assertProgramUpdateAndGet();
-
- // Verify that IPv4 frame will be dropped
- setIpv4VersionFields(packet);
- assertDrop(program, packet.array());
-
- // Verify that IPv6 frame will be dropped
- setIpv6VersionFields(packet);
- assertDrop(program, packet.array());
- }
-
- private byte[] getProgram(MockIpClientCallback cb, TestLegacyApfFilter filter,
- LinkProperties lp) {
- cb.resetApfProgramWait();
- filter.setLinkProperties(lp);
- return cb.assertProgramUpdateAndGet();
- }
-
- private void verifyArpFilter(byte[] program, int filterResult) {
- // Verify ARP request packet
- assertPass(program, arpRequestBroadcast(MOCK_IPV4_ADDR));
- assertVerdict(filterResult, program, arpRequestBroadcast(ANOTHER_IPV4_ADDR));
- assertVerdict(DROP, program, arpRequestBroadcast(IPV4_ANY_HOST_ADDR));
-
- // Verify ARP reply packets from different source ip
- assertDrop(program, arpReply(IPV4_ANY_HOST_ADDR, IPV4_ANY_HOST_ADDR));
- assertPass(program, arpReply(ANOTHER_IPV4_SOURCE_ADDR, IPV4_ANY_HOST_ADDR));
- assertPass(program, arpReply(BUG_PROBE_SOURCE_ADDR1, IPV4_ANY_HOST_ADDR));
- assertPass(program, arpReply(BUG_PROBE_SOURCE_ADDR2, IPV4_ANY_HOST_ADDR));
-
- // Verify unicast ARP reply packet is always accepted.
- assertPass(program, arpReply(IPV4_SOURCE_ADDR, MOCK_IPV4_ADDR));
- assertPass(program, arpReply(IPV4_SOURCE_ADDR, ANOTHER_IPV4_ADDR));
- assertPass(program, arpReply(IPV4_SOURCE_ADDR, IPV4_ANY_HOST_ADDR));
-
- // Verify GARP reply packets are always filtered
- assertDrop(program, garpReply());
- }
-
- @Test
- public void testApfFilterArp() throws Exception {
- MockIpClientCallback ipClientCallback = new MockIpClientCallback();
- ApfConfiguration config = getDefaultConfig();
- config.multicastFilter = DROP_MULTICAST;
- config.ieee802_3Filter = DROP_802_3_FRAMES;
- TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback,
- mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
-
- // Verify initially ARP request filter is off, and GARP filter is on.
- verifyArpFilter(ipClientCallback.assertProgramUpdateAndGet(), PASS);
-
- // Inform ApfFilter of our address and verify ARP filtering is on
- LinkAddress linkAddress = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 24);
- LinkProperties lp = new LinkProperties();
- assertTrue(lp.addLinkAddress(linkAddress));
- verifyArpFilter(getProgram(ipClientCallback, apfFilter, lp), DROP);
-
- // Inform ApfFilter of loss of IP and verify ARP filtering is off
- verifyArpFilter(getProgram(ipClientCallback, apfFilter, new LinkProperties()), PASS);
- }
-
- private static byte[] arpReply(byte[] sip, byte[] tip) {
- ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
- packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
- put(packet, ARP_HEADER_OFFSET, ARP_IPV4_REPLY_HEADER);
- put(packet, ARP_SOURCE_IP_ADDRESS_OFFSET, sip);
- put(packet, ARP_TARGET_IP_ADDRESS_OFFSET, tip);
- return packet.array();
- }
-
- private static byte[] arpRequestBroadcast(byte[] tip) {
- ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
- packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
- put(packet, ETH_DEST_ADDR_OFFSET, ETH_BROADCAST_MAC_ADDRESS);
- put(packet, ARP_HEADER_OFFSET, ARP_IPV4_REQUEST_HEADER);
- put(packet, ARP_TARGET_IP_ADDRESS_OFFSET, tip);
- return packet.array();
- }
-
- private static byte[] garpReply() {
- ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
- packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
- put(packet, ETH_DEST_ADDR_OFFSET, ETH_BROADCAST_MAC_ADDRESS);
- put(packet, ARP_HEADER_OFFSET, ARP_IPV4_REPLY_HEADER);
- put(packet, ARP_TARGET_IP_ADDRESS_OFFSET, IPV4_ANY_HOST_ADDR);
- return packet.array();
- }
-
- private static final byte[] IPV4_KEEPALIVE_SRC_ADDR = {10, 0, 0, 5};
- private static final byte[] IPV4_KEEPALIVE_DST_ADDR = {10, 0, 0, 6};
- private static final byte[] IPV4_ANOTHER_ADDR = {10, 0 , 0, 7};
- private static final byte[] IPV6_KEEPALIVE_SRC_ADDR =
- {(byte) 0x24, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xfa, (byte) 0xf1};
- private static final byte[] IPV6_KEEPALIVE_DST_ADDR =
- {(byte) 0x24, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xfa, (byte) 0xf2};
- private static final byte[] IPV6_ANOTHER_ADDR =
- {(byte) 0x24, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xfa, (byte) 0xf5};
-
- @Test
- public void testApfFilterKeepaliveAck() throws Exception {
- final MockIpClientCallback cb = new MockIpClientCallback();
- final ApfConfiguration config = getDefaultConfig();
- config.multicastFilter = DROP_MULTICAST;
- config.ieee802_3Filter = DROP_802_3_FRAMES;
- TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, cb,
- mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
- byte[] program;
- final int srcPort = 12345;
- final int dstPort = 54321;
- final int seqNum = 2123456789;
- final int ackNum = 1234567890;
- final int anotherSrcPort = 23456;
- final int anotherDstPort = 65432;
- final int anotherSeqNum = 2123456780;
- final int anotherAckNum = 1123456789;
- final int slot1 = 1;
- final int slot2 = 2;
- final int window = 14480;
- final int windowScale = 4;
-
- // src: 10.0.0.5, port: 12345
- // dst: 10.0.0.6, port: 54321
- InetAddress srcAddr = InetAddress.getByAddress(IPV4_KEEPALIVE_SRC_ADDR);
- InetAddress dstAddr = InetAddress.getByAddress(IPV4_KEEPALIVE_DST_ADDR);
-
- final TcpKeepalivePacketDataParcelable parcel = new TcpKeepalivePacketDataParcelable();
- parcel.srcAddress = srcAddr.getAddress();
- parcel.srcPort = srcPort;
- parcel.dstAddress = dstAddr.getAddress();
- parcel.dstPort = dstPort;
- parcel.seq = seqNum;
- parcel.ack = ackNum;
-
- apfFilter.addTcpKeepalivePacketFilter(slot1, parcel);
- program = cb.assertProgramUpdateAndGet();
-
- // Verify IPv4 keepalive ack packet is dropped
- // src: 10.0.0.6, port: 54321
- // dst: 10.0.0.5, port: 12345
- assertDrop(program,
- ipv4TcpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
- dstPort, srcPort, ackNum, seqNum + 1, 0 /* dataLength */));
- // Verify IPv4 non-keepalive ack packet from the same source address is passed
- assertPass(program,
- ipv4TcpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
- dstPort, srcPort, ackNum + 100, seqNum, 0 /* dataLength */));
- assertPass(program,
- ipv4TcpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
- dstPort, srcPort, ackNum, seqNum + 1, 10 /* dataLength */));
- // Verify IPv4 packet from another address is passed
- assertPass(program,
- ipv4TcpPacket(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR, anotherSrcPort,
- anotherDstPort, anotherSeqNum, anotherAckNum, 0 /* dataLength */));
-
- // Remove IPv4 keepalive filter
- apfFilter.removeKeepalivePacketFilter(slot1);
-
- try {
- // src: 2404:0:0:0:0:0:faf1, port: 12345
- // dst: 2404:0:0:0:0:0:faf2, port: 54321
- srcAddr = InetAddress.getByAddress(IPV6_KEEPALIVE_SRC_ADDR);
- dstAddr = InetAddress.getByAddress(IPV6_KEEPALIVE_DST_ADDR);
-
- final TcpKeepalivePacketDataParcelable ipv6Parcel =
- new TcpKeepalivePacketDataParcelable();
- ipv6Parcel.srcAddress = srcAddr.getAddress();
- ipv6Parcel.srcPort = srcPort;
- ipv6Parcel.dstAddress = dstAddr.getAddress();
- ipv6Parcel.dstPort = dstPort;
- ipv6Parcel.seq = seqNum;
- ipv6Parcel.ack = ackNum;
-
- apfFilter.addTcpKeepalivePacketFilter(slot1, ipv6Parcel);
- program = cb.assertProgramUpdateAndGet();
-
- // Verify IPv6 keepalive ack packet is dropped
- // src: 2404:0:0:0:0:0:faf2, port: 54321
- // dst: 2404:0:0:0:0:0:faf1, port: 12345
- assertDrop(program,
- ipv6TcpPacket(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR,
- dstPort, srcPort, ackNum, seqNum + 1));
- // Verify IPv6 non-keepalive ack packet from the same source address is passed
- assertPass(program,
- ipv6TcpPacket(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR,
- dstPort, srcPort, ackNum + 100, seqNum));
- // Verify IPv6 packet from another address is passed
- assertPass(program,
- ipv6TcpPacket(IPV6_ANOTHER_ADDR, IPV6_KEEPALIVE_SRC_ADDR, anotherSrcPort,
- anotherDstPort, anotherSeqNum, anotherAckNum));
-
- // Remove IPv6 keepalive filter
- apfFilter.removeKeepalivePacketFilter(slot1);
-
- // Verify multiple filters
- apfFilter.addTcpKeepalivePacketFilter(slot1, parcel);
- apfFilter.addTcpKeepalivePacketFilter(slot2, ipv6Parcel);
- program = cb.assertProgramUpdateAndGet();
-
- // Verify IPv4 keepalive ack packet is dropped
- // src: 10.0.0.6, port: 54321
- // dst: 10.0.0.5, port: 12345
- assertDrop(program,
- ipv4TcpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
- dstPort, srcPort, ackNum, seqNum + 1, 0 /* dataLength */));
- // Verify IPv4 non-keepalive ack packet from the same source address is passed
- assertPass(program,
- ipv4TcpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
- dstPort, srcPort, ackNum + 100, seqNum, 0 /* dataLength */));
- // Verify IPv4 packet from another address is passed
- assertPass(program,
- ipv4TcpPacket(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR, anotherSrcPort,
- anotherDstPort, anotherSeqNum, anotherAckNum, 0 /* dataLength */));
-
- // Verify IPv6 keepalive ack packet is dropped
- // src: 2404:0:0:0:0:0:faf2, port: 54321
- // dst: 2404:0:0:0:0:0:faf1, port: 12345
- assertDrop(program,
- ipv6TcpPacket(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR,
- dstPort, srcPort, ackNum, seqNum + 1));
- // Verify IPv6 non-keepalive ack packet from the same source address is passed
- assertPass(program,
- ipv6TcpPacket(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR,
- dstPort, srcPort, ackNum + 100, seqNum));
- // Verify IPv6 packet from another address is passed
- assertPass(program,
- ipv6TcpPacket(IPV6_ANOTHER_ADDR, IPV6_KEEPALIVE_SRC_ADDR, anotherSrcPort,
- anotherDstPort, anotherSeqNum, anotherAckNum));
-
- // Remove keepalive filters
- apfFilter.removeKeepalivePacketFilter(slot1);
- apfFilter.removeKeepalivePacketFilter(slot2);
- } catch (UnsupportedOperationException e) {
- // TODO: support V6 packets
- }
-
- program = cb.assertProgramUpdateAndGet();
-
- // Verify IPv4, IPv6 packets are passed
- assertPass(program,
- ipv4TcpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
- dstPort, srcPort, ackNum, seqNum + 1, 0 /* dataLength */));
- assertPass(program,
- ipv6TcpPacket(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR,
- dstPort, srcPort, ackNum, seqNum + 1));
- assertPass(program,
- ipv4TcpPacket(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR, srcPort,
- dstPort, anotherSeqNum, anotherAckNum, 0 /* dataLength */));
- assertPass(program,
- ipv6TcpPacket(IPV6_ANOTHER_ADDR, IPV6_KEEPALIVE_SRC_ADDR, srcPort,
- dstPort, anotherSeqNum, anotherAckNum));
- }
-
- private static byte[] ipv4TcpPacket(byte[] sip, byte[] dip, int sport,
- int dport, int seq, int ack, int dataLength) {
- final int totalLength = dataLength + IPV4_HEADER_LEN + IPV4_TCP_HEADER_LEN;
-
- ByteBuffer packet = ByteBuffer.wrap(new byte[totalLength + ETH_HEADER_LEN]);
-
- // Ethertype and IPv4 header
- setIpv4VersionFields(packet);
- packet.putShort(IPV4_TOTAL_LENGTH_OFFSET, (short) totalLength);
- packet.put(IPV4_PROTOCOL_OFFSET, (byte) IPPROTO_TCP);
- put(packet, IPV4_SRC_ADDR_OFFSET, sip);
- put(packet, IPV4_DEST_ADDR_OFFSET, dip);
- packet.putShort(IPV4_TCP_SRC_PORT_OFFSET, (short) sport);
- packet.putShort(IPV4_TCP_DEST_PORT_OFFSET, (short) dport);
- packet.putInt(IPV4_TCP_SEQ_NUM_OFFSET, seq);
- packet.putInt(IPV4_TCP_ACK_NUM_OFFSET, ack);
-
- // TCP header length 5(20 bytes), reserved 3 bits, NS=0
- packet.put(IPV4_TCP_HEADER_LENGTH_OFFSET, (byte) 0x50);
- // TCP flags: ACK set
- packet.put(IPV4_TCP_HEADER_FLAG_OFFSET, (byte) 0x10);
- return packet.array();
- }
-
- private static byte[] ipv6TcpPacket(byte[] sip, byte[] tip, int sport,
- int dport, int seq, int ack) {
- ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
- setIpv6VersionFields(packet);
- packet.put(IPV6_NEXT_HEADER_OFFSET, (byte) IPPROTO_TCP);
- put(packet, IPV6_SRC_ADDR_OFFSET, sip);
- put(packet, IPV6_DEST_ADDR_OFFSET, tip);
- packet.putShort(IPV6_TCP_SRC_PORT_OFFSET, (short) sport);
- packet.putShort(IPV6_TCP_DEST_PORT_OFFSET, (short) dport);
- packet.putInt(IPV6_TCP_SEQ_NUM_OFFSET, seq);
- packet.putInt(IPV6_TCP_ACK_NUM_OFFSET, ack);
- return packet.array();
- }
-
- @Test
- public void testApfFilterNattKeepalivePacket() throws Exception {
- final MockIpClientCallback cb = new MockIpClientCallback();
- final ApfConfiguration config = getDefaultConfig();
- config.multicastFilter = DROP_MULTICAST;
- config.ieee802_3Filter = DROP_802_3_FRAMES;
- TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, cb,
- mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
- byte[] program;
- final int srcPort = 1024;
- final int dstPort = 4500;
- final int slot1 = 1;
- // NAT-T keepalive
- final byte[] kaPayload = {(byte) 0xff};
- final byte[] nonKaPayload = {(byte) 0xfe};
-
- // src: 10.0.0.5, port: 1024
- // dst: 10.0.0.6, port: 4500
- InetAddress srcAddr = InetAddress.getByAddress(IPV4_KEEPALIVE_SRC_ADDR);
- InetAddress dstAddr = InetAddress.getByAddress(IPV4_KEEPALIVE_DST_ADDR);
-
- final NattKeepalivePacketDataParcelable parcel = new NattKeepalivePacketDataParcelable();
- parcel.srcAddress = srcAddr.getAddress();
- parcel.srcPort = srcPort;
- parcel.dstAddress = dstAddr.getAddress();
- parcel.dstPort = dstPort;
-
- apfFilter.addNattKeepalivePacketFilter(slot1, parcel);
- program = cb.assertProgramUpdateAndGet();
-
- // Verify IPv4 keepalive packet is dropped
- // src: 10.0.0.6, port: 4500
- // dst: 10.0.0.5, port: 1024
- byte[] pkt = ipv4UdpPacket(IPV4_KEEPALIVE_DST_ADDR,
- IPV4_KEEPALIVE_SRC_ADDR, dstPort, srcPort, 1 /* dataLength */);
- System.arraycopy(kaPayload, 0, pkt, IPV4_UDP_PAYLOAD_OFFSET, kaPayload.length);
- assertDrop(program, pkt);
-
- // Verify a packet with payload length 1 byte but it is not 0xff will pass the filter.
- System.arraycopy(nonKaPayload, 0, pkt, IPV4_UDP_PAYLOAD_OFFSET, nonKaPayload.length);
- assertPass(program, pkt);
-
- // Verify IPv4 non-keepalive response packet from the same source address is passed
- assertPass(program,
- ipv4UdpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
- dstPort, srcPort, 10 /* dataLength */));
-
- // Verify IPv4 non-keepalive response packet from other source address is passed
- assertPass(program,
- ipv4UdpPacket(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
- dstPort, srcPort, 10 /* dataLength */));
-
- apfFilter.removeKeepalivePacketFilter(slot1);
- }
-
- private static byte[] ipv4UdpPacket(byte[] sip, byte[] dip, int sport,
- int dport, int dataLength) {
- final int totalLength = dataLength + IPV4_HEADER_LEN + UDP_HEADER_LEN;
- final int udpLength = UDP_HEADER_LEN + dataLength;
- ByteBuffer packet = ByteBuffer.wrap(new byte[totalLength + ETH_HEADER_LEN]);
-
- // Ethertype and IPv4 header
- setIpv4VersionFields(packet);
- packet.putShort(IPV4_TOTAL_LENGTH_OFFSET, (short) totalLength);
- packet.put(IPV4_PROTOCOL_OFFSET, (byte) IPPROTO_UDP);
- put(packet, IPV4_SRC_ADDR_OFFSET, sip);
- put(packet, IPV4_DEST_ADDR_OFFSET, dip);
- packet.putShort(IPV4_UDP_SRC_PORT_OFFSET, (short) sport);
- packet.putShort(IPV4_UDP_DEST_PORT_OFFSET, (short) dport);
- packet.putShort(IPV4_UDP_LENGTH_OFFSET, (short) udpLength);
-
- return packet.array();
- }
-
- private static class RaPacketBuilder {
- final ByteArrayOutputStream mPacket = new ByteArrayOutputStream();
- int mFlowLabel = 0x12345;
- int mReachableTime = 30_000;
- int mRetransmissionTimer = 1000;
-
- public RaPacketBuilder(int routerLft) throws Exception {
- InetAddress src = InetAddress.getByName("fe80::1234:abcd");
- ByteBuffer buffer = ByteBuffer.allocate(ICMP6_RA_OPTION_OFFSET);
-
- buffer.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IPV6);
- buffer.position(ETH_HEADER_LEN);
-
- // skip version, tclass, flowlabel; set in build()
- buffer.position(buffer.position() + 4);
-
- buffer.putShort((short) 0); // Payload length; updated later
- buffer.put((byte) IPPROTO_ICMPV6); // Next header
- buffer.put((byte) 0xff); // Hop limit
- buffer.put(src.getAddress()); // Source address
- buffer.put(IPV6_ALL_NODES_ADDRESS); // Destination address
-
- buffer.put((byte) ICMP6_ROUTER_ADVERTISEMENT); // Type
- buffer.put((byte) 0); // Code (0)
- buffer.putShort((short) 0); // Checksum (ignored)
- buffer.put((byte) 64); // Hop limit
- buffer.put((byte) 0); // M/O, reserved
- buffer.putShort((short) routerLft); // Router lifetime
- // skip reachable time; set in build()
- // skip retransmission timer; set in build();
-
- mPacket.write(buffer.array(), 0, buffer.capacity());
- }
-
- public RaPacketBuilder setFlowLabel(int flowLabel) {
- mFlowLabel = flowLabel;
- return this;
- }
-
- public RaPacketBuilder setReachableTime(int reachable) {
- mReachableTime = reachable;
- return this;
- }
-
- public RaPacketBuilder setRetransmissionTimer(int retrans) {
- mRetransmissionTimer = retrans;
- return this;
- }
-
- public RaPacketBuilder addPioOption(int valid, int preferred, String prefixString)
- throws Exception {
- ByteBuffer buffer = ByteBuffer.allocate(ICMP6_PREFIX_OPTION_LEN);
-
- IpPrefix prefix = new IpPrefix(prefixString);
- buffer.put((byte) ICMP6_PREFIX_OPTION_TYPE); // Type
- buffer.put((byte) 4); // Length in 8-byte units
- buffer.put((byte) prefix.getPrefixLength()); // Prefix length
- buffer.put((byte) 0b11000000); // L = 1, A = 1
- buffer.putInt(valid);
- buffer.putInt(preferred);
- buffer.putInt(0); // Reserved
- buffer.put(prefix.getRawAddress());
-
- mPacket.write(buffer.array(), 0, buffer.capacity());
- return this;
- }
-
- public RaPacketBuilder addRioOption(int lifetime, String prefixString) throws Exception {
- IpPrefix prefix = new IpPrefix(prefixString);
-
- int optionLength;
- if (prefix.getPrefixLength() == 0) {
- optionLength = 1;
- } else if (prefix.getPrefixLength() <= 64) {
- optionLength = 2;
- } else {
- optionLength = 3;
- }
-
- ByteBuffer buffer = ByteBuffer.allocate(optionLength * 8);
-
- buffer.put((byte) ICMP6_ROUTE_INFO_OPTION_TYPE); // Type
- buffer.put((byte) optionLength); // Length in 8-byte units
- buffer.put((byte) prefix.getPrefixLength()); // Prefix length
- buffer.put((byte) 0b00011000); // Pref = high
- buffer.putInt(lifetime); // Lifetime
-
- byte[] prefixBytes = prefix.getRawAddress();
- buffer.put(prefixBytes, 0, (optionLength - 1) * 8);
-
- mPacket.write(buffer.array(), 0, buffer.capacity());
- return this;
- }
-
- public RaPacketBuilder addDnsslOption(int lifetime, String... domains) {
- ByteArrayOutputStream dnssl = new ByteArrayOutputStream();
- for (String domain : domains) {
- for (String label : domain.split("\\.")) {
- final byte[] bytes = label.getBytes(StandardCharsets.UTF_8);
- dnssl.write((byte) bytes.length);
- dnssl.write(bytes, 0, bytes.length);
- }
- dnssl.write((byte) 0);
- }
-
- // Extend with 0s to make it 8-byte aligned.
- while (dnssl.size() % 8 != 0) {
- dnssl.write((byte) 0);
- }
-
- final int length = ICMP6_4_BYTE_OPTION_LEN + dnssl.size();
- ByteBuffer buffer = ByteBuffer.allocate(length);
-
- buffer.put((byte) ICMP6_DNSSL_OPTION_TYPE); // Type
- buffer.put((byte) (length / 8)); // Length
- // skip past reserved bytes
- buffer.position(buffer.position() + 2);
- buffer.putInt(lifetime); // Lifetime
- buffer.put(dnssl.toByteArray()); // Domain names
-
- mPacket.write(buffer.array(), 0, buffer.capacity());
- return this;
- }
-
- public RaPacketBuilder addRdnssOption(int lifetime, String... servers) throws Exception {
- int optionLength = 1 + 2 * servers.length; // In 8-byte units
- ByteBuffer buffer = ByteBuffer.allocate(optionLength * 8);
-
- buffer.put((byte) ICMP6_RDNSS_OPTION_TYPE); // Type
- buffer.put((byte) optionLength); // Length
- buffer.putShort((short) 0); // Reserved
- buffer.putInt(lifetime); // Lifetime
- for (String server : servers) {
- buffer.put(InetAddress.getByName(server).getAddress());
- }
-
- mPacket.write(buffer.array(), 0, buffer.capacity());
- return this;
- }
-
- public RaPacketBuilder addZeroLengthOption() throws Exception {
- ByteBuffer buffer = ByteBuffer.allocate(ICMP6_4_BYTE_OPTION_LEN);
- buffer.put((byte) ICMP6_PREFIX_OPTION_TYPE);
- buffer.put((byte) 0);
-
- mPacket.write(buffer.array(), 0, buffer.capacity());
- return this;
- }
-
- public byte[] build() {
- ByteBuffer buffer = ByteBuffer.wrap(mPacket.toByteArray());
- // IPv6, traffic class = 0, flow label = mFlowLabel
- buffer.putInt(IP_HEADER_OFFSET, 0x60000000 | (0xFFFFF & mFlowLabel));
- buffer.putShort(IPV6_PAYLOAD_LENGTH_OFFSET, (short) buffer.capacity());
-
- buffer.position(ICMP6_RA_REACHABLE_TIME_OFFSET);
- buffer.putInt(mReachableTime);
- buffer.putInt(mRetransmissionTimer);
-
- return buffer.array();
- }
- }
-
- private byte[] buildLargeRa() throws Exception {
- RaPacketBuilder builder = new RaPacketBuilder(1800 /* router lft */);
-
- builder.addRioOption(1200, "64:ff9b::/96");
- builder.addRdnssOption(7200, "2001:db8:1::1", "2001:db8:1::2");
- builder.addRioOption(2100, "2000::/3");
- builder.addRioOption(2400, "::/0");
- builder.addPioOption(600, 300, "2001:db8:a::/64");
- builder.addRioOption(1500, "2001:db8:c:d::/64");
- builder.addPioOption(86400, 43200, "fd95:d1e:12::/64");
-
- return builder.build();
- }
-
- // Verify that the last program pushed to the IpClient.Callback properly filters the
- // given packet for the given lifetime.
- private void verifyRaLifetime(byte[] program, ByteBuffer packet, int lifetime) {
- verifyRaLifetime(program, packet, lifetime, 0);
- }
-
- // Verify that the last program pushed to the IpClient.Callback properly filters the
- // given packet for the given lifetime and programInstallTime. programInstallTime is
- // the time difference between when RA is last seen and the program is installed.
- private void verifyRaLifetime(byte[] program, ByteBuffer packet, int lifetime,
- int programInstallTime) {
- final int FRACTION_OF_LIFETIME = 6;
- final int ageLimit = lifetime / FRACTION_OF_LIFETIME - programInstallTime;
-
- // Verify new program should drop RA for 1/6th its lifetime and pass afterwards.
- assertDrop(program, packet.array());
- assertDrop(program, packet.array(), ageLimit);
- assertPass(program, packet.array(), ageLimit + 1);
- assertPass(program, packet.array(), lifetime);
- // Verify RA checksum is ignored
- final short originalChecksum = packet.getShort(ICMP6_RA_CHECKSUM_OFFSET);
- packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, (short)12345);
- assertDrop(program, packet.array());
- packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, (short)-12345);
- assertDrop(program, packet.array());
- packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, originalChecksum);
-
- // Verify other changes to RA (e.g., a change in the source address) make it not match.
- final int offset = IPV6_SRC_ADDR_OFFSET + 5;
- final byte originalByte = packet.get(offset);
- packet.put(offset, (byte) (~originalByte));
- assertPass(program, packet.array());
- packet.put(offset, originalByte);
- assertDrop(program, packet.array());
- }
-
- // Test that when ApfFilter is shown the given packet, it generates a program to filter it
- // for the given lifetime.
- private void verifyRaLifetime(TestLegacyApfFilter apfFilter,
- MockIpClientCallback ipClientCallback, ByteBuffer packet, int lifetime)
- throws IOException, ErrnoException {
- // Verify new program generated if ApfFilter witnesses RA
- apfFilter.pretendPacketReceived(packet.array());
- byte[] program = ipClientCallback.assertProgramUpdateAndGet();
- verifyRaLifetime(program, packet, lifetime);
- }
-
- private void assertInvalidRa(TestLegacyApfFilter apfFilter,
- MockIpClientCallback ipClientCallback, ByteBuffer packet)
- throws IOException, ErrnoException {
- apfFilter.pretendPacketReceived(packet.array());
- ipClientCallback.assertNoProgramUpdate();
- }
-
- @Test
- public void testApfFilterRa() throws Exception {
- MockIpClientCallback ipClientCallback = new MockIpClientCallback();
- ApfConfiguration config = getDefaultConfig();
- config.multicastFilter = DROP_MULTICAST;
- config.ieee802_3Filter = DROP_802_3_FRAMES;
- TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback,
- mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
- byte[] program = ipClientCallback.assertProgramUpdateAndGet();
-
- final int ROUTER_LIFETIME = 1000;
- final int PREFIX_VALID_LIFETIME = 200;
- final int PREFIX_PREFERRED_LIFETIME = 100;
- final int RDNSS_LIFETIME = 300;
- final int ROUTE_LIFETIME = 400;
- // Note that lifetime of 2000 will be ignored in favor of shorter route lifetime of 1000.
- final int DNSSL_LIFETIME = 2000;
-
- // Verify RA is passed the first time
- RaPacketBuilder ra = new RaPacketBuilder(ROUTER_LIFETIME);
- ByteBuffer basePacket = ByteBuffer.wrap(ra.build());
- assertPass(program, basePacket.array());
-
- verifyRaLifetime(apfFilter, ipClientCallback, basePacket, ROUTER_LIFETIME);
-
- ra = new RaPacketBuilder(ROUTER_LIFETIME);
- // Check that changes are ignored in every byte of the flow label.
- ra.setFlowLabel(0x56789);
- ByteBuffer newFlowLabelPacket = ByteBuffer.wrap(ra.build());
-
- // Ensure zero-length options cause the packet to be silently skipped.
- // Do this before we test other packets. http://b/29586253
- ra = new RaPacketBuilder(ROUTER_LIFETIME);
- ra.addZeroLengthOption();
- ByteBuffer zeroLengthOptionPacket = ByteBuffer.wrap(ra.build());
- assertInvalidRa(apfFilter, ipClientCallback, zeroLengthOptionPacket);
-
- // Generate several RAs with different options and lifetimes, and verify when
- // ApfFilter is shown these packets, it generates programs to filter them for the
- // appropriate lifetime.
- ra = new RaPacketBuilder(ROUTER_LIFETIME);
- ra.addPioOption(PREFIX_VALID_LIFETIME, PREFIX_PREFERRED_LIFETIME, "2001:db8::/64");
- ByteBuffer prefixOptionPacket = ByteBuffer.wrap(ra.build());
- verifyRaLifetime(
- apfFilter, ipClientCallback, prefixOptionPacket, PREFIX_PREFERRED_LIFETIME);
-
- ra = new RaPacketBuilder(ROUTER_LIFETIME);
- ra.addRdnssOption(RDNSS_LIFETIME, "2001:4860:4860::8888", "2001:4860:4860::8844");
- ByteBuffer rdnssOptionPacket = ByteBuffer.wrap(ra.build());
- verifyRaLifetime(apfFilter, ipClientCallback, rdnssOptionPacket, RDNSS_LIFETIME);
-
- final int lowLifetime = 60;
- ra = new RaPacketBuilder(ROUTER_LIFETIME);
- ra.addRdnssOption(lowLifetime, "2620:fe::9");
- ByteBuffer lowLifetimeRdnssOptionPacket = ByteBuffer.wrap(ra.build());
- verifyRaLifetime(apfFilter, ipClientCallback, lowLifetimeRdnssOptionPacket,
- ROUTER_LIFETIME);
-
- ra = new RaPacketBuilder(ROUTER_LIFETIME);
- ra.addRioOption(ROUTE_LIFETIME, "64:ff9b::/96");
- ByteBuffer routeInfoOptionPacket = ByteBuffer.wrap(ra.build());
- verifyRaLifetime(apfFilter, ipClientCallback, routeInfoOptionPacket, ROUTE_LIFETIME);
-
- // Check that RIOs differing only in the first 4 bytes are different.
- ra = new RaPacketBuilder(ROUTER_LIFETIME);
- ra.addRioOption(ROUTE_LIFETIME, "64:ff9b::/64");
- // Packet should be passed because it is different.
- program = ipClientCallback.assertProgramUpdateAndGet();
- assertPass(program, ra.build());
-
- ra = new RaPacketBuilder(ROUTER_LIFETIME);
- ra.addDnsslOption(DNSSL_LIFETIME, "test.example.com", "one.more.example.com");
- ByteBuffer dnsslOptionPacket = ByteBuffer.wrap(ra.build());
- verifyRaLifetime(apfFilter, ipClientCallback, dnsslOptionPacket, ROUTER_LIFETIME);
-
- ByteBuffer largeRaPacket = ByteBuffer.wrap(buildLargeRa());
- verifyRaLifetime(apfFilter, ipClientCallback, largeRaPacket, 300);
-
- // Verify that current program filters all the RAs (note: ApfFilter.MAX_RAS == 10).
- program = ipClientCallback.assertProgramUpdateAndGet();
- verifyRaLifetime(program, basePacket, ROUTER_LIFETIME);
- verifyRaLifetime(program, newFlowLabelPacket, ROUTER_LIFETIME);
- verifyRaLifetime(program, prefixOptionPacket, PREFIX_PREFERRED_LIFETIME);
- verifyRaLifetime(program, rdnssOptionPacket, RDNSS_LIFETIME);
- verifyRaLifetime(program, lowLifetimeRdnssOptionPacket, ROUTER_LIFETIME);
- verifyRaLifetime(program, routeInfoOptionPacket, ROUTE_LIFETIME);
- verifyRaLifetime(program, dnsslOptionPacket, ROUTER_LIFETIME);
- verifyRaLifetime(program, largeRaPacket, 300);
- }
-
- @Test
- public void testRaWithDifferentReachableTimeAndRetransTimer() throws Exception {
- final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
- final ApfConfiguration config = getDefaultConfig();
- config.multicastFilter = DROP_MULTICAST;
- config.ieee802_3Filter = DROP_802_3_FRAMES;
- final TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config,
- ipClientCallback, mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
- byte[] program = ipClientCallback.assertProgramUpdateAndGet();
- final int RA_REACHABLE_TIME = 1800;
- final int RA_RETRANSMISSION_TIMER = 1234;
-
- // Create an Ra packet without options
- // Reachable time = 1800, retransmission timer = 1234
- RaPacketBuilder ra = new RaPacketBuilder(1800 /* router lft */);
- ra.setReachableTime(RA_REACHABLE_TIME);
- ra.setRetransmissionTimer(RA_RETRANSMISSION_TIMER);
- byte[] raPacket = ra.build();
- // First RA passes filter
- assertPass(program, raPacket);
-
- // Assume apf is shown the given RA, it generates program to filter it.
- apfFilter.pretendPacketReceived(raPacket);
- program = ipClientCallback.assertProgramUpdateAndGet();
- assertDrop(program, raPacket);
-
- // A packet with different reachable time should be passed.
- // Reachable time = 2300, retransmission timer = 1234
- ra.setReachableTime(RA_REACHABLE_TIME + 500);
- raPacket = ra.build();
- assertPass(program, raPacket);
-
- // A packet with different retransmission timer should be passed.
- // Reachable time = 1800, retransmission timer = 2234
- ra.setReachableTime(RA_REACHABLE_TIME);
- ra.setRetransmissionTimer(RA_RETRANSMISSION_TIMER + 1000);
- raPacket = ra.build();
- assertPass(program, raPacket);
- }
-
- /**
- * Stage a file for testing, i.e. make it native accessible. Given a resource ID,
- * copy that resource into the app's data directory and return the path to it.
- */
- private String stageFile(int rawId) throws Exception {
- File file = new File(InstrumentationRegistry.getContext().getFilesDir(), "staged_file");
- new File(file.getParent()).mkdirs();
- InputStream in = null;
- OutputStream out = null;
- try {
- in = InstrumentationRegistry.getContext().getResources().openRawResource(rawId);
- out = new FileOutputStream(file);
- Streams.copy(in, out);
- } finally {
- if (in != null) in.close();
- if (out != null) out.close();
- }
- return file.getAbsolutePath();
- }
-
- private static void put(ByteBuffer buffer, int position, byte[] bytes) {
- final int original = buffer.position();
- buffer.position(position);
- buffer.put(bytes);
- buffer.position(original);
- }
-
- @Test
- public void testRaParsing() throws Exception {
- final int maxRandomPacketSize = 512;
- final Random r = new Random();
- MockIpClientCallback cb = new MockIpClientCallback();
- ApfConfiguration config = getDefaultConfig();
- config.multicastFilter = DROP_MULTICAST;
- config.ieee802_3Filter = DROP_802_3_FRAMES;
- final TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config,
- cb, mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
- for (int i = 0; i < 1000; i++) {
- byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
- r.nextBytes(packet);
- try {
- apfFilter.new Ra(packet, packet.length);
- } catch (LegacyApfFilter.InvalidRaException e) {
- } catch (Exception e) {
- throw new Exception("bad packet: " + HexDump.toHexString(packet), e);
- }
- }
- }
-
- @Test
- public void testRaProcessing() throws Exception {
- final int maxRandomPacketSize = 512;
- final Random r = new Random();
- MockIpClientCallback cb = new MockIpClientCallback();
- ApfConfiguration config = getDefaultConfig();
- config.multicastFilter = DROP_MULTICAST;
- config.ieee802_3Filter = DROP_802_3_FRAMES;
- final TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config,
- cb, mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
- for (int i = 0; i < 1000; i++) {
- byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
- r.nextBytes(packet);
- try {
- apfFilter.processRa(packet, packet.length);
- } catch (Exception e) {
- throw new Exception("bad packet: " + HexDump.toHexString(packet), e);
- }
- }
- }
-
- @Test
- public void testProcessRaWithInfiniteLifeTimeWithoutCrash() throws Exception {
- final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
- // configure accept_ra_min_lft
- final ApfConfiguration config = getDefaultConfig();
- config.acceptRaMinLft = 180;
- TestLegacyApfFilter apfFilter;
- // Template packet:
- // Frame 1: 150 bytes on wire (1200 bits), 150 bytes captured (1200 bits)
- // Ethernet II, Src: Netgear_23:67:2c (28:c6:8e:23:67:2c), Dst: IPv6mcast_01 (33:33:00:00:00:01)
- // Internet Protocol Version 6, Src: fe80::2ac6:8eff:fe23:672c, Dst: ff02::1
- // Internet Control Message Protocol v6
- // Type: Router Advertisement (134)
- // Code: 0
- // Checksum: 0x0acd [correct]
- // Checksum Status: Good
- // Cur hop limit: 64
- // Flags: 0xc0, Managed address configuration, Other configuration, Prf (Default Router Preference): Medium
- // Router lifetime (s): 7000
- // Reachable time (ms): 0
- // Retrans timer (ms): 0
- // ICMPv6 Option (Source link-layer address : 28:c6:8e:23:67:2c)
- // Type: Source link-layer address (1)
- // Length: 1 (8 bytes)
- // Link-layer address: Netgear_23:67:2c (28:c6:8e:23:67:2c)
- // Source Link-layer address: Netgear_23:67:2c (28:c6:8e:23:67:2c)
- // ICMPv6 Option (MTU : 1500)
- // Type: MTU (5)
- // Length: 1 (8 bytes)
- // Reserved
- // MTU: 1500
- // ICMPv6 Option (Prefix information : 2401:fa00:480:f000::/64)
- // Type: Prefix information (3)
- // Length: 4 (32 bytes)
- // Prefix Length: 64
- // Flag: 0xc0, On-link flag(L), Autonomous address-configuration flag(A)
- // Valid Lifetime: Infinity (4294967295)
- // Preferred Lifetime: Infinity (4294967295)
- // Reserved
- // Prefix: 2401:fa00:480:f000::
- // ICMPv6 Option (Recursive DNS Server 2401:fa00:480:f000::1)
- // Type: Recursive DNS Server (25)
- // Length: 3 (24 bytes)
- // Reserved
- // Lifetime: 7000
- // Recursive DNS Servers: 2401:fa00:480:f000::1
- // ICMPv6 Option (Advertisement Interval : 600000)
- // Type: Advertisement Interval (7)
- // Length: 1 (8 bytes)
- // Reserved
- // Advertisement Interval: 600000
- final String packetStringFmt = "33330000000128C68E23672C86DD60054C6B00603AFFFE800000000000002AC68EFFFE23672CFF02000000000000000000000000000186000ACD40C01B580000000000000000010128C68E23672C05010000000005DC030440C0%s000000002401FA000480F00000000000000000001903000000001B582401FA000480F000000000000000000107010000000927C0";
- final List<String> lifetimes = List.of("FFFFFFFF", "00000001", "00001B58");
- for (String lifetime : lifetimes) {
- apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback,
- mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
- final byte[] ra = hexStringToByteArray(
- String.format(packetStringFmt, lifetime + lifetime));
- // feed the RA into APF and generate the filter, the filter shouldn't crash.
- apfFilter.pretendPacketReceived(ra);
- ipClientCallback.assertProgramUpdateAndGet();
- }
- }
-
- private TestAndroidPacketFilter makeTestApfFilter(ApfConfiguration config,
- MockIpClientCallback ipClientCallback) throws Exception {
- return new TestLegacyApfFilter(mContext, config, ipClientCallback, mIpConnectivityLog,
- mNetworkQuirkMetrics, mDependencies, mClock);
- }
-
-
- @Test
- public void testInstallPacketFilterFailure_LegacyApfFilter() throws Exception {
- final MockIpClientCallback ipClientCallback = new MockIpClientCallback(false);
- final ApfConfiguration config = getDefaultConfig();
- final TestAndroidPacketFilter apfFilter = makeTestApfFilter(config, ipClientCallback);
- 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();
- }
-
- @Test
- public void testApfProgramOverSize_LegacyApfFilter() throws Exception {
- final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
- final ApfConfiguration config = getDefaultConfig();
- config.apfVersionSupported = 2;
- config.apfRamSize = 512;
- final TestAndroidPacketFilter apfFilter = makeTestApfFilter(config, ipClientCallback);
- 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();
- }
-
- @Test
- public void testGenerateApfProgramException_LegacyApfFilter() throws Exception {
- final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
- final ApfConfiguration config = getDefaultConfig();
- final TestAndroidPacketFilter apfFilter;
- apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback, mIpConnectivityLog,
- mNetworkQuirkMetrics, mDependencies,
- true /* throwsExceptionWhenGeneratesProgram */);
- synchronized (apfFilter) {
- apfFilter.installNewProgramLocked();
- }
- verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_GENERATE_FILTER_EXCEPTION);
- verify(mNetworkQuirkMetrics).statsWrite();
- }
-
- @Test
- public void testApfSessionInfoMetrics_LegacyApfFilter() throws Exception {
- final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
- final ApfConfiguration config = getDefaultConfig();
- config.apfVersionSupported = 4;
- config.apfRamSize = 4096;
- final long startTimeMs = 12345;
- final long durationTimeMs = config.minMetricsSessionDurationMs;
- doReturn(startTimeMs).when(mClock).elapsedRealtime();
- final TestAndroidPacketFilter apfFilter = makeTestApfFilter(config, ipClientCallback);
- 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;
- assertDataMemoryContentsIgnoreVersion(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;
- assertDataMemoryContentsIgnoreVersion(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 testIpClientRaInfoMetrics_LegacyApfFilter() 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);
- 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());
- ipClientCallback.assertNoProgramUpdate();
- apfFilter.pretendPacketReceived(raZeroPioValidLifetime.build());
- ipClientCallback.assertNoProgramUpdate();
- apfFilter.pretendPacketReceived(raZeroRdnssLifetime.build());
- ipClientCallback.assertNoProgramUpdate();
- apfFilter.pretendPacketReceived(raZeroRioRouteLifetime.build());
- ipClientCallback.assertNoProgramUpdate();
-
- // Write metrics data to statsd pipeline when shutdown.
- doReturn(startTimeMs + durationTimeMs).when(mClock).elapsedRealtime();
- apfFilter.shutdown();
-
- // Verify each metric fields in IpClientRaInfoMetrics.
- // 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);
- 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 testNoMetricsWrittenForShortDuration_LegacyApfFilter() 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);
- 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.
- LegacyApfFilter.Clock clock = mock(LegacyApfFilter.Clock.class);
- doReturn(startTimeMs).when(clock).elapsedRealtime();
- final TestAndroidPacketFilter apfFilter2 = new TestLegacyApfFilter(mContext, config,
- ipClientCallback, mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, clock);
- doReturn(startTimeMs + durationTimeMs).when(clock).elapsedRealtime();
- apfFilter2.shutdown();
- verify(mApfSessionInfoMetrics).statsWrite();
- verify(mIpClientRaInfoMetrics).statsWrite();
- }
-
- /**
- * The Mock ip client callback class.
- */
- private 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), mock(SharedLog.class),
- NetworkInformationShimImpl.newInstance(), false);
- }
-
- MockIpClientCallback(boolean installPacketFilterReturn) {
- super(mock(IIpClientCallbacks.class), mock(SharedLog.class), mock(SharedLog.class),
- NetworkInformationShimImpl.newInstance(), false);
- mInstallPacketFilterReturn = installPacketFilterReturn;
- }
-
- @Override
- public boolean installPacketFilter(byte[] filter) {
- mLastApfProgram = filter;
- mGotApfProgram.open();
- return mInstallPacketFilterReturn;
- }
-
- /**
- * Reset the apf program and wait for the next update.
- */
- public void resetApfProgramWait() {
- mGotApfProgram.close();
- }
-
- /**
- * Assert the program is update within TIMEOUT_MS and return the program.
- */
- public byte[] assertProgramUpdateAndGet() {
- assertTrue(mGotApfProgram.block(TIMEOUT_MS));
- return mLastApfProgram;
- }
-
- /**
- * Assert the program is not update within TIMEOUT_MS.
- */
- public void assertNoProgramUpdate() {
- assertFalse(mGotApfProgram.block(TIMEOUT_MS));
- }
- }
-
- /**
- * The test legacy apf filter class.
- */
- private 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;
-
- 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 Clock());
- }
-
- 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 Clock());
- }
-
- TestLegacyApfFilter(Context context, ApfFilter.ApfConfiguration config,
- MockIpClientCallback ipClientCallback, IpConnectivityLog ipConnectivityLog,
- NetworkQuirkMetrics networkQuirkMetrics, ApfFilter.Dependencies dependencies,
- Clock clock) throws Exception {
- this(context, config, ipClientCallback, ipConnectivityLog, networkQuirkMetrics,
- dependencies, false /* throwsExceptionWhenGeneratesProgram */, clock);
- }
-
- TestLegacyApfFilter(Context context, ApfFilter.ApfConfiguration config,
- MockIpClientCallback ipClientCallback, IpConnectivityLog ipConnectivityLog,
- NetworkQuirkMetrics networkQuirkMetrics, ApfFilter.Dependencies dependencies,
- boolean throwsExceptionWhenGeneratesProgram, 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 ApfV4Generator emitPrologueLocked() throws
- BaseApfGenerator.IllegalInstructionException {
- if (mThrowsExceptionWhenGeneratesProgram) {
- throw new IllegalStateException();
- }
- return super.emitPrologueLocked();
- }
- }
-}
diff --git a/tests/unit/src/android/net/apf/TestAndroidPacketFilter.java b/tests/unit/src/android/net/apf/TestAndroidPacketFilter.java
deleted file mode 100644
index 39386cd..0000000
--- a/tests/unit/src/android/net/apf/TestAndroidPacketFilter.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package 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/ip/ConnectivityPacketTrackerTest.kt b/tests/unit/src/android/net/ip/ConnectivityPacketTrackerTest.kt
index 51a871d..675e6c8 100644
--- a/tests/unit/src/android/net/ip/ConnectivityPacketTrackerTest.kt
+++ b/tests/unit/src/android/net/ip/ConnectivityPacketTrackerTest.kt
@@ -39,6 +39,7 @@
import org.junit.After
import org.junit.Before
import org.junit.Test
+import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito
@@ -78,7 +79,7 @@
MockitoAnnotations.initMocks(this)
val readSocket = FileDescriptor()
Os.socketpair(AF_UNIX, SOCK_STREAM or SOCK_NONBLOCK, 0, writeSocket, readSocket)
- doReturn(readSocket).`when`(mDependencies).createPacketReaderSocket(anyInt())
+ doReturn(readSocket).`when`(mDependencies).createPacketReaderSocket(anyInt(), anyBoolean())
doReturn(TEST_MAX_CAPTURE_PKT_SIZE).`when`(mDependencies).maxCapturePktSize
}
@@ -123,6 +124,26 @@
}
@Test
+ fun testPacketMatchPattern() {
+ val packetTracker = getConnectivityPacketTracker()
+ // Using scapy to generate ARP request packet:
+ // eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06")
+ // arp = ARP()
+ // pkt = eth/arp
+ val arpPkt = """
+ 010203040506000102030405080600010800060400015c857e3c74e1c0a8012200000000000000000000
+ """.replace("\\s+".toRegex(), "").trim().uppercase()
+ val arpPktByteArray = HexDump.hexStringToByteArray(arpPkt)
+
+ // start capture packet
+ setCapture(packetTracker, true)
+
+ pretendPacketReceive(arpPktByteArray)
+ assertEquals(1, getMatchedPacketCount(packetTracker, arpPkt))
+ assertEquals(1, getMatchedPacketCount(packetTracker, arpPkt.lowercase()))
+ }
+
+ @Test
fun testMaxCapturePacketSize() {
doReturn(3).`when`(mDependencies).maxCapturePktSize
val packetTracker = getConnectivityPacketTracker(mDependencies)
@@ -171,7 +192,13 @@
val result = CompletableFuture<ConnectivityPacketTracker>()
handler.post {
try {
- val tracker = ConnectivityPacketTracker(handler, ifParams, localLog, dependencies)
+ val tracker = ConnectivityPacketTracker(
+ handler,
+ ifParams,
+ localLog,
+ dependencies,
+ true
+ )
tracker.start(TAG)
result.complete(tracker)
} catch (e: Exception) {
@@ -230,4 +257,4 @@
return result.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
}
-}
\ No newline at end of file
+}
diff --git a/tests/unit/src/android/net/ip/IpClientTest.java b/tests/unit/src/android/net/ip/IpClientTest.java
index f5fd22b..1527714 100644
--- a/tests/unit/src/android/net/ip/IpClientTest.java
+++ b/tests/unit/src/android/net/ip/IpClientTest.java
@@ -35,6 +35,7 @@
import static com.android.net.module.util.netlink.NetlinkConstants.RTN_UNICAST;
import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_ACK;
import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
+import static com.android.networkstack.util.NetworkStackUtils.APF_ENABLE;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@@ -76,8 +77,8 @@
import android.net.MacAddress;
import android.net.NetworkStackIpMemoryStore;
import android.net.RouteInfo;
-import android.net.apf.AndroidPacketFilter;
import android.net.apf.ApfCapabilities;
+import android.net.apf.ApfFilter;
import android.net.apf.ApfFilter.ApfConfiguration;
import android.net.ip.IpClientLinkObserver.IpClientNetlinkMonitor;
import android.net.ip.IpClientLinkObserver.IpClientNetlinkMonitor.INetlinkMessageProcessor;
@@ -191,8 +192,8 @@
@Mock private FileDescriptor mFd;
@Mock private PrintWriter mWriter;
@Mock private IpClientNetlinkMonitor mNetlinkMonitor;
- @Mock private AndroidPacketFilter mApfFilter;
@Mock private PackageManager mPackageManager;
+ @Mock private ApfFilter mApfFilter;
private InterfaceParams mIfParams;
private INetlinkMessageProcessor mNetlinkMessageProcessor;
@@ -216,10 +217,10 @@
when(mDependencies.getDeviceConfigPropertyInt(eq(CONFIG_SOCKET_RECV_BUFSIZE), anyInt()))
.thenReturn(SOCKET_RECV_BUFSIZE);
when(mDependencies.makeIpClientNetlinkMonitor(
- any(), any(), any(), anyInt(), any())).thenReturn(mNetlinkMonitor);
+ any(), any(), any(), anyInt(), anyBoolean(), any())).thenReturn(mNetlinkMonitor);
when(mNetlinkMonitor.start()).thenReturn(true);
- when(mContext.getPackageManager()).thenReturn(mPackageManager);
- when(mPackageManager.hasSystemFeature(eq(PackageManager.FEATURE_WATCH))).thenReturn(false);
+ doReturn(mPackageManager).when(mContext).getPackageManager();
+ doReturn(true).when(mDependencies).isFeatureNotChickenedOut(mContext, APF_ENABLE);
mIfParams = null;
}
@@ -240,7 +241,7 @@
final ArgumentCaptor<INetlinkMessageProcessor> processorCaptor =
ArgumentCaptor.forClass(INetlinkMessageProcessor.class);
verify(mDependencies).makeIpClientNetlinkMonitor(any(), any(), any(), anyInt(),
- processorCaptor.capture());
+ anyBoolean(), processorCaptor.capture());
mNetlinkMessageProcessor = processorCaptor.getValue();
reset(mNetd);
// Verify IpClient doesn't call onLinkPropertiesChange() when it starts.
@@ -356,7 +357,6 @@
mNetlinkMessageProcessor.processNetlinkMessage(msg, TEST_UNUSED_REAL_TIME /* whenMs */);
}
-
@Test
public void testNullInterfaceNameMostDefinitelyThrows() throws Exception {
setTestInterfaceParams(null);
@@ -855,10 +855,10 @@
ApfConfiguration.class);
if (isApfSupported) {
verify(mDependencies, timeout(TEST_TIMEOUT_MS)).maybeCreateApfFilter(
- any(), any(), configCaptor.capture(), any(), any(), any(), anyBoolean());
+ any(), any(), configCaptor.capture(), any(), any(), any());
} else {
verify(mDependencies, never()).maybeCreateApfFilter(
- any(), any(), configCaptor.capture(), any(), any(), any(), anyBoolean());
+ any(), any(), configCaptor.capture(), any(), any(), any());
}
return isApfSupported ? configCaptor.getValue() : null;
@@ -927,7 +927,7 @@
final ArgumentCaptor<ApfConfiguration> configCaptor = ArgumentCaptor.forClass(
ApfConfiguration.class);
verify(mDependencies, timeout(TEST_TIMEOUT_MS)).maybeCreateApfFilter(
- any(), any(), configCaptor.capture(), any(), any(), any(), anyBoolean());
+ any(), any(), configCaptor.capture(), any(), any(), any());
final ApfConfiguration actual = configCaptor.getValue();
assertNotNull(actual);
assertEquals(SdkLevel.isAtLeastS() ? 4 : 3, actual.apfVersionSupported);
@@ -937,6 +937,28 @@
}
@Test
+ public void testForceApfV2OnLowRam() throws Exception {
+ final IpClient ipc = makeIpClient(TEST_IFNAME);
+ ProvisioningConfiguration.Builder config = new ProvisioningConfiguration.Builder()
+ .withoutIPv4()
+ .withoutIpReachabilityMonitor()
+ .withInitialConfiguration(
+ conf(links(TEST_LOCAL_ADDRESSES), prefixes(TEST_PREFIXES), ips()))
+ .withApfCapabilities(
+ new ApfCapabilities(3 /* version */, 512 /* maxProgramSize */,
+ ARPHRD_ETHER));
+ ipc.startProvisioning(config.build());
+ final ArgumentCaptor<ApfConfiguration> configCaptor = ArgumentCaptor.forClass(
+ ApfConfiguration.class);
+ verify(mDependencies, timeout(TEST_TIMEOUT_MS)).maybeCreateApfFilter(
+ any(), any(), configCaptor.capture(), any(), any(), any());
+
+ final ApfConfiguration apfConfig = configCaptor.getValue();
+ assertEquals(2, apfConfig.apfVersionSupported);
+ verifyShutdown(ipc);
+ }
+
+ @Test
public void testDumpApfFilter_withNoException() throws Exception {
final IpClient ipc = makeIpClient(TEST_IFNAME);
final ApfConfiguration config = verifyApfFilterCreatedOnStart(ipc,
@@ -962,7 +984,7 @@
ipc.updateApfCapabilities(newApfCapabilities);
HandlerUtils.waitForIdle(ipc.getHandler(), TEST_TIMEOUT_MS);
verify(mDependencies, never()).maybeCreateApfFilter(any(), any(), any(), any(), any(),
- any(), anyBoolean());
+ any());
verifyShutdown(ipc);
}
@@ -978,16 +1000,54 @@
ipc.updateApfCapabilities(null /* apfCapabilities */);
HandlerUtils.waitForIdle(ipc.getHandler(), TEST_TIMEOUT_MS);
verify(mDependencies, never()).maybeCreateApfFilter(any(), any(), any(), any(), any(),
- any(), anyBoolean());
+ any());
verifyShutdown(ipc);
}
@Test
+ public void testApfUpdateCapabilities_raceBetweenStopAndStartIpClient() throws Exception {
+ final IpClient ipc = makeIpClient(TEST_IFNAME);
+ ProvisioningConfiguration.Builder config = new ProvisioningConfiguration.Builder()
+ .withoutIPv4()
+ .withoutIpReachabilityMonitor()
+ .withInitialConfiguration(
+ conf(links(TEST_LOCAL_ADDRESSES), prefixes(TEST_PREFIXES), ips()))
+ .withApfCapabilities(new ApfCapabilities(4 /* version */,
+ 4096 /* maxProgramSize */, ARPHRD_ETHER));
+ ipc.startProvisioning(config.build());
+
+ // Verify that APF filter can be created successfully.
+ ArgumentCaptor<ApfConfiguration> configCaptor = ArgumentCaptor.forClass(
+ ApfConfiguration.class);
+ verify(mDependencies, timeout(TEST_TIMEOUT_MS)).maybeCreateApfFilter(
+ any(), any(), configCaptor.capture(), any(), any(), any());
+ ApfConfiguration apfConfig = configCaptor.getValue();
+ assertEquals(SdkLevel.isAtLeastS() ? 4 : 3, apfConfig.apfVersionSupported);
+ assertEquals(4096, apfConfig.apfRamSize);
+
+ clearInvocations(mDependencies);
+
+ // Simulate stopping IpClient and restarting provisioning immediately, verify IpClient
+ // can still create APF filter successfully, make sure the race of mApfCapabilities
+ // initialization has been fixed.
+ ipc.stop();
+ // Update the maxProgramSize to differentiate with above APF config.
+ config.withApfCapabilities(new ApfCapabilities(4 /* version */,
+ 2048 /* maxProgramSize */, ARPHRD_ETHER));
+ ipc.startProvisioning(config.build());
+ verify(mDependencies, timeout(TEST_TIMEOUT_MS)).maybeCreateApfFilter(
+ any(), any(), configCaptor.capture(), any(), any(), any());
+ apfConfig = configCaptor.getValue();
+ assertEquals(SdkLevel.isAtLeastS() ? 4 : 3, apfConfig.apfVersionSupported);
+ assertEquals(2048, apfConfig.apfRamSize);
+ }
+
+ @Test
@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public void testVendorNdOffloadDisabledWhenApfV6Supported() throws Exception {
- when(mDependencies.maybeCreateApfFilter(any(), any(), any(), any(), any(), any(),
- anyBoolean())).thenReturn(mApfFilter);
- when(mApfFilter.supportNdOffload()).thenReturn(true);
+ when(mDependencies.maybeCreateApfFilter(any(), any(), any(), any(), any(),
+ any())).thenReturn(mApfFilter);
+ when(mApfFilter.enableNdOffload()).thenReturn(true);
final IpClient ipc = makeIpClient(TEST_IFNAME);
ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
.withoutIPv4()
@@ -1011,9 +1071,9 @@
@Test
@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public void testVendorNdOffloadEnabledWhenApfV6NotSupported() throws Exception {
- when(mDependencies.maybeCreateApfFilter(any(), any(), any(), any(), any(), any(),
- anyBoolean())).thenReturn(mApfFilter);
- when(mApfFilter.supportNdOffload()).thenReturn(false);
+ when(mDependencies.maybeCreateApfFilter(any(), any(), any(), any(), any(),
+ any())).thenReturn(mApfFilter);
+ when(mApfFilter.enableNdOffload()).thenReturn(false);
final IpClient ipc = makeIpClient(TEST_IFNAME);
ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
.withoutIPv4()
@@ -1035,9 +1095,9 @@
@Test
@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public void testVendorNdOffloadDisabledWhenApfCapabilitiesUpdated() throws Exception {
- when(mDependencies.maybeCreateApfFilter(any(), any(), any(), any(), any(), any(),
- anyBoolean())).thenReturn(mApfFilter);
- when(mApfFilter.supportNdOffload()).thenReturn(true);
+ when(mDependencies.maybeCreateApfFilter(any(), any(), any(), any(), any(),
+ any())).thenReturn(mApfFilter);
+ when(mApfFilter.enableNdOffload()).thenReturn(true);
final IpClient ipc = makeIpClient(TEST_IFNAME);
ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
.withoutIPv4()
@@ -1058,8 +1118,8 @@
@Test
public void testLinkPropertiesUpdate_callSetLinkPropertiesOnApfFilter() throws Exception {
- when(mDependencies.maybeCreateApfFilter(any(), any(), any(), any(), any(), any(),
- anyBoolean())).thenReturn(mApfFilter);
+ when(mDependencies.maybeCreateApfFilter(any(), any(), any(), any(), any(),
+ any())).thenReturn(mApfFilter);
final IpClient ipc = makeIpClient(TEST_IFNAME);
verifyApfFilterCreatedOnStart(ipc, true /* isApfSupported */);
onInterfaceAddressUpdated(
diff --git a/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt b/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt
index 518cec7..343848b 100644
--- a/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt
+++ b/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt
@@ -59,7 +59,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_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION
import com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION
import com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_MCAST_RESOLICIT_VERSION
import com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_ROUTER_MAC_CHANGE_FAILURE_ONLY_AFTER_ROAM_VERSION
@@ -334,6 +333,10 @@
fun testLoseProvisioning_FirstProbeIsFailed() {
reachabilityMonitor.updateLinkProperties(TEST_LINK_PROPERTIES)
+ // Make the IPv4 DNS as reachable first.
+ neighborMonitor.enqueuePacket(makeNewNeighMessage(TEST_IPV4_DNS, NUD_REACHABLE))
+ handlerThread.waitForIdle(TEST_TIMEOUT_MS)
+
neighborMonitor.enqueuePacket(makeNewNeighMessage(TEST_IPV4_DNS, NUD_FAILED))
verify(callback, timeout(TEST_TIMEOUT_MS)).notifyLost(
anyString(),
@@ -341,6 +344,10 @@
)
}
+ // Given the flag which ignores the NUD failure from the neighbor that is never reachable
+ // before has been enabled by default, we have to make the neighbor as reachable first and
+ // simulate a NUD failure by making a new NUD_FAILED neighbor message. So change the param
+ // "everReachable" to true always.
private fun runLoseProvisioningTest(
newLp: LinkProperties,
lostNeighbor: InetAddress,
@@ -350,7 +357,7 @@
newLp,
lostNeighbor,
eventType,
- false, /* everReachable */
+ true, /* everReachable */
true /* expectedNotifyLost */
)
}
@@ -368,11 +375,21 @@
neighborMonitor.enqueuePacket(makeNewNeighMessage(TEST_IPV6_GATEWAY, NUD_STALE))
neighborMonitor.enqueuePacket(makeNewNeighMessage(TEST_IPV4_DNS, NUD_STALE))
neighborMonitor.enqueuePacket(makeNewNeighMessage(TEST_IPV6_DNS, NUD_STALE))
+ neighborMonitor.enqueuePacket(
+ makeNewNeighMessage(TEST_IPV6_LINKLOCAL_SCOPED_GATEWAY, NUD_STALE)
+ )
+ neighborMonitor.enqueuePacket(makeNewNeighMessage(TEST_IPV4_GATEWAY_DNS, NUD_STALE))
+
+ // Make all neighbors used in the test as reachable.
if (everReachable) {
neighborMonitor.enqueuePacket(makeNewNeighMessage(TEST_IPV4_DNS, NUD_REACHABLE))
- neighborMonitor.enqueuePacket(makeNewNeighMessage(TEST_IPV4_GATEWAY, NUD_REACHABLE))
neighborMonitor.enqueuePacket(makeNewNeighMessage(TEST_IPV6_DNS, NUD_REACHABLE))
neighborMonitor.enqueuePacket(makeNewNeighMessage(TEST_IPV6_GATEWAY, NUD_REACHABLE))
+ neighborMonitor.enqueuePacket(makeNewNeighMessage(TEST_IPV4_GATEWAY, NUD_REACHABLE))
+ neighborMonitor.enqueuePacket(
+ makeNewNeighMessage(TEST_IPV6_LINKLOCAL_SCOPED_GATEWAY, NUD_REACHABLE)
+ )
+ neighborMonitor.enqueuePacket(makeNewNeighMessage(TEST_IPV4_GATEWAY_DNS, NUD_REACHABLE))
}
neighborMonitor.enqueuePacket(makeNewNeighMessage(lostNeighbor, NUD_PROBE))
@@ -517,7 +534,6 @@
}
@Test
- @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = true)
fun testLoseProvisioning_ignoreNeverReachableIpv6GatewayLost() {
runLoseProvisioningTest(
TEST_LINK_PROPERTIES,
@@ -529,7 +545,6 @@
}
@Test
- @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = true)
fun testLoseProvisioning_ignoreNeverReachableIpv6DnsLost() {
runLoseProvisioningTest(
TEST_LINK_PROPERTIES,
@@ -541,7 +556,6 @@
}
@Test
- @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = true)
fun testLoseProvisioning_notIgnoreEverReachableIpv6GatewayLost() {
runLoseProvisioningTest(
TEST_LINK_PROPERTIES,
@@ -553,7 +567,6 @@
}
@Test
- @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = true)
fun testLoseProvisioning_notIgnoreEverReachableIpv6DnsLost() {
runLoseProvisioningTest(
TEST_LINK_PROPERTIES,
@@ -565,7 +578,6 @@
}
@Test
- @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = true)
fun testLoseProvisioning_ignoreNeverReachableIpv4DnsLost() {
runLoseProvisioningTest(
TEST_LINK_PROPERTIES,
@@ -577,7 +589,6 @@
}
@Test
- @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = true)
fun testLoseProvisioning_notIgnoreEverReachableIpv4GatewayLost() {
runLoseProvisioningTest(
TEST_LINK_PROPERTIES,
@@ -589,7 +600,6 @@
}
@Test
- @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = true)
fun testLoseProvisioning_notIgnoreEverReachableIpv4DnsLost() {
runLoseProvisioningTest(
TEST_LINK_PROPERTIES,
@@ -601,7 +611,6 @@
}
@Test
- @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = true)
fun testLoseProvisioning_ignoreNeverReachableIpv6GatewayLost_withTwoIPv6DnsServers() {
reachabilityMonitor.updateLinkProperties(TEST_DUAL_LINK_PROPERTIES)
@@ -639,7 +648,6 @@
}
@Test
- @Flag(name = IP_REACHABILITY_IGNORE_NEVER_REACHABLE_NEIGHBOR_VERSION, enabled = true)
fun testLoseProvisioning_ignoreNeverReachableIpv6DnsLost_withTwoIPv6Routes() {
val TEST_DUAL_IPV6_ROUTERS_LINK_PROPERTIES = LinkProperties().apply {
interfaceName = TEST_IFACE.name
@@ -861,7 +869,7 @@
TEST_IPV6_LINKLOCAL_SCOPED_GATEWAY,
NUD_CONFIRM_FAILED_CRITICAL,
IPV6,
- NUD_NEIGHBOR_GATEWAY
+ NUD_NEIGHBOR_GATEWAY
)
}
@@ -927,7 +935,7 @@
TEST_IPV6_LINKLOCAL_SCOPED_GATEWAY,
NUD_ORGANIC_FAILED_CRITICAL,
IPV6,
- NUD_NEIGHBOR_GATEWAY
+ NUD_NEIGHBOR_GATEWAY
)
}
diff --git a/tests/unit/src/android/net/ip/MulticastReportMonitorTest.kt b/tests/unit/src/android/net/ip/MulticastReportMonitorTest.kt
new file mode 100644
index 0000000..cbd2b17
--- /dev/null
+++ b/tests/unit/src/android/net/ip/MulticastReportMonitorTest.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.ip
+
+import android.net.MacAddress
+import android.net.ip.MulticastReportMonitor.Callback
+import android.os.Handler
+import android.os.HandlerThread
+import android.system.Os
+import android.system.OsConstants.AF_UNIX
+import android.system.OsConstants.SOCK_NONBLOCK
+import android.system.OsConstants.SOCK_STREAM
+import androidx.test.filters.SmallTest
+import com.android.net.module.util.HexDump
+import com.android.net.module.util.InterfaceParams
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.visibleOnHandlerThread
+import com.android.testutils.waitForIdle
+import java.io.FileDescriptor
+import libcore.io.IoUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.timeout
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/**
+ * Test for MulticastReportMonitor.
+ */
+@SmallTest
+@DevSdkIgnoreRunner.MonitorThreadLeak
+class MulticastReportMonitorTest {
+ companion object {
+ private const val TIMEOUT_MS: Long = 1000
+ private const val SLEEP_TIMEOUT_MS: Long = 100
+ private val TAG = this::class.simpleName
+ }
+
+ private val loInterfaceParams = InterfaceParams.getByName("lo")
+ private val ifParams =
+ InterfaceParams(
+ "lo",
+ loInterfaceParams.index,
+ MacAddress.fromBytes(byteArrayOf(2, 3, 4, 5, 6, 7)),
+ loInterfaceParams.defaultMtu
+ )
+
+ private val handlerThread by lazy {
+ HandlerThread("$TAG-handler-thread").apply{ start() }
+ }
+ private val handler by lazy { Handler(handlerThread.looper) }
+ private var writeSocket = FileDescriptor()
+ private lateinit var mMulticastReportMonitor: MulticastReportMonitor
+
+ @Mock private lateinit var callback: Callback
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ val readSocket = FileDescriptor()
+ Os.socketpair(AF_UNIX, SOCK_STREAM or SOCK_NONBLOCK, 0, writeSocket, readSocket)
+ mMulticastReportMonitor = MulticastReportMonitor(handler, ifParams, callback, readSocket)
+ visibleOnHandlerThread(handler) {
+ mMulticastReportMonitor.start()
+ }
+ }
+
+ @After
+ fun tearDown() {
+ IoUtils.closeQuietly(writeSocket)
+ handler.waitForIdle(TIMEOUT_MS)
+ Mockito.framework().clearInlineMocks()
+ handlerThread.quitSafely()
+ handlerThread.join()
+ }
+
+ @Test
+ fun testMulticastReportMonitorCallback() {
+ val matchedPacket = HexDump.hexStringToByteArray("000000")
+ val pktCnt = 2
+ for (i in 0..<pktCnt) {
+ Os.write(writeSocket, matchedPacket, 0, matchedPacket.size)
+ Thread.sleep(SLEEP_TIMEOUT_MS)
+ }
+ verify(callback, timeout(TIMEOUT_MS).times(pktCnt)).notifyMulticastAddrChange()
+ }
+}
diff --git a/tests/unit/src/com/android/networkstack/NetworkStackNotifierTest.kt b/tests/unit/src/com/android/networkstack/NetworkStackNotifierTest.kt
index efd4069..88319d4 100644
--- a/tests/unit/src/com/android/networkstack/NetworkStackNotifierTest.kt
+++ b/tests/unit/src/com/android/networkstack/NetworkStackNotifierTest.kt
@@ -67,6 +67,7 @@
import org.mockito.MockitoAnnotations
import kotlin.reflect.KClass
import kotlin.test.assertEquals
+import kotlin.test.assertTrue
@RunWith(AndroidTestingRunner::class)
@SmallTest
@@ -206,6 +207,10 @@
assertEquals(mPendingIntent, note.contentIntent)
assertEquals(CHANNEL_CONNECTED, note.channelId)
assertEquals(timeout, note.timeoutAfter)
+ assertTrue(
+ note.flags and Notification.FLAG_LOCAL_ONLY != 0,
+ "Connected notifications should be local only"
+ )
verify(mDependencies).getActivityPendingIntent(
eq(mCurrentUserContext), mIntentCaptor.capture(),
intThat { it or FLAG_IMMUTABLE != 0 })
@@ -294,6 +299,10 @@
eq(mCurrentUserContext), mIntentCaptor.capture(),
intThat { it or FLAG_IMMUTABLE != 0 })
verifyVenueInfoIntent(mIntentCaptor.value)
+ assertTrue(
+ mNoteCaptor.value.flags and Notification.FLAG_LOCAL_ONLY != 0,
+ "Venue info notifications should be local only"
+ )
verifyCanceledNotificationAfterDefaultNetworkLost()
}
diff --git a/tests/unit/src/com/android/networkstack/NetworkStackServiceTest.kt b/tests/unit/src/com/android/networkstack/NetworkStackServiceTest.kt
index 7770eca..7f47f8b 100644
--- a/tests/unit/src/com/android/networkstack/NetworkStackServiceTest.kt
+++ b/tests/unit/src/com/android/networkstack/NetworkStackServiceTest.kt
@@ -29,8 +29,6 @@
import android.net.dhcp.IDhcpServerCallbacks
import android.net.ip.IIpClientCallbacks
import android.net.ip.IpClient
-import android.os.Binder
-import android.os.Build
import android.os.IBinder
import android.os.Process
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -42,8 +40,6 @@
import com.android.server.NetworkStackService.PermissionChecker
import com.android.server.connectivity.NetworkMonitor
import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter
-import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.assertThrows
import java.io.FileDescriptor
import java.io.PrintWriter
@@ -56,11 +52,9 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.eq
import org.mockito.Mockito.mock
-import org.mockito.Mockito.spy
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
@@ -83,8 +77,13 @@
private val deps = mock(Dependencies::class.java).apply {
doReturn(mockIpMemoryStoreService).`when`(this).makeIpMemoryStoreService(any())
doReturn(mockDhcpServer).`when`(this).makeDhcpServer(any(), any(), any(), any())
- doReturn(mockNetworkMonitor).`when`(this).makeNetworkMonitor(any(), any(), any(), any(),
- any())
+ doReturn(mockNetworkMonitor).`when`(this).makeNetworkMonitor(
+ any(),
+ any(),
+ any(),
+ any(),
+ any()
+ )
doReturn(mockIpClient).`when`(this).makeIpClient(any(), any(), any(), any())
}
private val netd = mock(INetd::class.java).apply {
@@ -100,22 +99,7 @@
private val connector = NetworkStackConnector(context, permChecker, deps)
- @Test @IgnoreAfter(Build.VERSION_CODES.Q)
- fun testDumpVersion_Q() {
- prepareDumpVersionTest()
-
- val dumpsysOut = StringWriter()
- connector.dump(FileDescriptor(), PrintWriter(dumpsysOut, true /* autoFlush */),
- arrayOf("version") /* args */)
-
- assertEquals("NetworkStack version:\n" +
- "NetworkStackConnector: ${INetworkStackConnector.VERSION}\n" +
- "SystemServer: {9990001, 9990002, 9990003, 9990004, 9990005}\n" +
- "Netd: $TEST_NETD_VERSION\n\n",
- dumpsysOut.toString())
- }
-
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
fun testDumpVersion() {
prepareDumpVersionTest()
@@ -123,10 +107,14 @@
val connectorHash = INetworkStackConnector.HASH
val dumpsysOut = StringWriter()
- connector.dump(FileDescriptor(), PrintWriter(dumpsysOut, true /* autoFlush */),
- arrayOf("version") /* args */)
+ connector.dump(
+ FileDescriptor(),
+ PrintWriter(dumpsysOut, true /* autoFlush */),
+ arrayOf("version") /* args */
+ )
- assertEquals("NetworkStack version:\n" +
+ assertEquals(
+ "NetworkStack version:\n" +
"LocalInterface:$connectorVersion:$connectorHash\n" +
"ipmemorystore:9990001:ipmemorystore_hash\n" +
"netd:$TEST_NETD_VERSION:$TEST_NETD_HASH\n" +
@@ -134,7 +122,8 @@
"networkstack:9990003:networkmonitor_hash\n" +
"networkstack:9990004:ipclient_hash\n" +
"networkstack:9990005:multiple_use_hash\n\n",
- dumpsysOut.toString())
+ dumpsysOut.toString()
+ )
}
fun prepareDumpVersionTest() {
@@ -170,16 +159,9 @@
verify(mockDhcpCb).onDhcpServerCreated(eq(IDhcpServer.STATUS_SUCCESS), any())
// Call makeNetworkMonitor
- // Use a spy of INetworkMonitorCallbacks and not a mock, as mockito can't create a mock on Q
- // because of the missing CaptivePortalData class that is an argument of one of the methods
- val mockBinder = mock(IBinder::class.java)
- val mockNetworkMonitorCb = spy(INetworkMonitorCallbacks.Stub.asInterface(mockBinder))
+ val mockNetworkMonitorCb = mock(INetworkMonitorCallbacks::class.java)
doReturn(9990003).`when`(mockNetworkMonitorCb).interfaceVersion
doReturn("networkmonitor_hash").`when`(mockNetworkMonitorCb).interfaceHash
- // Oneway transactions are always successful (return true). INetworkMonitorCallbacks is a
- // oneway interface. This avoids the stub throwing because the method is not implemented by
- // the (mock) remote.
- doReturn(true).`when`(mockBinder).transact(anyInt(), any(), any(), eq(Binder.FLAG_ONEWAY))
connector.makeNetworkMonitor(Network(123), "test_nm", mockNetworkMonitorCb)
@@ -187,8 +169,6 @@
verify(mockNetworkMonitorCb).onNetworkMonitorCreated(any())
// Call makeIpClient
- // Use a spy of IIpClientCallbacks instead of a mock, as mockito cannot create a mock on Q
- // because of the missing CaptivePortalData class that is an argument on one of the methods
val mockIpClientCb = mock(IIpClientCallbacks::class.java)
doReturn(9990004).`when`(mockIpClientCb).interfaceVersion
doReturn("ipclient_hash").`when`(mockIpClientCb).interfaceHash
diff --git a/tests/unit/src/com/android/networkstack/ipmemorystore/IpMemoryStoreServiceTest.java b/tests/unit/src/com/android/networkstack/ipmemorystore/IpMemoryStoreServiceTest.java
index ee6c48b..02612f4 100644
--- a/tests/unit/src/com/android/networkstack/ipmemorystore/IpMemoryStoreServiceTest.java
+++ b/tests/unit/src/com/android/networkstack/ipmemorystore/IpMemoryStoreServiceTest.java
@@ -19,6 +19,7 @@
import static android.net.ip.IpClient.NETWORK_EVENT_NUD_FAILURE_TYPES;
import static android.net.ip.IpClient.ONE_DAY_IN_MS;
import static android.net.ip.IpClient.ONE_WEEK_IN_MS;
+import static android.net.ip.IpClient.SIX_HOURS_IN_MS;
import static android.net.IIpMemoryStore.NETWORK_EVENT_NUD_FAILURE_ROAM;
import static android.net.IIpMemoryStore.NETWORK_EVENT_NUD_FAILURE_CONFIRM;
import static android.net.IIpMemoryStore.NETWORK_EVENT_NUD_FAILURE_ORGANIC;
@@ -1459,6 +1460,71 @@
}
@Test
+ public void testStoreNetworkEvent_deleteCluster() {
+ final long now = System.currentTimeMillis();
+ storeNetworkEventsForNudFailures(now);
+
+ // Delete the entries with TEST_CLUSTER from the fixture table.
+ doLatched("Did not finish deleting", latch ->
+ mService.deleteCluster(TEST_CLUSTER, false /* needWipe */,
+ onDeleteStatus((status, deletedCount) -> {
+ assertTrue("Delete failed : " + status.resultCode, status.isSuccess());
+ // The fixture stores 40 events under TEST_CLUSTER
+ assertEquals("Unexpected deleted count : " + deletedCount,
+ 40, deletedCount.intValue());
+ latch.countDown();
+ })), LONG_TIMEOUT_MS);
+
+ // Query network event counts for NUD failures within TEST_CLUSTER, should be empty given
+ // we've already deleted that cluster.
+ final long[] sinceTimes = new long[3];
+ sinceTimes[0] = now - ONE_WEEK_IN_MS;
+ sinceTimes[1] = now - ONE_DAY_IN_MS;
+ sinceTimes[2] = now - SIX_HOURS_IN_MS;
+ doLatched("Did not complete retrieving network event count", latch ->
+ mService.retrieveNetworkEventCount(TEST_CLUSTER,
+ sinceTimes,
+ NETWORK_EVENT_NUD_FAILURE_TYPES,
+ onNetworkEventCountRetrieved(
+ (status, counts) -> {
+ assertTrue("Retrieve network event counts not successful : "
+ + status.resultCode, status.isSuccess());
+ assertTrue(counts.length == 3);
+ assertEquals(0, counts[0]);
+ assertEquals(0, counts[1]);
+ assertEquals(0, counts[2]);
+ latch.countDown();
+ })));
+
+ // Delete the entries with TEST_CLUSTER_1 from the fixture table.
+ doLatched("Did not finish deleting", latch ->
+ mService.deleteCluster(TEST_CLUSTER_1, false /* needWipe */,
+ onDeleteStatus((status, deletedCount) -> {
+ assertTrue("Delete failed : " + status.resultCode, status.isSuccess());
+ // The fixture stores 40 events under TEST_CLUSTER
+ assertEquals("Unexpected deleted count : " + deletedCount,
+ 20, deletedCount.intValue());
+ latch.countDown();
+ })), LONG_TIMEOUT_MS);
+ // Query network event counts for NUD failures within TEST_CLUSTER_1, should be empty given
+ // we've already deleted that cluster as well.
+ doLatched("Did not complete retrieving network event count", latch ->
+ mService.retrieveNetworkEventCount(TEST_CLUSTER_1,
+ sinceTimes,
+ NETWORK_EVENT_NUD_FAILURE_TYPES,
+ onNetworkEventCountRetrieved(
+ (status, counts) -> {
+ assertTrue("Retrieve network event counts not successful : "
+ + status.resultCode, status.isSuccess());
+ assertTrue(counts.length == 3);
+ assertEquals(0, counts[0]);
+ assertEquals(0, counts[1]);
+ assertEquals(0, counts[2]);
+ latch.countDown();
+ })));
+ }
+
+ @Test
public void testRenameDb_noExistingDb_newDbCreated() throws Exception {
mService.shutdown();
TEST_DB.delete();
diff --git a/tests/unit/src/com/android/networkstack/metrics/ApfSessionInfoMetricsTest.java b/tests/unit/src/com/android/networkstack/metrics/ApfSessionInfoMetricsTest.java
index 69464cf..3e23a14 100644
--- a/tests/unit/src/com/android/networkstack/metrics/ApfSessionInfoMetricsTest.java
+++ b/tests/unit/src/com/android/networkstack/metrics/ApfSessionInfoMetricsTest.java
@@ -94,21 +94,21 @@
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_ETHER_OUR_SRC_MAC, CounterName.CN_PASSED_OUR_SRC_MAC);
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_IPV4_UNICAST, CounterName.CN_PASSED_IPV4_UNICAST);
+ verifyCounterName(Counter.PASSED_IPV6_HOPOPTS, CounterName.CN_PASSED_IPV6_HOPOPTS);
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_ETHER_OUR_SRC_MAC,
+ CounterName.CN_DROPPED_ETHER_OUR_SRC_MAC);
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);
@@ -118,14 +118,21 @@
CounterName.CN_DROPPED_IPV4_BROADCAST_ADDR);
verifyCounterName(Counter.DROPPED_IPV4_BROADCAST_NET,
CounterName.CN_DROPPED_IPV4_BROADCAST_NET);
+ verifyCounterName(Counter.DROPPED_IPV4_ICMP_INVALID,
+ CounterName.CN_DROPPED_IPV4_ICMP_INVALID);
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_MLD_INVALID,
+ CounterName.CN_DROPPED_IPV6_MLD_INVALID);
+ verifyCounterName(Counter.DROPPED_IPV6_MLD_REPORT,
+ CounterName.CN_DROPPED_IPV6_MLD_REPORT);
+ verifyCounterName(Counter.DROPPED_IPV6_MLD_V1_GENERAL_QUERY_REPLIED,
+ CounterName.CN_DROPPED_IPV6_MLD_V1_GENERAL_QUERY_REPLIED);
+ verifyCounterName(Counter.DROPPED_IPV6_MLD_V2_GENERAL_QUERY_REPLIED,
+ CounterName.CN_DROPPED_IPV6_MLD_V2_GENERAL_QUERY_REPLIED);
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);
@@ -135,12 +142,12 @@
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_MDNS_REPLIED, CounterName.CN_DROPPED_MDNS_REPLIED);
+ verifyCounterName(Counter.DROPPED_IPV4_TCP_PORT7_UNICAST,
+ CounterName.CN_DROPPED_IPV4_TCP_PORT7_UNICAST);
verifyCounterName(Counter.DROPPED_ARP_NON_IPV4, CounterName.CN_DROPPED_ARP_NON_IPV4);
verifyCounterName(Counter.DROPPED_ARP_UNKNOWN, CounterName.CN_DROPPED_ARP_UNKNOWN);
verifyCounterName(Counter.PASSED_ARP_BROADCAST_REPLY,
@@ -148,24 +155,26 @@
verifyCounterName(Counter.PASSED_ARP_REQUEST, CounterName.CN_PASSED_ARP_REQUEST);
verifyCounterName(Counter.PASSED_IPV4_FROM_DHCPV4_SERVER,
CounterName.CN_PASSED_IPV4_FROM_DHCPV4_SERVER);
- verifyCounterName(Counter.PASSED_IPV6_NS_DAD, CounterName.CN_PASSED_IPV6_NS_DAD);
- verifyCounterName(Counter.PASSED_IPV6_NS_NO_ADDRESS,
- CounterName.CN_PASSED_IPV6_NS_NO_ADDRESS);
- verifyCounterName(Counter.PASSED_IPV6_NS_NO_SLLA_OPTION,
- CounterName.CN_PASSED_IPV6_NS_NO_SLLA_OPTION);
- verifyCounterName(Counter.PASSED_IPV6_NS_TENTATIVE,
- CounterName.CN_PASSED_IPV6_NS_TENTATIVE);
- verifyCounterName(Counter.PASSED_MLD, CounterName.CN_PASSED_MLD);
verifyCounterName(Counter.DROPPED_IPV4_NON_DHCP4, CounterName.CN_DROPPED_IPV4_NON_DHCP4);
+ verifyCounterName(Counter.DROPPED_IPV4_PING_REQUEST_REPLIED,
+ CounterName.CN_DROPPED_IPV4_PING_REQUEST_REPLIED);
+ verifyCounterName(Counter.DROPPED_IPV6_ICMP6_ECHO_REQUEST_INVALID,
+ CounterName.CN_DROPPED_IPV6_ICMP6_ECHO_REQUEST_INVALID);
+ verifyCounterName(Counter.DROPPED_IPV6_ICMP6_ECHO_REQUEST_REPLIED,
+ CounterName.CN_DROPPED_IPV6_ICMP6_ECHO_REQUEST_REPLIED);
verifyCounterName(Counter.DROPPED_IPV6_NS_INVALID, CounterName.CN_DROPPED_IPV6_NS_INVALID);
verifyCounterName(Counter.DROPPED_IPV6_NS_OTHER_HOST,
CounterName.CN_DROPPED_IPV6_NS_OTHER_HOST);
verifyCounterName(Counter.DROPPED_IPV6_NS_REPLIED_NON_DAD,
CounterName.CN_DROPPED_IPV6_NS_REPLIED_NON_DAD);
- verifyCounterName(Counter.DROPPED_ARP_REQUEST_ANYHOST,
- CounterName.CN_DROPPED_ARP_REQUEST_ANYHOST);
verifyCounterName(Counter.DROPPED_ARP_REQUEST_REPLIED,
CounterName.CN_DROPPED_ARP_REQUEST_REPLIED);
verifyCounterName(Counter.DROPPED_ARP_V6_ONLY, CounterName.CN_DROPPED_ARP_V6_ONLY);
+ verifyCounterName(Counter.DROPPED_IGMP_INVALID, CounterName.CN_DROPPED_IGMP_INVALID);
+ verifyCounterName(Counter.DROPPED_IGMP_V3_GENERAL_QUERY_REPLIED,
+ CounterName.CN_DROPPED_IGMP_V3_GENERAL_QUERY_REPLIED);
+ verifyCounterName(Counter.DROPPED_IGMP_V2_GENERAL_QUERY_REPLIED,
+ CounterName.CN_DROPPED_IGMP_V2_GENERAL_QUERY_REPLIED);
+ verifyCounterName(Counter.DROPPED_IGMP_REPORT, CounterName.CN_DROPPED_IGMP_REPORT);
}
}
diff --git a/tests/unit/src/com/android/networkstack/util/ProcfsParsingUtilsTest.kt b/tests/unit/src/com/android/networkstack/util/ProcfsParsingUtilsTest.kt
index 7f8cacb..7f99aca 100644
--- a/tests/unit/src/com/android/networkstack/util/ProcfsParsingUtilsTest.kt
+++ b/tests/unit/src/com/android/networkstack/util/ProcfsParsingUtilsTest.kt
@@ -18,9 +18,11 @@
import android.net.MacAddress
import android.net.apf.ProcfsParsingUtils
import androidx.test.filters.SmallTest
-import com.android.internal.util.HexDump
+import com.android.net.module.util.HexDump
+import java.net.Inet4Address
import java.net.Inet6Address
import java.net.InetAddress
+import java.nio.ByteOrder
import kotlin.test.assertEquals
import org.junit.Test
@@ -38,6 +40,34 @@
}
@Test
+ fun testParseDefaultTtl() {
+ assertEquals(
+ 128,
+ ProcfsParsingUtils.parseDefaultTtl(listOf("128"))
+ )
+
+ assertEquals(
+ 64,
+ ProcfsParsingUtils.parseDefaultTtl(listOf())
+ )
+
+ assertEquals(
+ 1,
+ ProcfsParsingUtils.parseDefaultTtl(listOf("0"))
+ )
+
+ assertEquals(
+ 255,
+ ProcfsParsingUtils.parseDefaultTtl(listOf("256"))
+ )
+
+ assertEquals(
+ 64,
+ ProcfsParsingUtils.parseDefaultTtl(listOf("ABC"))
+ )
+ }
+
+ @Test
fun testParseAnycast6Address() {
val inputString = listOf(
"41 eth0 2a0034e2abc1334591a733387s2e322e 2",
@@ -135,4 +165,149 @@
ProcfsParsingUtils.parseIPv6MulticastAddresses(inputString, "wlan0")
)
}
+
+ @Test
+ fun testParseIpv4MulticastAddressLittleEndian() {
+ val order = ByteOrder.LITTLE_ENDIAN
+
+ // the format refer to net/ipv4/igmp.c#igmp_mc_seq_show
+ val inputString = listOf(
+ "Idx\tDevice : Count Querier\tGroup Users Timer\tReporter",
+ "1\tlo : 1 V3",
+ "\t\t\t\t010000E0 1 0:00000000\t\t0",
+ "2\tdummy0 : 1 V3",
+ "\t\t\t\t010000E0 1 0:00000000\t\t0",
+ "47\twlan0 : 1 V3",
+ "\t\t\t\t020000EF 1 0:00000000\t\t0",
+ "\t\t\t\t010000EF 1 0:00000000\t\t0",
+ "\t\t\t\t010000E0 1 0:00000000\t\t0",
+ "51\tv4-wlan0 : 1 V3",
+ "\t\t\t\t010000E0 1 0:00000000\t\t0"
+ )
+
+ val expectedResult = listOf(
+ InetAddress.getByAddress(
+ HexDump.hexStringToByteArray("EF000002")
+ ) as Inet4Address,
+ InetAddress.getByAddress(
+ HexDump.hexStringToByteArray("EF000001")
+ ) as Inet4Address,
+ InetAddress.getByAddress(
+ HexDump.hexStringToByteArray("E0000001")
+ ) as Inet4Address,
+ )
+
+ assertEquals(
+ expectedResult,
+ ProcfsParsingUtils.parseIPv4MulticastAddresses(
+ inputString, "wlan0", order)
+ )
+
+ assertEquals(
+ emptyList<Inet4Address>(),
+ ProcfsParsingUtils.parseIPv4MulticastAddresses(
+ inputString, "eth0", order)
+ )
+
+ assertEquals(
+ emptyList<Inet4Address>(),
+ ProcfsParsingUtils.parseIPv4MulticastAddresses(
+ emptyList<String>(), "eth0", order)
+ )
+ }
+
+ @Test
+ fun testParseIpv4MulticastAddressBigEndian() {
+ val order = ByteOrder.BIG_ENDIAN
+
+ // the format refer to net/ipv4/igmp.c#igmp_mc_seq_show
+ val inputString = listOf(
+ "Idx\tDevice : Count Querier\tGroup Users Timer\tReporter",
+ "1\tlo : 1 V3",
+ "\t\t\t\tE0000001 1 0:00000000\t\t0",
+ "2\tdummy0 : 1 V3",
+ "\t\t\t\tE0000001 1 0:00000000\t\t0",
+ "47\twlan0 : 1 V3",
+ "\t\t\t\tEF000002 1 0:00000000\t\t0",
+ "\t\t\t\tEF000001 1 0:00000000\t\t0",
+ "\t\t\t\tE0000001 1 0:00000000\t\t0",
+ "51\tv4-wlan0 : 1 V3",
+ "\t\t\t\tE0000001 1 0:00000000\t\t0"
+ )
+
+ val expectedResult = listOf(
+ InetAddress.getByAddress(
+ HexDump.hexStringToByteArray("EF000002")
+ ) as Inet4Address,
+ InetAddress.getByAddress(
+ HexDump.hexStringToByteArray("EF000001")
+ ) as Inet4Address,
+ InetAddress.getByAddress(
+ HexDump.hexStringToByteArray("E0000001")
+ ) as Inet4Address,
+ )
+
+ assertEquals(
+ expectedResult,
+ ProcfsParsingUtils.parseIPv4MulticastAddresses(
+ inputString, "wlan0", order)
+ )
+
+ assertEquals(
+ emptyList<Inet4Address>(),
+ ProcfsParsingUtils.parseIPv4MulticastAddresses(
+ inputString, "eth0", order)
+ )
+
+ assertEquals(
+ emptyList<Inet4Address>(),
+ ProcfsParsingUtils.parseIPv4MulticastAddresses(
+ emptyList<String>(), "eth0", order)
+ )
+ }
+
+ @Test
+ fun testParseIpv4MulticastAddressError() {
+ val order = ByteOrder.LITTLE_ENDIAN
+
+ // the format refer to net/ipv4/igmp.c#igmp_mc_seq_show
+ // wlan0 addresses contain invalid char 'X'
+ val inputString = listOf(
+ "Idx\tDevice : Count Querier\tGroup Users Timer\tReporter",
+ "1\tlo : 1 V3",
+ "\t\t\t\t010000E0 1 0:00000000\t\t0",
+ "2\tdummy0 : 1 V3",
+ "\t\t\t\t010000E0 1 0:00000000\t\t0",
+ "47\twlan0 : 1 V3",
+ "\t\t\t\t02XXXXEF 1 0:00000000\t\t0",
+ "\t\t\t\t01XXXXEF 1 0:00000000\t\t0",
+ "\t\t\t\t01XXXXE0 1 0:00000000\t\t0",
+ "51\tv4-wlan0 : 1 V3",
+ "\t\t\t\t010000E0 1 0:00000000\t\t0"
+ )
+
+ val expectedResult = listOf(
+ InetAddress.getByAddress(
+ HexDump.hexStringToByteArray("E0000001")
+ ) as Inet4Address
+ )
+
+ assertEquals(
+ expectedResult,
+ ProcfsParsingUtils.parseIPv4MulticastAddresses(
+ inputString, "wlan0", order)
+ )
+
+ assertEquals(
+ emptyList<Inet4Address>(),
+ ProcfsParsingUtils.parseIPv4MulticastAddresses(
+ inputString, "eth0", order)
+ )
+
+ assertEquals(
+ emptyList<Inet4Address>(),
+ ProcfsParsingUtils.parseIPv4MulticastAddresses(
+ emptyList<String>(), "eth0", order)
+ )
+ }
}
diff --git a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
index 92120cf..e9bd616 100644
--- a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
+++ b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
@@ -208,7 +208,6 @@
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.lang.reflect.Constructor;
import java.net.HttpURLConnection;
import java.net.Inet6Address;
import java.net.InetAddress;
@@ -2523,9 +2522,9 @@
verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
.notifyPrivateDnsConfigResolved(any());
- // Change the mode to opportunistic mode. Verify the callback.
+ // Change the mode to opportunistic mode. Verify the callback is fired a second time.
wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig(true));
- verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyPrivateDnsConfigResolved(
+ verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(2)).notifyPrivateDnsConfigResolved(
matchPrivateDnsConfigParcelWithDohOnly("some.doh.name" /* dohName */,
new String[0] /* dohIps */, "/dns-query{?dns}" /* dohPath */,
443 /* dohPort */));