diff --git a/Tethering/common/TetheringLib/Android.bp b/Tethering/common/TetheringLib/Android.bp index a4db776068ecc2ec6dda4026d6c4463dc485ba1c..6e8d0c9ca2a325ea0475749e27768e6d2e061a78 100644 --- a/Tethering/common/TetheringLib/Android.bp +++ b/Tethering/common/TetheringLib/Android.bp @@ -37,10 +37,10 @@ java_sdk_library { "//frameworks/base/core/tests/utillib", "//frameworks/base/packages/Connectivity/tests:__subpackages__", "//frameworks/base/tests/vcn", - "//frameworks/libs/net/common/testutils", - "//frameworks/libs/net/common/tests:__subpackages__", "//frameworks/opt/telephony/tests/telephonytests", "//packages/modules/CaptivePortalLogin/tests", + "//packages/modules/Connectivity/staticlibs/testutils", + "//packages/modules/Connectivity/staticlibs/tests:__subpackages__", "//packages/modules/Connectivity/Tethering/tests:__subpackages__", "//packages/modules/Connectivity/tests:__subpackages__", "//packages/modules/IPsec/tests/iketests", diff --git a/framework-t/Android.bp b/framework-t/Android.bp index 5ae1ef9836fd2030055f19fd3c44860228844d64..1d459a382b17e716c46675a97dbae381bbfe0efd 100644 --- a/framework-t/Android.bp +++ b/framework-t/Android.bp @@ -175,11 +175,11 @@ java_sdk_library { "//frameworks/base/core/tests/benchmarks", "//frameworks/base/core/tests/utillib", "//frameworks/base/tests/vcn", - "//frameworks/libs/net/common/testutils", - "//frameworks/libs/net/common/tests:__subpackages__", "//frameworks/opt/net/ethernet/tests:__subpackages__", "//frameworks/opt/telephony/tests/telephonytests", "//packages/modules/CaptivePortalLogin/tests", + "//packages/modules/Connectivity/staticlibs/testutils", + "//packages/modules/Connectivity/staticlibs/tests:__subpackages__", "//packages/modules/Connectivity/Tethering/tests:__subpackages__", "//packages/modules/Connectivity/tests:__subpackages__", "//packages/modules/IPsec/tests/iketests", diff --git a/framework/Android.bp b/framework/Android.bp index e577e6daed861803a2d363843204c4f4757797fe..bfbc3b92e686c3dc78c176de0d7fea76d3a9d8c2 100644 --- a/framework/Android.bp +++ b/framework/Android.bp @@ -175,11 +175,11 @@ java_sdk_library { "//frameworks/base/core/tests/benchmarks", "//frameworks/base/core/tests/utillib", "//frameworks/base/tests/vcn", - "//frameworks/libs/net/common/testutils", - "//frameworks/libs/net/common/tests:__subpackages__", "//frameworks/opt/net/ethernet/tests:__subpackages__", "//frameworks/opt/telephony/tests/telephonytests", "//packages/modules/CaptivePortalLogin/tests", + "//packages/modules/Connectivity/staticlibs/testutils", + "//packages/modules/Connectivity/staticlibs/tests:__subpackages__", "//packages/modules/Connectivity/Cronet/tests:__subpackages__", "//packages/modules/Connectivity/Tethering/tests:__subpackages__", "//packages/modules/Connectivity/tests:__subpackages__", diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..ee79ef2b120a2a03f941da4ebf2d74a33e2f48f2 --- /dev/null +++ b/staticlibs/Android.bp @@ -0,0 +1,434 @@ +// 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. + +// 1. The "net-utils-framework-common" library is also compiled into the framework and placed on the +// boot classpath. It uses jarjar rules so that anything outside the framework can use this +// library directly. +// 2. The "net-utils-services-common" library is for use by modules and frameworks/base/services. +// It does not need to be jarjared because it is not placed on the bootclasspath. +// 3. The "net-utils-telephony-common-srcs" filegroup is for use specifically by telephony, which +// places many of its classes, even non-API service classes, on the boot classpath. Any file that +// is added to this filegroup *must* have a corresponding jarjar rule in the telephony jarjar +// rules file. Otherwise, it will end up on the boot classpath and other modules will not be able +// to provide their own copy. + +// Note: all filegroups here must have the right path attribute because otherwise, if they are +// included in the bootclasspath, they could incorrectly be included in the SDK documentation even +// though they are not in the current.txt files. + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +java_library { + name: "net-utils-device-common", + srcs: [ + "device/com/android/net/module/util/arp/ArpPacket.java", + "device/com/android/net/module/util/DeviceConfigUtils.java", + "device/com/android/net/module/util/DomainUtils.java", + "device/com/android/net/module/util/FdEventsReader.java", + "device/com/android/net/module/util/NetworkMonitorUtils.java", + "device/com/android/net/module/util/PacketReader.java", + "device/com/android/net/module/util/SharedLog.java", + "device/com/android/net/module/util/SocketUtils.java", + "device/com/android/net/module/util/FeatureVersions.java", + // This library is used by system modules, for which the system health impact of Kotlin + // has not yet been evaluated. Annotations may need jarjar'ing. + // "src_devicecommon/**/*.kt", + ], + sdk_version: "module_current", + min_sdk_version: "29", + target_sdk_version: "30", + apex_available: [ + "//apex_available:anyapex", + "//apex_available:platform", + ], + visibility: [ + "//frameworks/base/packages/Tethering", + "//packages/modules/Connectivity:__subpackages__", + "//packages/modules/Connectivity/framework:__subpackages__", + "//frameworks/opt/net/ike", + "//frameworks/opt/net/wifi/service", + "//packages/modules/Wifi/service", + "//frameworks/opt/net/telephony", + "//packages/modules/NetworkStack:__subpackages__", + "//packages/modules/CaptivePortalLogin", + ], + static_libs: [ + "net-utils-framework-common", + ], + libs: [ + "androidx.annotation_annotation", + "framework-annotations-lib", + "framework-configinfrastructure", + "framework-connectivity.stubs.module_lib", + ], + lint: { strict_updatability_linting: true }, +} + +java_defaults { + name: "lib_mockito_extended", + static_libs: [ + "mockito-target-extended-minus-junit4" + ], + jni_libs: [ + "libdexmakerjvmtiagent", + "libstaticjvmtiagent", + ], +} + +java_library { + name: "net-utils-dnspacket-common", + srcs: [ + "framework/**/DnsPacket.java", + "framework/**/DnsPacketUtils.java", + ], + sdk_version: "module_current", + visibility: [ + "//packages/services/Iwlan:__subpackages__", + ], + libs: [ + "framework-annotations-lib", + "framework-connectivity.stubs.module_lib", + ], +} + +filegroup { + name: "net-utils-framework-common-srcs", + srcs: ["framework/**/*.java"], + path: "framework", + visibility: [ + "//frameworks/base", + "//packages/modules/Connectivity:__subpackages__", + ], +} + +java_library { + name: "net-utils-device-common-bpf", + srcs: [ + "device/com/android/net/module/util/BpfBitmap.java", + "device/com/android/net/module/util/BpfDump.java", + "device/com/android/net/module/util/BpfMap.java", + "device/com/android/net/module/util/BpfUtils.java", + "device/com/android/net/module/util/IBpfMap.java", + "device/com/android/net/module/util/JniUtil.java", + "device/com/android/net/module/util/Struct.java", + "device/com/android/net/module/util/TcUtils.java", + "framework/com/android/net/module/util/HexDump.java", + ], + sdk_version: "module_current", + min_sdk_version: "29", + visibility: [ + "//packages/modules/Connectivity:__subpackages__", + "//packages/modules/NetworkStack:__subpackages__", + ], + libs: [ + "androidx.annotation_annotation", + "framework-connectivity.stubs.module_lib", + ], + apex_available: [ + "com.android.tethering", + "//apex_available:platform", + ], + lint: { strict_updatability_linting: true }, +} + +java_library { + name: "net-utils-device-common-struct", + srcs: [ + "device/com/android/net/module/util/Ipv6Utils.java", + "device/com/android/net/module/util/PacketBuilder.java", + "device/com/android/net/module/util/Struct.java", + "device/com/android/net/module/util/structs/*.java", + ], + sdk_version: "module_current", + min_sdk_version: "29", + visibility: [ + "//packages/modules/Connectivity:__subpackages__", + "//packages/modules/NetworkStack:__subpackages__", + ], + static_libs: [ + "net-utils-framework-common", + ], + libs: [ + "androidx.annotation_annotation", + "framework-connectivity.stubs.module_lib", + ], + apex_available: [ + "com.android.tethering", + "//apex_available:platform", + ], + lint: { strict_updatability_linting: true }, +} + +java_library { + name: "net-utils-device-common-netlink", + srcs: [ + "device/com/android/net/module/util/netlink/*.java", + ], + sdk_version: "module_current", + min_sdk_version: "29", + visibility: [ + "//packages/modules/Connectivity:__subpackages__", + "//packages/modules/NetworkStack:__subpackages__", + ], + static_libs: [ + "net-utils-device-common-struct", + ], + libs: [ + "androidx.annotation_annotation", + "framework-connectivity.stubs.module_lib", + ], + apex_available: [ + "com.android.tethering", + "//apex_available:platform", + ], + lint: { strict_updatability_linting: true }, +} + +java_library { + // TODO : this target should probably be folded into net-utils-device-common + name: "net-utils-device-common-ip", + srcs: [ + "device/com/android/net/module/util/ip/*.java", + ], + sdk_version: "module_current", + min_sdk_version: "29", + visibility: [ + "//packages/modules/Connectivity:__subpackages__", + "//packages/modules/NetworkStack:__subpackages__", + ], + libs: [ + "framework-annotations-lib", + "framework-connectivity", + ], + static_libs: [ + "net-utils-device-common", + "net-utils-device-common-netlink", + "net-utils-framework-common", + "netd-client", + ], + apex_available: [ + "com.android.tethering", + "//apex_available:platform", + ], + lint: { strict_updatability_linting: true }, +} + +java_library { + name: "net-utils-framework-common", + srcs: [ + ":net-utils-framework-common-srcs", + ], + sdk_version: "module_current", + min_sdk_version: "29", + libs: [ + "androidx.annotation_annotation", + "framework-annotations-lib", + "framework-connectivity.stubs.module_lib", + "framework-connectivity-t.stubs.module_lib", + "framework-location.stubs.module_lib", + ], + jarjar_rules: "jarjar-rules-shared.txt", + visibility: [ + "//cts/tests/tests/net", + "//cts/tests/tests/wifi", + "//packages/modules/Connectivity/tests/cts/net", + "//frameworks/base/packages/Tethering", + "//packages/modules/Connectivity/Tethering", + "//frameworks/base/tests:__subpackages__", + "//frameworks/opt/net/ike", + "//frameworks/opt/telephony", + "//frameworks/base/wifi:__subpackages__", + "//frameworks/base/packages/Connectivity:__subpackages__", + "//packages/modules/Connectivity:__subpackages__", + "//packages/modules/NetworkStack:__subpackages__", + "//packages/modules/CaptivePortalLogin", + "//packages/modules/Wifi/framework/tests:__subpackages__", + "//packages/apps/Settings", + ], + lint: { strict_updatability_linting: true }, + errorprone: { + enabled: true, + // Error-prone checking only warns of problems when building. To make the build fail with + // these errors, list the specific error-prone problems below. + javacflags: [ + "-Xep:NullablePrimitive:ERROR", + ], + }, +} + +java_library { + name: "net-utils-services-common", + srcs: [ + "device/android/net/NetworkFactory.java", + "device/android/net/NetworkFactoryImpl.java", + "device/android/net/NetworkFactoryLegacyImpl.java", + "device/android/net/NetworkFactoryShim.java", + ], + sdk_version: "module_current", + min_sdk_version: "30", + static_libs: [ + "modules-utils-build_system", + ], + libs: [ + "framework-annotations-lib", + "framework-connectivity", + ], + // TODO: remove "apex_available:platform". + apex_available: [ + "//apex_available:platform", + "com.android.btservices", + "com.android.tethering", + "com.android.wifi", + ], + visibility: [ + // TODO: remove after NetworkStatsService moves to the module. + "//frameworks/base/services/net", + "//packages/modules/Connectivity/service", + "//packages/modules/Connectivity/tests:__subpackages__", + "//packages/modules/Bluetooth/android/app", + "//packages/modules/Wifi/service:__subpackages__", + ], + lint: { strict_updatability_linting: true }, +} + +java_library { + name: "net-utils-device-common-async", + srcs: [ + "device/com/android/net/module/util/async/*.java", + ], + sdk_version: "module_current", + min_sdk_version: "29", + visibility: [ + "//packages/modules/Connectivity:__subpackages__", + ], + libs: [ + "framework-annotations-lib", + ], + static_libs: [ + ], + apex_available: [ + "com.android.tethering", + "//apex_available:platform", + ], + lint: { strict_updatability_linting: true }, +} + +java_library { + name: "net-utils-device-common-wear", + srcs: [ + "device/com/android/net/module/util/wear/*.java", + ], + sdk_version: "module_current", + min_sdk_version: "29", + visibility: [ + "//packages/modules/Connectivity:__subpackages__", + ], + libs: [ + "framework-annotations-lib", + ], + static_libs: [ + "net-utils-device-common-async", + ], + apex_available: [ + "com.android.tethering", + "//apex_available:platform", + ], + lint: { strict_updatability_linting: true }, +} + +// Limited set of utilities for use by service-connectivity-mdns-standalone-build-test, to make sure +// the mDNS code can build with only system APIs. +// The mDNS code is platform code so it should use framework-annotations-lib, contrary to apps that +// should use sdk_version: "system_current" and only androidx.annotation_annotation. But this build +// rule verifies that the mDNS code can be built into apps, if code transformations are applied to +// the annotations. +// When using "system_current", framework annotations are not available; they would appear as +// package-private as they are marked as such in the system_current stubs. So build against +// core_platform and add the stubs manually in "libs". See http://b/147773144#comment7. +java_library { + name: "net-utils-device-common-mdns-standalone-build-test", + // Build against core_platform and add the stub libraries manually in "libs", as annotations + // are already included in android_system_stubs_current but package-private, so + // "framework-annotations-lib" needs to be manually included before + // "android_system_stubs_current" (b/272392042) + sdk_version: "core_platform", + srcs: [ + "device/com/android/net/module/util/FdEventsReader.java", + "device/com/android/net/module/util/SharedLog.java", + "framework/com/android/net/module/util/ByteUtils.java", + "framework/com/android/net/module/util/CollectionUtils.java", + "framework/com/android/net/module/util/HexDump.java", + "framework/com/android/net/module/util/LinkPropertiesUtils.java", + ], + libs: [ + "framework-annotations-lib", + "android_system_stubs_current", + "androidx.annotation_annotation", + ], + visibility: ["//packages/modules/Connectivity/service-t"], +} + +// Use a filegroup and not a library for telephony sources, as framework-annotations cannot be +// included either (some annotations would be duplicated on the bootclasspath). +filegroup { + name: "net-utils-telephony-common-srcs", + srcs: [ + // Any class here *must* have a corresponding jarjar rule in the telephony build rules. + "device/android/net/NetworkFactory.java", + "device/android/net/NetworkFactoryImpl.java", + "device/android/net/NetworkFactoryLegacyImpl.java", + "device/android/net/NetworkFactoryShim.java", + ], + path: "device", + visibility: [ + "//frameworks/opt/telephony", + ], +} + +// Use a filegroup and not a library for wifi sources, as this needs corresponding jar-jar +// rules on the wifi side. +// Any class here *must* have a corresponding jarjar rule in the wifi build rules. +filegroup { + name: "net-utils-framework-wifi-common-srcs", + srcs: [ + "framework/com/android/net/module/util/DnsSdTxtRecord.java", + "framework/com/android/net/module/util/Inet4AddressUtils.java", + "framework/com/android/net/module/util/InetAddressUtils.java", + "framework/com/android/net/module/util/MacAddressUtils.java", + "framework/com/android/net/module/util/NetUtils.java", + ], + path: "framework", + visibility: [ + "//frameworks/base", + ], +} + +// Use a filegroup and not a library for wifi sources, as this needs corresponding jar-jar +// rules on the wifi side. +// Any class here *must* have a corresponding jarjar rule in the wifi build rules. +filegroup { + name: "net-utils-wifi-service-common-srcs", + srcs: [ + "device/android/net/NetworkFactory.java", + "device/android/net/NetworkFactoryImpl.java", + "device/android/net/NetworkFactoryLegacyImpl.java", + "device/android/net/NetworkFactoryShim.java", + ], + visibility: [ + "//frameworks/opt/net/wifi/service", + "//packages/modules/Wifi/service", + ], +} diff --git a/staticlibs/TEST_MAPPING b/staticlibs/TEST_MAPPING new file mode 100644 index 0000000000000000000000000000000000000000..b1cb6b52a4336f1911ab77f2437d92af515e6b0e --- /dev/null +++ b/staticlibs/TEST_MAPPING @@ -0,0 +1,28 @@ +{ + "presubmit": [ + { + "name": "netdutils_test" + } + ], + "imports": [ + { + "path": "frameworks/base/core/java/android/net" + }, + // Below tests already run the library tests as part of their coverage tests + { + "path": "packages/modules/NetworkStack" + }, + { + "path": "packages/modules/CaptivePortalLogin" + }, + { + "path": "frameworks/base/packages/Tethering" + }, + { + "path": "packages/modules/Wifi/framework" + }, + { + "path": "vendor/xts/gts-tests/hostsidetests/networkstack" + } + ] +} diff --git a/staticlibs/client-libs/Android.bp b/staticlibs/client-libs/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..c5600453c8f6fff43d680546938f5128b99c7eee --- /dev/null +++ b/staticlibs/client-libs/Android.bp @@ -0,0 +1,26 @@ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +java_library { + name: "netd-client", + srcs: ["netd/**/*"], + sdk_version: "system_current", + min_sdk_version: "29", + apex_available: [ + "//apex_available:platform", + "com.android.tethering", + "com.android.wifi" + ], + visibility: [ + "//packages/modules/Connectivity:__subpackages__", + "//frameworks/base/services:__subpackages__", + "//frameworks/base/packages:__subpackages__", + "//packages/modules/Wifi/service:__subpackages__" + ], + libs: ["androidx.annotation_annotation"], + static_libs: [ + "netd_aidl_interface-lateststable-java", + "netd_event_listener_interface-lateststable-java" + ] +} diff --git a/staticlibs/client-libs/netd/com/android/net/module/util/BaseNetdEventListener.java b/staticlibs/client-libs/netd/com/android/net/module/util/BaseNetdEventListener.java new file mode 100644 index 0000000000000000000000000000000000000000..4180732f310bf6f480ca55e06cdf0c0287801946 --- /dev/null +++ b/staticlibs/client-libs/netd/com/android/net/module/util/BaseNetdEventListener.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.net.module.util; + +import android.net.metrics.INetdEventListener; + +/** + * Base {@link INetdEventListener} that provides no-op implementations which can + * be overridden. + */ +public class BaseNetdEventListener extends INetdEventListener.Stub { + + @Override + public void onDnsEvent(int netId, int eventType, int returnCode, + int latencyMs, String hostname, String[] ipAddresses, + int ipAddressesCount, int uid) { } + + @Override + public void onPrivateDnsValidationEvent(int netId, String ipAddress, + String hostname, boolean validated) { } + + @Override + public void onConnectEvent(int netId, int error, int latencyMs, + String ipAddr, int port, int uid) { } + + @Override + public void onWakeupEvent(String prefix, int uid, int ethertype, + int ipNextHeader, byte[] dstHw, String srcIp, String dstIp, + int srcPort, int dstPort, long timestampNs) { } + + @Override + public void onTcpSocketStatsEvent(int[] networkIds, int[] sentPackets, + int[] lostPackets, int[] rttUs, int[] sentAckDiffMs) { } + + @Override + public void onNat64PrefixEvent(int netId, boolean added, + String prefixString, int prefixLength) { } + + @Override + public int getInterfaceVersion() { + return INetdEventListener.VERSION; + } + + @Override + public String getInterfaceHash() { + return INetdEventListener.HASH; + } +} diff --git a/staticlibs/client-libs/netd/com/android/net/module/util/BaseNetdUnsolicitedEventListener.java b/staticlibs/client-libs/netd/com/android/net/module/util/BaseNetdUnsolicitedEventListener.java new file mode 100644 index 0000000000000000000000000000000000000000..526dd8b1cb08362ce086c990261f9482bedee3b9 --- /dev/null +++ b/staticlibs/client-libs/netd/com/android/net/module/util/BaseNetdUnsolicitedEventListener.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.net.module.util; + +import android.net.INetdUnsolicitedEventListener; + +import androidx.annotation.NonNull; + +/** + * Base {@link INetdUnsolicitedEventListener} that provides no-op implementations which can be + * overridden. + */ +public class BaseNetdUnsolicitedEventListener extends INetdUnsolicitedEventListener.Stub { + + @Override + public void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, + int uid) { } + + @Override + public void onQuotaLimitReached(@NonNull String alertName, @NonNull String ifName) { } + + @Override + public void onInterfaceDnsServerInfo(@NonNull String ifName, long lifetimeS, + @NonNull String[] servers) { } + + @Override + public void onInterfaceAddressUpdated(@NonNull String addr, String ifName, int flags, + int scope) { } + + @Override + public void onInterfaceAddressRemoved(@NonNull String addr, @NonNull String ifName, int flags, + int scope) { } + + @Override + public void onInterfaceAdded(@NonNull String ifName) { } + + @Override + public void onInterfaceRemoved(@NonNull String ifName) { } + + @Override + public void onInterfaceChanged(@NonNull String ifName, boolean up) { } + + @Override + public void onInterfaceLinkStateChanged(@NonNull String ifName, boolean up) { } + + @Override + public void onRouteChanged(boolean updated, @NonNull String route, @NonNull String gateway, + @NonNull String ifName) { } + + @Override + public void onStrictCleartextDetected(int uid, @NonNull String hex) { } + + @Override + public int getInterfaceVersion() { + return INetdUnsolicitedEventListener.VERSION; + } + + @Override + public String getInterfaceHash() { + return INetdUnsolicitedEventListener.HASH; + } +} diff --git a/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java b/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..98fda56547b8a54d3e97bc11b495bf6bbb8ea1a7 --- /dev/null +++ b/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java @@ -0,0 +1,279 @@ +/* + * 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. + */ + +package com.android.net.module.util; + +import static android.net.INetd.IF_STATE_DOWN; +import static android.net.INetd.IF_STATE_UP; +import static android.net.RouteInfo.RTN_THROW; +import static android.net.RouteInfo.RTN_UNICAST; +import static android.net.RouteInfo.RTN_UNREACHABLE; +import static android.system.OsConstants.EBUSY; + +import android.annotation.SuppressLint; +import android.net.INetd; +import android.net.InterfaceConfigurationParcel; +import android.net.IpPrefix; +import android.net.RouteInfo; +import android.net.TetherConfigParcel; +import android.os.RemoteException; +import android.os.ServiceSpecificException; +import android.os.SystemClock; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Collection of utilities for netd. + */ +public class NetdUtils { + private static final String TAG = NetdUtils.class.getSimpleName(); + + /** Used to modify the specified route. */ + public enum ModifyOperation { + ADD, + REMOVE, + } + + /** + * Get InterfaceConfigurationParcel from netd. + */ + public static InterfaceConfigurationParcel getInterfaceConfigParcel(@NonNull INetd netd, + @NonNull String iface) { + try { + return netd.interfaceGetCfg(iface); + } catch (RemoteException | ServiceSpecificException e) { + throw new IllegalStateException(e); + } + } + + private static void validateFlag(String flag) { + if (flag.indexOf(' ') >= 0) { + throw new IllegalArgumentException("flag contains space: " + flag); + } + } + + /** + * Check whether the InterfaceConfigurationParcel contains the target flag or not. + * + * @param config The InterfaceConfigurationParcel instance. + * @param flag Target flag string to be checked. + */ + public static boolean hasFlag(@NonNull final InterfaceConfigurationParcel config, + @NonNull final String flag) { + validateFlag(flag); + final Set<String> flagList = new HashSet<String>(Arrays.asList(config.flags)); + return flagList.contains(flag); + } + + @VisibleForTesting + protected static String[] removeAndAddFlags(@NonNull String[] flags, @NonNull String remove, + @NonNull String add) { + final ArrayList<String> result = new ArrayList<>(); + try { + // Validate the add flag first, so that the for-loop can be ignore once the format of + // add flag is invalid. + validateFlag(add); + for (String flag : flags) { + // Simply ignore both of remove and add flags first, then add the add flag after + // exiting the loop to prevent adding the duplicate flag. + if (remove.equals(flag) || add.equals(flag)) continue; + result.add(flag); + } + result.add(add); + return result.toArray(new String[result.size()]); + } catch (IllegalArgumentException iae) { + throw new IllegalStateException("Invalid InterfaceConfigurationParcel", iae); + } + } + + /** + * Set interface configuration to netd by passing InterfaceConfigurationParcel. + */ + public static void setInterfaceConfig(INetd netd, InterfaceConfigurationParcel configParcel) { + try { + netd.interfaceSetCfg(configParcel); + } catch (RemoteException | ServiceSpecificException e) { + throw new IllegalStateException(e); + } + } + + /** + * Set the given interface up. + */ + public static void setInterfaceUp(INetd netd, String iface) { + final InterfaceConfigurationParcel configParcel = getInterfaceConfigParcel(netd, iface); + configParcel.flags = removeAndAddFlags(configParcel.flags, IF_STATE_DOWN /* remove */, + IF_STATE_UP /* add */); + setInterfaceConfig(netd, configParcel); + } + + /** + * Set the given interface down. + */ + public static void setInterfaceDown(INetd netd, String iface) { + final InterfaceConfigurationParcel configParcel = getInterfaceConfigParcel(netd, iface); + configParcel.flags = removeAndAddFlags(configParcel.flags, IF_STATE_UP /* remove */, + IF_STATE_DOWN /* add */); + setInterfaceConfig(netd, configParcel); + } + + /** Start tethering. */ + public static void tetherStart(final INetd netd, final boolean usingLegacyDnsProxy, + final String[] dhcpRange) throws RemoteException, ServiceSpecificException { + final TetherConfigParcel config = new TetherConfigParcel(); + config.usingLegacyDnsProxy = usingLegacyDnsProxy; + config.dhcpRanges = dhcpRange; + netd.tetherStartWithConfiguration(config); + } + + /** Setup interface for tethering. */ + public static void tetherInterface(final INetd netd, final String iface, final IpPrefix dest) + throws RemoteException, ServiceSpecificException { + tetherInterface(netd, iface, dest, 20 /* maxAttempts */, 50 /* pollingIntervalMs */); + } + + /** Setup interface with configurable retries for tethering. */ + public static void tetherInterface(final INetd netd, final String iface, final IpPrefix dest, + int maxAttempts, int pollingIntervalMs) + throws RemoteException, ServiceSpecificException { + netd.tetherInterfaceAdd(iface); + networkAddInterface(netd, iface, maxAttempts, pollingIntervalMs); + List<RouteInfo> routes = new ArrayList<>(); + routes.add(new RouteInfo(dest, null, iface, RTN_UNICAST)); + addRoutesToLocalNetwork(netd, iface, routes); + } + + /** + * Retry Netd#networkAddInterface for EBUSY error code. + * If the same interface (e.g., wlan0) is in client mode and then switches to tethered mode. + * There can be a race where puts the interface into the local network but interface is still + * in use in netd because the ConnectivityService thread hasn't processed the disconnect yet. + * See b/158269544 for detail. + */ + private static void networkAddInterface(final INetd netd, final String iface, + int maxAttempts, int pollingIntervalMs) + throws ServiceSpecificException, RemoteException { + for (int i = 1; i <= maxAttempts; i++) { + try { + netd.networkAddInterface(INetd.LOCAL_NET_ID, iface); + return; + } catch (ServiceSpecificException e) { + if (e.errorCode == EBUSY && i < maxAttempts) { + SystemClock.sleep(pollingIntervalMs); + continue; + } + + Log.e(TAG, "Retry Netd#networkAddInterface failure: " + e); + throw e; + } + } + } + + /** Reset interface for tethering. */ + public static void untetherInterface(final INetd netd, String iface) + throws RemoteException, ServiceSpecificException { + try { + netd.tetherInterfaceRemove(iface); + } finally { + netd.networkRemoveInterface(INetd.LOCAL_NET_ID, iface); + } + } + + /** Add |routes| to local network. */ + public static void addRoutesToLocalNetwork(final INetd netd, final String iface, + final List<RouteInfo> routes) { + + for (RouteInfo route : routes) { + if (!route.isDefaultRoute()) { + modifyRoute(netd, ModifyOperation.ADD, INetd.LOCAL_NET_ID, route); + } + } + + // IPv6 link local should be activated always. + modifyRoute(netd, ModifyOperation.ADD, INetd.LOCAL_NET_ID, + new RouteInfo(new IpPrefix("fe80::/64"), null, iface, RTN_UNICAST)); + } + + /** Remove routes from local network. */ + public static int removeRoutesFromLocalNetwork(final INetd netd, final List<RouteInfo> routes) { + int failures = 0; + + for (RouteInfo route : routes) { + try { + modifyRoute(netd, ModifyOperation.REMOVE, INetd.LOCAL_NET_ID, route); + } catch (IllegalStateException e) { + failures++; + } + } + + return failures; + } + + @SuppressLint("NewApi") + private static String findNextHop(final RouteInfo route) { + final String nextHop; + switch (route.getType()) { + case RTN_UNICAST: + if (route.hasGateway()) { + nextHop = route.getGateway().getHostAddress(); + } else { + nextHop = INetd.NEXTHOP_NONE; + } + break; + case RTN_UNREACHABLE: + nextHop = INetd.NEXTHOP_UNREACHABLE; + break; + case RTN_THROW: + nextHop = INetd.NEXTHOP_THROW; + break; + default: + nextHop = INetd.NEXTHOP_NONE; + break; + } + return nextHop; + } + + /** Add or remove |route|. */ + public static void modifyRoute(final INetd netd, final ModifyOperation op, final int netId, + final RouteInfo route) { + final String ifName = route.getInterface(); + final String dst = route.getDestination().toString(); + final String nextHop = findNextHop(route); + + try { + switch(op) { + case ADD: + netd.networkAddRoute(netId, ifName, dst, nextHop); + break; + case REMOVE: + netd.networkRemoveRoute(netId, ifName, dst, nextHop); + break; + default: + throw new IllegalStateException("Unsupported modify operation:" + op); + } + } catch (RemoteException | ServiceSpecificException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/staticlibs/client-libs/tests/unit/Android.bp b/staticlibs/client-libs/tests/unit/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..220a6c190419c4001e2cfc2b8d2e05391334a39e --- /dev/null +++ b/staticlibs/client-libs/tests/unit/Android.bp @@ -0,0 +1,44 @@ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_library { + name: "NetdStaticLibTestsLib", + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], + min_sdk_version: "29", + static_libs: [ + "androidx.test.rules", + "mockito-target-extended-minus-junit4", + "net-tests-utils-host-device-common", + "netd-client", + ], + libs: [ + "android.test.runner", + "android.test.base", + ], + visibility: [ + // Visible for Tethering and NetworkStack integration test and link NetdStaticLibTestsLib + // there, so that the tests under client-libs can also be run when running tethering and + // NetworkStack MTS. + "//packages/modules/Connectivity/tests:__subpackages__", + "//packages/modules/Connectivity/Tethering/tests:__subpackages__", + "//packages/modules/NetworkStack/tests/integration", + ] +} + +android_test { + name: "NetdStaticLibTests", + certificate: "platform", + static_libs: [ + "NetdStaticLibTestsLib", + ], + jni_libs: [ + // For mockito extended + "libdexmakerjvmtiagent", + "libstaticjvmtiagent", + ], + test_suites: ["device-tests"], +} diff --git a/staticlibs/client-libs/tests/unit/AndroidManifest.xml b/staticlibs/client-libs/tests/unit/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..7a07d3d549acb6b8ce2059746d734ffecbdf58f5 --- /dev/null +++ b/staticlibs/client-libs/tests/unit/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.frameworks.clientlibs.tests"> + + <application android:debuggable="true"> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.frameworks.clientlibs.tests" + android:label="Netd Static Library Tests" /> +</manifest> diff --git a/staticlibs/client-libs/tests/unit/src/com/android/net/module/util/NetdUtilsTest.java b/staticlibs/client-libs/tests/unit/src/com/android/net/module/util/NetdUtilsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..506967223d182592b05e4e9203a7d482f607bb6e --- /dev/null +++ b/staticlibs/client-libs/tests/unit/src/com/android/net/module/util/NetdUtilsTest.java @@ -0,0 +1,276 @@ +/* + * 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. + */ + +package com.android.net.module.util; + +import static android.net.INetd.LOCAL_NET_ID; +import static android.system.OsConstants.EBUSY; + +import static com.android.testutils.MiscAsserts.assertThrows; + +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.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.net.INetd; +import android.net.InterfaceConfigurationParcel; +import android.net.IpPrefix; +import android.os.RemoteException; +import android.os.ServiceSpecificException; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Arrays; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NetdUtilsTest { + @Mock private INetd mNetd; + + private static final String IFACE = "TEST_IFACE"; + private static final IpPrefix TEST_IPPREFIX = new IpPrefix("192.168.42.1/24"); + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + } + + private void setupFlagsForInterfaceConfiguration(String[] flags) throws Exception { + final InterfaceConfigurationParcel config = new InterfaceConfigurationParcel(); + config.flags = flags; + when(mNetd.interfaceGetCfg(eq(IFACE))).thenReturn(config); + } + + private void verifyMethodsAndArgumentsOfSetInterface(boolean ifaceUp) throws Exception { + final String[] flagsContainDownAndUp = new String[] {"flagA", "down", "flagB", "up"}; + final String[] flagsForInterfaceDown = new String[] {"flagA", "down", "flagB"}; + final String[] flagsForInterfaceUp = new String[] {"flagA", "up", "flagB"}; + final String[] expectedFinalFlags; + setupFlagsForInterfaceConfiguration(flagsContainDownAndUp); + if (ifaceUp) { + // "down" flag will be removed from flagsContainDownAndUp when interface is up. Set + // expectedFinalFlags to flagsForInterfaceUp. + expectedFinalFlags = flagsForInterfaceUp; + NetdUtils.setInterfaceUp(mNetd, IFACE); + } else { + // "up" flag will be removed from flagsContainDownAndUp when interface is down. Set + // expectedFinalFlags to flagsForInterfaceDown. + expectedFinalFlags = flagsForInterfaceDown; + NetdUtils.setInterfaceDown(mNetd, IFACE); + } + verify(mNetd).interfaceSetCfg( + argThat(config -> + // Check if actual flags are the same as expected flags. + // TODO: Have a function in MiscAsserts to check if two arrays are the same. + CollectionUtils.all(Arrays.asList(expectedFinalFlags), + flag -> Arrays.asList(config.flags).contains(flag)) + && CollectionUtils.all(Arrays.asList(config.flags), + flag -> Arrays.asList(expectedFinalFlags).contains(flag)))); + } + + @Test + public void testSetInterfaceUp() throws Exception { + verifyMethodsAndArgumentsOfSetInterface(true /* ifaceUp */); + } + + @Test + public void testSetInterfaceDown() throws Exception { + verifyMethodsAndArgumentsOfSetInterface(false /* ifaceUp */); + } + + @Test + public void testRemoveAndAddFlags() throws Exception { + final String[] flags = new String[] {"flagA", "down", "flagB"}; + // Add an invalid flag and expect to get an IllegalStateException. + assertThrows(IllegalStateException.class, + () -> NetdUtils.removeAndAddFlags(flags, "down" /* remove */, "u p" /* add */)); + } + + private void setNetworkAddInterfaceOutcome(final Exception cause, int numLoops) + throws Exception { + // This cannot be an int because local variables referenced from a lambda expression must + // be final or effectively final. + final Counter myCounter = new Counter(); + doAnswer((invocation) -> { + myCounter.count(); + if (myCounter.isCounterReached(numLoops)) { + if (cause == null) return null; + + throw cause; + } + + throw new ServiceSpecificException(EBUSY); + }).when(mNetd).networkAddInterface(LOCAL_NET_ID, IFACE); + } + + class Counter { + private int mValue = 0; + + private void count() { + mValue++; + } + + private boolean isCounterReached(int target) { + return mValue >= target; + } + } + + @Test + public void testTetherInterfaceSuccessful() throws Exception { + // Expect #networkAddInterface successful at first tries. + verifyTetherInterfaceSucceeds(1); + + // Expect #networkAddInterface successful after 10 tries. + verifyTetherInterfaceSucceeds(10); + } + + private void runTetherInterfaceWithServiceSpecificException(int expectedTries, + int expectedCode) throws Exception { + setNetworkAddInterfaceOutcome(new ServiceSpecificException(expectedCode), expectedTries); + + try { + NetdUtils.tetherInterface(mNetd, IFACE, TEST_IPPREFIX, 20, 0); + fail("Expect throw ServiceSpecificException"); + } catch (ServiceSpecificException e) { + assertEquals(e.errorCode, expectedCode); + } + + verifyNetworkAddInterfaceFails(expectedTries); + reset(mNetd); + } + + private void runTetherInterfaceWithRemoteException(int expectedTries) throws Exception { + setNetworkAddInterfaceOutcome(new RemoteException(), expectedTries); + + try { + NetdUtils.tetherInterface(mNetd, IFACE, TEST_IPPREFIX, 20, 0); + fail("Expect throw RemoteException"); + } catch (RemoteException e) { } + + verifyNetworkAddInterfaceFails(expectedTries); + reset(mNetd); + } + + private void verifyNetworkAddInterfaceFails(int expectedTries) throws Exception { + verify(mNetd).tetherInterfaceAdd(IFACE); + verify(mNetd, times(expectedTries)).networkAddInterface(LOCAL_NET_ID, IFACE); + verify(mNetd, never()).networkAddRoute(anyInt(), anyString(), any(), any()); + verifyNoMoreInteractions(mNetd); + } + + private void verifyTetherInterfaceSucceeds(int expectedTries) throws Exception { + setNetworkAddInterfaceOutcome(null, expectedTries); + + NetdUtils.tetherInterface(mNetd, IFACE, TEST_IPPREFIX); + verify(mNetd).tetherInterfaceAdd(IFACE); + verify(mNetd, times(expectedTries)).networkAddInterface(LOCAL_NET_ID, IFACE); + verify(mNetd, times(2)).networkAddRoute(eq(LOCAL_NET_ID), eq(IFACE), any(), any()); + verifyNoMoreInteractions(mNetd); + reset(mNetd); + } + + @Test + public void testTetherInterfaceFailOnNetworkAddInterface() throws Exception { + // Test throwing ServiceSpecificException with EBUSY failure. + runTetherInterfaceWithServiceSpecificException(20, EBUSY); + + // Test throwing ServiceSpecificException with unexpectedError. + final int unexpectedError = 999; + runTetherInterfaceWithServiceSpecificException(1, unexpectedError); + + // Test throwing ServiceSpecificException with unexpectedError after 7 tries. + runTetherInterfaceWithServiceSpecificException(7, unexpectedError); + + // Test throwing RemoteException. + runTetherInterfaceWithRemoteException(1); + + // Test throwing RemoteException after 3 tries. + runTetherInterfaceWithRemoteException(3); + } + + @Test + public void testNetdUtilsHasFlag() throws Exception { + final String[] flags = new String[] {"up", "broadcast", "running", "multicast"}; + setupFlagsForInterfaceConfiguration(flags); + + // Set interface up. + NetdUtils.setInterfaceUp(mNetd, IFACE); + final ArgumentCaptor<InterfaceConfigurationParcel> arg = + ArgumentCaptor.forClass(InterfaceConfigurationParcel.class); + verify(mNetd, times(1)).interfaceSetCfg(arg.capture()); + + final InterfaceConfigurationParcel p = arg.getValue(); + assertTrue(NetdUtils.hasFlag(p, "up")); + assertTrue(NetdUtils.hasFlag(p, "running")); + assertTrue(NetdUtils.hasFlag(p, "broadcast")); + assertTrue(NetdUtils.hasFlag(p, "multicast")); + assertFalse(NetdUtils.hasFlag(p, "down")); + } + + @Test + public void testNetdUtilsHasFlag_flagContainsSpace() throws Exception { + final String[] flags = new String[] {"up", "broadcast", "running", "multicast"}; + setupFlagsForInterfaceConfiguration(flags); + + // Set interface up. + NetdUtils.setInterfaceUp(mNetd, IFACE); + final ArgumentCaptor<InterfaceConfigurationParcel> arg = + ArgumentCaptor.forClass(InterfaceConfigurationParcel.class); + verify(mNetd, times(1)).interfaceSetCfg(arg.capture()); + + final InterfaceConfigurationParcel p = arg.getValue(); + assertThrows(IllegalArgumentException.class, () -> NetdUtils.hasFlag(p, "up ")); + } + + @Test + public void testNetdUtilsHasFlag_UppercaseString() throws Exception { + final String[] flags = new String[] {"up", "broadcast", "running", "multicast"}; + setupFlagsForInterfaceConfiguration(flags); + + // Set interface up. + NetdUtils.setInterfaceUp(mNetd, IFACE); + final ArgumentCaptor<InterfaceConfigurationParcel> arg = + ArgumentCaptor.forClass(InterfaceConfigurationParcel.class); + verify(mNetd, times(1)).interfaceSetCfg(arg.capture()); + + final InterfaceConfigurationParcel p = arg.getValue(); + assertFalse(NetdUtils.hasFlag(p, "UP")); + assertFalse(NetdUtils.hasFlag(p, "BROADCAST")); + assertFalse(NetdUtils.hasFlag(p, "RUNNING")); + assertFalse(NetdUtils.hasFlag(p, "MULTICAST")); + } +} diff --git a/staticlibs/device/android/net/NetworkFactory.java b/staticlibs/device/android/net/NetworkFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..87f6dee6ecbe0771341513013900bcc216188df6 --- /dev/null +++ b/staticlibs/device/android/net/NetworkFactory.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2014 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; + +import static com.android.modules.utils.build.SdkLevel.isAtLeastS; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.os.Looper; +import android.os.Message; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +/** + * A NetworkFactory is an entity that creates NetworkAgent objects. + * The bearers register with ConnectivityService using {@link #register} and + * their factory will start receiving scored NetworkRequests. NetworkRequests + * can be filtered 3 ways: by NetworkCapabilities, by score and more complexly by + * overridden function. All of these can be dynamic - changing NetworkCapabilities + * or score forces re-evaluation of all current requests. + * + * If any requests pass the filter some overrideable functions will be called. + * If the bearer only cares about very simple start/stopNetwork callbacks, those + * functions can be overridden. If the bearer needs more interaction, it can + * override addNetworkRequest and removeNetworkRequest which will give it each + * request that passes their current filters. + * + * This class is mostly a shim which delegates to one of two implementations depending + * on the SDK level of the device it's running on. + * + * @hide + **/ +public class NetworkFactory { + static final boolean DBG = true; + static final boolean VDBG = false; + + final NetworkFactoryShim mImpl; + + private final String LOG_TAG; + + // Ideally the filter argument would be non-null, but null has historically meant to see + // no requests and telephony passes null. + public NetworkFactory(Looper looper, Context context, String logTag, + @Nullable final NetworkCapabilities filter) { + LOG_TAG = logTag; + if (isAtLeastS()) { + mImpl = new NetworkFactoryImpl(this, looper, context, filter); + } else { + mImpl = new NetworkFactoryLegacyImpl(this, looper, context, filter); + } + } + + // TODO : these two constants and the method are only used by telephony tests. Replace it in + // the tests and remove them and the associated code. + public static final int CMD_REQUEST_NETWORK = 1; + public static final int CMD_CANCEL_REQUEST = 2; + /** Like Handler#obtainMessage */ + @VisibleForTesting + public Message obtainMessage(final int what, final int arg1, final int arg2, + final @Nullable Object obj) { + return mImpl.obtainMessage(what, arg1, arg2, obj); + } + + // Called by BluetoothNetworkFactory + public final Looper getLooper() { + return mImpl.getLooper(); + } + + // Refcount for simple mode requests + private int mRefCount = 0; + + /* Registers this NetworkFactory with the system. May only be called once per factory. */ + public void register() { + mImpl.register(LOG_TAG); + } + + /** + * Registers this NetworkFactory with the system ignoring the score filter. This will let + * the factory always see all network requests matching its capabilities filter. + * May only be called once per factory. + */ + public void registerIgnoringScore() { + mImpl.registerIgnoringScore(LOG_TAG); + } + + /** Unregisters this NetworkFactory. After this call, the object can no longer be used. */ + public void terminate() { + mImpl.terminate(); + } + + protected final void reevaluateAllRequests() { + mImpl.reevaluateAllRequests(); + } + + /** + * Overridable function to provide complex filtering. + * Called for every request every time a new NetworkRequest is seen + * and whenever the filterScore or filterNetworkCapabilities change. + * + * acceptRequest can be overridden to provide complex filter behavior + * for the incoming requests + * + * For output, this class will call {@link #needNetworkFor} and + * {@link #releaseNetworkFor} for every request that passes the filters. + * If you don't need to see every request, you can leave the base + * implementations of those two functions and instead override + * {@link #startNetwork} and {@link #stopNetwork}. + * + * If you want to see every score fluctuation on every request, set + * your score filter to a very high number and watch {@link #needNetworkFor}. + * + * @return {@code true} to accept the request. + */ + public boolean acceptRequest(@NonNull final NetworkRequest request) { + return true; + } + + /** + * Can be called by a factory to release a request as unfulfillable: the request will be + * removed, and the caller will get a + * {@link ConnectivityManager.NetworkCallback#onUnavailable()} callback after this function + * returns. + * + * Note: this should only be called by factory which KNOWS that it is the ONLY factory which + * is able to fulfill this request! + */ + protected void releaseRequestAsUnfulfillableByAnyFactory(NetworkRequest r) { + mImpl.releaseRequestAsUnfulfillableByAnyFactory(r); + } + + // override to do simple mode (request independent) + protected void startNetwork() { } + protected void stopNetwork() { } + + // override to do fancier stuff + protected void needNetworkFor(@NonNull final NetworkRequest networkRequest) { + if (++mRefCount == 1) startNetwork(); + } + + protected void releaseNetworkFor(@NonNull final NetworkRequest networkRequest) { + if (--mRefCount == 0) stopNetwork(); + } + + /** + * @deprecated this method was never part of the API (system or public) and is only added + * for migration of existing clients. + */ + @Deprecated + public void setScoreFilter(final int score) { + mImpl.setScoreFilter(score); + } + + /** + * Set a score filter for this factory. + * + * This should include the transports the factory knows its networks will have, and + * an optimistic view of the attributes it may have. This does not commit the factory + * to being able to bring up such a network ; it only lets it avoid hearing about + * requests that it has no chance of fulfilling. + * + * @param score the filter + */ + public void setScoreFilter(@NonNull final NetworkScore score) { + mImpl.setScoreFilter(score); + } + + public void setCapabilityFilter(NetworkCapabilities netCap) { + mImpl.setCapabilityFilter(netCap); + } + + @VisibleForTesting + protected int getRequestCount() { + return mImpl.getRequestCount(); + } + + public int getSerialNumber() { + return mImpl.getSerialNumber(); + } + + public NetworkProvider getProvider() { + return mImpl.getProvider(); + } + + protected void log(String s) { + Log.d(LOG_TAG, s); + } + + public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + mImpl.dump(fd, writer, args); + } + + @Override + public String toString() { + return "{" + LOG_TAG + " " + mImpl.toString() + "}"; + } +} diff --git a/staticlibs/device/android/net/NetworkFactoryImpl.java b/staticlibs/device/android/net/NetworkFactoryImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..9c1190c219dcd49b866b0b60b48abf7cc0425043 --- /dev/null +++ b/staticlibs/device/android/net/NetworkFactoryImpl.java @@ -0,0 +1,322 @@ +/* + * 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. + */ + +package android.net; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.net.NetworkProvider.NetworkOfferCallback; +import android.os.Looper; +import android.os.Message; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.Executor; + +/** + * A NetworkFactory is an entity that creates NetworkAgent objects. + * The bearers register with ConnectivityService using {@link #register} and + * their factory will start receiving scored NetworkRequests. NetworkRequests + * can be filtered 3 ways: by NetworkCapabilities, by score and more complexly by + * overridden function. All of these can be dynamic - changing NetworkCapabilities + * or score forces re-evaluation of all current requests. + * + * If any requests pass the filter some overrideable functions will be called. + * If the bearer only cares about very simple start/stopNetwork callbacks, those + * functions can be overridden. If the bearer needs more interaction, it can + * override addNetworkRequest and removeNetworkRequest which will give it each + * request that passes their current filters. + * @hide + **/ +// TODO(b/187083878): factor out common code between this and NetworkFactoryLegacyImpl +class NetworkFactoryImpl extends NetworkFactoryLegacyImpl { + private static final boolean DBG = NetworkFactory.DBG; + private static final boolean VDBG = NetworkFactory.VDBG; + + // A score that will win against everything, so that score filtering will let all requests + // through + // TODO : remove this and replace with an API to listen to all requests. + @NonNull + private static final NetworkScore INVINCIBLE_SCORE = + new NetworkScore.Builder().setLegacyInt(1000).build(); + + // TODO(b/187082970): Replace CMD_* with Handler.post(() -> { ... }) since all the CMDs do is to + // run the tasks asynchronously on the Handler thread. + + /** + * Pass a network request to the bearer. If the bearer believes it can + * satisfy the request it should connect to the network and create a + * NetworkAgent. Once the NetworkAgent is fully functional it will + * register itself with ConnectivityService using registerNetworkAgent. + * If the bearer cannot immediately satisfy the request (no network, + * user disabled the radio, lower-scored network) it should remember + * any NetworkRequests it may be able to satisfy in the future. It may + * disregard any that it will never be able to service, for example + * those requiring a different bearer. + * msg.obj = NetworkRequest + */ + // TODO : this and CANCEL_REQUEST are only used by telephony tests. Replace it in the tests + // and remove them and the associated code. + private static final int CMD_REQUEST_NETWORK = NetworkFactory.CMD_REQUEST_NETWORK; + + /** + * Cancel a network request + * msg.obj = NetworkRequest + */ + private static final int CMD_CANCEL_REQUEST = NetworkFactory.CMD_CANCEL_REQUEST; + + /** + * Internally used to set our best-guess score. + * msg.obj = new score + */ + private static final int CMD_SET_SCORE = 3; + + /** + * Internally used to set our current filter for coarse bandwidth changes with + * technology changes. + * msg.obj = new filter + */ + private static final int CMD_SET_FILTER = 4; + + /** + * Internally used to send the network offer associated with this factory. + * No arguments, will read from members + */ + private static final int CMD_OFFER_NETWORK = 5; + + /** + * Internally used to send the request to listen to all requests. + * No arguments, will read from members + */ + private static final int CMD_LISTEN_TO_ALL_REQUESTS = 6; + + private final Map<NetworkRequest, NetworkRequestInfo> mNetworkRequests = + new LinkedHashMap<>(); + + @NonNull private NetworkScore mScore = new NetworkScore.Builder().setLegacyInt(0).build(); + + private final NetworkOfferCallback mRequestCallback = new NetworkOfferCallback() { + @Override + public void onNetworkNeeded(@NonNull final NetworkRequest request) { + handleAddRequest(request); + } + + @Override + public void onNetworkUnneeded(@NonNull final NetworkRequest request) { + handleRemoveRequest(request); + } + }; + @NonNull private final Executor mExecutor = command -> post(command); + + + // Ideally the filter argument would be non-null, but null has historically meant to see + // no requests and telephony passes null. + NetworkFactoryImpl(NetworkFactory parent, Looper looper, Context context, + @Nullable final NetworkCapabilities filter) { + super(parent, looper, context, + null != filter ? filter : + NetworkCapabilities.Builder.withoutDefaultCapabilities().build()); + } + + /* Registers this NetworkFactory with the system. May only be called once per factory. */ + @Override public void register(final String logTag) { + register(logTag, false); + } + + /** + * Registers this NetworkFactory with the system ignoring the score filter. This will let + * the factory always see all network requests matching its capabilities filter. + * May only be called once per factory. + */ + @Override public void registerIgnoringScore(final String logTag) { + register(logTag, true); + } + + private void register(final String logTag, final boolean listenToAllRequests) { + if (mProvider != null) { + throw new IllegalStateException("A NetworkFactory must only be registered once"); + } + if (DBG) mParent.log("Registering NetworkFactory"); + + mProvider = new NetworkProvider(mContext, NetworkFactoryImpl.this.getLooper(), logTag) { + @Override + public void onNetworkRequested(@NonNull NetworkRequest request, int score, + int servingProviderId) { + handleAddRequest(request); + } + + @Override + public void onNetworkRequestWithdrawn(@NonNull NetworkRequest request) { + handleRemoveRequest(request); + } + }; + + ((ConnectivityManager) mContext.getSystemService( + Context.CONNECTIVITY_SERVICE)).registerNetworkProvider(mProvider); + + // The mScore and mCapabilityFilter members can only be accessed on the handler thread. + // TODO : offer a separate API to listen to all requests instead + if (listenToAllRequests) { + sendMessage(obtainMessage(CMD_LISTEN_TO_ALL_REQUESTS)); + } else { + sendMessage(obtainMessage(CMD_OFFER_NETWORK)); + } + } + + private void handleOfferNetwork(@NonNull final NetworkScore score) { + mProvider.registerNetworkOffer(score, mCapabilityFilter, mExecutor, mRequestCallback); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case CMD_REQUEST_NETWORK: { + handleAddRequest((NetworkRequest) msg.obj); + break; + } + case CMD_CANCEL_REQUEST: { + handleRemoveRequest((NetworkRequest) msg.obj); + break; + } + case CMD_SET_SCORE: { + handleSetScore((NetworkScore) msg.obj); + break; + } + case CMD_SET_FILTER: { + handleSetFilter((NetworkCapabilities) msg.obj); + break; + } + case CMD_OFFER_NETWORK: { + handleOfferNetwork(mScore); + break; + } + case CMD_LISTEN_TO_ALL_REQUESTS: { + handleOfferNetwork(INVINCIBLE_SCORE); + break; + } + } + } + + private static class NetworkRequestInfo { + @NonNull public final NetworkRequest request; + public boolean requested; // do we have a request outstanding, limited by score + + NetworkRequestInfo(@NonNull final NetworkRequest request) { + this.request = request; + this.requested = false; + } + + @Override + public String toString() { + return "{" + request + ", requested=" + requested + "}"; + } + } + + /** + * Add a NetworkRequest that the bearer may want to attempt to satisfy. + * @see #CMD_REQUEST_NETWORK + * + * @param request the request to handle. + */ + private void handleAddRequest(@NonNull final NetworkRequest request) { + NetworkRequestInfo n = mNetworkRequests.get(request); + if (n == null) { + if (DBG) mParent.log("got request " + request); + n = new NetworkRequestInfo(request); + mNetworkRequests.put(n.request, n); + } else { + if (VDBG) mParent.log("handle existing request " + request); + } + if (VDBG) mParent.log(" my score=" + mScore + ", my filter=" + mCapabilityFilter); + + if (mParent.acceptRequest(request)) { + n.requested = true; + mParent.needNetworkFor(request); + } + } + + private void handleRemoveRequest(NetworkRequest request) { + NetworkRequestInfo n = mNetworkRequests.get(request); + if (n != null) { + mNetworkRequests.remove(request); + if (n.requested) mParent.releaseNetworkFor(n.request); + } + } + + private void handleSetScore(@NonNull final NetworkScore score) { + if (mScore.equals(score)) return; + mScore = score; + mParent.reevaluateAllRequests(); + } + + private void handleSetFilter(@NonNull final NetworkCapabilities netCap) { + if (netCap.equals(mCapabilityFilter)) return; + mCapabilityFilter = netCap; + mParent.reevaluateAllRequests(); + } + + @Override public final void reevaluateAllRequests() { + if (mProvider == null) return; + mProvider.registerNetworkOffer(mScore, mCapabilityFilter, mExecutor, mRequestCallback); + } + + /** + * @deprecated this method was never part of the API (system or public) and is only added + * for migration of existing clients. + */ + @Deprecated + public void setScoreFilter(final int score) { + setScoreFilter(new NetworkScore.Builder().setLegacyInt(score).build()); + } + + /** + * Set a score filter for this factory. + * + * This should include the transports the factory knows its networks will have, and + * an optimistic view of the attributes it may have. This does not commit the factory + * to being able to bring up such a network ; it only lets it avoid hearing about + * requests that it has no chance of fulfilling. + * + * @param score the filter + */ + @Override public void setScoreFilter(@NonNull final NetworkScore score) { + sendMessage(obtainMessage(CMD_SET_SCORE, score)); + } + + @Override public void setCapabilityFilter(NetworkCapabilities netCap) { + sendMessage(obtainMessage(CMD_SET_FILTER, new NetworkCapabilities(netCap))); + } + + @Override public int getRequestCount() { + return mNetworkRequests.size(); + } + + @Override public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + writer.println(toString()); + for (NetworkRequestInfo n : mNetworkRequests.values()) { + writer.println(" " + n); + } + } + + @Override public String toString() { + return "providerId=" + (mProvider != null ? mProvider.getProviderId() : "null") + + ", ScoreFilter=" + mScore + ", Filter=" + mCapabilityFilter + + ", requests=" + mNetworkRequests.size(); + } +} diff --git a/staticlibs/device/android/net/NetworkFactoryLegacyImpl.java b/staticlibs/device/android/net/NetworkFactoryLegacyImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..6cba6258880ac30c2af68971fd6d15c4cbe2df47 --- /dev/null +++ b/staticlibs/device/android/net/NetworkFactoryLegacyImpl.java @@ -0,0 +1,397 @@ +/* + * 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. + */ + +package android.net; + +import android.annotation.NonNull; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * A NetworkFactory is an entity that creates NetworkAgent objects. + * The bearers register with ConnectivityService using {@link #register} and + * their factory will start receiving scored NetworkRequests. NetworkRequests + * can be filtered 3 ways: by NetworkCapabilities, by score and more complexly by + * overridden function. All of these can be dynamic - changing NetworkCapabilities + * or score forces re-evaluation of all current requests. + * + * If any requests pass the filter some overrideable functions will be called. + * If the bearer only cares about very simple start/stopNetwork callbacks, those + * functions can be overridden. If the bearer needs more interaction, it can + * override addNetworkRequest and removeNetworkRequest which will give it each + * request that passes their current filters. + * @hide + **/ +// TODO(b/187083878): factor out common code between this and NetworkFactoryImpl +class NetworkFactoryLegacyImpl extends Handler + implements NetworkFactoryShim { + private static final boolean DBG = NetworkFactory.DBG; + private static final boolean VDBG = NetworkFactory.VDBG; + + // TODO(b/187082970): Replace CMD_* with Handler.post(() -> { ... }) since all the CMDs do is to + // run the tasks asynchronously on the Handler thread. + + /** + * Pass a network request to the bearer. If the bearer believes it can + * satisfy the request it should connect to the network and create a + * NetworkAgent. Once the NetworkAgent is fully functional it will + * register itself with ConnectivityService using registerNetworkAgent. + * If the bearer cannot immediately satisfy the request (no network, + * user disabled the radio, lower-scored network) it should remember + * any NetworkRequests it may be able to satisfy in the future. It may + * disregard any that it will never be able to service, for example + * those requiring a different bearer. + * msg.obj = NetworkRequest + * msg.arg1 = score - the score of the network currently satisfying this + * request. If this bearer knows in advance it cannot + * exceed this score it should not try to connect, holding the request + * for the future. + * Note that subsequent events may give a different (lower + * or higher) score for this request, transmitted to each + * NetworkFactory through additional CMD_REQUEST_NETWORK msgs + * with the same NetworkRequest but an updated score. + * Also, network conditions may change for this bearer + * allowing for a better score in the future. + * msg.arg2 = the ID of the NetworkProvider currently responsible for the + * NetworkAgent handling this request, or NetworkProvider.ID_NONE if none. + */ + public static final int CMD_REQUEST_NETWORK = 1; + + /** + * Cancel a network request + * msg.obj = NetworkRequest + */ + public static final int CMD_CANCEL_REQUEST = 2; + + /** + * Internally used to set our best-guess score. + * msg.arg1 = new score + */ + private static final int CMD_SET_SCORE = 3; + + /** + * Internally used to set our current filter for coarse bandwidth changes with + * technology changes. + * msg.obj = new filter + */ + private static final int CMD_SET_FILTER = 4; + + final Context mContext; + final NetworkFactory mParent; + + private final Map<NetworkRequest, NetworkRequestInfo> mNetworkRequests = + new LinkedHashMap<>(); + + private int mScore; + NetworkCapabilities mCapabilityFilter; + + NetworkProvider mProvider = null; + + NetworkFactoryLegacyImpl(NetworkFactory parent, Looper looper, Context context, + NetworkCapabilities filter) { + super(looper); + mParent = parent; + mContext = context; + mCapabilityFilter = filter; + } + + /* Registers this NetworkFactory with the system. May only be called once per factory. */ + @Override public void register(final String logTag) { + if (mProvider != null) { + throw new IllegalStateException("A NetworkFactory must only be registered once"); + } + if (DBG) mParent.log("Registering NetworkFactory"); + + mProvider = new NetworkProvider(mContext, NetworkFactoryLegacyImpl.this.getLooper(), + logTag) { + @Override + public void onNetworkRequested(@NonNull NetworkRequest request, int score, + int servingProviderId) { + handleAddRequest(request, score, servingProviderId); + } + + @Override + public void onNetworkRequestWithdrawn(@NonNull NetworkRequest request) { + handleRemoveRequest(request); + } + }; + + ((ConnectivityManager) mContext.getSystemService( + Context.CONNECTIVITY_SERVICE)).registerNetworkProvider(mProvider); + } + + /** Unregisters this NetworkFactory. After this call, the object can no longer be used. */ + @Override public void terminate() { + if (mProvider == null) { + throw new IllegalStateException("This NetworkFactory was never registered"); + } + if (DBG) mParent.log("Unregistering NetworkFactory"); + + ((ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE)) + .unregisterNetworkProvider(mProvider); + + // Remove all pending messages, since this object cannot be reused. Any message currently + // being processed will continue to run. + removeCallbacksAndMessages(null); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case CMD_REQUEST_NETWORK: { + handleAddRequest((NetworkRequest) msg.obj, msg.arg1, msg.arg2); + break; + } + case CMD_CANCEL_REQUEST: { + handleRemoveRequest((NetworkRequest) msg.obj); + break; + } + case CMD_SET_SCORE: { + handleSetScore(msg.arg1); + break; + } + case CMD_SET_FILTER: { + handleSetFilter((NetworkCapabilities) msg.obj); + break; + } + } + } + + private static class NetworkRequestInfo { + public final NetworkRequest request; + public int score; + public boolean requested; // do we have a request outstanding, limited by score + public int providerId; + + NetworkRequestInfo(NetworkRequest request, int score, int providerId) { + this.request = request; + this.score = score; + this.requested = false; + this.providerId = providerId; + } + + @Override + public String toString() { + return "{" + request + ", score=" + score + ", requested=" + requested + "}"; + } + } + + /** + * Add a NetworkRequest that the bearer may want to attempt to satisfy. + * @see #CMD_REQUEST_NETWORK + * + * @param request the request to handle. + * @param score the score of the NetworkAgent currently satisfying this request. + * @param servingProviderId the ID of the NetworkProvider that created the NetworkAgent + * currently satisfying this request. + */ + @VisibleForTesting + protected void handleAddRequest(NetworkRequest request, int score, int servingProviderId) { + NetworkRequestInfo n = mNetworkRequests.get(request); + if (n == null) { + if (DBG) { + mParent.log("got request " + request + " with score " + score + + " and providerId " + servingProviderId); + } + n = new NetworkRequestInfo(request, score, servingProviderId); + mNetworkRequests.put(n.request, n); + } else { + if (VDBG) { + mParent.log("new score " + score + " for existing request " + request + + " and providerId " + servingProviderId); + } + n.score = score; + n.providerId = servingProviderId; + } + if (VDBG) mParent.log(" my score=" + mScore + ", my filter=" + mCapabilityFilter); + + evalRequest(n); + } + + private void handleRemoveRequest(NetworkRequest request) { + NetworkRequestInfo n = mNetworkRequests.get(request); + if (n != null) { + mNetworkRequests.remove(request); + if (n.requested) mParent.releaseNetworkFor(n.request); + } + } + + private void handleSetScore(int score) { + mScore = score; + evalRequests(); + } + + private void handleSetFilter(NetworkCapabilities netCap) { + mCapabilityFilter = netCap; + evalRequests(); + } + + /** + * Overridable function to provide complex filtering. + * Called for every request every time a new NetworkRequest is seen + * and whenever the filterScore or filterNetworkCapabilities change. + * + * acceptRequest can be overridden to provide complex filter behavior + * for the incoming requests + * + * For output, this class will call {@link NetworkFactory#needNetworkFor} and + * {@link NetworkFactory#releaseNetworkFor} for every request that passes the filters. + * If you don't need to see every request, you can leave the base + * implementations of those two functions and instead override + * {@link NetworkFactory#startNetwork} and {@link NetworkFactory#stopNetwork}. + * + * If you want to see every score fluctuation on every request, set + * your score filter to a very high number and watch {@link NetworkFactory#needNetworkFor}. + * + * @return {@code true} to accept the request. + */ + public boolean acceptRequest(NetworkRequest request) { + return mParent.acceptRequest(request); + } + + private void evalRequest(NetworkRequestInfo n) { + if (VDBG) { + mParent.log("evalRequest"); + mParent.log(" n.requests = " + n.requested); + mParent.log(" n.score = " + n.score); + mParent.log(" mScore = " + mScore); + mParent.log(" request.providerId = " + n.providerId); + mParent.log(" mProvider.id = " + mProvider.getProviderId()); + } + if (shouldNeedNetworkFor(n)) { + if (VDBG) mParent.log(" needNetworkFor"); + mParent.needNetworkFor(n.request); + n.requested = true; + } else if (shouldReleaseNetworkFor(n)) { + if (VDBG) mParent.log(" releaseNetworkFor"); + mParent.releaseNetworkFor(n.request); + n.requested = false; + } else { + if (VDBG) mParent.log(" done"); + } + } + + private boolean shouldNeedNetworkFor(NetworkRequestInfo n) { + // If this request is already tracked, it doesn't qualify for need + return !n.requested + // If the score of this request is higher or equal to that of this factory and some + // other factory is responsible for it, then this factory should not track the request + // because it has no hope of satisfying it. + && (n.score < mScore || n.providerId == mProvider.getProviderId()) + // If this factory can't satisfy the capability needs of this request, then it + // should not be tracked. + && n.request.canBeSatisfiedBy(mCapabilityFilter) + // Finally if the concrete implementation of the factory rejects the request, then + // don't track it. + && acceptRequest(n.request); + } + + private boolean shouldReleaseNetworkFor(NetworkRequestInfo n) { + // Don't release a request that's not tracked. + return n.requested + // The request should be released if it can't be satisfied by this factory. That + // means either of the following conditions are met : + // - Its score is too high to be satisfied by this factory and it's not already + // assigned to the factory + // - This factory can't satisfy the capability needs of the request + // - The concrete implementation of the factory rejects the request + && ((n.score > mScore && n.providerId != mProvider.getProviderId()) + || !n.request.canBeSatisfiedBy(mCapabilityFilter) + || !acceptRequest(n.request)); + } + + private void evalRequests() { + for (NetworkRequestInfo n : mNetworkRequests.values()) { + evalRequest(n); + } + } + + /** + * Post a command, on this NetworkFactory Handler, to re-evaluate all + * outstanding requests. Can be called from a factory implementation. + */ + @Override public void reevaluateAllRequests() { + post(this::evalRequests); + } + + /** + * Can be called by a factory to release a request as unfulfillable: the request will be + * removed, and the caller will get a + * {@link ConnectivityManager.NetworkCallback#onUnavailable()} callback after this function + * returns. + * + * Note: this should only be called by factory which KNOWS that it is the ONLY factory which + * is able to fulfill this request! + */ + @Override public void releaseRequestAsUnfulfillableByAnyFactory(NetworkRequest r) { + post(() -> { + if (DBG) mParent.log("releaseRequestAsUnfulfillableByAnyFactory: " + r); + final NetworkProvider provider = mProvider; + if (provider == null) { + mParent.log("Ignoring attempt to release unregistered request as unfulfillable"); + return; + } + provider.declareNetworkRequestUnfulfillable(r); + }); + } + + @Override public void setScoreFilter(int score) { + sendMessage(obtainMessage(CMD_SET_SCORE, score, 0)); + } + + @Override public void setScoreFilter(@NonNull final NetworkScore score) { + setScoreFilter(score.getLegacyInt()); + } + + @Override public void setCapabilityFilter(NetworkCapabilities netCap) { + sendMessage(obtainMessage(CMD_SET_FILTER, new NetworkCapabilities(netCap))); + } + + @Override public int getRequestCount() { + return mNetworkRequests.size(); + } + + /* TODO: delete when all callers have migrated to NetworkProvider IDs. */ + @Override public int getSerialNumber() { + return mProvider.getProviderId(); + } + + @Override public NetworkProvider getProvider() { + return mProvider; + } + + @Override public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + writer.println(toString()); + for (NetworkRequestInfo n : mNetworkRequests.values()) { + writer.println(" " + n); + } + } + + @Override public String toString() { + return "providerId=" + (mProvider != null ? mProvider.getProviderId() : "null") + + ", ScoreFilter=" + mScore + ", Filter=" + mCapabilityFilter + + ", requests=" + mNetworkRequests.size(); + } +} diff --git a/staticlibs/device/android/net/NetworkFactoryShim.java b/staticlibs/device/android/net/NetworkFactoryShim.java new file mode 100644 index 0000000000000000000000000000000000000000..dfbb5ec104e9c99fe78ff044eeffa1b67571fc25 --- /dev/null +++ b/staticlibs/device/android/net/NetworkFactoryShim.java @@ -0,0 +1,71 @@ +/* + * 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. + */ + +package android.net; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Looper; +import android.os.Message; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +/** + * Extract an interface for multiple implementation of {@link NetworkFactory}, depending on the SDK + * version. + * + * Known implementations: + * - {@link NetworkFactoryImpl}: For Android S+ + * - {@link NetworkFactoryLegacyImpl}: For Android R- + * + * @hide + */ +interface NetworkFactoryShim { + void register(String logTag); + + default void registerIgnoringScore(String logTag) { + throw new UnsupportedOperationException(); + } + + void terminate(); + + void releaseRequestAsUnfulfillableByAnyFactory(NetworkRequest r); + + void reevaluateAllRequests(); + + void setScoreFilter(int score); + + void setScoreFilter(@NonNull NetworkScore score); + + void setCapabilityFilter(NetworkCapabilities netCap); + + int getRequestCount(); + + int getSerialNumber(); + + NetworkProvider getProvider(); + + void dump(FileDescriptor fd, PrintWriter writer, String[] args); + + // All impls inherit Handler + @VisibleForTesting + Message obtainMessage(int what, int arg1, int arg2, @Nullable Object obj); + + Looper getLooper(); +} diff --git a/staticlibs/device/com/android/net/module/util/BpfBitmap.java b/staticlibs/device/com/android/net/module/util/BpfBitmap.java new file mode 100644 index 0000000000000000000000000000000000000000..d2a5b654507e147877dc28c181dc17ee8a2101cb --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/BpfBitmap.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util; + +import android.system.ErrnoException; + +import androidx.annotation.NonNull; + + /** + * + * Generic bitmap class for use with BPF programs. Corresponds to a BpfMap + * array type with key->int and value->uint64_t defined in the bpf program. + * + */ +public class BpfBitmap { + private BpfMap<Struct.S32, Struct.S64> mBpfMap; + + /** + * Create a BpfBitmap map wrapper with "path" of filesystem. + * + * @param path The path of the BPF map. + */ + public BpfBitmap(@NonNull String path) throws ErrnoException { + mBpfMap = new BpfMap<Struct.S32, Struct.S64>(path, BpfMap.BPF_F_RDWR, + Struct.S32.class, Struct.S64.class); + } + + /** + * Retrieves the value from BpfMap for the given key. + * + * @param key The key in the map corresponding to the value to return. + */ + private long getBpfMapValue(Struct.S32 key) throws ErrnoException { + Struct.S64 curVal = mBpfMap.getValue(key); + if (curVal != null) { + return curVal.val; + } else { + return 0; + } + } + + /** + * Retrieves the bit for the given index in the bitmap. + * + * @param index Position in bitmap. + */ + public boolean get(int index) throws ErrnoException { + if (index < 0) return false; + + Struct.S32 key = new Struct.S32(index >> 6); + return ((getBpfMapValue(key) >>> (index & 63)) & 1L) != 0; + } + + /** + * Set the specified index in the bitmap. + * + * @param index Position to set in bitmap. + */ + public void set(int index) throws ErrnoException { + set(index, true); + } + + /** + * Unset the specified index in the bitmap. + * + * @param index Position to unset in bitmap. + */ + public void unset(int index) throws ErrnoException { + set(index, false); + } + + /** + * Change the specified index in the bitmap to set value. + * + * @param index Position to unset in bitmap. + * @param set Boolean indicating to set or unset index. + */ + public void set(int index, boolean set) throws ErrnoException { + if (index < 0) throw new IllegalArgumentException("Index out of bounds."); + + Struct.S32 key = new Struct.S32(index >> 6); + long mask = (1L << (index & 63)); + long val = getBpfMapValue(key); + if (set) val |= mask; else val &= ~mask; + mBpfMap.updateEntry(key, new Struct.S64(val)); + } + + /** + * Clears the map. The map may already be empty. + * + * @throws ErrnoException if updating entry to 0 fails. + */ + public void clear() throws ErrnoException { + mBpfMap.forEach((key, value) -> { + mBpfMap.updateEntry(key, new Struct.S64(0)); + }); + } + + /** + * Checks if all bitmap values are 0. + */ + public boolean isEmpty() throws ErrnoException { + Struct.S32 key = mBpfMap.getFirstKey(); + while (key != null) { + if (getBpfMapValue(key) != 0) { + return false; + } + key = mBpfMap.getNextKey(key); + } + return true; + } +} diff --git a/staticlibs/device/com/android/net/module/util/BpfDump.java b/staticlibs/device/com/android/net/module/util/BpfDump.java new file mode 100644 index 0000000000000000000000000000000000000000..7549e712301e65361e32d52a58397a0dc5d734dc --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/BpfDump.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.net.module.util; + +import static android.system.OsConstants.R_OK; + +import android.system.ErrnoException; +import android.system.Os; +import android.util.Base64; +import android.util.Pair; + +import androidx.annotation.NonNull; + +import java.io.PrintWriter; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.function.BiFunction; + +/** + * The classes and the methods for BPF dump utilization. + */ +public class BpfDump { + // Using "," as a separator between base64 encoded key and value is safe because base64 + // characters are [0-9a-zA-Z/=+]. + private static final String BASE64_DELIMITER = ","; + + /** + * Encode BPF key and value into a base64 format string which uses the delimiter ',': + * <base64 encoded key>,<base64 encoded value> + */ + public static <K extends Struct, V extends Struct> String toBase64EncodedString( + @NonNull final K key, @NonNull final V value) { + final byte[] keyBytes = key.writeToBytes(); + final String keyBase64Str = Base64.encodeToString(keyBytes, Base64.DEFAULT) + .replace("\n", ""); + final byte[] valueBytes = value.writeToBytes(); + final String valueBase64Str = Base64.encodeToString(valueBytes, Base64.DEFAULT) + .replace("\n", ""); + + return keyBase64Str + BASE64_DELIMITER + valueBase64Str; + } + + /** + * Decode Struct from a base64 format string + */ + private static <T extends Struct> T parseStruct( + Class<T> structClass, @NonNull String base64Str) { + final byte[] bytes = Base64.decode(base64Str, Base64.DEFAULT); + final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + byteBuffer.order(ByteOrder.nativeOrder()); + return Struct.parse(structClass, byteBuffer); + } + + /** + * Decode BPF key and value from a base64 format string which uses the delimiter ',': + * <base64 encoded key>,<base64 encoded value> + */ + public static <K extends Struct, V extends Struct> Pair<K, V> fromBase64EncodedString( + Class<K> keyClass, Class<V> valueClass, @NonNull String base64Str) { + String[] keyValueStrs = base64Str.split(BASE64_DELIMITER); + if (keyValueStrs.length != 2 /* key + value */) { + throw new IllegalArgumentException("Invalid base64Str (" + base64Str + "), base64Str" + + " must contain exactly one delimiter '" + BASE64_DELIMITER + "'"); + } + final K k = parseStruct(keyClass, keyValueStrs[0]); + final V v = parseStruct(valueClass, keyValueStrs[1]); + return new Pair<>(k, v); + } + + /** + * Dump the BpfMap name and entries + */ + public static <K extends Struct, V extends Struct> void dumpMap(IBpfMap<K, V> map, + PrintWriter pw, String mapName, BiFunction<K, V, String> entryToString) { + dumpMap(map, pw, mapName, "" /* header */, entryToString); + } + + /** + * Dump the BpfMap name, header, and entries + */ + public static <K extends Struct, V extends Struct> void dumpMap(IBpfMap<K, V> map, + PrintWriter pw, String mapName, String header, BiFunction<K, V, String> entryToString) { + pw.println(mapName + ":"); + if (!header.isEmpty()) { + pw.println(" " + header); + } + try { + map.forEach((key, value) -> { + // Value could be null if there is a concurrent entry deletion. + // http://b/220084230. + if (value != null) { + pw.println(" " + entryToString.apply(key, value)); + } else { + pw.println("Entry is deleted while dumping, iterating from first entry"); + } + }); + } catch (ErrnoException e) { + pw.println("Map dump end with error: " + Os.strerror(e.errno)); + } + } + + /** + * Dump the BpfMap status + */ + public static <K extends Struct, V extends Struct> void dumpMapStatus(IBpfMap<K, V> map, + PrintWriter pw, String mapName, String path) { + if (map != null) { + pw.println(mapName + ": OK"); + return; + } + try { + Os.access(path, R_OK); + pw.println(mapName + ": NULL(map is pinned to " + path + ")"); + } catch (ErrnoException e) { + pw.println(mapName + ": NULL(map is not pinned to " + path + ": " + + Os.strerror(e.errno) + ")"); + } + } + + // TODO: add a helper to dump bpf map content with the map name, the header line + // (ex: "BPF ingress map: iif nat64Prefix v6Addr -> v4Addr oif"), a lambda that + // knows how to dump each line, and the PrintWriter. +} diff --git a/staticlibs/device/com/android/net/module/util/BpfMap.java b/staticlibs/device/com/android/net/module/util/BpfMap.java new file mode 100644 index 0000000000000000000000000000000000000000..d45caceb92b8ead9ca466e4a6966a452b329a2b5 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/BpfMap.java @@ -0,0 +1,312 @@ +/* + * 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. + */ +package com.android.net.module.util; + +import static android.system.OsConstants.EEXIST; +import static android.system.OsConstants.ENOENT; + +import android.os.ParcelFileDescriptor; +import android.system.ErrnoException; +import android.util.Pair; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +/** + * BpfMap is a key -> value mapping structure that is designed to maintained the bpf map entries. + * This is a wrapper class of in-kernel data structure. The in-kernel data can be read/written by + * passing syscalls with map file descriptor. + * + * @param <K> the key of the map. + * @param <V> the value of the map. + */ +public class BpfMap<K extends Struct, V extends Struct> implements IBpfMap<K, V> { + static { + System.loadLibrary(JniUtil.getJniLibraryName(BpfMap.class.getPackage())); + } + + // Following definitions from kernel include/uapi/linux/bpf.h + public static final int BPF_F_RDWR = 0; + public static final int BPF_F_RDONLY = 1 << 3; + public static final int BPF_F_WRONLY = 1 << 4; + + public static final int BPF_MAP_TYPE_HASH = 1; + + private static final int BPF_F_NO_PREALLOC = 1; + + private static final int BPF_ANY = 0; + private static final int BPF_NOEXIST = 1; + private static final int BPF_EXIST = 2; + + private final ParcelFileDescriptor mMapFd; + private final Class<K> mKeyClass; + private final Class<V> mValueClass; + private final int mKeySize; + private final int mValueSize; + + private static ConcurrentHashMap<Pair<String, Integer>, ParcelFileDescriptor> sFdCache = + new ConcurrentHashMap<>(); + + private static ParcelFileDescriptor cachedBpfFdGet(String path, int mode, + int keySize, int valueSize) + throws ErrnoException, NullPointerException { + // Supports up to 1023 byte key and 65535 byte values + // Creating a BpfMap with larger keys/values seems like a bad idea any way... + keySize &= 1023; // 10-bits + valueSize &= 65535; // 16-bits + var key = Pair.create(path, (mode << 26) ^ (keySize << 16) ^ valueSize); + // unlocked fetch is safe: map is concurrent read capable, and only inserted into + ParcelFileDescriptor fd = sFdCache.get(key); + if (fd != null) return fd; + // ok, no cached fd present, need to grab a lock + synchronized (BpfMap.class) { + // need to redo the check + fd = sFdCache.get(key); + if (fd != null) return fd; + // okay, we really haven't opened this before... + fd = ParcelFileDescriptor.adoptFd(nativeBpfFdGet(path, mode, keySize, valueSize)); + sFdCache.put(key, fd); + return fd; + } + } + + /** + * Create a BpfMap map wrapper with "path" of filesystem. + * + * @param flag the access mode, one of BPF_F_RDWR, BPF_F_RDONLY, or BPF_F_WRONLY. + * @throws ErrnoException if the BPF map associated with {@code path} cannot be retrieved. + * @throws NullPointerException if {@code path} is null. + */ + public BpfMap(@NonNull final String path, final int flag, final Class<K> key, + final Class<V> value) throws ErrnoException, NullPointerException { + mKeyClass = key; + mValueClass = value; + mKeySize = Struct.getSize(key); + mValueSize = Struct.getSize(value); + mMapFd = cachedBpfFdGet(path, flag, mKeySize, mValueSize); + } + + /** + * Update an existing or create a new key -> value entry in an eBbpf map. + * (use insertOrReplaceEntry() if you need to know whether insert or replace happened) + */ + @Override + public void updateEntry(K key, V value) throws ErrnoException { + nativeWriteToMapEntry(mMapFd.getFd(), key.writeToBytes(), value.writeToBytes(), BPF_ANY); + } + + /** + * If the key does not exist in the map, insert key -> value entry into eBpf map. + * Otherwise IllegalStateException will be thrown. + */ + @Override + public void insertEntry(K key, V value) + throws ErrnoException, IllegalStateException { + try { + nativeWriteToMapEntry(mMapFd.getFd(), key.writeToBytes(), value.writeToBytes(), + BPF_NOEXIST); + } catch (ErrnoException e) { + if (e.errno == EEXIST) throw new IllegalStateException(key + " already exists"); + + throw e; + } + } + + /** + * If the key already exists in the map, replace its value. Otherwise NoSuchElementException + * will be thrown. + */ + @Override + public void replaceEntry(K key, V value) + throws ErrnoException, NoSuchElementException { + try { + nativeWriteToMapEntry(mMapFd.getFd(), key.writeToBytes(), value.writeToBytes(), + BPF_EXIST); + } catch (ErrnoException e) { + if (e.errno == ENOENT) throw new NoSuchElementException(key + " not found"); + + throw e; + } + } + + /** + * Update an existing or create a new key -> value entry in an eBbpf map. + * Returns true if inserted, false if replaced. + * (use updateEntry() if you don't care whether insert or replace happened) + * Note: see inline comment below if running concurrently with delete operations. + */ + @Override + public boolean insertOrReplaceEntry(K key, V value) + throws ErrnoException { + try { + nativeWriteToMapEntry(mMapFd.getFd(), key.writeToBytes(), value.writeToBytes(), + BPF_NOEXIST); + return true; /* insert succeeded */ + } catch (ErrnoException e) { + if (e.errno != EEXIST) throw e; + } + try { + nativeWriteToMapEntry(mMapFd.getFd(), key.writeToBytes(), value.writeToBytes(), + BPF_EXIST); + return false; /* replace succeeded */ + } catch (ErrnoException e) { + if (e.errno != ENOENT) throw e; + } + /* If we reach here somebody deleted after our insert attempt and before our replace: + * this implies a race happened. The kernel bpf delete interface only takes a key, + * and not the value, so we can safely pretend the replace actually succeeded and + * was immediately followed by the other thread's delete, since the delete cannot + * observe the potential change to the value. + */ + return false; /* pretend replace succeeded */ + } + + /** Remove existing key from eBpf map. Return false if map was not modified. */ + @Override + public boolean deleteEntry(K key) throws ErrnoException { + return nativeDeleteMapEntry(mMapFd.getFd(), key.writeToBytes()); + } + + /** Returns {@code true} if this map contains no elements. */ + @Override + public boolean isEmpty() throws ErrnoException { + return getFirstKey() == null; + } + + private K getNextKeyInternal(@Nullable K key) throws ErrnoException { + byte[] rawKey = new byte[mKeySize]; + + if (!nativeGetNextMapKey(mMapFd.getFd(), + key == null ? null : key.writeToBytes(), + rawKey)) return null; + + final ByteBuffer buffer = ByteBuffer.wrap(rawKey); + buffer.order(ByteOrder.nativeOrder()); + return Struct.parse(mKeyClass, buffer); + } + + /** + * Get the next key of the passed-in key. If the passed-in key is not found, return the first + * key. If the passed-in key is the last one, return null. + * + * TODO: consider allowing null passed-in key. + */ + @Override + public K getNextKey(@NonNull K key) throws ErrnoException { + Objects.requireNonNull(key); + return getNextKeyInternal(key); + } + + /** Get the first key of eBpf map. */ + @Override + public K getFirstKey() throws ErrnoException { + return getNextKeyInternal(null); + } + + /** Check whether a key exists in the map. */ + @Override + public boolean containsKey(@NonNull K key) throws ErrnoException { + Objects.requireNonNull(key); + + byte[] rawValue = new byte[mValueSize]; + return nativeFindMapEntry(mMapFd.getFd(), key.writeToBytes(), rawValue); + } + + /** Retrieve a value from the map. Return null if there is no such key. */ + @Override + public V getValue(@NonNull K key) throws ErrnoException { + Objects.requireNonNull(key); + + byte[] rawValue = new byte[mValueSize]; + if (!nativeFindMapEntry(mMapFd.getFd(), key.writeToBytes(), rawValue)) return null; + + final ByteBuffer buffer = ByteBuffer.wrap(rawValue); + buffer.order(ByteOrder.nativeOrder()); + return Struct.parse(mValueClass, buffer); + } + + /** + * Iterate through the map and handle each key -> value retrieved base on the given BiConsumer. + * The given BiConsumer may to delete the passed-in entry, but is not allowed to perform any + * other structural modifications to the map, such as adding entries or deleting other entries. + * Otherwise, iteration will result in undefined behaviour. + */ + @Override + public void forEach(ThrowingBiConsumer<K, V> action) throws ErrnoException { + @Nullable K nextKey = getFirstKey(); + + while (nextKey != null) { + @NonNull final K curKey = nextKey; + @NonNull final V value = getValue(curKey); + + nextKey = getNextKey(curKey); + action.accept(curKey, value); + } + } + + /* Empty implementation to implement AutoCloseable, so we can use BpfMaps + * with try with resources, but due to persistent FD cache, there is no actual + * need to close anything. File descriptors will actually be closed when we + * unlock the BpfMap class and destroy the ParcelFileDescriptor objects. + */ + @Override + public void close() throws IOException { + } + + /** + * Clears the map. The map may already be empty. + * + * @throws ErrnoException if the map is already closed, if an error occurred during iteration, + * or if a non-ENOENT error occurred when deleting a key. + */ + @Override + public void clear() throws ErrnoException { + K key = getFirstKey(); + while (key != null) { + deleteEntry(key); // ignores ENOENT. + key = getFirstKey(); + } + } + + private static native int nativeBpfFdGet(String path, int mode, int keySize, int valueSize) + throws ErrnoException, NullPointerException; + + // Note: the following methods appear to not require the object by virtue of taking the + // fd as an int argument, but the hidden reference to this is actually what prevents + // the object from being garbage collected (and thus potentially maps closed) prior + // to the native code actually running (with a possibly already closed fd). + + private native void nativeWriteToMapEntry(int fd, byte[] key, byte[] value, int flags) + throws ErrnoException; + + private native boolean nativeDeleteMapEntry(int fd, byte[] key) throws ErrnoException; + + // If key is found, the operation returns true and the nextKey would reference to the next + // element. If key is not found, the operation returns true and the nextKey would reference to + // the first element. If key is the last element, false is returned. + private native boolean nativeGetNextMapKey(int fd, byte[] key, byte[] nextKey) + throws ErrnoException; + + private native boolean nativeFindMapEntry(int fd, byte[] key, byte[] value) + throws ErrnoException; +} diff --git a/staticlibs/device/com/android/net/module/util/BpfUtils.java b/staticlibs/device/com/android/net/module/util/BpfUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..94af11b34723748f794efefe13ac2d80412e17e3 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/BpfUtils.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.net.module.util; + +import androidx.annotation.NonNull; + +import java.io.IOException; + +/** + * The classes and the methods for BPF utilization. + * + * {@hide} + */ +public class BpfUtils { + static { + System.loadLibrary(JniUtil.getJniLibraryName(BpfUtils.class.getPackage())); + } + + // Defined in include/uapi/linux/bpf.h. Only adding the CGROUPS currently being used for now. + public static final int BPF_CGROUP_INET_INGRESS = 0; + public static final int BPF_CGROUP_INET_EGRESS = 1; + public static final int BPF_CGROUP_INET4_BIND = 8; + public static final int BPF_CGROUP_INET6_BIND = 9; + + + /** + * Attach BPF program to CGROUP + */ + public static void attachProgram(int type, @NonNull String programPath, + @NonNull String cgroupPath, int flags) throws IOException { + native_attachProgramToCgroup(type, programPath, cgroupPath, flags); + } + + /** + * Detach BPF program from CGROUP + */ + public static void detachProgram(int type, @NonNull String cgroupPath) + throws IOException { + native_detachProgramFromCgroup(type, cgroupPath); + } + + /** + * Detach single BPF program from CGROUP + */ + public static void detachSingleProgram(int type, @NonNull String programPath, + @NonNull String cgroupPath) throws IOException { + native_detachSingleProgramFromCgroup(type, programPath, cgroupPath); + } + + private static native boolean native_attachProgramToCgroup(int type, String programPath, + String cgroupPath, int flags) throws IOException; + private static native boolean native_detachProgramFromCgroup(int type, String cgroupPath) + throws IOException; + private static native boolean native_detachSingleProgramFromCgroup(int type, + String programPath, String cgroupPath) throws IOException; +} diff --git a/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java b/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..e646f37a6e2d5ad25e8bb9b1902a2c960a2918be --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java @@ -0,0 +1,417 @@ +/* + * 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. + */ + +package com.android.net.module.util; + +import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; +import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; +import static android.provider.DeviceConfig.NAMESPACE_TETHERING; + +import static com.android.net.module.util.FeatureVersions.CONNECTIVITY_MODULE_ID; +import static com.android.net.module.util.FeatureVersions.MODULE_MASK; +import static com.android.net.module.util.FeatureVersions.NETWORK_STACK_MODULE_ID; +import static com.android.net.module.util.FeatureVersions.VERSION_MASK; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.provider.DeviceConfig; +import android.util.Log; + +import androidx.annotation.BoolRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + +import java.util.ArrayList; +import java.util.List; + +/** + * Utilities for modules to query {@link DeviceConfig} and flags. + */ +public final class DeviceConfigUtils { + private DeviceConfigUtils() {} + + private static final String TAG = DeviceConfigUtils.class.getSimpleName(); + /** + * DO NOT MODIFY: this may be used by multiple modules that will not see the updated value + * until they are recompiled, so modifying this constant means that different modules may + * be referencing a different tethering module variant, or having a stale reference. + */ + public static final String TETHERING_MODULE_NAME = "com.android.tethering"; + + @VisibleForTesting + public static final String RESOURCES_APK_INTENT = + "com.android.server.connectivity.intent.action.SERVICE_CONNECTIVITY_RESOURCES_APK"; + private static final String CONNECTIVITY_RES_PKG_DIR = "/apex/" + TETHERING_MODULE_NAME + "/"; + + @VisibleForTesting + public static final long DEFAULT_PACKAGE_VERSION = 1000; + + @VisibleForTesting + public static void resetPackageVersionCacheForTest() { + sPackageVersion = -1; + sModuleVersion = -1; + sNetworkStackModuleVersion = -1; + } + + private static volatile long sPackageVersion = -1; + private static long getPackageVersion(@NonNull final Context context) { + // sPackageVersion may be set by another thread just after this check, but querying the + // package version several times on rare occasions is fine. + if (sPackageVersion >= 0) { + return sPackageVersion; + } + try { + final long version = context.getPackageManager().getPackageInfo( + context.getPackageName(), 0).getLongVersionCode(); + sPackageVersion = version; + return version; + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Failed to get package info: " + e); + return DEFAULT_PACKAGE_VERSION; + } + } + + /** + * Look up the value of a property for a particular namespace from {@link DeviceConfig}. + * @param namespace The namespace containing the property to look up. + * @param name The name of the property to look up. + * @param defaultValue The value to return if the property does not exist or has no valid value. + * @return the corresponding value, or defaultValue if none exists. + */ + @Nullable + public static String getDeviceConfigProperty(@NonNull String namespace, @NonNull String name, + @Nullable String defaultValue) { + String value = DeviceConfig.getProperty(namespace, name); + return value != null ? value : defaultValue; + } + + /** + * Look up the value of a property for a particular namespace from {@link DeviceConfig}. + * @param namespace The namespace containing the property to look up. + * @param name The name of the property to look up. + * @param defaultValue The value to return if the property does not exist or its value is null. + * @return the corresponding value, or defaultValue if none exists. + */ + public static int getDeviceConfigPropertyInt(@NonNull String namespace, @NonNull String name, + int defaultValue) { + String value = getDeviceConfigProperty(namespace, name, null /* defaultValue */); + try { + return (value != null) ? Integer.parseInt(value) : defaultValue; + } catch (NumberFormatException e) { + return defaultValue; + } + } + + /** + * Look up the value of a property for a particular namespace from {@link DeviceConfig}. + * + * Flags like timeouts should use this method and set an appropriate min/max range: if invalid + * values like "0" or "1" are pushed to devices, everything would timeout. The min/max range + * protects against this kind of breakage. + * @param namespace The namespace containing the property to look up. + * @param name The name of the property to look up. + * @param minimumValue The minimum value of a property. + * @param maximumValue The maximum value of a property. + * @param defaultValue The value to return if the property does not exist or its value is null. + * @return the corresponding value, or defaultValue if none exists or the fetched value is + * not in the provided range. + */ + public static int getDeviceConfigPropertyInt(@NonNull String namespace, @NonNull String name, + int minimumValue, int maximumValue, int defaultValue) { + int value = getDeviceConfigPropertyInt(namespace, name, defaultValue); + if (value < minimumValue || value > maximumValue) return defaultValue; + return value; + } + + /** + * Look up the value of a property for a particular namespace from {@link DeviceConfig}. + * @param namespace The namespace containing the property to look up. + * @param name The name of the property to look up. + * @param defaultValue The value to return if the property does not exist or its value is null. + * @return the corresponding value, or defaultValue if none exists. + */ + public static boolean getDeviceConfigPropertyBoolean(@NonNull String namespace, + @NonNull String name, boolean defaultValue) { + String value = getDeviceConfigProperty(namespace, name, null /* defaultValue */); + return (value != null) ? Boolean.parseBoolean(value) : defaultValue; + } + + /** + * Check whether or not one specific experimental feature for a particular namespace from + * {@link DeviceConfig} is enabled by comparing module package version + * with current version of property. If this property version is valid, the corresponding + * experimental feature would be enabled, otherwise disabled. + * + * This is useful to ensure that if a module install is rolled back, flags are not left fully + * rolled out on a version where they have not been well tested. + * @param context The global context information about an app environment. + * @param name The name of the property to look up. + * @return true if this feature is enabled, or false if disabled. + */ + public static boolean isNetworkStackFeatureEnabled(@NonNull Context context, + @NonNull String name) { + return isNetworkStackFeatureEnabled(context, name, false /* defaultEnabled */); + } + + /** + * Check whether or not one specific experimental feature for a particular namespace from + * {@link DeviceConfig} is enabled by comparing module package version + * with current version of property. If this property version is valid, the corresponding + * experimental feature would be enabled, otherwise disabled. + * + * This is useful to ensure that if a module install is rolled back, flags are not left fully + * rolled out on a version where they have not been well tested. + * @param context The global context information about an app environment. + * @param name The name of the property to look up. + * @param defaultEnabled The value to return if the property does not exist or its value is + * null. + * @return true if this feature is enabled, or false if disabled. + */ + public static boolean isNetworkStackFeatureEnabled(@NonNull Context context, + @NonNull String name, boolean defaultEnabled) { + final long packageVersion = getPackageVersion(context); + return isFeatureEnabled(context, packageVersion, NAMESPACE_CONNECTIVITY, name, + defaultEnabled); + } + + /** + * Check whether or not one specific experimental feature for a particular namespace from + * {@link DeviceConfig} is enabled by comparing module package version + * with current version of property. If this property version is valid, the corresponding + * experimental feature would be enabled, otherwise disabled. + * + * This is useful to ensure that if a module install is rolled back, flags are not left fully + * rolled out on a version where they have not been well tested. + * + * If the feature is disabled by default and enabled by flag push, this method should be used. + * If the feature is enabled by default and disabled by flag push (kill switch), + * {@link #isTetheringFeatureNotChickenedOut(String)} should be used. + * + * @param context The global context information about an app environment. + * @param name The name of the property to look up. + * @return true if this feature is enabled, or false if disabled. + */ + public static boolean isTetheringFeatureEnabled(@NonNull Context context, + @NonNull String name) { + final long packageVersion = getTetheringModuleVersion(context); + return isFeatureEnabled(context, packageVersion, NAMESPACE_TETHERING, name, + false /* defaultEnabled */); + } + + private static boolean isFeatureEnabled(@NonNull Context context, long packageVersion, + @NonNull String namespace, String name, boolean defaultEnabled) { + final int propertyVersion = getDeviceConfigPropertyInt(namespace, name, + 0 /* default value */); + return (propertyVersion == 0 && defaultEnabled) + || (propertyVersion != 0 && packageVersion >= (long) propertyVersion); + } + + // Guess the tethering module name based on the package prefix of the connectivity resources + // Take the resource package name, cut it before "connectivity" and append "tethering". + // Then resolve that package version number with packageManager. + // If that fails retry by appending "go.tethering" instead + private static long resolveTetheringModuleVersion(@NonNull Context context) + throws PackageManager.NameNotFoundException { + final String pkgPrefix = resolvePkgPrefix(context); + final PackageManager packageManager = context.getPackageManager(); + try { + return packageManager.getPackageInfo(pkgPrefix + "tethering", + PackageManager.MATCH_APEX).getLongVersionCode(); + } catch (PackageManager.NameNotFoundException e) { + Log.d(TAG, "Device is using go modules"); + // fall through + } + + return packageManager.getPackageInfo(pkgPrefix + "go.tethering", + PackageManager.MATCH_APEX).getLongVersionCode(); + } + + private static String resolvePkgPrefix(Context context) { + final String connResourcesPackage = getConnectivityResourcesPackageName(context); + final int pkgPrefixLen = connResourcesPackage.indexOf("connectivity"); + if (pkgPrefixLen < 0) { + throw new IllegalStateException( + "Invalid connectivity resources package: " + connResourcesPackage); + } + + return connResourcesPackage.substring(0, pkgPrefixLen); + } + + private static volatile long sModuleVersion = -1; + private static long getTetheringModuleVersion(@NonNull Context context) { + if (sModuleVersion >= 0) return sModuleVersion; + + try { + sModuleVersion = resolveTetheringModuleVersion(context); + } catch (PackageManager.NameNotFoundException e) { + // It's expected to fail tethering module version resolution on the devices with + // flattened apex + Log.e(TAG, "Failed to resolve tethering module version: " + e); + return DEFAULT_PACKAGE_VERSION; + } + return sModuleVersion; + } + + private static volatile long sNetworkStackModuleVersion = -1; + + /** + * Get networkstack module version. + */ + @VisibleForTesting + static long getNetworkStackModuleVersion(@NonNull Context context) { + if (sNetworkStackModuleVersion >= 0) return sNetworkStackModuleVersion; + + try { + sNetworkStackModuleVersion = resolveNetworkStackModuleVersion(context); + } catch (PackageManager.NameNotFoundException e) { + Log.wtf(TAG, "Failed to resolve networkstack module version: " + e); + return DEFAULT_PACKAGE_VERSION; + } + return sNetworkStackModuleVersion; + } + + private static long resolveNetworkStackModuleVersion(@NonNull Context context) + throws PackageManager.NameNotFoundException { + // TODO(b/293975546): Strictly speaking this is the prefix for connectivity and not + // network stack. In practice, it's the same. Read the prefix from network stack instead. + final String pkgPrefix = resolvePkgPrefix(context); + final PackageManager packageManager = context.getPackageManager(); + try { + return packageManager.getPackageInfo(pkgPrefix + "networkstack", + PackageManager.MATCH_SYSTEM_ONLY).getLongVersionCode(); + } catch (PackageManager.NameNotFoundException e) { + Log.d(TAG, "Device is using go or non-mainline modules"); + // fall through + } + + return packageManager.getPackageInfo(pkgPrefix + "go.networkstack", + PackageManager.MATCH_ALL).getLongVersionCode(); + } + + /** + * Check whether one specific feature is supported from the feature Id. The feature Id is + * composed by a module package Id and version Id from {@link FeatureVersions}. + * + * This is useful when a feature required minimal module version supported and cannot function + * well with a standalone newer module. + * @param context The global context information about an app environment. + * @param featureId The feature id that contains required module id and minimal module version + * @return true if this feature is supported, or false if not supported. + **/ + public static boolean isFeatureSupported(@NonNull Context context, long featureId) { + final long moduleVersion; + final long moduleId = featureId & MODULE_MASK; + if (moduleId == CONNECTIVITY_MODULE_ID) { + moduleVersion = getTetheringModuleVersion(context); + } else if (moduleId == NETWORK_STACK_MODULE_ID) { + moduleVersion = getNetworkStackModuleVersion(context); + } else { + throw new IllegalArgumentException("Unknown module " + moduleId); + } + // Support by default if no module version is available. + return moduleVersion == DEFAULT_PACKAGE_VERSION + || moduleVersion >= (featureId & VERSION_MASK); + } + + /** + * Check whether one specific experimental feature in specific namespace from + * {@link DeviceConfig} is not disabled. Feature can be disabled by setting a non-zero + * value in the property. If the feature is enabled by default and disabled by flag push + * (kill switch), this method should be used. If the feature is disabled by default and + * enabled by flag push, {@link #isFeatureEnabled} should be used. + * + * @param namespace The namespace containing the property to look up. + * @param name The name of the property to look up. + * @return true if this feature is enabled, or false if disabled. + */ + private static boolean isFeatureNotChickenedOut(String namespace, String name) { + final int propertyVersion = getDeviceConfigPropertyInt(namespace, name, + 0 /* default value */); + return propertyVersion == 0; + } + + /** + * Check whether one specific experimental feature in Tethering module from {@link DeviceConfig} + * is not disabled. + * + * @param name The name of the property in tethering module to look up. + * @return true if this feature is enabled, or false if disabled. + */ + public static boolean isTetheringFeatureNotChickenedOut(String name) { + return isFeatureNotChickenedOut(NAMESPACE_TETHERING, name); + } + + /** + * Check whether one specific experimental feature in NetworkStack module from + * {@link DeviceConfig} is not disabled. + * + * @param name The name of the property in NetworkStack module to look up. + * @return true if this feature is enabled, or false if disabled. + */ + public static boolean isNetworkStackFeatureNotChickenedOut(String name) { + return isFeatureNotChickenedOut(NAMESPACE_CONNECTIVITY, name); + } + + /** + * Gets boolean config from resources. + */ + public static boolean getResBooleanConfig(@NonNull final Context context, + @BoolRes int configResource, final boolean defaultValue) { + final Resources res = context.getResources(); + try { + return res.getBoolean(configResource); + } catch (Resources.NotFoundException e) { + return defaultValue; + } + } + + /** + * Gets int config from resources. + */ + public static int getResIntegerConfig(@NonNull final Context context, + @BoolRes int configResource, final int defaultValue) { + final Resources res = context.getResources(); + try { + return res.getInteger(configResource); + } catch (Resources.NotFoundException e) { + return defaultValue; + } + } + + /** + * Get the package name of the ServiceConnectivityResources package, used to provide resources + * for service-connectivity. + */ + @NonNull + public static String getConnectivityResourcesPackageName(@NonNull Context context) { + final List<ResolveInfo> pkgs = new ArrayList<>(context.getPackageManager() + .queryIntentActivities(new Intent(RESOURCES_APK_INTENT), MATCH_SYSTEM_ONLY)); + pkgs.removeIf(pkg -> !pkg.activityInfo.applicationInfo.sourceDir.startsWith( + CONNECTIVITY_RES_PKG_DIR)); + if (pkgs.size() > 1) { + Log.wtf(TAG, "More than one connectivity resources package found: " + pkgs); + } + if (pkgs.isEmpty()) { + throw new IllegalStateException("No connectivity resource package found"); + } + + return pkgs.get(0).activityInfo.applicationInfo.packageName; + } +} diff --git a/staticlibs/device/com/android/net/module/util/DomainUtils.java b/staticlibs/device/com/android/net/module/util/DomainUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..b327fd085c29c1b64c02b897ef0da6e9fd1ae0e2 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/DomainUtils.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util; + +import android.util.ArrayMap; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.net.module.util.DnsPacketUtils.DnsRecordParser; + +import java.nio.BufferOverflowException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; + +/** + * Utilities for encoding/decoding the domain name or domain search list. + * + * @hide + */ +public final class DomainUtils { + private static final String TAG = "DomainUtils"; + private static final int MAX_OPTION_LEN = 255; + + @NonNull + private static String getSubstring(@NonNull final String string, @NonNull final String[] labels, + int index) { + int beginIndex = 0; + for (int i = 0; i < index; i++) { + beginIndex += labels[i].length() + 1; // include the dot + } + return string.substring(beginIndex); + } + + /** + * Encode the given single domain name to byte array, comply with RFC1035 section-3.1. + * + * @return null if the given domain string is invalid, otherwise, return a byte array + * wrapping the encoded domain, not including any padded octets, caller should + * pad zero octets at the end if needed. + */ + @Nullable + public static byte[] encode(@NonNull final String domain) { + if (!DnsRecordParser.isHostName(domain)) return null; + return encode(new String[]{ domain }, false /* compression */); + } + + /** + * Encode the given multiple domain names to byte array, comply with RFC1035 section-3.1 + * and section 4.1.4 (message compression) if enabled. + * + * @return Null if encode fails due to BufferOverflowException, otherwise, return a byte + * array wrapping the encoded domains, not including any padded octets, caller + * should pad zero octets at the end if needed. The byte array may be empty if + * the given domain strings are invalid. + */ + @Nullable + public static byte[] encode(@NonNull final String[] domains, boolean compression) { + try { + final ByteBuffer buffer = ByteBuffer.allocate(MAX_OPTION_LEN); + final ArrayMap<String, Integer> offsetMap = new ArrayMap<>(); + for (int i = 0; i < domains.length; i++) { + if (!DnsRecordParser.isHostName(domains[i])) { + Log.e(TAG, "Skip invalid domain name " + domains[i]); + continue; + } + final String[] labels = domains[i].split("\\."); + for (int j = 0; j < labels.length; j++) { + if (compression) { + final String suffix = getSubstring(domains[i], labels, j); + if (offsetMap.containsKey(suffix)) { + int offsetOfSuffix = offsetMap.get(suffix); + offsetOfSuffix |= 0xC000; + buffer.putShort((short) offsetOfSuffix); + break; // unnecessary to put the compressed string into map + } else { + offsetMap.put(suffix, buffer.position()); + } + } + // encode the domain name string without compression when: + // - compression feature isn't enabled, + // - suffix does not match any string in the map. + final byte[] labelBytes = labels[j].getBytes(StandardCharsets.UTF_8); + buffer.put((byte) labelBytes.length); + buffer.put(labelBytes); + if (j == labels.length - 1) { + // Pad terminate label at the end of last label. + buffer.put((byte) 0); + } + } + } + buffer.flip(); + final byte[] out = new byte[buffer.limit()]; + buffer.get(out); + return out; + } catch (BufferOverflowException e) { + Log.e(TAG, "Fail to encode domain name and stop encoding", e); + return null; + } + } + + /** + * Decode domain name(s) from the given byteBuffer. Decode follows RFC1035 section 3.1 and + * section 4.1.4(message compression). + * + * @return domain name(s) string array with space separated, or empty string if decode fails. + */ + @NonNull + public static ArrayList<String> decode(@NonNull final ByteBuffer buffer, boolean compression) { + final ArrayList<String> domainList = new ArrayList<>(); + while (buffer.remaining() > 0) { + try { + // TODO: replace the recursion with loop in parseName and don't need to pass in the + // maxLabelCount parameter to prevent recursion from overflowing stack. + final String domain = DnsRecordParser.parseName(buffer, 0 /* depth */, + 15 /* maxLabelCount */, compression); + if (!DnsRecordParser.isHostName(domain)) continue; + domainList.add(domain); + } catch (BufferUnderflowException | DnsPacket.ParseException e) { + Log.e(TAG, "Fail to parse domain name and stop parsing", e); + break; + } + } + return domainList; + } +} diff --git a/staticlibs/device/com/android/net/module/util/FdEventsReader.java b/staticlibs/device/com/android/net/module/util/FdEventsReader.java new file mode 100644 index 0000000000000000000000000000000000000000..f88883bf22f7799df1b0e207e329f6dfd23c8b33 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/FdEventsReader.java @@ -0,0 +1,294 @@ +/* + * 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 com.android.net.module.util; + +import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR; +import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.util.SocketUtils; +import android.os.Handler; +import android.os.Looper; +import android.os.MessageQueue; +import android.system.ErrnoException; +import android.system.OsConstants; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.FileDescriptor; +import java.io.IOException; + + +/** + * This class encapsulates the mechanics of registering a file descriptor + * with a thread's Looper and handling read events (and errors). + * + * Subclasses MUST implement createFd() and SHOULD override handlePacket(). They MAY override + * onStop() and onStart(). + * + * Subclasses can expect a call life-cycle like the following: + * + * [1] when a client calls start(), createFd() is called, followed by the onStart() hook if all + * goes well. Implementations may override onStart() for additional initialization. + * + * [2] yield, waiting for read event or error notification: + * + * [a] readPacket() && handlePacket() + * + * [b] if (no error): + * goto 2 + * else: + * goto 3 + * + * [3] when a client calls stop(), the onStop() hook is called (unless already stopped or never + * started). Implementations may override onStop() for additional cleanup. + * + * The packet receive buffer is recycled on every read call, so subclasses + * should make any copies they would like inside their handlePacket() + * implementation. + * + * All public methods MUST only be called from the same thread with which + * the Handler constructor argument is associated. + * + * @param <BufferType> the type of the buffer used to read data. + */ +public abstract class FdEventsReader<BufferType> { + private static final String TAG = FdEventsReader.class.getSimpleName(); + private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR; + private static final int UNREGISTER_THIS_FD = 0; + + @NonNull + private final Handler mHandler; + @NonNull + private final MessageQueue mQueue; + @NonNull + private final BufferType mBuffer; + @Nullable + private FileDescriptor mFd; + private long mPacketsReceived; + + protected static void closeFd(FileDescriptor fd) { + try { + SocketUtils.closeSocket(fd); + } catch (IOException ignored) { + } + } + + protected FdEventsReader(@NonNull Handler h, @NonNull BufferType buffer) { + mHandler = h; + mQueue = mHandler.getLooper().getQueue(); + mBuffer = buffer; + } + + @VisibleForTesting + @NonNull + protected MessageQueue getMessageQueue() { + return mQueue; + } + + /** Start this FdEventsReader. */ + public boolean start() { + if (!onCorrectThread()) { + throw new IllegalStateException("start() called from off-thread"); + } + + return createAndRegisterFd(); + } + + /** Stop this FdEventsReader and destroy the file descriptor. */ + public void stop() { + if (!onCorrectThread()) { + throw new IllegalStateException("stop() called from off-thread"); + } + + unregisterAndDestroyFd(); + } + + @NonNull + public Handler getHandler() { + return mHandler; + } + + protected abstract int recvBufSize(@NonNull BufferType buffer); + + /** Returns the size of the receive buffer. */ + public int recvBufSize() { + return recvBufSize(mBuffer); + } + + /** + * Get the number of successful calls to {@link #readPacket(FileDescriptor, Object)}. + * + * <p>A call was successful if {@link #readPacket(FileDescriptor, Object)} returned a value > 0. + */ + public final long numPacketsReceived() { + return mPacketsReceived; + } + + /** + * Subclasses MUST create the listening socket here, including setting all desired socket + * options, interface or address/port binding, etc. The socket MUST be created nonblocking. + */ + @Nullable + protected abstract FileDescriptor createFd(); + + /** + * Implementations MUST return the bytes read or throw an Exception. + * + * <p>The caller may throw a {@link ErrnoException} with {@link OsConstants#EAGAIN} or + * {@link OsConstants#EINTR}, in which case {@link FdEventsReader} will ignore the buffer + * contents and respectively wait for further input or retry the read immediately. For all other + * exceptions, the {@link FdEventsReader} will be stopped with no more interactions with this + * method. + */ + protected abstract int readPacket(@NonNull FileDescriptor fd, @NonNull BufferType buffer) + throws Exception; + + /** + * Called by the main loop for every packet. Any desired copies of + * |recvbuf| should be made in here, as the underlying byte array is + * reused across all reads. + */ + protected void handlePacket(@NonNull BufferType recvbuf, int length) {} + + /** + * Called by the subclasses of FdEventsReader, decide whether it should stop reading packet or + * just ignore the specific error other than EAGAIN or EINTR. + * + * @return {@code true} if this FdEventsReader should stop reading from the socket. + * {@code false} if it should continue. + */ + protected boolean handleReadError(@NonNull ErrnoException e) { + logError("readPacket error: ", e); + return true; // by default, stop reading on any error. + } + + /** + * Called by the subclasses of FdEventsReader, decide whether it should stop reading from the + * socket or process the packet and continue to read upon receiving a zero-length packet. + * + * @return {@code true} if this FdEventsReader should process the zero-length packet. + * {@code false} if it should stop reading from the socket. + */ + protected boolean shouldProcessZeroLengthPacket() { + return false; // by default, stop reading upon receiving zero-length packet. + } + + /** + * Called by the main loop to log errors. In some cases |e| may be null. + */ + protected void logError(@NonNull String msg, @Nullable Exception e) {} + + /** + * Called by start(), if successful, just prior to returning. + */ + protected void onStart() {} + + /** + * Called by stop() just prior to returning. + */ + protected void onStop() {} + + private boolean createAndRegisterFd() { + if (mFd != null) return true; + + try { + mFd = createFd(); + } catch (Exception e) { + logError("Failed to create socket: ", e); + closeFd(mFd); + mFd = null; + } + + if (mFd == null) return false; + + getMessageQueue().addOnFileDescriptorEventListener( + mFd, + FD_EVENTS, + (fd, events) -> { + // Always call handleInput() so read/recvfrom are given + // a proper chance to encounter a meaningful errno and + // perhaps log a useful error message. + if (!isRunning() || !handleInput()) { + unregisterAndDestroyFd(); + return UNREGISTER_THIS_FD; + } + return FD_EVENTS; + }); + onStart(); + return true; + } + + protected boolean isRunning() { + return (mFd != null) && mFd.valid(); + } + + // Keep trying to read until we get EAGAIN/EWOULDBLOCK or some fatal error. + private boolean handleInput() { + while (isRunning()) { + final int bytesRead; + + try { + bytesRead = readPacket(mFd, mBuffer); + if (bytesRead == 0 && !shouldProcessZeroLengthPacket()) { + if (isRunning()) logError("Socket closed, exiting", null); + break; + } + mPacketsReceived++; + } catch (ErrnoException e) { + if (e.errno == OsConstants.EAGAIN) { + // We've read everything there is to read this time around. + return true; + } else if (e.errno == OsConstants.EINTR) { + continue; + } else { + if (!isRunning()) break; + final boolean shouldStop = handleReadError(e); + if (shouldStop) break; + continue; + } + } catch (Exception e) { + if (isRunning()) logError("readPacket error: ", e); + break; + } + + try { + handlePacket(mBuffer, bytesRead); + } catch (Exception e) { + logError("handlePacket error: ", e); + Log.wtf(TAG, "Error handling packet", e); + } + } + + return false; + } + + private void unregisterAndDestroyFd() { + if (mFd == null) return; + + getMessageQueue().removeOnFileDescriptorEventListener(mFd); + closeFd(mFd); + mFd = null; + onStop(); + } + + private boolean onCorrectThread() { + return (mHandler.getLooper() == Looper.myLooper()); + } +} diff --git a/staticlibs/device/com/android/net/module/util/FeatureVersions.java b/staticlibs/device/com/android/net/module/util/FeatureVersions.java new file mode 100644 index 0000000000000000000000000000000000000000..149756c38f1913dd3b77cec59f494fa46e05a343 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/FeatureVersions.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util; + +/** + * Class to centralize feature version control that requires a specific module or a specific + * module version. + * @hide + */ +public class FeatureVersions { + /** + * This constant is used to do bitwise shift operation to create module ids. + * The module version is composed with 9 digits which is placed in the lower 36 bits. + */ + private static final int MODULE_SHIFT = 36; + /** + * The bitmask to do bitwise-and(i.e. {@code &}) operation to get the module id. + */ + public static final long MODULE_MASK = 0xFF0_0000_0000L; + /** + * The bitmask to do bitwise-and(i.e. {@code &}) operation to get the module version. + */ + public static final long VERSION_MASK = 0x00F_FFFF_FFFFL; + public static final long CONNECTIVITY_MODULE_ID = 0x01L << MODULE_SHIFT; + public static final long NETWORK_STACK_MODULE_ID = 0x02L << MODULE_SHIFT; + // CLAT_ADDRESS_TRANSLATE is a feature of the network stack, which doesn't throw when system + // try to add a NAT-T keepalive packet filter with v6 address, introduced in version + // M-2023-Sept on July 3rd, 2023. + public static final long FEATURE_CLAT_ADDRESS_TRANSLATE = + NETWORK_STACK_MODULE_ID + 34_09_00_000L; +} diff --git a/staticlibs/device/com/android/net/module/util/IBpfMap.java b/staticlibs/device/com/android/net/module/util/IBpfMap.java new file mode 100644 index 0000000000000000000000000000000000000000..83ff875c6036fa2bc41489ce0fe2ea68996d9144 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/IBpfMap.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.net.module.util; + +import android.system.ErrnoException; + +import androidx.annotation.NonNull; + +import java.io.IOException; +import java.util.NoSuchElementException; + +/** + * The interface of BpfMap. This could be used to inject for testing. + * So the testing code won't load the JNI and update the entries to kernel. + * + * @param <K> the key of the map. + * @param <V> the value of the map. + */ +public interface IBpfMap<K extends Struct, V extends Struct> extends AutoCloseable { + /** Update an existing or create a new key -> value entry in an eBbpf map. */ + void updateEntry(K key, V value) throws ErrnoException; + + /** If the key does not exist in the map, insert key -> value entry into eBpf map. */ + void insertEntry(K key, V value) throws ErrnoException, IllegalStateException; + + /** If the key already exists in the map, replace its value. */ + void replaceEntry(K key, V value) throws ErrnoException, NoSuchElementException; + + /** + * Update an existing or create a new key -> value entry in an eBbpf map. Returns true if + * inserted, false if replaced. (use updateEntry() if you don't care whether insert or replace + * happened). + */ + boolean insertOrReplaceEntry(K key, V value) throws ErrnoException; + + /** Remove existing key from eBpf map. Return true if something was deleted. */ + boolean deleteEntry(K key) throws ErrnoException; + + /** Returns {@code true} if this map contains no elements. */ + boolean isEmpty() throws ErrnoException; + + /** Get the key after the passed-in key. */ + K getNextKey(@NonNull K key) throws ErrnoException; + + /** Get the first key of the eBpf map. */ + K getFirstKey() throws ErrnoException; + + /** Check whether a key exists in the map. */ + boolean containsKey(@NonNull K key) throws ErrnoException; + + /** Retrieve a value from the map. */ + V getValue(@NonNull K key) throws ErrnoException; + + public interface ThrowingBiConsumer<T,U> { + void accept(T t, U u) throws ErrnoException; + } + + /** + * Iterate through the map and handle each key -> value retrieved base on the given BiConsumer. + */ + void forEach(ThrowingBiConsumer<K, V> action) throws ErrnoException; + + /** Clears the map. */ + void clear() throws ErrnoException; + + /** Close for AutoCloseable. */ + @Override + void close() throws IOException; +} diff --git a/staticlibs/device/com/android/net/module/util/Ipv6Utils.java b/staticlibs/device/com/android/net/module/util/Ipv6Utils.java new file mode 100644 index 0000000000000000000000000000000000000000..d5382215c3387580e19af1a9af69900601a7abf1 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/Ipv6Utils.java @@ -0,0 +1,187 @@ +/* + * 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. + */ + +package com.android.net.module.util; + +import static android.system.OsConstants.IPPROTO_ICMPV6; + +import static com.android.net.module.util.IpUtils.icmpv6Checksum; +import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6; +import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REPLY_TYPE; +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_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 android.net.MacAddress; + +import com.android.net.module.util.structs.EthernetHeader; +import com.android.net.module.util.structs.Icmpv6Header; +import com.android.net.module.util.structs.Ipv6Header; +import com.android.net.module.util.structs.NaHeader; +import com.android.net.module.util.structs.NsHeader; +import com.android.net.module.util.structs.RaHeader; +import com.android.net.module.util.structs.RsHeader; + +import java.net.Inet6Address; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Utilities for IPv6 packets. + */ +public class Ipv6Utils { + /** + * Build a generic ICMPv6 packet without Ethernet header. + */ + public static ByteBuffer buildIcmpv6Packet(final Inet6Address srcIp, final Inet6Address dstIp, + short type, short code, final ByteBuffer... options) { + final int ipv6HeaderLen = Struct.getSize(Ipv6Header.class); + final int icmpv6HeaderLen = Struct.getSize(Icmpv6Header.class); + int payloadLen = 0; + for (ByteBuffer option: options) { + payloadLen += option.limit(); + } + final ByteBuffer packet = ByteBuffer.allocate(ipv6HeaderLen + icmpv6HeaderLen + payloadLen); + final Ipv6Header ipv6Header = + new Ipv6Header((int) 0x60000000 /* version, traffic class, flowlabel */, + icmpv6HeaderLen + payloadLen /* payload length */, + (byte) IPPROTO_ICMPV6 /* next header */, (byte) 0xff /* hop limit */, srcIp, dstIp); + final Icmpv6Header icmpv6Header = new Icmpv6Header(type, code, (short) 0 /* checksum */); + + ipv6Header.writeToByteBuffer(packet); + icmpv6Header.writeToByteBuffer(packet); + for (ByteBuffer option : options) { + packet.put(option); + // in case option might be reused by caller, restore the position and + // limit of bytebuffer. + option.clear(); + } + packet.flip(); + + // Populate the ICMPv6 checksum field. + packet.putShort(ipv6HeaderLen + 2, icmpv6Checksum(packet, 0 /* ipOffset */, + ipv6HeaderLen /* transportOffset */, + (short) (icmpv6HeaderLen + payloadLen) /* transportLen */)); + return packet; + + } + + /** + * Build a generic ICMPv6 packet(e.g., packet used in the neighbor discovery protocol). + */ + public static ByteBuffer buildIcmpv6Packet(final MacAddress srcMac, final MacAddress dstMac, + final Inet6Address srcIp, final Inet6Address dstIp, short type, short code, + final ByteBuffer... options) { + final ByteBuffer payload = buildIcmpv6Packet(srcIp, dstIp, type, code, options); + + final int etherHeaderLen = Struct.getSize(EthernetHeader.class); + final ByteBuffer packet = ByteBuffer.allocate(etherHeaderLen + payload.limit()); + final EthernetHeader ethHeader = + new EthernetHeader(dstMac, srcMac, (short) ETHER_TYPE_IPV6); + + ethHeader.writeToByteBuffer(packet); + packet.put(payload); + packet.flip(); + + return packet; + } + + /** + * Build the ICMPv6 packet payload including payload header and specific options. + */ + private static ByteBuffer[] buildIcmpv6Payload(final ByteBuffer payloadHeader, + final ByteBuffer... options) { + final ByteBuffer[] payload = new ByteBuffer[options.length + 1]; + payload[0] = payloadHeader; + System.arraycopy(options, 0, payload, 1, options.length); + return payload; + } + + /** + * Build an ICMPv6 Router Advertisement packet from the required specified parameters. + */ + public static ByteBuffer buildRaPacket(final MacAddress srcMac, final MacAddress dstMac, + final Inet6Address srcIp, final Inet6Address dstIp, final byte flags, + final int lifetime, final long reachableTime, final long retransTimer, + final ByteBuffer... options) { + final RaHeader raHeader = new RaHeader((byte) 0 /* hopLimit, unspecified */, + flags, lifetime, reachableTime, retransTimer); + final ByteBuffer[] payload = buildIcmpv6Payload( + ByteBuffer.wrap(raHeader.writeToBytes(ByteOrder.BIG_ENDIAN)), options); + return buildIcmpv6Packet(srcMac, dstMac, srcIp, dstIp, + (byte) ICMPV6_ROUTER_ADVERTISEMENT /* type */, (byte) 0 /* code */, payload); + } + + /** + * Build an ICMPv6 Neighbor Advertisement packet from the required specified parameters. + */ + public static ByteBuffer buildNaPacket(final MacAddress srcMac, final MacAddress dstMac, + final Inet6Address srcIp, final Inet6Address dstIp, final int flags, + final Inet6Address target, final ByteBuffer... options) { + final NaHeader naHeader = new NaHeader(flags, target); + final ByteBuffer[] payload = buildIcmpv6Payload( + ByteBuffer.wrap(naHeader.writeToBytes(ByteOrder.BIG_ENDIAN)), options); + return buildIcmpv6Packet(srcMac, dstMac, srcIp, dstIp, + (byte) ICMPV6_NEIGHBOR_ADVERTISEMENT /* type */, (byte) 0 /* code */, payload); + } + + /** + * Build an ICMPv6 Neighbor Solicitation packet from the required specified parameters. + */ + public static ByteBuffer buildNsPacket(final MacAddress srcMac, final MacAddress dstMac, + final Inet6Address srcIp, final Inet6Address dstIp, + final Inet6Address target, final ByteBuffer... options) { + final NsHeader nsHeader = new NsHeader(target); + final ByteBuffer[] payload = buildIcmpv6Payload( + ByteBuffer.wrap(nsHeader.writeToBytes(ByteOrder.BIG_ENDIAN)), options); + return buildIcmpv6Packet(srcMac, dstMac, srcIp, dstIp, + (byte) ICMPV6_NEIGHBOR_SOLICITATION /* type */, (byte) 0 /* code */, payload); + } + + /** + * Build an ICMPv6 Router Solicitation packet from the required specified parameters. + */ + public static ByteBuffer buildRsPacket(final MacAddress srcMac, final MacAddress dstMac, + final Inet6Address srcIp, final Inet6Address dstIp, final ByteBuffer... options) { + final RsHeader rsHeader = new RsHeader((int) 0 /* reserved */); + final ByteBuffer[] payload = buildIcmpv6Payload( + ByteBuffer.wrap(rsHeader.writeToBytes(ByteOrder.BIG_ENDIAN)), options); + return buildIcmpv6Packet(srcMac, dstMac, srcIp, dstIp, + (byte) ICMPV6_ROUTER_SOLICITATION /* type */, (byte) 0 /* code */, payload); + } + + /** + * Build an ICMPv6 Echo Request packet from the required specified parameters. + */ + public static ByteBuffer buildEchoRequestPacket(final MacAddress srcMac, + final MacAddress dstMac, final Inet6Address srcIp, final Inet6Address dstIp) { + final ByteBuffer payload = ByteBuffer.allocate(4); // ID and Sequence number may be zero. + return buildIcmpv6Packet(srcMac, dstMac, srcIp, dstIp, + (byte) ICMPV6_ECHO_REQUEST_TYPE /* type */, (byte) 0 /* code */, payload); + } + + /** + * Build an ICMPv6 Echo Reply packet without ethernet header. + */ + public static ByteBuffer buildEchoReplyPacket(final Inet6Address srcIp, + final Inet6Address dstIp) { + final ByteBuffer payload = ByteBuffer.allocate(4); // ID and Sequence number may be zero. + return buildIcmpv6Packet(srcIp, dstIp, (byte) ICMPV6_ECHO_REPLY_TYPE /* type */, + (byte) 0 /* code */, payload); + } +} diff --git a/staticlibs/device/com/android/net/module/util/JniUtil.java b/staticlibs/device/com/android/net/module/util/JniUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..5210a3ef0eb34361224ff3d4eb524bc0a7838d18 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/JniUtil.java @@ -0,0 +1,35 @@ +/* + * 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. + */ + +package com.android.net.module.util; + +/** + * Utilities for modules to use jni. + */ +public final class JniUtil { + /** + * The method to find jni library accroding to the giving package name. + * + * The jni library name would be packageName + _jni.so. E.g. + * com_android_networkstack_tethering_util_jni for tethering, + * com_android_connectivity_util_jni for connectivity. + */ + public static String getJniLibraryName(final Package pkg) { + final String libPrefix = pkg.getName().replaceAll("\\.", "_"); + + return libPrefix + "_jni"; + } +} diff --git a/staticlibs/device/com/android/net/module/util/NetworkMonitorUtils.java b/staticlibs/device/com/android/net/module/util/NetworkMonitorUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..5a4412f506bf0d8781f666c65c2fa21f758fcb0e --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/NetworkMonitorUtils.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util; + +import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID; +import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED; +import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; + +import android.annotation.NonNull; +import android.net.NetworkCapabilities; +import android.os.Build; + +/** @hide */ +public class NetworkMonitorUtils { + // This class is used by both NetworkMonitor and ConnectivityService, so it cannot use + // NetworkStack shims, but at the same time cannot use non-system APIs. + // TRANSPORT_TEST is test API as of R (so it is enforced to always be 7 and can't be changed), + // and it is being added as a system API in S. + // TODO: use NetworkCapabilities.TRANSPORT_TEST once NetworkStack builds against API 31. + private static final int TRANSPORT_TEST = 7; + + // This class is used by both NetworkMonitor and ConnectivityService, so it cannot use + // NetworkStack shims, but at the same time cannot use non-system APIs. + // NET_CAPABILITY_NOT_VCN_MANAGED is system API as of S (so it is enforced to always be 28 and + // can't be changed). + // TODO: use NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED once NetworkStack builds against + // API 31. + public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28; + + // Network conditions broadcast constants + public static final String ACTION_NETWORK_CONDITIONS_MEASURED = + "android.net.conn.NETWORK_CONDITIONS_MEASURED"; + public static final String EXTRA_CONNECTIVITY_TYPE = "extra_connectivity_type"; + public static final String EXTRA_NETWORK_TYPE = "extra_network_type"; + public static final String EXTRA_RESPONSE_RECEIVED = "extra_response_received"; + public static final String EXTRA_IS_CAPTIVE_PORTAL = "extra_is_captive_portal"; + public static final String EXTRA_CELL_ID = "extra_cellid"; + public static final String EXTRA_SSID = "extra_ssid"; + public static final String EXTRA_BSSID = "extra_bssid"; + /** real time since boot */ + public static final String EXTRA_REQUEST_TIMESTAMP_MS = "extra_request_timestamp_ms"; + public static final String EXTRA_RESPONSE_TIMESTAMP_MS = "extra_response_timestamp_ms"; + public static final String PERMISSION_ACCESS_NETWORK_CONDITIONS = + "android.permission.ACCESS_NETWORK_CONDITIONS"; + + /** + * Return whether validation is required for private DNS in strict mode. + * @param nc Network capabilities of the network to test. + */ + public static boolean isPrivateDnsValidationRequired(@NonNull final NetworkCapabilities nc) { + final boolean isVcnManaged = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) + && !nc.hasCapability(NET_CAPABILITY_NOT_VCN_MANAGED); + final boolean isOemPaid = nc.hasCapability(NET_CAPABILITY_OEM_PAID) + && nc.hasCapability(NET_CAPABILITY_TRUSTED); + final boolean isDefaultCapable = nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) + && nc.hasCapability(NET_CAPABILITY_TRUSTED); + + // TODO: Consider requiring validation for DUN networks. + if (nc.hasCapability(NET_CAPABILITY_INTERNET) + && (isVcnManaged || isOemPaid || isDefaultCapable)) { + return true; + } + + // Test networks that also have one of the major transport types are attempting to replicate + // that transport on a test interface (for example, test ethernet networks with + // EthernetManager#setIncludeTestInterfaces). Run validation on them for realistic tests. + // See also comments on EthernetManager#setIncludeTestInterfaces and on TestNetworkManager. + if (nc.hasTransport(TRANSPORT_TEST) && nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) && ( + nc.hasTransport(TRANSPORT_WIFI) + || nc.hasTransport(TRANSPORT_CELLULAR) + || nc.hasTransport(TRANSPORT_BLUETOOTH) + || nc.hasTransport(TRANSPORT_ETHERNET))) { + return true; + } + + return false; + } + + /** + * Return whether validation is required for a network. + * @param isVpnValidationRequired Whether network validation should be performed for VPN + * networks. + * @param nc Network capabilities of the network to test. + */ + public static boolean isValidationRequired(boolean isDunValidationRequired, + boolean isVpnValidationRequired, + @NonNull final NetworkCapabilities nc) { + if (isDunValidationRequired && nc.hasCapability(NET_CAPABILITY_DUN)) { + return true; + } + if (!nc.hasCapability(NET_CAPABILITY_NOT_VPN)) { + return isVpnValidationRequired; + } + return isPrivateDnsValidationRequired(nc); + } +} diff --git a/staticlibs/device/com/android/net/module/util/PacketBuilder.java b/staticlibs/device/com/android/net/module/util/PacketBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..33e5bfad21c75c6a27914fccc667cd2f51fb33bb --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/PacketBuilder.java @@ -0,0 +1,302 @@ +/* + * 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. + */ + +package com.android.net.module.util; + +import static android.system.OsConstants.IPPROTO_IP; +import static android.system.OsConstants.IPPROTO_IPV6; +import static android.system.OsConstants.IPPROTO_TCP; +import static android.system.OsConstants.IPPROTO_UDP; + +import static com.android.net.module.util.IpUtils.ipChecksum; +import static com.android.net.module.util.IpUtils.tcpChecksum; +import static com.android.net.module.util.IpUtils.udpChecksum; +import static com.android.net.module.util.NetworkStackConstants.IPV4_CHECKSUM_OFFSET; +import static com.android.net.module.util.NetworkStackConstants.IPV4_LENGTH_OFFSET; +import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN; +import static com.android.net.module.util.NetworkStackConstants.IPV6_LEN_OFFSET; +import static com.android.net.module.util.NetworkStackConstants.TCP_CHECKSUM_OFFSET; +import static com.android.net.module.util.NetworkStackConstants.UDP_CHECKSUM_OFFSET; +import static com.android.net.module.util.NetworkStackConstants.UDP_LENGTH_OFFSET; + +import android.net.MacAddress; + +import androidx.annotation.NonNull; + +import com.android.net.module.util.structs.EthernetHeader; +import com.android.net.module.util.structs.Ipv4Header; +import com.android.net.module.util.structs.Ipv6Header; +import com.android.net.module.util.structs.TcpHeader; +import com.android.net.module.util.structs.UdpHeader; + +import java.io.IOException; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.nio.BufferOverflowException; +import java.nio.ByteBuffer; + +/** + * The class is used to build a packet. + * + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Layer 2 header (EthernetHeader) | (optional) + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Layer 3 header (Ipv4Header, Ipv6Header) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Layer 4 header (TcpHeader, UdpHeader) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Payload | (optional) + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * Below is a sample code to build a packet. + * + * // Initialize builder + * final ByteBuffer buf = ByteBuffer.allocate(...); + * final PacketBuilder pb = new PacketBuilder(buf); + * // Write headers + * pb.writeL2Header(...); + * pb.writeIpHeader(...); + * pb.writeTcpHeader(...); + * // Write payload + * buf.putInt(...); + * buf.putShort(...); + * buf.putByte(...); + * // Finalize and use the packet + * pb.finalizePacket(); + * sendPacket(buf); + */ +public class PacketBuilder { + private static final int INVALID_OFFSET = -1; + + private final ByteBuffer mBuffer; + + private int mIpv4HeaderOffset = INVALID_OFFSET; + private int mIpv6HeaderOffset = INVALID_OFFSET; + private int mTcpHeaderOffset = INVALID_OFFSET; + private int mUdpHeaderOffset = INVALID_OFFSET; + + public PacketBuilder(@NonNull ByteBuffer buffer) { + mBuffer = buffer; + } + + /** + * Write an ethernet header. + * + * @param srcMac source MAC address + * @param dstMac destination MAC address + * @param etherType ether type + */ + public void writeL2Header(MacAddress srcMac, MacAddress dstMac, short etherType) throws + IOException { + final EthernetHeader ethHeader = new EthernetHeader(dstMac, srcMac, etherType); + try { + ethHeader.writeToByteBuffer(mBuffer); + } catch (IllegalArgumentException | BufferOverflowException e) { + throw new IOException("Error writing to buffer: ", e); + } + } + + /** + * Write an IPv4 header. + * The IP header length and checksum are calculated and written back in #finalizePacket. + * + * @param tos type of service + * @param id the identification + * @param flagsAndFragmentOffset flags and fragment offset + * @param ttl time to live + * @param protocol protocol + * @param srcIp source IP address + * @param dstIp destination IP address + */ + public void writeIpv4Header(byte tos, short id, short flagsAndFragmentOffset, byte ttl, + byte protocol, @NonNull final Inet4Address srcIp, @NonNull final Inet4Address dstIp) + throws IOException { + mIpv4HeaderOffset = mBuffer.position(); + final Ipv4Header ipv4Header = new Ipv4Header(tos, + (short) 0 /* totalLength, calculate in #finalizePacket */, id, + flagsAndFragmentOffset, ttl, protocol, + (short) 0 /* checksum, calculate in #finalizePacket */, srcIp, dstIp); + + try { + ipv4Header.writeToByteBuffer(mBuffer); + } catch (IllegalArgumentException | BufferOverflowException e) { + throw new IOException("Error writing to buffer: ", e); + } + } + + /** + * Write an IPv6 header. + * The IP header length is calculated and written back in #finalizePacket. + * + * @param vtf version, traffic class and flow label + * @param nextHeader the transport layer protocol + * @param hopLimit hop limit + * @param srcIp source IP address + * @param dstIp destination IP address + */ + public void writeIpv6Header(int vtf, byte nextHeader, short hopLimit, + @NonNull final Inet6Address srcIp, @NonNull final Inet6Address dstIp) + throws IOException { + mIpv6HeaderOffset = mBuffer.position(); + final Ipv6Header ipv6Header = new Ipv6Header(vtf, + (short) 0 /* payloadLength, calculate in #finalizePacket */, nextHeader, + hopLimit, srcIp, dstIp); + + try { + ipv6Header.writeToByteBuffer(mBuffer); + } catch (IllegalArgumentException | BufferOverflowException e) { + throw new IOException("Error writing to buffer: ", e); + } + } + + /** + * Write a TCP header. + * The TCP header checksum is calculated and written back in #finalizePacket. + * + * @param srcPort source port + * @param dstPort destination port + * @param seq sequence number + * @param ack acknowledgement number + * @param tcpFlags tcp flags + * @param window window size + * @param urgentPointer urgent pointer + */ + public void writeTcpHeader(short srcPort, short dstPort, short seq, short ack, + byte tcpFlags, short window, short urgentPointer) throws IOException { + mTcpHeaderOffset = mBuffer.position(); + final TcpHeader tcpHeader = new TcpHeader(srcPort, dstPort, seq, ack, + (short) ((short) 0x5000 | ((byte) 0x3f & tcpFlags)) /* dataOffsetAndControlBits, + dataOffset is always 5(*4bytes) because options not supported */, window, + (short) 0 /* checksum, calculate in #finalizePacket */, + urgentPointer); + + try { + tcpHeader.writeToByteBuffer(mBuffer); + } catch (IllegalArgumentException | BufferOverflowException e) { + throw new IOException("Error writing to buffer: ", e); + } + } + + /** + * Write a UDP header. + * The UDP header length and checksum are calculated and written back in #finalizePacket. + * + * @param srcPort source port + * @param dstPort destination port + */ + public void writeUdpHeader(short srcPort, short dstPort) throws IOException { + mUdpHeaderOffset = mBuffer.position(); + final UdpHeader udpHeader = new UdpHeader(srcPort, dstPort, + (short) 0 /* length, calculate in #finalizePacket */, + (short) 0 /* checksum, calculate in #finalizePacket */); + + try { + udpHeader.writeToByteBuffer(mBuffer); + } catch (IllegalArgumentException | BufferOverflowException e) { + throw new IOException("Error writing to buffer: ", e); + } + } + + /** + * Finalize the packet. + * + * Call after writing L4 header (no payload) or payload to the buffer used by the builder. + * L3 header length, L3 header checksum and L4 header checksum are calculated and written back + * after finalization. + */ + @NonNull + public ByteBuffer finalizePacket() throws IOException { + // [1] Finalize IPv4 or IPv6 header. + int ipHeaderOffset = INVALID_OFFSET; + if (mIpv4HeaderOffset != INVALID_OFFSET) { + ipHeaderOffset = mIpv4HeaderOffset; + + // Populate the IPv4 totalLength field. + mBuffer.putShort(mIpv4HeaderOffset + IPV4_LENGTH_OFFSET, + (short) (mBuffer.position() - mIpv4HeaderOffset)); + + // Populate the IPv4 header checksum field. + mBuffer.putShort(mIpv4HeaderOffset + IPV4_CHECKSUM_OFFSET, + ipChecksum(mBuffer, mIpv4HeaderOffset /* headerOffset */)); + } else if (mIpv6HeaderOffset != INVALID_OFFSET) { + ipHeaderOffset = mIpv6HeaderOffset; + + // Populate the IPv6 payloadLength field. + // The payload length doesn't include IPv6 header length. See rfc8200 section 3. + mBuffer.putShort(mIpv6HeaderOffset + IPV6_LEN_OFFSET, + (short) (mBuffer.position() - mIpv6HeaderOffset - IPV6_HEADER_LEN)); + } else { + throw new IOException("Packet is missing neither IPv4 nor IPv6 header"); + } + + // [2] Finalize TCP or UDP header. + if (mTcpHeaderOffset != INVALID_OFFSET) { + // Populate the TCP header checksum field. + mBuffer.putShort(mTcpHeaderOffset + TCP_CHECKSUM_OFFSET, tcpChecksum(mBuffer, + ipHeaderOffset /* ipOffset */, mTcpHeaderOffset /* transportOffset */, + mBuffer.position() - mTcpHeaderOffset /* transportLen */)); + } else if (mUdpHeaderOffset != INVALID_OFFSET) { + // Populate the UDP header length field. + mBuffer.putShort(mUdpHeaderOffset + UDP_LENGTH_OFFSET, + (short) (mBuffer.position() - mUdpHeaderOffset)); + + // Populate the UDP header checksum field. + mBuffer.putShort(mUdpHeaderOffset + UDP_CHECKSUM_OFFSET, udpChecksum(mBuffer, + ipHeaderOffset /* ipOffset */, mUdpHeaderOffset /* transportOffset */)); + } else { + throw new IOException("Packet is missing neither TCP nor UDP header"); + } + + mBuffer.flip(); + return mBuffer; + } + + /** + * Allocate bytebuffer for building the packet. + * + * @param hasEther has ethernet header. Set this flag to indicate that the packet has an + * ethernet header. + * @param l3proto the layer 3 protocol. Only {@code IPPROTO_IP} and {@code IPPROTO_IPV6} + * currently supported. + * @param l4proto the layer 4 protocol. Only {@code IPPROTO_TCP} and {@code IPPROTO_UDP} + * currently supported. + * @param payloadLen length of the payload. + */ + @NonNull + public static ByteBuffer allocate(boolean hasEther, int l3proto, int l4proto, int payloadLen) { + if (l3proto != IPPROTO_IP && l3proto != IPPROTO_IPV6) { + throw new IllegalArgumentException("Unsupported layer 3 protocol " + l3proto); + } + + if (l4proto != IPPROTO_TCP && l4proto != IPPROTO_UDP) { + throw new IllegalArgumentException("Unsupported layer 4 protocol " + l4proto); + } + + if (payloadLen < 0) { + throw new IllegalArgumentException("Invalid payload length " + payloadLen); + } + + int packetLen = 0; + if (hasEther) packetLen += Struct.getSize(EthernetHeader.class); + packetLen += (l3proto == IPPROTO_IP) ? Struct.getSize(Ipv4Header.class) + : Struct.getSize(Ipv6Header.class); + packetLen += (l4proto == IPPROTO_TCP) ? Struct.getSize(TcpHeader.class) + : Struct.getSize(UdpHeader.class); + packetLen += payloadLen; + + return ByteBuffer.allocate(packetLen); + } +} diff --git a/staticlibs/device/com/android/net/module/util/PacketReader.java b/staticlibs/device/com/android/net/module/util/PacketReader.java new file mode 100644 index 0000000000000000000000000000000000000000..66c47888c384e7361eeff4a20052195be5ac060c --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/PacketReader.java @@ -0,0 +1,59 @@ +/* + * 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. + */ + +package com.android.net.module.util; + +import static java.lang.Math.max; + +import android.os.Handler; +import android.system.Os; + +import java.io.FileDescriptor; + +/** + * Specialization of {@link FdEventsReader} that reads packets into a byte array. + * + * TODO: rename this class to something more correctly descriptive (something + * like [or less horrible than] FdReadEventsHandler?). + */ +public abstract class PacketReader extends FdEventsReader<byte[]> { + + public static final int DEFAULT_RECV_BUF_SIZE = 2 * 1024; + + protected PacketReader(Handler h) { + this(h, DEFAULT_RECV_BUF_SIZE); + } + + protected PacketReader(Handler h, int recvBufSize) { + super(h, new byte[max(recvBufSize, DEFAULT_RECV_BUF_SIZE)]); + } + + @Override + protected final int recvBufSize(byte[] buffer) { + return buffer.length; + } + + /** + * Subclasses MAY override this to change the default read() implementation + * in favour of, say, recvfrom(). + * + * Implementations MUST return the bytes read or throw an Exception. + */ + @Override + protected int readPacket(FileDescriptor fd, byte[] packetBuffer) throws Exception { + return Os.read(fd, packetBuffer, 0, packetBuffer.length); + } +} diff --git a/staticlibs/device/com/android/net/module/util/SharedLog.java b/staticlibs/device/com/android/net/module/util/SharedLog.java new file mode 100644 index 0000000000000000000000000000000000000000..6b12c80d3a281b773315de909ad7af3879da0508 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/SharedLog.java @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.text.TextUtils; +import android.util.Log; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.time.LocalDateTime; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Iterator; +import java.util.StringJoiner; + + +/** + * Class to centralize logging functionality for tethering. + * + * All access to class methods other than dump() must be on the same thread. + * + * @hide + */ +public class SharedLog { + private static final int DEFAULT_MAX_RECORDS = 500; + private static final String COMPONENT_DELIMITER = "."; + + private enum Category { + NONE, + ERROR, + MARK, + WARN, + VERBOSE, + TERRIBLE, + } + + private final LocalLog mLocalLog; + // The tag to use for output to the system log. This is not output to the + // LocalLog because that would be redundant. + private final String mTag; + // The component (or subcomponent) of a system that is sharing this log. + // This can grow in depth if components call forSubComponent() to obtain + // their SharedLog instance. The tag is not included in the component for + // brevity. + private final String mComponent; + + public SharedLog(String tag) { + this(DEFAULT_MAX_RECORDS, tag); + } + + public SharedLog(int maxRecords, String tag) { + this(new LocalLog(maxRecords), tag, tag); + } + + private SharedLog(LocalLog localLog, String tag, String component) { + mLocalLog = localLog; + mTag = tag; + mComponent = component; + } + + public String getTag() { + return mTag; + } + + /** + * Create a SharedLog based on this log with an additional component prefix on each logged line. + */ + public SharedLog forSubComponent(String component) { + if (!isRootLogInstance()) { + component = mComponent + COMPONENT_DELIMITER + component; + } + return new SharedLog(mLocalLog, mTag, component); + } + + /** + * Dump the contents of this log. + * + * <p>This method may be called on any thread. + */ + public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + mLocalLog.dump(writer); + } + + /** + * Reverse dump the contents of this log. + * + * <p>This method may be called on any thread. + */ + public void reverseDump(PrintWriter writer) { + mLocalLog.reverseDump(writer); + } + + ////// + // Methods that both log an entry and emit it to the system log. + ////// + + /** + * Log an error due to an exception. This does not include the exception stacktrace. + * + * <p>The log entry will be also added to the system log. + * @see #e(String, Throwable) + */ + public void e(Exception e) { + Log.e(mTag, record(Category.ERROR, e.toString())); + } + + /** + * Log an error message. + * + * <p>The log entry will be also added to the system log. + */ + public void e(String msg) { + Log.e(mTag, record(Category.ERROR, msg)); + } + + /** + * Log an error due to an exception, with the exception stacktrace if provided. + * + * <p>The error and exception message appear in the shared log, but the stacktrace is only + * logged in general log output (logcat). The log entry will be also added to the system log. + */ + public void e(@NonNull String msg, @Nullable Throwable exception) { + if (exception == null) { + e(msg); + return; + } + Log.e(mTag, record(Category.ERROR, msg + ": " + exception.getMessage()), exception); + } + + /** + * Log an informational message. + * + * <p>The log entry will be also added to the system log. + */ + public void i(String msg) { + Log.i(mTag, record(Category.NONE, msg)); + } + + /** + * Log a warning message. + * + * <p>The log entry will be also added to the system log. + */ + public void w(String msg) { + Log.w(mTag, record(Category.WARN, msg)); + } + + /** + * Log a verbose message. + * + * <p>The log entry will be also added to the system log. + */ + public void v(String msg) { + Log.v(mTag, record(Category.VERBOSE, msg)); + } + + /** + * Log a terrible failure message. + * + * <p>The log entry will be also added to the system log and will trigger system reporting + * for terrible failures. + */ + public void wtf(String msg) { + Log.wtf(mTag, record(Category.TERRIBLE, msg)); + } + + /** + * Log a terrible failure due to an exception, with the exception stacktrace if provided. + * + * <p>The error and exception message appear in the shared log, but the stacktrace is only + * logged in general log output (logcat). The log entry will be also added to the system log + * and will trigger system reporting for terrible failures. + */ + public void wtf(@NonNull String msg, @Nullable Throwable exception) { + if (exception == null) { + e(msg); + return; + } + Log.wtf(mTag, record(Category.TERRIBLE, msg + ": " + exception.getMessage()), exception); + } + + + ////// + // Methods that only log an entry (and do NOT emit to the system log). + ////// + + /** + * Log a general message to be only included in the in-memory log. + * + * <p>The log entry will *not* be added to the system log. + */ + public void log(String msg) { + record(Category.NONE, msg); + } + + /** + * Log a general, formatted message to be only included in the in-memory log. + * + * <p>The log entry will *not* be added to the system log. + * @see String#format(String, Object...) + */ + public void logf(String fmt, Object... args) { + log(String.format(fmt, args)); + } + + /** + * Log a message with MARK level. + * + * <p>The log entry will *not* be added to the system log. + */ + public void mark(String msg) { + record(Category.MARK, msg); + } + + private String record(Category category, String msg) { + final String entry = logLine(category, msg); + mLocalLog.append(entry); + return entry; + } + + private String logLine(Category category, String msg) { + final StringJoiner sj = new StringJoiner(" "); + if (!isRootLogInstance()) sj.add("[" + mComponent + "]"); + if (category != Category.NONE) sj.add(category.toString()); + return sj.add(msg).toString(); + } + + // Check whether this SharedLog instance is nominally the top level in + // a potential hierarchy of shared logs (the root of a tree), + // or is a subcomponent within the hierarchy. + private boolean isRootLogInstance() { + return TextUtils.isEmpty(mComponent) || mComponent.equals(mTag); + } + + private static final class LocalLog { + private final Deque<String> mLog; + private final int mMaxLines; + + LocalLog(int maxLines) { + mMaxLines = Math.max(0, maxLines); + mLog = new ArrayDeque<>(mMaxLines); + } + + synchronized void append(String logLine) { + if (mMaxLines <= 0) return; + while (mLog.size() >= mMaxLines) { + mLog.remove(); + } + mLog.add(LocalDateTime.now() + " - " + logLine); + } + + /** + * Dumps the content of local log to print writer with each log entry + * + * @param pw printer writer to write into + */ + synchronized void dump(PrintWriter pw) { + for (final String s : mLog) { + pw.println(s); + } + } + + synchronized void reverseDump(PrintWriter pw) { + final Iterator<String> itr = mLog.descendingIterator(); + while (itr.hasNext()) { + pw.println(itr.next()); + } + } + } +} diff --git a/staticlibs/device/com/android/net/module/util/SocketUtils.java b/staticlibs/device/com/android/net/module/util/SocketUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..9878ea5c9579f10eecb1ee0c75e43a9f24a6d6cf --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/SocketUtils.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util; + +import static android.net.util.SocketUtils.closeSocket; + +import android.annotation.NonNull; +import android.system.NetlinkSocketAddress; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.net.SocketAddress; + +/** + * Collection of utilities to interact with raw sockets. + * + * This class also provides utilities to interact with {@link android.net.util.SocketUtils} + * because it is in the API surface could not be simply move the frameworks/libs/net/ + * to share with module. + * + * TODO: deprecate android.net.util.SocketUtils and replace with this class. + */ +public class SocketUtils { + + /** + * Make a socket address to communicate with netlink. + */ + @NonNull + public static SocketAddress makeNetlinkSocketAddress(int portId, int groupsMask) { + return new NetlinkSocketAddress(portId, groupsMask); + } + + /** + * Close a socket, ignoring any exception while closing. + */ + public static void closeSocketQuietly(FileDescriptor fd) { + try { + closeSocket(fd); + } catch (IOException ignored) { + } + } + + private SocketUtils() {} +} diff --git a/staticlibs/device/com/android/net/module/util/Struct.java b/staticlibs/device/com/android/net/module/util/Struct.java new file mode 100644 index 0000000000000000000000000000000000000000..b638a4615da0ed4492be29941667cf08d99b21a6 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/Struct.java @@ -0,0 +1,774 @@ +/* + * 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. + */ + +package com.android.net.module.util; + +import android.net.MacAddress; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Define a generic class that helps to parse the structured message. + * + * Example usage: + * + * // C-style NduserOption message header definition in the kernel: + * struct nduseroptmsg { + * unsigned char nduseropt_family; + * unsigned char nduseropt_pad1; + * unsigned short nduseropt_opts_len; + * int nduseropt_ifindex; + * __u8 nduseropt_icmp_type; + * __u8 nduseropt_icmp_code; + * unsigned short nduseropt_pad2; + * unsigned int nduseropt_pad3; + * } + * + * - Declare a subclass with explicit constructor or not which extends from this class to parse + * NduserOption header from raw bytes array. + * + * - Option w/ explicit constructor: + * static class NduserOptHeaderMessage extends Struct { + * @Field(order = 0, type = Type.U8, padding = 1) + * final short family; + * @Field(order = 1, type = Type.U16) + * final int len; + * @Field(order = 2, type = Type.S32) + * final int ifindex; + * @Field(order = 3, type = Type.U8) + * final short type; + * @Field(order = 4, type = Type.U8, padding = 6) + * final short code; + * + * NduserOptHeaderMessage(final short family, final int len, final int ifindex, + * final short type, final short code) { + * this.family = family; + * this.len = len; + * this.ifindex = ifindex; + * this.type = type; + * this.code = code; + * } + * } + * + * - Option w/o explicit constructor: + * static class NduserOptHeaderMessage extends Struct { + * @Field(order = 0, type = Type.U8, padding = 1) + * short family; + * @Field(order = 1, type = Type.U16) + * int len; + * @Field(order = 2, type = Type.S32) + * int ifindex; + * @Field(order = 3, type = Type.U8) + * short type; + * @Field(order = 4, type = Type.U8, padding = 6) + * short code; + * } + * + * - Parse the target message and refer the members. + * final ByteBuffer buf = ByteBuffer.wrap(RAW_BYTES_ARRAY); + * buf.order(ByteOrder.nativeOrder()); + * final NduserOptHeaderMessage nduserHdrMsg = Struct.parse(NduserOptHeaderMessage.class, buf); + * assertEquals(10, nduserHdrMsg.family); + */ +public class Struct { + public enum Type { + U8, // unsigned byte, size = 1 byte + U16, // unsigned short, size = 2 bytes + U32, // unsigned int, size = 4 bytes + U63, // unsigned long(MSB: 0), size = 8 bytes + U64, // unsigned long, size = 8 bytes + S8, // signed byte, size = 1 byte + S16, // signed short, size = 2 bytes + S32, // signed int, size = 4 bytes + S64, // signed long, size = 8 bytes + UBE16, // unsigned short in network order, size = 2 bytes + UBE32, // unsigned int in network order, size = 4 bytes + UBE63, // unsigned long(MSB: 0) in network order, size = 8 bytes + UBE64, // unsigned long in network order, size = 8 bytes + ByteArray, // byte array with predefined length + EUI48, // IEEE Extended Unique Identifier, a 48-bits long MAC address in network order + Ipv4Address, // IPv4 address in network order + Ipv6Address, // IPv6 address in network order + } + + /** + * Indicate that the field marked with this annotation will automatically be managed by this + * class (e.g., will be parsed by #parse). + * + * order: The placeholder associated with each field, consecutive order starting from zero. + * type: The primitive data type listed in above Type enumeration. + * padding: Padding bytes appear after the field for alignment. + * arraysize: The length of byte array. + * + * Annotation associated with field MUST have order and type properties at least, padding + * and arraysize properties depend on the specific usage, if these properties are absent, + * then default value 0 will be applied. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public @interface Field { + int order(); + Type type(); + int padding() default 0; + int arraysize() default 0; + } + + private static class FieldInfo { + @NonNull + public final Field annotation; + @NonNull + public final java.lang.reflect.Field field; + + FieldInfo(final Field annotation, final java.lang.reflect.Field field) { + this.annotation = annotation; + this.field = field; + } + } + private static ConcurrentHashMap<Class, FieldInfo[]> sFieldCache = new ConcurrentHashMap(); + + private static void checkAnnotationType(final Field annotation, final Class fieldType) { + switch (annotation.type()) { + case U8: + case S16: + if (fieldType == Short.TYPE) return; + break; + case U16: + case S32: + case UBE16: + if (fieldType == Integer.TYPE) return; + break; + case U32: + case U63: + case S64: + case UBE32: + case UBE63: + if (fieldType == Long.TYPE) return; + break; + case U64: + case UBE64: + if (fieldType == BigInteger.class) return; + break; + case S8: + if (fieldType == Byte.TYPE) return; + break; + case ByteArray: + if (fieldType != byte[].class) break; + if (annotation.arraysize() <= 0) { + throw new IllegalArgumentException("Invalid ByteArray size: " + + annotation.arraysize()); + } + return; + case EUI48: + if (fieldType == MacAddress.class) return; + break; + case Ipv4Address: + if (fieldType == Inet4Address.class) return; + break; + case Ipv6Address: + if (fieldType == Inet6Address.class) return; + break; + default: + throw new IllegalArgumentException("Unknown type" + annotation.type()); + } + throw new IllegalArgumentException("Invalid primitive data type: " + fieldType + + " for annotation type: " + annotation.type()); + } + + private static int getFieldLength(final Field annotation) { + int length = 0; + switch (annotation.type()) { + case U8: + case S8: + length = 1; + break; + case U16: + case S16: + case UBE16: + length = 2; + break; + case U32: + case S32: + case UBE32: + length = 4; + break; + case U63: + case U64: + case S64: + case UBE63: + case UBE64: + length = 8; + break; + case ByteArray: + length = annotation.arraysize(); + break; + case EUI48: + length = 6; + break; + case Ipv4Address: + length = 4; + break; + case Ipv6Address: + length = 16; + break; + default: + throw new IllegalArgumentException("Unknown type" + annotation.type()); + } + return length + annotation.padding(); + } + + private static boolean isStructSubclass(final Class clazz) { + return clazz != null && Struct.class.isAssignableFrom(clazz) && Struct.class != clazz; + } + + private static int getAnnotationFieldCount(final Class clazz) { + int count = 0; + for (java.lang.reflect.Field field : clazz.getDeclaredFields()) { + if (field.isAnnotationPresent(Field.class)) count++; + } + return count; + } + + private static boolean allFieldsFinal(final FieldInfo[] fields, boolean immutable) { + for (FieldInfo fi : fields) { + if (Modifier.isFinal(fi.field.getModifiers()) != immutable) return false; + } + return true; + } + + private static boolean hasBothMutableAndImmutableFields(final FieldInfo[] fields) { + return !allFieldsFinal(fields, true /* immutable */) + && !allFieldsFinal(fields, false /* mutable */); + } + + private static boolean matchConstructor(final Constructor cons, final FieldInfo[] fields) { + final Class[] paramTypes = cons.getParameterTypes(); + if (paramTypes.length != fields.length) return false; + for (int i = 0; i < paramTypes.length; i++) { + if (!paramTypes[i].equals(fields[i].field.getType())) return false; + } + return true; + } + + /** + * Read U64/UBE64 type data from ByteBuffer and output a BigInteger instance. + * + * @param buf The byte buffer to read. + * @param type The annotation type. + * + * The magnitude argument of BigInteger constructor is a byte array in big-endian order. + * If BigInteger data is read from the byte buffer in little-endian, reverse the order of + * the bytes is required; if BigInteger data is read from the byte buffer in big-endian, + * then just keep it as-is. + */ + private static BigInteger readBigInteger(final ByteBuffer buf, final Type type) { + final byte[] input = new byte[8]; + boolean reverseBytes = (type == Type.U64 && buf.order() == ByteOrder.LITTLE_ENDIAN); + for (int i = 0; i < 8; i++) { + input[reverseBytes ? input.length - 1 - i : i] = buf.get(); + } + return new BigInteger(1, input); + } + + /** + * Get the last 8 bytes of a byte array. If there are less than 8 bytes, + * the first bytes are replaced with zeroes. + */ + private static byte[] getLast8Bytes(final byte[] input) { + final byte[] tmp = new byte[8]; + System.arraycopy( + input, + Math.max(0, input.length - 8), // srcPos: read at most last 8 bytes + tmp, + Math.max(0, 8 - input.length), // dstPos: pad output with that many zeroes + Math.min(8, input.length)); // length + return tmp; + } + + /** + * Convert U64/UBE64 type data interpreted by BigInteger class to bytes array, output are + * always 8 bytes. + * + * @param bigInteger The number to convert. + * @param order Indicate ByteBuffer is read as little-endian or big-endian. + * @param type The annotation U64 type. + * + * BigInteger#toByteArray returns a byte array containing the 2's complement representation + * of this BigInteger, in big-endian. If annotation type is U64 and ByteBuffer is read as + * little-endian, then reversing the order of the bytes is required. + */ + private static byte[] bigIntegerToU64Bytes(final BigInteger bigInteger, final ByteOrder order, + final Type type) { + final byte[] bigIntegerBytes = bigInteger.toByteArray(); + final byte[] output = getLast8Bytes(bigIntegerBytes); + + if (type == Type.U64 && order == ByteOrder.LITTLE_ENDIAN) { + for (int i = 0; i < 4; i++) { + byte tmp = output[i]; + output[i] = output[7 - i]; + output[7 - i] = tmp; + } + } + return output; + } + + private static Object getFieldValue(final ByteBuffer buf, final FieldInfo fieldInfo) + throws BufferUnderflowException { + final Object value; + checkAnnotationType(fieldInfo.annotation, fieldInfo.field.getType()); + switch (fieldInfo.annotation.type()) { + case U8: + value = (short) (buf.get() & 0xFF); + break; + case U16: + value = (int) (buf.getShort() & 0xFFFF); + break; + case U32: + value = (long) (buf.getInt() & 0xFFFFFFFFL); + break; + case U64: + value = readBigInteger(buf, Type.U64); + break; + case S8: + value = buf.get(); + break; + case S16: + value = buf.getShort(); + break; + case S32: + value = buf.getInt(); + break; + case U63: + case S64: + value = buf.getLong(); + break; + case UBE16: + if (buf.order() == ByteOrder.LITTLE_ENDIAN) { + value = (int) (Short.reverseBytes(buf.getShort()) & 0xFFFF); + } else { + value = (int) (buf.getShort() & 0xFFFF); + } + break; + case UBE32: + if (buf.order() == ByteOrder.LITTLE_ENDIAN) { + value = (long) (Integer.reverseBytes(buf.getInt()) & 0xFFFFFFFFL); + } else { + value = (long) (buf.getInt() & 0xFFFFFFFFL); + } + break; + case UBE63: + if (buf.order() == ByteOrder.LITTLE_ENDIAN) { + value = Long.reverseBytes(buf.getLong()); + } else { + value = buf.getLong(); + } + break; + case UBE64: + value = readBigInteger(buf, Type.UBE64); + break; + case ByteArray: + final byte[] array = new byte[fieldInfo.annotation.arraysize()]; + buf.get(array); + value = array; + break; + case EUI48: + final byte[] macAddress = new byte[6]; + buf.get(macAddress); + value = MacAddress.fromBytes(macAddress); + break; + case Ipv4Address: + case Ipv6Address: + final boolean isIpv6 = (fieldInfo.annotation.type() == Type.Ipv6Address); + final byte[] address = new byte[isIpv6 ? 16 : 4]; + buf.get(address); + try { + value = InetAddress.getByAddress(address); + } catch (UnknownHostException e) { + throw new IllegalArgumentException("illegal length of IP address", e); + } + break; + default: + throw new IllegalArgumentException("Unknown type:" + fieldInfo.annotation.type()); + } + + // Skip the padding data for alignment if any. + if (fieldInfo.annotation.padding() > 0) { + buf.position(buf.position() + fieldInfo.annotation.padding()); + } + return value; + } + + @Nullable + private Object getFieldValue(@NonNull java.lang.reflect.Field field) { + try { + return field.get(this); + } catch (IllegalAccessException e) { + throw new IllegalStateException("Cannot access field: " + field, e); + } + } + + private static void putFieldValue(final ByteBuffer output, final FieldInfo fieldInfo, + final Object value) throws BufferUnderflowException { + switch (fieldInfo.annotation.type()) { + case U8: + output.put((byte) (((short) value) & 0xFF)); + break; + case U16: + output.putShort((short) (((int) value) & 0xFFFF)); + break; + case U32: + output.putInt((int) (((long) value) & 0xFFFFFFFFL)); + break; + case U63: + output.putLong((long) value); + break; + case U64: + output.put(bigIntegerToU64Bytes((BigInteger) value, output.order(), Type.U64)); + break; + case S8: + output.put((byte) value); + break; + case S16: + output.putShort((short) value); + break; + case S32: + output.putInt((int) value); + break; + case S64: + output.putLong((long) value); + break; + case UBE16: + if (output.order() == ByteOrder.LITTLE_ENDIAN) { + output.putShort(Short.reverseBytes((short) (((int) value) & 0xFFFF))); + } else { + output.putShort((short) (((int) value) & 0xFFFF)); + } + break; + case UBE32: + if (output.order() == ByteOrder.LITTLE_ENDIAN) { + output.putInt(Integer.reverseBytes( + (int) (((long) value) & 0xFFFFFFFFL))); + } else { + output.putInt((int) (((long) value) & 0xFFFFFFFFL)); + } + break; + case UBE63: + if (output.order() == ByteOrder.LITTLE_ENDIAN) { + output.putLong(Long.reverseBytes((long) value)); + } else { + output.putLong((long) value); + } + break; + case UBE64: + output.put(bigIntegerToU64Bytes((BigInteger) value, output.order(), Type.UBE64)); + break; + case ByteArray: + checkByteArraySize((byte[]) value, fieldInfo); + output.put((byte[]) value); + break; + case EUI48: + final byte[] macAddress = ((MacAddress) value).toByteArray(); + output.put(macAddress); + break; + case Ipv4Address: + case Ipv6Address: + final byte[] address = ((InetAddress) value).getAddress(); + output.put(address); + break; + default: + throw new IllegalArgumentException("Unknown type:" + fieldInfo.annotation.type()); + } + + // padding zero after field value for alignment. + for (int i = 0; i < fieldInfo.annotation.padding(); i++) output.put((byte) 0); + } + + private static FieldInfo[] getClassFieldInfo(final Class clazz) { + if (!isStructSubclass(clazz)) { + throw new IllegalArgumentException(clazz.getName() + " is not a subclass of " + + Struct.class.getName() + ", its superclass is " + + clazz.getSuperclass().getName()); + } + + final FieldInfo[] cachedAnnotationFields = sFieldCache.get(clazz); + if (cachedAnnotationFields != null) { + return cachedAnnotationFields; + } + + // Since array returned from Class#getDeclaredFields doesn't guarantee the actual order + // of field appeared in the class, that is a problem when parsing raw data read from + // ByteBuffer. Store the fields appeared by the order() defined in the Field annotation. + final FieldInfo[] annotationFields = new FieldInfo[getAnnotationFieldCount(clazz)]; + for (java.lang.reflect.Field field : clazz.getDeclaredFields()) { + if (Modifier.isStatic(field.getModifiers())) continue; + + final Field annotation = field.getAnnotation(Field.class); + if (annotation == null) { + throw new IllegalArgumentException("Field " + field.getName() + + " is missing the " + Field.class.getSimpleName() + + " annotation"); + } + if (annotation.order() < 0 || annotation.order() >= annotationFields.length) { + throw new IllegalArgumentException("Annotation order: " + annotation.order() + + " is negative or non-consecutive"); + } + if (annotationFields[annotation.order()] != null) { + throw new IllegalArgumentException("Duplicated annotation order: " + + annotation.order()); + } + annotationFields[annotation.order()] = new FieldInfo(annotation, field); + } + sFieldCache.putIfAbsent(clazz, annotationFields); + return annotationFields; + } + + /** + * Parse raw data from ByteBuffer according to the pre-defined annotation rule and return + * the type-variable object which is subclass of Struct class. + * + * TODO: + * 1. Support subclass inheritance. + * 2. Introduce annotation processor to enforce the subclass naming schema. + */ + public static <T> T parse(final Class<T> clazz, final ByteBuffer buf) { + try { + final FieldInfo[] foundFields = getClassFieldInfo(clazz); + if (hasBothMutableAndImmutableFields(foundFields)) { + throw new IllegalArgumentException("Class has both final and non-final fields"); + } + + Constructor<?> constructor = null; + Constructor<?> defaultConstructor = null; + final Constructor<?>[] constructors = clazz.getDeclaredConstructors(); + for (Constructor cons : constructors) { + if (matchConstructor(cons, foundFields)) constructor = cons; + if (cons.getParameterTypes().length == 0) defaultConstructor = cons; + } + + if (constructor == null && defaultConstructor == null) { + throw new IllegalArgumentException("Fail to find available constructor"); + } + if (constructor != null) { + final Object[] args = new Object[foundFields.length]; + for (int i = 0; i < args.length; i++) { + args[i] = getFieldValue(buf, foundFields[i]); + } + return (T) constructor.newInstance(args); + } + + final Object instance = defaultConstructor.newInstance(); + for (FieldInfo fi : foundFields) { + fi.field.set(instance, getFieldValue(buf, fi)); + } + return (T) instance; + } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) { + throw new IllegalArgumentException("Fail to create a instance from constructor", e); + } catch (BufferUnderflowException e) { + throw new IllegalArgumentException("Fail to read raw data from ByteBuffer", e); + } + } + + private static int getSizeInternal(final FieldInfo[] fieldInfos) { + int size = 0; + for (FieldInfo fi : fieldInfos) { + size += getFieldLength(fi.annotation); + } + return size; + } + + // Check whether the actual size of byte array matches the array size declared in + // annotation. For other annotation types, the actual length of field could be always + // deduced from annotation correctly. + private static void checkByteArraySize(@Nullable final byte[] array, + @NonNull final FieldInfo fieldInfo) { + Objects.requireNonNull(array, "null byte array for field " + fieldInfo.field.getName()); + int annotationArraySize = fieldInfo.annotation.arraysize(); + if (array.length == annotationArraySize) return; + throw new IllegalStateException("byte array actual length: " + + array.length + " doesn't match the declared array size: " + annotationArraySize); + } + + private void writeToByteBufferInternal(final ByteBuffer output, final FieldInfo[] fieldInfos) { + for (FieldInfo fi : fieldInfos) { + final Object value = getFieldValue(fi.field); + try { + putFieldValue(output, fi, value); + } catch (BufferUnderflowException e) { + throw new IllegalArgumentException("Fail to fill raw data to ByteBuffer", e); + } + } + } + + /** + * Get the size of Struct subclass object. + */ + public static <T extends Struct> int getSize(final Class<T> clazz) { + final FieldInfo[] fieldInfos = getClassFieldInfo(clazz); + return getSizeInternal(fieldInfos); + } + + /** + * Convert the parsed Struct subclass object to ByteBuffer. + * + * @param output ByteBuffer passed-in from the caller. + */ + public final void writeToByteBuffer(final ByteBuffer output) { + final FieldInfo[] fieldInfos = getClassFieldInfo(this.getClass()); + writeToByteBufferInternal(output, fieldInfos); + } + + /** + * Convert the parsed Struct subclass object to byte array. + * + * @param order indicate ByteBuffer is outputted as little-endian or big-endian. + */ + public final byte[] writeToBytes(final ByteOrder order) { + final FieldInfo[] fieldInfos = getClassFieldInfo(this.getClass()); + final byte[] output = new byte[getSizeInternal(fieldInfos)]; + final ByteBuffer buffer = ByteBuffer.wrap(output); + buffer.order(order); + writeToByteBufferInternal(buffer, fieldInfos); + return output; + } + + /** Convert the parsed Struct subclass object to byte array with native order. */ + public final byte[] writeToBytes() { + return writeToBytes(ByteOrder.nativeOrder()); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || this.getClass() != obj.getClass()) return false; + + final FieldInfo[] fieldInfos = getClassFieldInfo(this.getClass()); + for (int i = 0; i < fieldInfos.length; i++) { + try { + final Object value = fieldInfos[i].field.get(this); + final Object otherValue = fieldInfos[i].field.get(obj); + + // Use Objects#deepEquals because the equals method on arrays does not check the + // contents of the array. The only difference between Objects#deepEquals and + // Objects#equals is that the former will call Arrays#deepEquals when comparing + // arrays. In turn, the only difference between Arrays#deepEquals is that it + // supports nested arrays. Struct does not currently support these, and if it did, + // Objects#deepEquals might be more correct. + if (!Objects.deepEquals(value, otherValue)) return false; + } catch (IllegalAccessException e) { + throw new IllegalStateException("Cannot access field: " + fieldInfos[i].field, e); + } + } + return true; + } + + @Override + public int hashCode() { + final FieldInfo[] fieldInfos = getClassFieldInfo(this.getClass()); + final Object[] values = new Object[fieldInfos.length]; + for (int i = 0; i < fieldInfos.length; i++) { + final Object value = getFieldValue(fieldInfos[i].field); + // For byte array field, put the hash code generated based on the array content into + // the Object array instead of the reference to byte array, which might change and cause + // to get a different hash code even with the exact same elements. + if (fieldInfos[i].field.getType() == byte[].class) { + values[i] = Arrays.hashCode((byte[]) value); + } else { + values[i] = value; + } + } + return Objects.hash(values); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + final FieldInfo[] fieldInfos = getClassFieldInfo(this.getClass()); + for (int i = 0; i < fieldInfos.length; i++) { + sb.append(fieldInfos[i].field.getName()).append(": "); + final Object value = getFieldValue(fieldInfos[i].field); + if (value == null) { + sb.append("null"); + } else if (fieldInfos[i].annotation.type() == Type.ByteArray) { + sb.append("0x").append(HexDump.toHexString((byte[]) value)); + } else if (fieldInfos[i].annotation.type() == Type.Ipv4Address + || fieldInfos[i].annotation.type() == Type.Ipv6Address) { + sb.append(((InetAddress) value).getHostAddress()); + } else { + sb.append(value.toString()); + } + if (i != fieldInfos.length - 1) sb.append(", "); + } + return sb.toString(); + } + + /** A simple Struct which only contains a u8 field. */ + public static class U8 extends Struct { + @Struct.Field(order = 0, type = Struct.Type.U8) + public final short val; + + public U8(final short val) { + this.val = val; + } + } + + /** A simple Struct which only contains an s32 field. */ + public static class S32 extends Struct { + @Struct.Field(order = 0, type = Struct.Type.S32) + public final int val; + + public S32(final int val) { + this.val = val; + } + } + + /** A simple Struct which only contains a u32 field. */ + public static class U32 extends Struct { + @Struct.Field(order = 0, type = Struct.Type.U32) + public final long val; + + public U32(final long val) { + this.val = val; + } + } + + /** A simple Struct which only contains an s64 field. */ + public static class S64 extends Struct { + @Struct.Field(order = 0, type = Struct.Type.S64) + public final long val; + + public S64(final long val) { + this.val = val; + } + } +} diff --git a/staticlibs/device/com/android/net/module/util/TcUtils.java b/staticlibs/device/com/android/net/module/util/TcUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..9d2fb7f9d42e11f8be349aa884c08c15e9abbd8e --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/TcUtils.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util; + +import java.io.IOException; + +/** + * Contains mostly tc-related functionality. + */ +public class TcUtils { + static { + System.loadLibrary(JniUtil.getJniLibraryName(TcUtils.class.getPackage())); + } + + /** + * Checks if the network interface uses an ethernet L2 header. + * + * @param iface the network interface. + * @return true if the interface uses an ethernet L2 header. + * @throws IOException + */ + public static native boolean isEthernet(String iface) throws IOException; + + /** + * Attach a tc bpf filter. + * + * Equivalent to the following 'tc' command: + * tc filter add dev .. in/egress prio .. protocol ipv6/ip bpf object-pinned + * /sys/fs/bpf/... direct-action + * + * @param ifIndex the network interface index. + * @param ingress ingress or egress qdisc. + * @param prio + * @param proto + * @param bpfProgPath + * @throws IOException + */ + public static native void tcFilterAddDevBpf(int ifIndex, boolean ingress, short prio, + short proto, String bpfProgPath) throws IOException; + + /** + * Attach a tc police action. + * + * Attaches a matchall filter to the clsact qdisc with a tc police and tc bpf action attached. + * This causes the ingress rate to be limited and exceeding packets to be forwarded to a bpf + * program (specified in bpfProgPah) that accounts for the packets before dropping them. + * + * Equivalent to the following 'tc' command: + * tc filter add dev .. ingress prio .. protocol .. matchall \ + * action police rate .. burst .. conform-exceed pipe/continue \ + * action bpf object-pinned .. \ + * drop + * + * @param ifIndex the network interface index. + * @param prio the filter preference. + * @param proto protocol. + * @param rateInBytesPerSec rate limit in bytes/s. + * @param bpfProgPath bpg program that accounts for rate exceeding packets before they are + * dropped. + * @throws IOException + */ + public static native void tcFilterAddDevIngressPolice(int ifIndex, short prio, short proto, + int rateInBytesPerSec, String bpfProgPath) throws IOException; + + /** + * Delete a tc filter. + * + * Equivalent to the following 'tc' command: + * tc filter del dev .. in/egress prio .. protocol .. + * + * @param ifIndex the network interface index. + * @param ingress ingress or egress qdisc. + * @param prio the filter preference. + * @param proto protocol. + * @throws IOException + */ + public static native void tcFilterDelDev(int ifIndex, boolean ingress, short prio, + short proto) throws IOException; + + /** + * Add a clsact qdisc. + * + * Equivalent to the following 'tc' command: + * tc qdisc add dev .. clsact + * + * @param ifIndex the network interface index. + * @throws IOException + */ + public static native void tcQdiscAddDevClsact(int ifIndex) throws IOException; +} diff --git a/staticlibs/device/com/android/net/module/util/arp/ArpPacket.java b/staticlibs/device/com/android/net/module/util/arp/ArpPacket.java new file mode 100644 index 0000000000000000000000000000000000000000..dab9694b2550ee079c5fec873845b8071fa1caa3 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/arp/ArpPacket.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.arp; + +import static android.system.OsConstants.ETH_P_ARP; +import static android.system.OsConstants.ETH_P_IP; + +import static com.android.net.module.util.NetworkStackConstants.ARP_ETHER_IPV4_LEN; +import static com.android.net.module.util.NetworkStackConstants.ARP_HWTYPE_ETHER; +import static com.android.net.module.util.NetworkStackConstants.ARP_REPLY; +import static com.android.net.module.util.NetworkStackConstants.ARP_REQUEST; +import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN; +import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_LEN; + +import android.net.MacAddress; + +import com.android.internal.annotations.VisibleForTesting; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; + +/** + * Defines basic data and operations needed to build and parse packets for the + * ARP protocol. + * + * @hide + */ +public class ArpPacket { + private static final String TAG = "ArpPacket"; + + public final short opCode; + public final Inet4Address senderIp; + public final Inet4Address targetIp; + public final MacAddress senderHwAddress; + public final MacAddress targetHwAddress; + + ArpPacket(short opCode, MacAddress senderHwAddress, Inet4Address senderIp, + MacAddress targetHwAddress, Inet4Address targetIp) { + this.opCode = opCode; + this.senderHwAddress = senderHwAddress; + this.senderIp = senderIp; + this.targetHwAddress = targetHwAddress; + this.targetIp = targetIp; + } + + /** + * Build an ARP packet from the required specified parameters. + */ + @VisibleForTesting + public static ByteBuffer buildArpPacket(final byte[] dstMac, final byte[] srcMac, + final byte[] targetIp, final byte[] targetHwAddress, byte[] senderIp, + final short opCode) { + final ByteBuffer buf = ByteBuffer.allocate(ARP_ETHER_IPV4_LEN); + + // Ether header + buf.put(dstMac); + buf.put(srcMac); + buf.putShort((short) ETH_P_ARP); + + // ARP header + buf.putShort((short) ARP_HWTYPE_ETHER); // hrd + buf.putShort((short) ETH_P_IP); // pro + buf.put((byte) ETHER_ADDR_LEN); // hln + buf.put((byte) IPV4_ADDR_LEN); // pln + buf.putShort(opCode); // op + buf.put(srcMac); // sha + buf.put(senderIp); // spa + buf.put(targetHwAddress); // tha + buf.put(targetIp); // tpa + buf.flip(); + return buf; + } + + /** + * Parse an ARP packet from a ByteBuffer object. + */ + @VisibleForTesting + public static ArpPacket parseArpPacket(final byte[] recvbuf, final int length) + throws ParseException { + try { + if (length < ARP_ETHER_IPV4_LEN || recvbuf.length < length) { + throw new ParseException("Invalid packet length: " + length); + } + + final ByteBuffer buffer = ByteBuffer.wrap(recvbuf, 0, length); + byte[] l2dst = new byte[ETHER_ADDR_LEN]; + byte[] l2src = new byte[ETHER_ADDR_LEN]; + buffer.get(l2dst); + buffer.get(l2src); + + final short etherType = buffer.getShort(); + if (etherType != ETH_P_ARP) { + throw new ParseException("Incorrect Ether Type: " + etherType); + } + + final short hwType = buffer.getShort(); + if (hwType != ARP_HWTYPE_ETHER) { + throw new ParseException("Incorrect HW Type: " + hwType); + } + + final short protoType = buffer.getShort(); + if (protoType != ETH_P_IP) { + throw new ParseException("Incorrect Protocol Type: " + protoType); + } + + final byte hwAddrLength = buffer.get(); + if (hwAddrLength != ETHER_ADDR_LEN) { + throw new ParseException("Incorrect HW address length: " + hwAddrLength); + } + + final byte ipAddrLength = buffer.get(); + if (ipAddrLength != IPV4_ADDR_LEN) { + throw new ParseException("Incorrect Protocol address length: " + ipAddrLength); + } + + final short opCode = buffer.getShort(); + if (opCode != ARP_REQUEST && opCode != ARP_REPLY) { + throw new ParseException("Incorrect opCode: " + opCode); + } + + byte[] senderHwAddress = new byte[ETHER_ADDR_LEN]; + byte[] senderIp = new byte[IPV4_ADDR_LEN]; + buffer.get(senderHwAddress); + buffer.get(senderIp); + + byte[] targetHwAddress = new byte[ETHER_ADDR_LEN]; + byte[] targetIp = new byte[IPV4_ADDR_LEN]; + buffer.get(targetHwAddress); + buffer.get(targetIp); + + return new ArpPacket(opCode, MacAddress.fromBytes(senderHwAddress), + (Inet4Address) InetAddress.getByAddress(senderIp), + MacAddress.fromBytes(targetHwAddress), + (Inet4Address) InetAddress.getByAddress(targetIp)); + } catch (IndexOutOfBoundsException e) { + throw new ParseException("Invalid index when wrapping a byte array into a buffer"); + } catch (BufferUnderflowException e) { + throw new ParseException("Invalid buffer position"); + } catch (IllegalArgumentException e) { + throw new ParseException("Invalid MAC address representation"); + } catch (UnknownHostException e) { + throw new ParseException("Invalid IP address of Host"); + } + } + + /** + * Thrown when parsing ARP packet failed. + */ + public static class ParseException extends Exception { + ParseException(String message) { + super(message); + } + } +} diff --git a/staticlibs/device/com/android/net/module/util/async/Assertions.java b/staticlibs/device/com/android/net/module/util/async/Assertions.java new file mode 100644 index 0000000000000000000000000000000000000000..ce701d0591e45881333969a5b322a822bb5b5228 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/async/Assertions.java @@ -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. + */ + +package com.android.net.module.util.async; + +import android.os.Build; + +/** + * Implements basic assert functions for runtime error-checking. + * + * @hide + */ +public final class Assertions { + public static final boolean IS_USER_BUILD = "user".equals(Build.TYPE); + + public static void throwsIfOutOfBounds(int totalLength, int pos, int len) { + if (!IS_USER_BUILD && ((totalLength | pos | len) < 0 || pos > totalLength - len)) { + throw new ArrayIndexOutOfBoundsException( + "length=" + totalLength + "; regionStart=" + pos + "; regionLength=" + len); + } + } + + public static void throwsIfOutOfBounds(byte[] buffer, int pos, int len) { + throwsIfOutOfBounds(buffer != null ? buffer.length : 0, pos, len); + } + + private Assertions() {} +} diff --git a/staticlibs/device/com/android/net/module/util/async/AsyncFile.java b/staticlibs/device/com/android/net/module/util/async/AsyncFile.java new file mode 100644 index 0000000000000000000000000000000000000000..2a3231b7180d156aadb488f080eaa332b8e8cb48 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/async/AsyncFile.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.async; + +import java.io.IOException; + +/** + * Represents an EventManager-managed file with Async IO semantics. + * + * Implements level-based Asyn IO semantics. This means that: + * - onReadReady() callback would keep happening as long as there's any remaining + * data to read, or the user calls enableReadEvents(false) + * - onWriteReady() callback would keep happening as long as there's remaining space + * to write to, or the user calls enableWriteEvents(false) + * + * All operations except close() must be called on the EventManager thread. + * + * @hide + */ +public interface AsyncFile { + /** + * Receives notifications when file readability or writeability changes. + * @hide + */ + public interface Listener { + /** Invoked after the underlying file has been closed. */ + void onClosed(AsyncFile file); + + /** Invoked while the file has readable data and read notifications are enabled. */ + void onReadReady(AsyncFile file); + + /** Invoked while the file has writeable space and write notifications are enabled. */ + void onWriteReady(AsyncFile file); + } + + /** Requests this file to be closed. */ + void close(); + + /** Enables or disables onReadReady() events. */ + void enableReadEvents(boolean enable); + + /** Enables or disables onWriteReady() events. */ + void enableWriteEvents(boolean enable); + + /** Returns true if the input stream has reached its end, or has been closed. */ + boolean reachedEndOfFile(); + + /** + * Reads available data from the given non-blocking file descriptor. + * + * Returns zero if there's no data to read at this moment. + * Returns -1 if the file has reached its end or the input stream has been closed. + * Otherwise returns the number of bytes read. + */ + int read(byte[] buffer, int pos, int len) throws IOException; + + /** + * Writes data into the given non-blocking file descriptor. + * + * Returns zero if there's no buffer space to write to at this moment. + * Otherwise returns the number of bytes written. + */ + int write(byte[] buffer, int pos, int len) throws IOException; +} diff --git a/staticlibs/device/com/android/net/module/util/async/BufferedFile.java b/staticlibs/device/com/android/net/module/util/async/BufferedFile.java new file mode 100644 index 0000000000000000000000000000000000000000..bb5736b0e04ba3c65c660b609b8687c57748b74b --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/async/BufferedFile.java @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.async; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Buffers inbound and outbound file data within given strict limits. + * + * Automatically manages all readability and writeability events in EventManager: + * - When read buffer has more space - asks EventManager to notify on more data + * - When write buffer has more space - asks the user to provide more data + * - When underlying file cannot accept more data - registers EventManager callback + * + * @hide + */ +public final class BufferedFile implements AsyncFile.Listener { + /** + * Receives notifications when new data or output space is available. + * @hide + */ + public interface Listener { + /** Invoked after the underlying file has been closed. */ + void onBufferedFileClosed(); + + /** Invoked when there's new data in the inbound buffer. */ + void onBufferedFileInboundData(int readByteCount); + + /** Notifies on data being flushed from output buffer. */ + void onBufferedFileOutboundSpace(); + + /** Notifies on unrecoverable error in file access. */ + void onBufferedFileIoError(String message); + } + + private final Listener mListener; + private final EventManager mEventManager; + private AsyncFile mFile; + + private final CircularByteBuffer mInboundBuffer; + private final AtomicLong mTotalBytesRead = new AtomicLong(); + private boolean mIsReadingShutdown; + + private final CircularByteBuffer mOutboundBuffer; + private final AtomicLong mTotalBytesWritten = new AtomicLong(); + + /** Creates BufferedFile based on the given file descriptor. */ + public static BufferedFile create( + EventManager eventManager, + FileHandle fileHandle, + Listener listener, + int inboundBufferSize, + int outboundBufferSize) throws IOException { + if (fileHandle == null) { + throw new NullPointerException(); + } + BufferedFile file = new BufferedFile( + eventManager, listener, inboundBufferSize, outboundBufferSize); + file.mFile = eventManager.registerFile(fileHandle, file); + return file; + } + + private BufferedFile( + EventManager eventManager, + Listener listener, + int inboundBufferSize, + int outboundBufferSize) { + if (eventManager == null || listener == null) { + throw new NullPointerException(); + } + mEventManager = eventManager; + mListener = listener; + + mInboundBuffer = new CircularByteBuffer(inboundBufferSize); + mOutboundBuffer = new CircularByteBuffer(outboundBufferSize); + } + + /** Requests this file to be closed. */ + public void close() { + mFile.close(); + } + + @Override + public void onClosed(AsyncFile file) { + mListener.onBufferedFileClosed(); + } + + /////////////////////////////////////////////////////////////////////////// + // READ PATH + /////////////////////////////////////////////////////////////////////////// + + /** Returns buffer that is automatically filled with inbound data. */ + public ReadableByteBuffer getInboundBuffer() { + return mInboundBuffer; + } + + public int getInboundBufferFreeSizeForTest() { + return mInboundBuffer.freeSize(); + } + + /** Permanently disables reading of this file, and clears all buffered data. */ + public void shutdownReading() { + mIsReadingShutdown = true; + mInboundBuffer.clear(); + mFile.enableReadEvents(false); + } + + /** Returns true after shutdownReading() has been called. */ + public boolean isReadingShutdown() { + return mIsReadingShutdown; + } + + /** Starts or resumes async read operations on this file. */ + public void continueReading() { + if (!mIsReadingShutdown && mInboundBuffer.freeSize() > 0) { + mFile.enableReadEvents(true); + } + } + + @Override + public void onReadReady(AsyncFile file) { + if (mIsReadingShutdown) { + return; + } + + int readByteCount; + try { + readByteCount = bufferInputData(); + } catch (IOException e) { + mListener.onBufferedFileIoError("IOException while reading: " + e.toString()); + return; + } + + if (readByteCount > 0) { + mListener.onBufferedFileInboundData(readByteCount); + } + + continueReading(); + } + + private int bufferInputData() throws IOException { + int totalReadCount = 0; + while (true) { + final int maxReadCount = mInboundBuffer.getDirectWriteSize(); + if (maxReadCount == 0) { + mFile.enableReadEvents(false); + break; + } + + final int bufferOffset = mInboundBuffer.getDirectWritePos(); + final byte[] buffer = mInboundBuffer.getDirectWriteBuffer(); + + final int readCount = mFile.read(buffer, bufferOffset, maxReadCount); + if (readCount <= 0) { + break; + } + + mInboundBuffer.accountForDirectWrite(readCount); + totalReadCount += readCount; + } + + mTotalBytesRead.addAndGet(totalReadCount); + return totalReadCount; + } + + /////////////////////////////////////////////////////////////////////////// + // WRITE PATH + /////////////////////////////////////////////////////////////////////////// + + /** Returns the number of bytes currently buffered for output. */ + public int getOutboundBufferSize() { + return mOutboundBuffer.size(); + } + + /** Returns the number of bytes currently available for buffering for output. */ + public int getOutboundBufferFreeSize() { + return mOutboundBuffer.freeSize(); + } + + /** + * Queues the given data for output. + * Throws runtime exception if there is not enough space. + */ + public boolean enqueueOutboundData(byte[] data, int pos, int len) { + return enqueueOutboundData(data, pos, len, null, 0, 0); + } + + /** + * Queues data1, then data2 for output. + * Throws runtime exception if there is not enough space. + */ + public boolean enqueueOutboundData( + byte[] data1, int pos1, int len1, + byte[] buffer2, int pos2, int len2) { + Assertions.throwsIfOutOfBounds(data1, pos1, len1); + Assertions.throwsIfOutOfBounds(buffer2, pos2, len2); + + final int totalLen = len1 + len2; + + if (totalLen > mOutboundBuffer.freeSize()) { + flushOutboundBuffer(); + + if (totalLen > mOutboundBuffer.freeSize()) { + return false; + } + } + + mOutboundBuffer.writeBytes(data1, pos1, len1); + + if (buffer2 != null) { + mOutboundBuffer.writeBytes(buffer2, pos2, len2); + } + + flushOutboundBuffer(); + + return true; + } + + private void flushOutboundBuffer() { + try { + while (mOutboundBuffer.getDirectReadSize() > 0) { + final int maxReadSize = mOutboundBuffer.getDirectReadSize(); + final int writeCount = mFile.write( + mOutboundBuffer.getDirectReadBuffer(), + mOutboundBuffer.getDirectReadPos(), + maxReadSize); + + if (writeCount == 0) { + mFile.enableWriteEvents(true); + break; + } + + if (writeCount > maxReadSize) { + throw new IllegalArgumentException( + "Write count " + writeCount + " above max " + maxReadSize); + } + + mOutboundBuffer.accountForDirectRead(writeCount); + } + } catch (IOException e) { + scheduleOnIoError("IOException while writing: " + e.toString()); + } + } + + private void scheduleOnIoError(String message) { + mEventManager.execute(() -> { + mListener.onBufferedFileIoError(message); + }); + } + + @Override + public void onWriteReady(AsyncFile file) { + mFile.enableWriteEvents(false); + flushOutboundBuffer(); + mListener.onBufferedFileOutboundSpace(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("file={"); + sb.append(mFile); + sb.append("}"); + if (mIsReadingShutdown) { + sb.append(", readingShutdown"); + } + sb.append("}, inboundBuffer={"); + sb.append(mInboundBuffer); + sb.append("}, outboundBuffer={"); + sb.append(mOutboundBuffer); + sb.append("}, totalBytesRead="); + sb.append(mTotalBytesRead); + sb.append(", totalBytesWritten="); + sb.append(mTotalBytesWritten); + return sb.toString(); + } +} diff --git a/staticlibs/device/com/android/net/module/util/async/CircularByteBuffer.java b/staticlibs/device/com/android/net/module/util/async/CircularByteBuffer.java new file mode 100644 index 0000000000000000000000000000000000000000..92daa08f5f83f8881e7a4c126137859326863f1c --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/async/CircularByteBuffer.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.async; + +import java.util.Arrays; + +/** + * Implements a circular read-write byte buffer. + * + * @hide + */ +public final class CircularByteBuffer implements ReadableByteBuffer { + private final byte[] mBuffer; + private final int mCapacity; + private int mReadPos; + private int mWritePos; + private int mSize; + + public CircularByteBuffer(int capacity) { + mCapacity = capacity; + mBuffer = new byte[mCapacity]; + } + + @Override + public void clear() { + mReadPos = 0; + mWritePos = 0; + mSize = 0; + Arrays.fill(mBuffer, (byte) 0); + } + + @Override + public int capacity() { + return mCapacity; + } + + @Override + public int size() { + return mSize; + } + + /** Returns the amount of remaining writeable space in this buffer. */ + public int freeSize() { + return mCapacity - mSize; + } + + @Override + public byte peek(int offset) { + if (offset < 0 || offset >= size()) { + throw new IllegalArgumentException("Invalid offset=" + offset + ", size=" + size()); + } + + return mBuffer[(mReadPos + offset) % mCapacity]; + } + + @Override + public void readBytes(byte[] dst, int dstPos, int dstLen) { + if (dst != null) { + Assertions.throwsIfOutOfBounds(dst, dstPos, dstLen); + } + if (dstLen > size()) { + throw new IllegalArgumentException("Invalid len=" + dstLen + ", size=" + size()); + } + + while (dstLen > 0) { + final int copyLen = getCopyLen(mReadPos, mWritePos, dstLen); + if (dst != null) { + System.arraycopy(mBuffer, mReadPos, dst, dstPos, copyLen); + } + dstPos += copyLen; + dstLen -= copyLen; + mSize -= copyLen; + mReadPos = (mReadPos + copyLen) % mCapacity; + } + + if (mSize == 0) { + // Reset to the beginning for better contiguous access. + mReadPos = 0; + mWritePos = 0; + } + } + + @Override + public void peekBytes(int offset, byte[] dst, int dstPos, int dstLen) { + Assertions.throwsIfOutOfBounds(dst, dstPos, dstLen); + if (offset + dstLen > size()) { + throw new IllegalArgumentException("Invalid len=" + dstLen + + ", offset=" + offset + ", size=" + size()); + } + + int tmpReadPos = (mReadPos + offset) % mCapacity; + while (dstLen > 0) { + final int copyLen = getCopyLen(tmpReadPos, mWritePos, dstLen); + System.arraycopy(mBuffer, tmpReadPos, dst, dstPos, copyLen); + dstPos += copyLen; + dstLen -= copyLen; + tmpReadPos = (tmpReadPos + copyLen) % mCapacity; + } + } + + @Override + public int getDirectReadSize() { + if (size() == 0) { + return 0; + } + return (mReadPos < mWritePos ? (mWritePos - mReadPos) : (mCapacity - mReadPos)); + } + + @Override + public int getDirectReadPos() { + return mReadPos; + } + + @Override + public byte[] getDirectReadBuffer() { + return mBuffer; + } + + @Override + public void accountForDirectRead(int len) { + if (len < 0 || len > size()) { + throw new IllegalArgumentException("Invalid len=" + len + ", size=" + size()); + } + + mSize -= len; + mReadPos = (mReadPos + len) % mCapacity; + } + + /** Copies given data to the end of the buffer. */ + public void writeBytes(byte[] buffer, int pos, int len) { + Assertions.throwsIfOutOfBounds(buffer, pos, len); + if (len > freeSize()) { + throw new IllegalArgumentException("Invalid len=" + len + ", size=" + freeSize()); + } + + while (len > 0) { + final int copyLen = getCopyLen(mWritePos, mReadPos,len); + System.arraycopy(buffer, pos, mBuffer, mWritePos, copyLen); + pos += copyLen; + len -= copyLen; + mSize += copyLen; + mWritePos = (mWritePos + copyLen) % mCapacity; + } + } + + private int getCopyLen(int startPos, int endPos, int len) { + if (startPos < endPos) { + return Math.min(len, endPos - startPos); + } else { + return Math.min(len, mCapacity - startPos); + } + } + + /** Returns the amount of contiguous writeable space. */ + public int getDirectWriteSize() { + if (freeSize() == 0) { + return 0; // Return zero in case buffer is full. + } + return (mWritePos < mReadPos ? (mReadPos - mWritePos) : (mCapacity - mWritePos)); + } + + /** Returns the position of contiguous writeable space. */ + public int getDirectWritePos() { + return mWritePos; + } + + /** Returns the buffer reference for direct write operation. */ + public byte[] getDirectWriteBuffer() { + return mBuffer; + } + + /** Must be called after performing a direct write using getDirectWriteBuffer(). */ + public void accountForDirectWrite(int len) { + if (len < 0 || len > freeSize()) { + throw new IllegalArgumentException("Invalid len=" + len + ", size=" + freeSize()); + } + + mSize += len; + mWritePos = (mWritePos + len) % mCapacity; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("CircularByteBuffer{c="); + sb.append(mCapacity); + sb.append(",s="); + sb.append(mSize); + sb.append(",r="); + sb.append(mReadPos); + sb.append(",w="); + sb.append(mWritePos); + sb.append('}'); + return sb.toString(); + } +} diff --git a/staticlibs/device/com/android/net/module/util/async/EventManager.java b/staticlibs/device/com/android/net/module/util/async/EventManager.java new file mode 100644 index 0000000000000000000000000000000000000000..4ed4a70404c27048122b4291ca74b938be00f14a --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/async/EventManager.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.async; + +import java.io.IOException; +import java.util.concurrent.Executor; + +/** + * Manages Async IO files and scheduled alarms, and executes all related callbacks + * in its own thread. + * + * All callbacks of AsyncFile, Alarm and EventManager will execute on EventManager's thread. + * + * Methods of this interface can be called from any thread. + * + * @hide + */ +public interface EventManager extends Executor { + /** + * Represents a scheduled alarm, allowing caller to attempt to cancel that alarm + * before it executes. + * + * @hide + */ + public interface Alarm { + /** @hide */ + public interface Listener { + void onAlarm(Alarm alarm, long elapsedTimeMs); + void onAlarmCancelled(Alarm alarm); + } + + /** + * Attempts to cancel this alarm. Note that this request is inherently + * racy if executed close to the alarm's expiration time. + */ + void cancel(); + } + + /** + * Requests EventManager to manage the given file. + * + * The file descriptors are not cloned, and EventManager takes ownership of all files passed. + * + * No event callbacks are enabled by this method. + */ + AsyncFile registerFile(FileHandle fileHandle, AsyncFile.Listener listener) throws IOException; + + /** + * Schedules Alarm with the given timeout. + * + * Timeout of zero can be used for immediate execution. + */ + Alarm scheduleAlarm(long timeout, Alarm.Listener callback); + + /** Schedules Runnable for immediate execution. */ + @Override + void execute(Runnable callback); + + /** Throws a runtime exception if the caller is not executing on this EventManager's thread. */ + void assertInThread(); +} diff --git a/staticlibs/device/com/android/net/module/util/async/FileHandle.java b/staticlibs/device/com/android/net/module/util/async/FileHandle.java new file mode 100644 index 0000000000000000000000000000000000000000..9f7942d425122998adccfcdfd6d3767314e8b16b --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/async/FileHandle.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.async; + +import android.os.ParcelFileDescriptor; + +import java.io.Closeable; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Represents an file descriptor or another way to access a file. + * + * @hide + */ +public final class FileHandle { + private final ParcelFileDescriptor mFd; + private final Closeable mCloseable; + private final InputStream mInputStream; + private final OutputStream mOutputStream; + + public static FileHandle fromFileDescriptor(ParcelFileDescriptor fd) { + if (fd == null) { + throw new NullPointerException(); + } + return new FileHandle(fd, null, null, null); + } + + public static FileHandle fromBlockingStream( + Closeable closeable, InputStream is, OutputStream os) { + if (closeable == null || is == null || os == null) { + throw new NullPointerException(); + } + return new FileHandle(null, closeable, is, os); + } + + private FileHandle(ParcelFileDescriptor fd, Closeable closeable, + InputStream is, OutputStream os) { + mFd = fd; + mCloseable = closeable; + mInputStream = is; + mOutputStream = os; + } + + ParcelFileDescriptor getFileDescriptor() { + return mFd; + } + + Closeable getCloseable() { + return mCloseable; + } + + InputStream getInputStream() { + return mInputStream; + } + + OutputStream getOutputStream() { + return mOutputStream; + } +} diff --git a/staticlibs/device/com/android/net/module/util/async/OsAccess.java b/staticlibs/device/com/android/net/module/util/async/OsAccess.java new file mode 100644 index 0000000000000000000000000000000000000000..df0ded219c9ee4d5b580a82ab488081b3b83c7dc --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/async/OsAccess.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.async; + +import android.os.ParcelFileDescriptor; +import android.system.StructPollfd; + +import java.io.FileDescriptor; +import java.io.IOException; + +/** + * Provides access to all relevant OS functions.. + * + * @hide + */ +public abstract class OsAccess { + /** Closes the given file, suppressing IO exceptions. */ + public abstract void close(ParcelFileDescriptor fd); + + /** Returns file name for debugging purposes. */ + public abstract String getFileDebugName(ParcelFileDescriptor fd); + + /** Returns inner FileDescriptor instance. */ + public abstract FileDescriptor getInnerFileDescriptor(ParcelFileDescriptor fd); + + /** + * Reads available data from the given non-blocking file descriptor. + * + * Returns zero if there's no data to read at this moment. + * Returns -1 if the file has reached its end or the input stream has been closed. + * Otherwise returns the number of bytes read. + */ + public abstract int read(FileDescriptor fd, byte[] buffer, int pos, int len) + throws IOException; + + /** + * Writes data into the given non-blocking file descriptor. + * + * Returns zero if there's no buffer space to write to at this moment. + * Otherwise returns the number of bytes written. + */ + public abstract int write(FileDescriptor fd, byte[] buffer, int pos, int len) + throws IOException; + + public abstract long monotonicTimeMillis(); + public abstract void setNonBlocking(FileDescriptor fd) throws IOException; + public abstract ParcelFileDescriptor[] pipe() throws IOException; + + public abstract int poll(StructPollfd[] fds, int timeoutMs) throws IOException; + public abstract short getPollInMask(); + public abstract short getPollOutMask(); +} diff --git a/staticlibs/device/com/android/net/module/util/async/ReadableByteBuffer.java b/staticlibs/device/com/android/net/module/util/async/ReadableByteBuffer.java new file mode 100644 index 0000000000000000000000000000000000000000..7f824049a259d208c883cae5df575faad81fc285 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/async/ReadableByteBuffer.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.async; + +/** + * Allows reading from a buffer of bytes. The data can be read and thus removed, + * or peeked at without disturbing the buffer state. + * + * @hide + */ +public interface ReadableByteBuffer { + /** Returns the size of the buffered data. */ + int size(); + + /** + * Returns the maximum amount of the buffered data. + * + * The caller may use this method in combination with peekBytes() + * to estimate when the buffer needs to be emptied using readData(). + */ + int capacity(); + + /** Clears all buffered data. */ + void clear(); + + /** Returns a single byte at the given offset. */ + byte peek(int offset); + + /** Copies an array of bytes from the given offset to "dst". */ + void peekBytes(int offset, byte[] dst, int dstPos, int dstLen); + + /** Reads and removes an array of bytes from the head of the buffer. */ + void readBytes(byte[] dst, int dstPos, int dstLen); + + /** Returns the amount of contiguous readable data. */ + int getDirectReadSize(); + + /** Returns the position of contiguous readable data. */ + int getDirectReadPos(); + + /** Returns the buffer reference for direct read operation. */ + byte[] getDirectReadBuffer(); + + /** Must be called after performing a direct read using getDirectReadBuffer(). */ + void accountForDirectRead(int len); +} diff --git a/staticlibs/device/com/android/net/module/util/ip/ConntrackMonitor.java b/staticlibs/device/com/android/net/module/util/ip/ConntrackMonitor.java new file mode 100644 index 0000000000000000000000000000000000000000..420a54460b89c963680bdff6b5c49e3d586b9a27 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/ip/ConntrackMonitor.java @@ -0,0 +1,203 @@ +/* + * 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. + */ + +package com.android.net.module.util.ip; + +import static com.android.net.module.util.netlink.ConntrackMessage.DYING_MASK; +import static com.android.net.module.util.netlink.ConntrackMessage.ESTABLISHED_MASK; + +import android.annotation.NonNull; +import android.os.Handler; +import android.system.OsConstants; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.net.module.util.SharedLog; +import com.android.net.module.util.netlink.ConntrackMessage; +import com.android.net.module.util.netlink.NetlinkConstants; +import com.android.net.module.util.netlink.NetlinkMessage; + +import java.util.Objects; + + +/** + * ConntrackMonitor. + * + * Monitors the netfilter conntrack notifications and presents to callers + * ConntrackEvents describing each event. + * + * @hide + */ +public class ConntrackMonitor extends NetlinkMonitor { + private static final String TAG = ConntrackMonitor.class.getSimpleName(); + private static final boolean DBG = false; + private static final boolean VDBG = false; + + // Reference kernel/uapi/linux/netfilter/nfnetlink_compat.h + public static final int NF_NETLINK_CONNTRACK_NEW = 1; + public static final int NF_NETLINK_CONNTRACK_UPDATE = 2; + public static final int NF_NETLINK_CONNTRACK_DESTROY = 4; + + // The socket receive buffer size in bytes. If too many conntrack messages are sent too + // quickly, the conntrack messages can overflow the socket receive buffer. This can happen + // if too many connections are disconnected by losing network and so on. Use a large-enough + // buffer to avoid the error ENOBUFS while listening to the conntrack messages. + private static final int SOCKET_RECV_BUFSIZE = 6 * 1024 * 1024; + + /** + * A class for describing parsed netfilter conntrack events. + */ + public static class ConntrackEvent { + /** + * Conntrack event type. + */ + public final short msgType; + /** + * Original direction conntrack tuple. + */ + public final ConntrackMessage.Tuple tupleOrig; + /** + * Reply direction conntrack tuple. + */ + public final ConntrackMessage.Tuple tupleReply; + /** + * Connection status. A bitmask of ip_conntrack_status enum flags. + */ + public final int status; + /** + * Conntrack timeout. + */ + public final int timeoutSec; + + public ConntrackEvent(ConntrackMessage msg) { + this.msgType = msg.getHeader().nlmsg_type; + this.tupleOrig = msg.tupleOrig; + this.tupleReply = msg.tupleReply; + this.status = msg.status; + this.timeoutSec = msg.timeoutSec; + } + + @VisibleForTesting + public ConntrackEvent(short msgType, ConntrackMessage.Tuple tupleOrig, + ConntrackMessage.Tuple tupleReply, int status, int timeoutSec) { + this.msgType = msgType; + this.tupleOrig = tupleOrig; + this.tupleReply = tupleReply; + this.status = status; + this.timeoutSec = timeoutSec; + } + + @Override + @VisibleForTesting + public boolean equals(Object o) { + if (!(o instanceof ConntrackEvent)) return false; + ConntrackEvent that = (ConntrackEvent) o; + return this.msgType == that.msgType + && Objects.equals(this.tupleOrig, that.tupleOrig) + && Objects.equals(this.tupleReply, that.tupleReply) + && this.status == that.status + && this.timeoutSec == that.timeoutSec; + } + + @Override + public int hashCode() { + return Objects.hash(msgType, tupleOrig, tupleReply, status, timeoutSec); + } + + @Override + public String toString() { + return "ConntrackEvent{" + + "msg_type{" + + NetlinkConstants.stringForNlMsgType(msgType, OsConstants.NETLINK_NETFILTER) + + "}, " + + "tuple_orig{" + tupleOrig + "}, " + + "tuple_reply{" + tupleReply + "}, " + + "status{" + + status + "(" + ConntrackMessage.stringForIpConntrackStatus(status) + ")" + + "}, " + + "timeout_sec{" + Integer.toUnsignedLong(timeoutSec) + "}" + + "}"; + } + + /** + * Check the established NAT session conntrack message. + * + * @param msg the conntrack message to check. + * @return true if an established NAT message, false if not. + */ + public static boolean isEstablishedNatSession(@NonNull ConntrackMessage msg) { + if (msg.getMessageType() != NetlinkConstants.IPCTNL_MSG_CT_NEW) return false; + if (msg.tupleOrig == null) return false; + if (msg.tupleReply == null) return false; + if (msg.timeoutSec == 0) return false; + if ((msg.status & ESTABLISHED_MASK) != ESTABLISHED_MASK) return false; + + return true; + } + + /** + * Check the dying NAT session conntrack message. + * Note that IPCTNL_MSG_CT_DELETE event has no CTA_TIMEOUT attribute. + * + * @param msg the conntrack message to check. + * @return true if a dying NAT message, false if not. + */ + public static boolean isDyingNatSession(@NonNull ConntrackMessage msg) { + if (msg.getMessageType() != NetlinkConstants.IPCTNL_MSG_CT_DELETE) return false; + if (msg.tupleOrig == null) return false; + if (msg.tupleReply == null) return false; + if (msg.timeoutSec != 0) return false; + if ((msg.status & DYING_MASK) != DYING_MASK) return false; + + return true; + } + } + + /** + * A callback to caller for conntrack event. + */ + public interface ConntrackEventConsumer { + /** + * Every conntrack event received on the netlink socket is passed in + * here. + */ + void accept(@NonNull ConntrackEvent event); + } + + private final ConntrackEventConsumer mConsumer; + + public ConntrackMonitor(@NonNull Handler h, @NonNull SharedLog log, + @NonNull ConntrackEventConsumer cb) { + super(h, log, TAG, OsConstants.NETLINK_NETFILTER, NF_NETLINK_CONNTRACK_NEW + | NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY, SOCKET_RECV_BUFSIZE); + mConsumer = cb; + } + + @Override + public void processNetlinkMessage(NetlinkMessage nlMsg, final long whenMs) { + if (!(nlMsg instanceof ConntrackMessage)) { + mLog.e("non-conntrack msg: " + nlMsg); + return; + } + + final ConntrackMessage conntrackMsg = (ConntrackMessage) nlMsg; + if (!(ConntrackEvent.isEstablishedNatSession(conntrackMsg) + || ConntrackEvent.isDyingNatSession(conntrackMsg))) { + return; + } + + mConsumer.accept(new ConntrackEvent(conntrackMsg)); + } +} diff --git a/staticlibs/device/com/android/net/module/util/ip/InterfaceController.java b/staticlibs/device/com/android/net/module/util/ip/InterfaceController.java new file mode 100644 index 0000000000000000000000000000000000000000..7277fec0b1058e952f52236993ab870bd018c4a9 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/ip/InterfaceController.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.ip; + +import static android.net.INetd.IF_STATE_DOWN; +import static android.net.INetd.IF_STATE_UP; + +import android.net.INetd; +import android.net.InterfaceConfigurationParcel; +import android.net.LinkAddress; +import android.os.RemoteException; +import android.os.ServiceSpecificException; +import android.system.OsConstants; + +import com.android.net.module.util.SharedLog; + +import java.net.Inet4Address; +import java.net.InetAddress; + +/** + * Encapsulates the multiple IP configuration operations performed on an interface. + * + * TODO: refactor/eliminate the redundant ways to set and clear addresses. + * + * @hide + */ +public class InterfaceController { + private static final boolean DBG = false; + + private final String mIfName; + private final INetd mNetd; + private final SharedLog mLog; + + public InterfaceController(String ifname, INetd netd, SharedLog log) { + mIfName = ifname; + mNetd = netd; + mLog = log; + } + + /** + * Set the IPv4 address and also optionally bring the interface up or down. + */ + public boolean setInterfaceConfiguration(final LinkAddress ipv4Addr, + final Boolean setIfaceUp) { + if (!(ipv4Addr.getAddress() instanceof Inet4Address)) { + throw new IllegalArgumentException("Invalid or mismatched Inet4Address"); + } + // Note: currently netd only support INetd#IF_STATE_UP and #IF_STATE_DOWN. + // Other flags would be ignored. + + final InterfaceConfigurationParcel ifConfig = new InterfaceConfigurationParcel(); + ifConfig.ifName = mIfName; + ifConfig.ipv4Addr = ipv4Addr.getAddress().getHostAddress(); + ifConfig.prefixLength = ipv4Addr.getPrefixLength(); + // Netd ignores hwaddr in interfaceSetCfg. + ifConfig.hwAddr = ""; + if (setIfaceUp == null) { + // Empty array means no change. + ifConfig.flags = new String[0]; + } else { + // Netd ignores any flag that's not IF_STATE_UP or IF_STATE_DOWN in interfaceSetCfg. + ifConfig.flags = setIfaceUp.booleanValue() + ? new String[] {IF_STATE_UP} : new String[] {IF_STATE_DOWN}; + } + try { + mNetd.interfaceSetCfg(ifConfig); + } catch (RemoteException | ServiceSpecificException e) { + logError("Setting IPv4 address to %s/%d failed: %s", + ifConfig.ipv4Addr, ifConfig.prefixLength, e); + return false; + } + return true; + } + + /** + * Set the IPv4 address of the interface. + */ + public boolean setIPv4Address(final LinkAddress address) { + return setInterfaceConfiguration(address, null); + } + + /** + * Clear the IPv4Address of the interface. + */ + public boolean clearIPv4Address() { + return setIPv4Address(new LinkAddress("0.0.0.0/0")); + } + + private boolean setEnableIPv6(boolean enabled) { + try { + mNetd.interfaceSetEnableIPv6(mIfName, enabled); + } catch (RemoteException | ServiceSpecificException e) { + logError("%s IPv6 failed: %s", (enabled ? "enabling" : "disabling"), e); + return false; + } + return true; + } + + /** + * Enable IPv6 on the interface. + */ + public boolean enableIPv6() { + return setEnableIPv6(true); + } + + /** + * Disable IPv6 on the interface. + */ + public boolean disableIPv6() { + return setEnableIPv6(false); + } + + /** + * Enable or disable IPv6 privacy extensions on the interface. + * @param enabled Whether the extensions should be enabled. + */ + public boolean setIPv6PrivacyExtensions(boolean enabled) { + try { + mNetd.interfaceSetIPv6PrivacyExtensions(mIfName, enabled); + } catch (RemoteException | ServiceSpecificException e) { + logError("error %s IPv6 privacy extensions: %s", + (enabled ? "enabling" : "disabling"), e); + return false; + } + return true; + } + + /** + * Set IPv6 address generation mode on the interface. + * + * <p>IPv6 should be disabled before changing the mode. + */ + public boolean setIPv6AddrGenModeIfSupported(int mode) { + try { + mNetd.setIPv6AddrGenMode(mIfName, mode); + } catch (RemoteException e) { + logError("Unable to set IPv6 addrgen mode: %s", e); + return false; + } catch (ServiceSpecificException e) { + if (e.errorCode != OsConstants.EOPNOTSUPP) { + logError("Unable to set IPv6 addrgen mode: %s", e); + return false; + } + } + return true; + } + + /** + * Add an address to the interface. + */ + public boolean addAddress(LinkAddress addr) { + return addAddress(addr.getAddress(), addr.getPrefixLength()); + } + + /** + * Add an address to the interface. + */ + public boolean addAddress(InetAddress ip, int prefixLen) { + try { + mNetd.interfaceAddAddress(mIfName, ip.getHostAddress(), prefixLen); + } catch (ServiceSpecificException | RemoteException e) { + logError("failed to add %s/%d: %s", ip, prefixLen, e); + return false; + } + return true; + } + + /** + * Remove an address from the interface. + */ + public boolean removeAddress(InetAddress ip, int prefixLen) { + try { + mNetd.interfaceDelAddress(mIfName, ip.getHostAddress(), prefixLen); + } catch (ServiceSpecificException | RemoteException e) { + logError("failed to remove %s/%d: %s", ip, prefixLen, e); + return false; + } + return true; + } + + /** + * Remove all addresses from the interface. + */ + public boolean clearAllAddresses() { + try { + mNetd.interfaceClearAddrs(mIfName); + } catch (Exception e) { + logError("Failed to clear addresses: %s", e); + return false; + } + return true; + } + + private void logError(String fmt, Object... args) { + mLog.e(String.format(fmt, args)); + } +} diff --git a/staticlibs/device/com/android/net/module/util/ip/IpNeighborMonitor.java b/staticlibs/device/com/android/net/module/util/ip/IpNeighborMonitor.java new file mode 100644 index 0000000000000000000000000000000000000000..e88e7f0c92c57560e934ef2eed74d9ada428cd4e --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/ip/IpNeighborMonitor.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.ip; + +import static android.system.OsConstants.NETLINK_ROUTE; + +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_DELNEIGH; +import static com.android.net.module.util.netlink.NetlinkConstants.hexify; +import static com.android.net.module.util.netlink.NetlinkConstants.stringForNlMsgType; + +import android.annotation.NonNull; +import android.net.MacAddress; +import android.os.Handler; +import android.system.ErrnoException; +import android.system.OsConstants; +import android.util.Log; + +import com.android.net.module.util.SharedLog; +import com.android.net.module.util.netlink.NetlinkMessage; +import com.android.net.module.util.netlink.NetlinkUtils; +import com.android.net.module.util.netlink.RtNetlinkNeighborMessage; +import com.android.net.module.util.netlink.StructNdMsg; + +import java.net.InetAddress; +import java.util.StringJoiner; + +/** + * IpNeighborMonitor. + * + * Monitors the kernel rtnetlink neighbor notifications and presents to callers + * NeighborEvents describing each event. Callers can provide a consumer instance + * to both filter (e.g. by interface index and IP address) and handle the + * generated NeighborEvents. + * + * @hide + */ +public class IpNeighborMonitor extends NetlinkMonitor { + private static final String TAG = IpNeighborMonitor.class.getSimpleName(); + private static final boolean DBG = false; + private static final boolean VDBG = false; + + /** + * Make the kernel perform neighbor reachability detection (IPv4 ARP or IPv6 ND) + * for the given IP address on the specified interface index. + * + * @return 0 if the request was successfully passed to the kernel; otherwise return + * a non-zero error code. + */ + public static int startKernelNeighborProbe(int ifIndex, InetAddress ip) { + final String msgSnippet = "probing ip=" + ip.getHostAddress() + "%" + ifIndex; + if (DBG) Log.d(TAG, msgSnippet); + + final byte[] msg = RtNetlinkNeighborMessage.newNewNeighborMessage( + 1, ip, StructNdMsg.NUD_PROBE, ifIndex, null); + + try { + NetlinkUtils.sendOneShotKernelMessage(NETLINK_ROUTE, msg); + } catch (ErrnoException e) { + Log.e(TAG, "Error " + msgSnippet + ": " + e); + return -e.errno; + } + + return 0; + } + + /** + * An event about a neighbor. + */ + public static class NeighborEvent { + public final long elapsedMs; + public final short msgType; + public final int ifindex; + @NonNull + public final InetAddress ip; + public final short nudState; + @NonNull + public final MacAddress macAddr; + + public NeighborEvent(long elapsedMs, short msgType, int ifindex, @NonNull InetAddress ip, + short nudState, @NonNull MacAddress macAddr) { + this.elapsedMs = elapsedMs; + this.msgType = msgType; + this.ifindex = ifindex; + this.ip = ip; + this.nudState = nudState; + this.macAddr = macAddr; + } + + boolean isConnected() { + return (msgType != RTM_DELNEIGH) && StructNdMsg.isNudStateConnected(nudState); + } + + public boolean isValid() { + return (msgType != RTM_DELNEIGH) && StructNdMsg.isNudStateValid(nudState); + } + + @Override + public String toString() { + final StringJoiner j = new StringJoiner(",", "NeighborEvent{", "}"); + return j.add("@" + elapsedMs) + .add(stringForNlMsgType(msgType, NETLINK_ROUTE)) + .add("if=" + ifindex) + .add(ip.getHostAddress()) + .add(StructNdMsg.stringForNudState(nudState)) + .add("[" + macAddr + "]") + .toString(); + } + } + + /** + * A client that consumes NeighborEvent instances. + * Implement this to listen to neighbor events. + */ + public interface NeighborEventConsumer { + // Every neighbor event received on the netlink socket is passed in + // here. Subclasses should filter for events of interest. + /** + * Consume a neighbor event + * @param event the event + */ + void accept(NeighborEvent event); + } + + private final NeighborEventConsumer mConsumer; + + public IpNeighborMonitor(Handler h, SharedLog log, NeighborEventConsumer cb) { + super(h, log, TAG, NETLINK_ROUTE, OsConstants.RTMGRP_NEIGH); + mConsumer = (cb != null) ? cb : (event) -> { /* discard */ }; + } + + @Override + public void processNetlinkMessage(NetlinkMessage nlMsg, final long whenMs) { + if (!(nlMsg instanceof RtNetlinkNeighborMessage)) { + mLog.e("non-rtnetlink neighbor msg: " + nlMsg); + return; + } + + final RtNetlinkNeighborMessage neighMsg = (RtNetlinkNeighborMessage) nlMsg; + final short msgType = neighMsg.getHeader().nlmsg_type; + final StructNdMsg ndMsg = neighMsg.getNdHeader(); + if (ndMsg == null) { + mLog.e("RtNetlinkNeighborMessage without ND message header!"); + return; + } + + final int ifindex = ndMsg.ndm_ifindex; + final InetAddress destination = neighMsg.getDestination(); + final short nudState = + (msgType == RTM_DELNEIGH) + ? StructNdMsg.NUD_NONE + : ndMsg.ndm_state; + + final NeighborEvent event = new NeighborEvent( + whenMs, msgType, ifindex, destination, nudState, + getMacAddress(neighMsg.getLinkLayerAddress())); + + if (VDBG) { + Log.d(TAG, neighMsg.toString()); + } + if (DBG) { + Log.d(TAG, event.toString()); + } + + mConsumer.accept(event); + } + + private static MacAddress getMacAddress(byte[] linkLayerAddress) { + if (linkLayerAddress != null) { + try { + return MacAddress.fromBytes(linkLayerAddress); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Failed to parse link-layer address: " + hexify(linkLayerAddress)); + } + } + + return null; + } +} diff --git a/staticlibs/device/com/android/net/module/util/ip/NetlinkMonitor.java b/staticlibs/device/com/android/net/module/util/ip/NetlinkMonitor.java new file mode 100644 index 0000000000000000000000000000000000000000..f882483da812724032ea35218bde4c345e29c0ae --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/ip/NetlinkMonitor.java @@ -0,0 +1,188 @@ +/* + * 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. + */ + +package com.android.net.module.util.ip; + +import static android.system.OsConstants.AF_NETLINK; +import static android.system.OsConstants.ENOBUFS; +import static android.system.OsConstants.SOCK_DGRAM; +import static android.system.OsConstants.SOCK_NONBLOCK; +import static android.system.OsConstants.SOL_SOCKET; +import static android.system.OsConstants.SO_RCVBUF; + +import static com.android.net.module.util.SocketUtils.closeSocketQuietly; +import static com.android.net.module.util.SocketUtils.makeNetlinkSocketAddress; +import static com.android.net.module.util.netlink.NetlinkConstants.hexify; + +import android.annotation.NonNull; +import android.os.Handler; +import android.os.SystemClock; +import android.system.ErrnoException; +import android.system.Os; +import android.util.Log; + +import com.android.net.module.util.PacketReader; +import com.android.net.module.util.SharedLog; +import com.android.net.module.util.netlink.NetlinkErrorMessage; +import com.android.net.module.util.netlink.NetlinkMessage; +import com.android.net.module.util.netlink.NetlinkUtils; + +import java.io.FileDescriptor; +import java.net.SocketAddress; +import java.net.SocketException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * A simple base class to listen for netlink broadcasts. + * + * Opens a netlink socket of the given family and binds to the specified groups. Polls the socket + * from the event loop of the passed-in {@link Handler}, and calls the subclass-defined + * {@link #processNetlinkMessage} method on the handler thread for each netlink message that + * arrives. Currently ignores all netlink errors. + * @hide + */ +public class NetlinkMonitor extends PacketReader { + protected final SharedLog mLog; + protected final String mTag; + private final int mFamily; + private final int mBindGroups; + private final int mSockRcvbufSize; + + private static final boolean DBG = false; + + // Default socket receive buffer size. This means the specific buffer size is not set. + private static final int DEFAULT_SOCKET_RECV_BUFSIZE = -1; + + /** + * Constructs a new {@code NetlinkMonitor} instance. + * + * @param h The Handler on which to poll for messages and on which to call + * {@link #processNetlinkMessage}. + * @param log A SharedLog to log to. + * @param tag The log tag to use for log messages. + * @param family the Netlink socket family to, e.g., {@code NETLINK_ROUTE}. + * @param bindGroups the netlink groups to bind to. + * @param sockRcvbufSize the specific socket receive buffer size in bytes. -1 means that don't + * set the specific socket receive buffer size in #createFd and use the default value in + * /proc/sys/net/core/rmem_default file. See SO_RCVBUF in man-pages/socket. + */ + public NetlinkMonitor(@NonNull Handler h, @NonNull SharedLog log, @NonNull String tag, + int family, int bindGroups, int sockRcvbufSize) { + super(h, NetlinkUtils.DEFAULT_RECV_BUFSIZE); + mLog = log.forSubComponent(tag); + mTag = tag; + mFamily = family; + mBindGroups = bindGroups; + mSockRcvbufSize = sockRcvbufSize; + } + + public NetlinkMonitor(@NonNull Handler h, @NonNull SharedLog log, @NonNull String tag, + int family, int bindGroups) { + this(h, log, tag, family, bindGroups, DEFAULT_SOCKET_RECV_BUFSIZE); + } + + @Override + protected FileDescriptor createFd() { + FileDescriptor fd = null; + + try { + fd = Os.socket(AF_NETLINK, SOCK_DGRAM | SOCK_NONBLOCK, mFamily); + if (mSockRcvbufSize != DEFAULT_SOCKET_RECV_BUFSIZE) { + try { + Os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, mSockRcvbufSize); + } catch (ErrnoException e) { + Log.wtf(mTag, "Failed to set SO_RCVBUF to " + mSockRcvbufSize, e); + } + } + Os.bind(fd, makeNetlinkSocketAddress(0, mBindGroups)); + NetlinkUtils.connectSocketToNetlink(fd); + + if (DBG) { + final SocketAddress nlAddr = Os.getsockname(fd); + Log.d(mTag, "bound to sockaddr_nl{" + nlAddr.toString() + "}"); + } + } catch (ErrnoException | SocketException e) { + logError("Failed to create rtnetlink socket", e); + closeSocketQuietly(fd); + return null; + } + + return fd; + } + + @Override + protected void handlePacket(byte[] recvbuf, int length) { + final long whenMs = SystemClock.elapsedRealtime(); + final ByteBuffer byteBuffer = ByteBuffer.wrap(recvbuf, 0, length); + byteBuffer.order(ByteOrder.nativeOrder()); + + while (byteBuffer.remaining() > 0) { + try { + final int position = byteBuffer.position(); + final NetlinkMessage nlMsg = NetlinkMessage.parse(byteBuffer, mFamily); + if (nlMsg == null || nlMsg.getHeader() == null) { + byteBuffer.position(position); + mLog.e("unparsable netlink msg: " + hexify(byteBuffer)); + break; + } + + if (nlMsg instanceof NetlinkErrorMessage) { + mLog.e("netlink error: " + nlMsg); + continue; + } + + processNetlinkMessage(nlMsg, whenMs); + } catch (Exception e) { + mLog.e("Error handling netlink message", e); + } + } + } + + @Override + protected void logError(String msg, Exception e) { + mLog.e(msg, e); + } + + // Ignoring ENOBUFS may miss any important netlink messages, there are some messages which + // cannot be recovered by dumping current state once missed since kernel doesn't keep state + // for it. In addition, dumping current state will not result in any RTM_DELxxx messages, so + // reconstructing current state from a dump will be difficult. However, for those netlink + // messages don't cause any state changes, e.g. RTM_NEWLINK with current link state, maybe + // it's okay to ignore them, because these netlink messages won't cause any changes on the + // LinkProperties. Given the above trade-offs, try to ignore ENOBUFS and that's similar to + // what netd does today. + // + // TODO: log metrics when ENOBUFS occurs, or even force a disconnect, it will help see how + // often this error occurs on fields with the associated socket receive buffer size. + @Override + protected boolean handleReadError(ErrnoException e) { + logError("readPacket error: ", e); + if (e.errno == ENOBUFS) { + Log.wtf(mTag, "Errno: ENOBUFS"); + return false; + } + return true; + } + + /** + * Processes one netlink message. Must be overridden by subclasses. + * @param nlMsg the message to process. + * @param whenMs the timestamp, as measured by {@link SystemClock#elapsedRealtime}, when the + * message was received. + */ + protected void processNetlinkMessage(NetlinkMessage nlMsg, long whenMs) { } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/ConntrackMessage.java b/staticlibs/device/com/android/net/module/util/netlink/ConntrackMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..dfed3efb6a01bf42fd951857d3b21b57b22d7148 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/ConntrackMessage.java @@ -0,0 +1,560 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import static android.system.OsConstants.IPPROTO_TCP; +import static android.system.OsConstants.IPPROTO_UDP; + +import static com.android.net.module.util.netlink.StructNlAttr.findNextAttrOfType; +import static com.android.net.module.util.netlink.StructNlAttr.makeNestedType; +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_ACK; +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REPLACE; +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST; + +import static java.nio.ByteOrder.BIG_ENDIAN; + +import android.system.OsConstants; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Objects; + +/** + * A NetlinkMessage subclass for netlink conntrack messages. + * + * see also: <linux_src>/include/uapi/linux/netfilter/nfnetlink_conntrack.h + * + * @hide + */ +public class ConntrackMessage extends NetlinkMessage { + public static final int STRUCT_SIZE = StructNlMsgHdr.STRUCT_SIZE + StructNfGenMsg.STRUCT_SIZE; + + // enum ctattr_type + public static final short CTA_TUPLE_ORIG = 1; + public static final short CTA_TUPLE_REPLY = 2; + public static final short CTA_STATUS = 3; + public static final short CTA_TIMEOUT = 7; + + // enum ctattr_tuple + public static final short CTA_TUPLE_IP = 1; + public static final short CTA_TUPLE_PROTO = 2; + + // enum ctattr_ip + public static final short CTA_IP_V4_SRC = 1; + public static final short CTA_IP_V4_DST = 2; + + // enum ctattr_l4proto + public static final short CTA_PROTO_NUM = 1; + public static final short CTA_PROTO_SRC_PORT = 2; + public static final short CTA_PROTO_DST_PORT = 3; + + // enum ip_conntrack_status + public static final int IPS_EXPECTED = 0x00000001; + public static final int IPS_SEEN_REPLY = 0x00000002; + public static final int IPS_ASSURED = 0x00000004; + public static final int IPS_CONFIRMED = 0x00000008; + public static final int IPS_SRC_NAT = 0x00000010; + public static final int IPS_DST_NAT = 0x00000020; + public static final int IPS_SEQ_ADJUST = 0x00000040; + public static final int IPS_SRC_NAT_DONE = 0x00000080; + public static final int IPS_DST_NAT_DONE = 0x00000100; + public static final int IPS_DYING = 0x00000200; + public static final int IPS_FIXED_TIMEOUT = 0x00000400; + public static final int IPS_TEMPLATE = 0x00000800; + public static final int IPS_UNTRACKED = 0x00001000; + public static final int IPS_HELPER = 0x00002000; + public static final int IPS_OFFLOAD = 0x00004000; + public static final int IPS_HW_OFFLOAD = 0x00008000; + + // ip_conntrack_status mask + // Interesting on the NAT conntrack session which has already seen two direction traffic. + // TODO: Probably IPS_{SRC, DST}_NAT_DONE are also interesting. + public static final int ESTABLISHED_MASK = IPS_CONFIRMED | IPS_ASSURED | IPS_SEEN_REPLY + | IPS_SRC_NAT; + // Interesting on the established NAT conntrack session which is dying. + public static final int DYING_MASK = ESTABLISHED_MASK | IPS_DYING; + + /** + * A tuple for the conntrack connection information. + * + * see also CTA_TUPLE_ORIG and CTA_TUPLE_REPLY. + */ + public static class Tuple { + public final Inet4Address srcIp; + public final Inet4Address dstIp; + + // Both port and protocol number are unsigned numbers stored in signed integers, and that + // callers that want to compare them to integers should either cast those integers, or + // convert them to unsigned using Byte.toUnsignedInt() and Short.toUnsignedInt(). + public final short srcPort; + public final short dstPort; + public final byte protoNum; + + public Tuple(TupleIpv4 ip, TupleProto proto) { + this.srcIp = ip.src; + this.dstIp = ip.dst; + this.srcPort = proto.srcPort; + this.dstPort = proto.dstPort; + this.protoNum = proto.protoNum; + } + + @Override + @VisibleForTesting + public boolean equals(Object o) { + if (!(o instanceof Tuple)) return false; + Tuple that = (Tuple) o; + return Objects.equals(this.srcIp, that.srcIp) + && Objects.equals(this.dstIp, that.dstIp) + && this.srcPort == that.srcPort + && this.dstPort == that.dstPort + && this.protoNum == that.protoNum; + } + + @Override + public int hashCode() { + return Objects.hash(srcIp, dstIp, srcPort, dstPort, protoNum); + } + + @Override + public String toString() { + final String srcIpStr = (srcIp == null) ? "null" : srcIp.getHostAddress(); + final String dstIpStr = (dstIp == null) ? "null" : dstIp.getHostAddress(); + final String protoStr = NetlinkConstants.stringForProtocol(protoNum); + + return "Tuple{" + + protoStr + ": " + + srcIpStr + ":" + Short.toUnsignedInt(srcPort) + " -> " + + dstIpStr + ":" + Short.toUnsignedInt(dstPort) + + "}"; + } + } + + /** + * A tuple for the conntrack connection address. + * + * see also CTA_TUPLE_IP. + */ + public static class TupleIpv4 { + public final Inet4Address src; + public final Inet4Address dst; + + public TupleIpv4(Inet4Address src, Inet4Address dst) { + this.src = src; + this.dst = dst; + } + } + + /** + * A tuple for the conntrack connection protocol. + * + * see also CTA_TUPLE_PROTO. + */ + public static class TupleProto { + public final byte protoNum; + public final short srcPort; + public final short dstPort; + + public TupleProto(byte protoNum, short srcPort, short dstPort) { + this.protoNum = protoNum; + this.srcPort = srcPort; + this.dstPort = dstPort; + } + } + + /** + * Create a netlink message to refresh IPv4 conntrack entry timeout. + */ + public static byte[] newIPv4TimeoutUpdateRequest( + int proto, Inet4Address src, int sport, Inet4Address dst, int dport, int timeoutSec) { + // *** STYLE WARNING *** + // + // Code below this point uses extra block indentation to highlight the + // packing of nested tuple netlink attribute types. + final StructNlAttr ctaTupleOrig = new StructNlAttr(CTA_TUPLE_ORIG, + new StructNlAttr(CTA_TUPLE_IP, + new StructNlAttr(CTA_IP_V4_SRC, src), + new StructNlAttr(CTA_IP_V4_DST, dst)), + new StructNlAttr(CTA_TUPLE_PROTO, + new StructNlAttr(CTA_PROTO_NUM, (byte) proto), + new StructNlAttr(CTA_PROTO_SRC_PORT, (short) sport, BIG_ENDIAN), + new StructNlAttr(CTA_PROTO_DST_PORT, (short) dport, BIG_ENDIAN))); + + final StructNlAttr ctaTimeout = new StructNlAttr(CTA_TIMEOUT, timeoutSec, BIG_ENDIAN); + + final int payloadLength = ctaTupleOrig.getAlignedLength() + ctaTimeout.getAlignedLength(); + final byte[] bytes = new byte[STRUCT_SIZE + payloadLength]; + final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + byteBuffer.order(ByteOrder.nativeOrder()); + + final ConntrackMessage ctmsg = new ConntrackMessage(); + ctmsg.mHeader.nlmsg_len = bytes.length; + ctmsg.mHeader.nlmsg_type = (NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8) + | NetlinkConstants.IPCTNL_MSG_CT_NEW; + ctmsg.mHeader.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE; + ctmsg.mHeader.nlmsg_seq = 1; + ctmsg.pack(byteBuffer); + + ctaTupleOrig.pack(byteBuffer); + ctaTimeout.pack(byteBuffer); + + return bytes; + } + + /** + * Parses a netfilter conntrack message from a {@link ByteBuffer}. + * + * @param header the netlink message header. + * @param byteBuffer The buffer from which to parse the netfilter conntrack message. + * @return the parsed netfilter conntrack message, or {@code null} if the netfilter conntrack + * message could not be parsed successfully (for example, if it was truncated). + */ + @Nullable + public static ConntrackMessage parse(@NonNull StructNlMsgHdr header, + @NonNull ByteBuffer byteBuffer) { + // Just build the netlink header and netfilter header for now and pretend the whole message + // was consumed. + // TODO: Parse the conntrack attributes. + final StructNfGenMsg nfGenMsg = StructNfGenMsg.parse(byteBuffer); + if (nfGenMsg == null) { + return null; + } + + final int baseOffset = byteBuffer.position(); + StructNlAttr nlAttr = findNextAttrOfType(CTA_STATUS, byteBuffer); + int status = 0; + if (nlAttr != null) { + status = nlAttr.getValueAsBe32(0); + } + + byteBuffer.position(baseOffset); + nlAttr = findNextAttrOfType(CTA_TIMEOUT, byteBuffer); + int timeoutSec = 0; + if (nlAttr != null) { + timeoutSec = nlAttr.getValueAsBe32(0); + } + + byteBuffer.position(baseOffset); + nlAttr = findNextAttrOfType(makeNestedType(CTA_TUPLE_ORIG), byteBuffer); + Tuple tupleOrig = null; + if (nlAttr != null) { + tupleOrig = parseTuple(nlAttr.getValueAsByteBuffer()); + } + + byteBuffer.position(baseOffset); + nlAttr = findNextAttrOfType(makeNestedType(CTA_TUPLE_REPLY), byteBuffer); + Tuple tupleReply = null; + if (nlAttr != null) { + tupleReply = parseTuple(nlAttr.getValueAsByteBuffer()); + } + + // Advance to the end of the message. + byteBuffer.position(baseOffset); + final int kMinConsumed = StructNlMsgHdr.STRUCT_SIZE + StructNfGenMsg.STRUCT_SIZE; + final int kAdditionalSpace = NetlinkConstants.alignedLengthOf( + header.nlmsg_len - kMinConsumed); + if (byteBuffer.remaining() < kAdditionalSpace) { + return null; + } + byteBuffer.position(baseOffset + kAdditionalSpace); + + return new ConntrackMessage(header, nfGenMsg, tupleOrig, tupleReply, status, timeoutSec); + } + + /** + * Parses a conntrack tuple from a {@link ByteBuffer}. + * + * The attribute parsing is interesting on: + * - CTA_TUPLE_IP + * CTA_IP_V4_SRC + * CTA_IP_V4_DST + * - CTA_TUPLE_PROTO + * CTA_PROTO_NUM + * CTA_PROTO_SRC_PORT + * CTA_PROTO_DST_PORT + * + * Assume that the minimum size is the sum of CTA_TUPLE_IP (size: 20) and CTA_TUPLE_PROTO + * (size: 28). Here is an example for an expected CTA_TUPLE_ORIG message in raw data: + * +--------------------------------------------------------------------------------------+ + * | CTA_TUPLE_ORIG | + * +--------------------------+-----------------------------------------------------------+ + * | 1400 | nla_len = 20 | + * | 0180 | nla_type = nested CTA_TUPLE_IP | + * | 0800 0100 C0A8500C | nla_type=CTA_IP_V4_SRC, ip=192.168.80.12 | + * | 0800 0200 8C700874 | nla_type=CTA_IP_V4_DST, ip=140.112.8.116 | + * | 1C00 | nla_len = 28 | + * | 0280 | nla_type = nested CTA_TUPLE_PROTO | + * | 0500 0100 06 000000 | nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6) | + * | 0600 0200 F3F1 0000 | nla_type=CTA_PROTO_SRC_PORT, port=62449 (big endian) | + * | 0600 0300 01BB 0000 | nla_type=CTA_PROTO_DST_PORT, port=433 (big endian) | + * +--------------------------+-----------------------------------------------------------+ + * + * The position of the byte buffer doesn't set to the end when the function returns. It is okay + * because the caller ConntrackMessage#parse has passed a copy which is used for this parser + * only. Moreover, the parser behavior is the same as other existing netlink struct class + * parser. Ex: StructInetDiagMsg#parse. + */ + @Nullable + private static Tuple parseTuple(@Nullable ByteBuffer byteBuffer) { + if (byteBuffer == null) return null; + + TupleIpv4 tupleIpv4 = null; + TupleProto tupleProto = null; + + final int baseOffset = byteBuffer.position(); + StructNlAttr nlAttr = findNextAttrOfType(makeNestedType(CTA_TUPLE_IP), byteBuffer); + if (nlAttr != null) { + tupleIpv4 = parseTupleIpv4(nlAttr.getValueAsByteBuffer()); + } + if (tupleIpv4 == null) return null; + + byteBuffer.position(baseOffset); + nlAttr = findNextAttrOfType(makeNestedType(CTA_TUPLE_PROTO), byteBuffer); + if (nlAttr != null) { + tupleProto = parseTupleProto(nlAttr.getValueAsByteBuffer()); + } + if (tupleProto == null) return null; + + return new Tuple(tupleIpv4, tupleProto); + } + + @Nullable + private static Inet4Address castToInet4Address(@Nullable InetAddress address) { + if (address == null || !(address instanceof Inet4Address)) return null; + return (Inet4Address) address; + } + + @Nullable + private static TupleIpv4 parseTupleIpv4(@Nullable ByteBuffer byteBuffer) { + if (byteBuffer == null) return null; + + Inet4Address src = null; + Inet4Address dst = null; + + final int baseOffset = byteBuffer.position(); + StructNlAttr nlAttr = findNextAttrOfType(CTA_IP_V4_SRC, byteBuffer); + if (nlAttr != null) { + src = castToInet4Address(nlAttr.getValueAsInetAddress()); + } + if (src == null) return null; + + byteBuffer.position(baseOffset); + nlAttr = findNextAttrOfType(CTA_IP_V4_DST, byteBuffer); + if (nlAttr != null) { + dst = castToInet4Address(nlAttr.getValueAsInetAddress()); + } + if (dst == null) return null; + + return new TupleIpv4(src, dst); + } + + @Nullable + private static TupleProto parseTupleProto(@Nullable ByteBuffer byteBuffer) { + if (byteBuffer == null) return null; + + byte protoNum = 0; + short srcPort = 0; + short dstPort = 0; + + final int baseOffset = byteBuffer.position(); + StructNlAttr nlAttr = findNextAttrOfType(CTA_PROTO_NUM, byteBuffer); + if (nlAttr != null) { + protoNum = nlAttr.getValueAsByte((byte) 0); + } + if (!(protoNum == IPPROTO_TCP || protoNum == IPPROTO_UDP)) return null; + + byteBuffer.position(baseOffset); + nlAttr = StructNlAttr.findNextAttrOfType(CTA_PROTO_SRC_PORT, byteBuffer); + if (nlAttr != null) { + srcPort = nlAttr.getValueAsBe16((short) 0); + } + if (srcPort == 0) return null; + + byteBuffer.position(baseOffset); + nlAttr = StructNlAttr.findNextAttrOfType(CTA_PROTO_DST_PORT, byteBuffer); + if (nlAttr != null) { + dstPort = nlAttr.getValueAsBe16((short) 0); + } + if (dstPort == 0) return null; + + return new TupleProto(protoNum, srcPort, dstPort); + } + + /** + * Netfilter header. + */ + public final StructNfGenMsg nfGenMsg; + /** + * Original direction conntrack tuple. + * + * The tuple is determined by the parsed attribute value CTA_TUPLE_ORIG, or null if the + * tuple could not be parsed successfully (for example, if it was truncated or absent). + */ + @Nullable + public final Tuple tupleOrig; + /** + * Reply direction conntrack tuple. + * + * The tuple is determined by the parsed attribute value CTA_TUPLE_REPLY, or null if the + * tuple could not be parsed successfully (for example, if it was truncated or absent). + */ + @Nullable + public final Tuple tupleReply; + /** + * Connection status. A bitmask of ip_conntrack_status enum flags. + * + * The status is determined by the parsed attribute value CTA_STATUS, or 0 if the status could + * not be parsed successfully (for example, if it was truncated or absent). For the message + * from kernel, the valid status is non-zero. For the message from user space, the status may + * be 0 (absent). + */ + public final int status; + /** + * Conntrack timeout. + * + * The timeout is determined by the parsed attribute value CTA_TIMEOUT, or 0 if the timeout + * could not be parsed successfully (for example, if it was truncated or absent). For + * IPCTNL_MSG_CT_NEW event, the valid timeout is non-zero. For IPCTNL_MSG_CT_DELETE event, the + * timeout is 0 (absent). + */ + public final int timeoutSec; + + private ConntrackMessage() { + super(new StructNlMsgHdr()); + nfGenMsg = new StructNfGenMsg((byte) OsConstants.AF_INET); + + // This constructor is only used by #newIPv4TimeoutUpdateRequest which doesn't use these + // data member for packing message. Simply fill them to null or 0. + tupleOrig = null; + tupleReply = null; + status = 0; + timeoutSec = 0; + } + + private ConntrackMessage(@NonNull StructNlMsgHdr header, @NonNull StructNfGenMsg nfGenMsg, + @Nullable Tuple tupleOrig, @Nullable Tuple tupleReply, int status, int timeoutSec) { + super(header); + this.nfGenMsg = nfGenMsg; + this.tupleOrig = tupleOrig; + this.tupleReply = tupleReply; + this.status = status; + this.timeoutSec = timeoutSec; + } + + /** + * Write a netfilter message to {@link ByteBuffer}. + */ + public void pack(ByteBuffer byteBuffer) { + mHeader.pack(byteBuffer); + nfGenMsg.pack(byteBuffer); + } + + public short getMessageType() { + return (short) (getHeader().nlmsg_type & ~(NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8)); + } + + /** + * Convert an ip conntrack status to a string. + */ + public static String stringForIpConntrackStatus(int flags) { + final StringBuilder sb = new StringBuilder(); + + if ((flags & IPS_EXPECTED) != 0) { + sb.append("IPS_EXPECTED"); + } + if ((flags & IPS_SEEN_REPLY) != 0) { + if (sb.length() > 0) sb.append("|"); + sb.append("IPS_SEEN_REPLY"); + } + if ((flags & IPS_ASSURED) != 0) { + if (sb.length() > 0) sb.append("|"); + sb.append("IPS_ASSURED"); + } + if ((flags & IPS_CONFIRMED) != 0) { + if (sb.length() > 0) sb.append("|"); + sb.append("IPS_CONFIRMED"); + } + if ((flags & IPS_SRC_NAT) != 0) { + if (sb.length() > 0) sb.append("|"); + sb.append("IPS_SRC_NAT"); + } + if ((flags & IPS_DST_NAT) != 0) { + if (sb.length() > 0) sb.append("|"); + sb.append("IPS_DST_NAT"); + } + if ((flags & IPS_SEQ_ADJUST) != 0) { + if (sb.length() > 0) sb.append("|"); + sb.append("IPS_SEQ_ADJUST"); + } + if ((flags & IPS_SRC_NAT_DONE) != 0) { + if (sb.length() > 0) sb.append("|"); + sb.append("IPS_SRC_NAT_DONE"); + } + if ((flags & IPS_DST_NAT_DONE) != 0) { + if (sb.length() > 0) sb.append("|"); + sb.append("IPS_DST_NAT_DONE"); + } + if ((flags & IPS_DYING) != 0) { + if (sb.length() > 0) sb.append("|"); + sb.append("IPS_DYING"); + } + if ((flags & IPS_FIXED_TIMEOUT) != 0) { + if (sb.length() > 0) sb.append("|"); + sb.append("IPS_FIXED_TIMEOUT"); + } + if ((flags & IPS_TEMPLATE) != 0) { + if (sb.length() > 0) sb.append("|"); + sb.append("IPS_TEMPLATE"); + } + if ((flags & IPS_UNTRACKED) != 0) { + if (sb.length() > 0) sb.append("|"); + sb.append("IPS_UNTRACKED"); + } + if ((flags & IPS_HELPER) != 0) { + if (sb.length() > 0) sb.append("|"); + sb.append("IPS_HELPER"); + } + if ((flags & IPS_OFFLOAD) != 0) { + if (sb.length() > 0) sb.append("|"); + sb.append("IPS_OFFLOAD"); + } + if ((flags & IPS_HW_OFFLOAD) != 0) { + if (sb.length() > 0) sb.append("|"); + sb.append("IPS_HW_OFFLOAD"); + } + return sb.toString(); + } + + @Override + public String toString() { + return "ConntrackMessage{" + + "nlmsghdr{" + + (mHeader == null ? "" : mHeader.toString(OsConstants.NETLINK_NETFILTER)) + + "}, " + + "nfgenmsg{" + nfGenMsg + "}, " + + "tuple_orig{" + tupleOrig + "}, " + + "tuple_reply{" + tupleReply + "}, " + + "status{" + status + "(" + stringForIpConntrackStatus(status) + ")" + "}, " + + "timeout_sec{" + Integer.toUnsignedLong(timeoutSec) + "}" + + "}"; + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java b/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..f8b47164a66aef1f8e7d05d306484a217a1fc202 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java @@ -0,0 +1,505 @@ +/* + * 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. + */ + +package com.android.net.module.util.netlink; + +import static android.os.Process.INVALID_UID; +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; +import static android.system.OsConstants.ENOENT; +import static android.system.OsConstants.IPPROTO_TCP; +import static android.system.OsConstants.IPPROTO_UDP; +import static android.system.OsConstants.NETLINK_INET_DIAG; + +import static com.android.net.module.util.netlink.NetlinkConstants.NLMSG_DONE; +import static com.android.net.module.util.netlink.NetlinkConstants.SOCK_DESTROY; +import static com.android.net.module.util.netlink.NetlinkConstants.SOCK_DIAG_BY_FAMILY; +import static com.android.net.module.util.netlink.NetlinkConstants.hexify; +import static com.android.net.module.util.netlink.NetlinkConstants.stringForAddressFamily; +import static com.android.net.module.util.netlink.NetlinkConstants.stringForProtocol; +import static com.android.net.module.util.netlink.NetlinkUtils.DEFAULT_RECV_BUFSIZE; +import static com.android.net.module.util.netlink.NetlinkUtils.IO_TIMEOUT_MS; +import static com.android.net.module.util.netlink.NetlinkUtils.TCP_ALIVE_STATE_FILTER; +import static com.android.net.module.util.netlink.NetlinkUtils.connectSocketToNetlink; +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP; +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST; + +import android.net.util.SocketUtils; +import android.os.Process; +import android.os.SystemClock; +import android.system.ErrnoException; +import android.util.Log; +import android.util.Range; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; + +/** + * A NetlinkMessage subclass for netlink inet_diag messages. + * + * see also: <linux_src>/include/uapi/linux/inet_diag.h + * + * @hide + */ +public class InetDiagMessage extends NetlinkMessage { + public static final String TAG = "InetDiagMessage"; + private static final int TIMEOUT_MS = 500; + + /** + * Construct an inet_diag_req_v2 message. This method will throw + * {@link IllegalArgumentException} if local and remote are not both null or both non-null. + */ + public static byte[] inetDiagReqV2(int protocol, InetSocketAddress local, + InetSocketAddress remote, int family, short flags) { + return inetDiagReqV2(protocol, local, remote, family, flags, 0 /* pad */, + 0 /* idiagExt */, StructInetDiagReqV2.INET_DIAG_REQ_V2_ALL_STATES); + } + + /** + * Construct an inet_diag_req_v2 message. This method will throw + * {@code IllegalArgumentException} if local and remote are not both null or both non-null. + * + * @param protocol the request protocol type. This should be set to one of IPPROTO_TCP, + * IPPROTO_UDP, or IPPROTO_UDPLITE. + * @param local local socket address of the target socket. This will be packed into a + * {@link StructInetDiagSockId}. Request to diagnose for all sockets if both of + * local or remote address is null. + * @param remote remote socket address of the target socket. This will be packed into a + * {@link StructInetDiagSockId}. Request to diagnose for all sockets if both of + * local or remote address is null. + * @param family the ip family of the request message. This should be set to either AF_INET or + * AF_INET6 for IPv4 or IPv6 sockets respectively. + * @param flags message flags. See <linux_src>/include/uapi/linux/netlink.h. + * @param pad for raw socket protocol specification. + * @param idiagExt a set of flags defining what kind of extended information to report. + * @param state a bit mask that defines a filter of socket states. + * + * @return bytes array representation of the message + */ + public static byte[] inetDiagReqV2(int protocol, @Nullable InetSocketAddress local, + @Nullable InetSocketAddress remote, int family, short flags, int pad, int idiagExt, + int state) throws IllegalArgumentException { + // Request for all sockets if no specific socket is requested. Specify the local and remote + // socket address information for target request socket. + if ((local == null) != (remote == null)) { + throw new IllegalArgumentException( + "Local and remote must be both null or both non-null"); + } + final StructInetDiagSockId id = ((local != null && remote != null) + ? new StructInetDiagSockId(local, remote) : null); + return inetDiagReqV2(protocol, id, family, + SOCK_DIAG_BY_FAMILY, flags, pad, idiagExt, state); + } + + /** + * Construct an inet_diag_req_v2 message. + * + * @param protocol the request protocol type. This should be set to one of IPPROTO_TCP, + * IPPROTO_UDP, or IPPROTO_UDPLITE. + * @param id inet_diag_sockid. See {@link StructInetDiagSockId} + * @param family the ip family of the request message. This should be set to either AF_INET or + * AF_INET6 for IPv4 or IPv6 sockets respectively. + * @param type message types. + * @param flags message flags. See <linux_src>/include/uapi/linux/netlink.h. + * @param pad for raw socket protocol specification. + * @param idiagExt a set of flags defining what kind of extended information to report. + * @param state a bit mask that defines a filter of socket states. + * @return bytes array representation of the message + */ + public static byte[] inetDiagReqV2(int protocol, @Nullable StructInetDiagSockId id, int family, + short type, short flags, int pad, int idiagExt, int state) { + final byte[] bytes = new byte[StructNlMsgHdr.STRUCT_SIZE + StructInetDiagReqV2.STRUCT_SIZE]; + final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + byteBuffer.order(ByteOrder.nativeOrder()); + + final StructNlMsgHdr nlMsgHdr = new StructNlMsgHdr(); + nlMsgHdr.nlmsg_len = bytes.length; + nlMsgHdr.nlmsg_type = type; + nlMsgHdr.nlmsg_flags = flags; + nlMsgHdr.pack(byteBuffer); + final StructInetDiagReqV2 inetDiagReqV2 = + new StructInetDiagReqV2(protocol, id, family, pad, idiagExt, state); + + inetDiagReqV2.pack(byteBuffer); + return bytes; + } + + public StructInetDiagMsg inetDiagMsg; + + @VisibleForTesting + public InetDiagMessage(@NonNull StructNlMsgHdr header) { + super(header); + inetDiagMsg = new StructInetDiagMsg(); + } + + /** + * Parse an inet_diag_req_v2 message from buffer. + */ + @Nullable + public static InetDiagMessage parse(@NonNull StructNlMsgHdr header, + @NonNull ByteBuffer byteBuffer) { + final InetDiagMessage msg = new InetDiagMessage(header); + msg.inetDiagMsg = StructInetDiagMsg.parse(byteBuffer); + if (msg.inetDiagMsg == null) { + return null; + } + return msg; + } + + private static void closeSocketQuietly(final FileDescriptor fd) { + try { + SocketUtils.closeSocket(fd); + } catch (IOException ignored) { + } + } + + private static int lookupUidByFamily(int protocol, InetSocketAddress local, + InetSocketAddress remote, int family, short flags, + FileDescriptor fd) + throws ErrnoException, InterruptedIOException { + byte[] msg = inetDiagReqV2(protocol, local, remote, family, flags); + NetlinkUtils.sendMessage(fd, msg, 0, msg.length, TIMEOUT_MS); + ByteBuffer response = NetlinkUtils.recvMessage(fd, DEFAULT_RECV_BUFSIZE, TIMEOUT_MS); + + final NetlinkMessage nlMsg = NetlinkMessage.parse(response, NETLINK_INET_DIAG); + if (nlMsg == null) { + return INVALID_UID; + } + final StructNlMsgHdr hdr = nlMsg.getHeader(); + if (hdr.nlmsg_type == NetlinkConstants.NLMSG_DONE) { + return INVALID_UID; + } + if (nlMsg instanceof InetDiagMessage) { + return ((InetDiagMessage) nlMsg).inetDiagMsg.idiag_uid; + } + return INVALID_UID; + } + + private static final int[] FAMILY = {AF_INET6, AF_INET}; + + private static int lookupUid(int protocol, InetSocketAddress local, + InetSocketAddress remote, FileDescriptor fd) + throws ErrnoException, InterruptedIOException { + int uid; + + for (int family : FAMILY) { + /** + * For exact match lookup, swap local and remote for UDP lookups due to kernel + * bug which will not be fixed. See aosp/755889 and + * https://www.mail-archive.com/netdev@vger.kernel.org/msg248638.html + */ + if (protocol == IPPROTO_UDP) { + uid = lookupUidByFamily(protocol, remote, local, family, NLM_F_REQUEST, fd); + } else { + uid = lookupUidByFamily(protocol, local, remote, family, NLM_F_REQUEST, fd); + } + if (uid != INVALID_UID) { + return uid; + } + } + + /** + * For UDP it's possible for a socket to send packets to arbitrary destinations, even if the + * socket is not connected (and even if the socket is connected to a different destination). + * If we want this API to work for such packets, then on miss we need to do a second lookup + * with only the local address and port filled in. + * Always use flags == NLM_F_REQUEST | NLM_F_DUMP for wildcard. + */ + if (protocol == IPPROTO_UDP) { + try { + InetSocketAddress wildcard = new InetSocketAddress( + Inet6Address.getByName("::"), 0); + uid = lookupUidByFamily(protocol, local, wildcard, AF_INET6, + (short) (NLM_F_REQUEST | NLM_F_DUMP), fd); + if (uid != INVALID_UID) { + return uid; + } + wildcard = new InetSocketAddress(Inet4Address.getByName("0.0.0.0"), 0); + uid = lookupUidByFamily(protocol, local, wildcard, AF_INET, + (short) (NLM_F_REQUEST | NLM_F_DUMP), fd); + if (uid != INVALID_UID) { + return uid; + } + } catch (UnknownHostException e) { + Log.e(TAG, e.toString()); + } + } + return INVALID_UID; + } + + /** + * Use an inet_diag socket to look up the UID associated with the input local and remote + * address/port and protocol of a connection. + */ + public static int getConnectionOwnerUid(int protocol, InetSocketAddress local, + InetSocketAddress remote) { + int uid = INVALID_UID; + FileDescriptor fd = null; + try { + fd = NetlinkUtils.netlinkSocketForProto(NETLINK_INET_DIAG); + NetlinkUtils.connectSocketToNetlink(fd); + uid = lookupUid(protocol, local, remote, fd); + } catch (ErrnoException | SocketException | IllegalArgumentException + | InterruptedIOException e) { + Log.e(TAG, e.toString()); + } finally { + closeSocketQuietly(fd); + } + return uid; + } + + /** + * Construct an inet_diag_req_v2 message for querying alive TCP sockets from kernel. + */ + public static byte[] buildInetDiagReqForAliveTcpSockets(int family) { + return inetDiagReqV2(IPPROTO_TCP, + null /* local addr */, + null /* remote addr */, + family, + (short) (StructNlMsgHdr.NLM_F_REQUEST | StructNlMsgHdr.NLM_F_DUMP) /* flag */, + 0 /* pad */, + 1 << NetlinkConstants.INET_DIAG_MEMINFO /* idiagExt */, + TCP_ALIVE_STATE_FILTER); + } + + private static void sendNetlinkDestroyRequest(FileDescriptor fd, int proto, + InetDiagMessage diagMsg) throws InterruptedIOException, ErrnoException { + final byte[] destroyMsg = InetDiagMessage.inetDiagReqV2( + proto, + diagMsg.inetDiagMsg.id, + diagMsg.inetDiagMsg.idiag_family, + SOCK_DESTROY, + (short) (StructNlMsgHdr.NLM_F_REQUEST | StructNlMsgHdr.NLM_F_ACK), + 0 /* pad */, + 0 /* idiagExt */, + 1 << diagMsg.inetDiagMsg.idiag_state + ); + NetlinkUtils.sendMessage(fd, destroyMsg, 0, destroyMsg.length, IO_TIMEOUT_MS); + NetlinkUtils.receiveNetlinkAck(fd); + } + + private static void sendNetlinkDumpRequest(FileDescriptor fd, int proto, int states, int family) + throws InterruptedIOException, ErrnoException { + final byte[] dumpMsg = InetDiagMessage.inetDiagReqV2( + proto, + null /* id */, + family, + SOCK_DIAG_BY_FAMILY, + (short) (StructNlMsgHdr.NLM_F_REQUEST | StructNlMsgHdr.NLM_F_DUMP), + 0 /* pad */, + 0 /* idiagExt */, + states); + NetlinkUtils.sendMessage(fd, dumpMsg, 0, dumpMsg.length, IO_TIMEOUT_MS); + } + + private static int processNetlinkDumpAndDestroySockets(FileDescriptor dumpFd, + FileDescriptor destroyFd, int proto, Predicate<InetDiagMessage> filter) + throws InterruptedIOException, ErrnoException { + int destroyedSockets = 0; + + while (true) { + final ByteBuffer buf = NetlinkUtils.recvMessage( + dumpFd, DEFAULT_RECV_BUFSIZE, IO_TIMEOUT_MS); + + while (buf.remaining() > 0) { + final int position = buf.position(); + final NetlinkMessage nlMsg = NetlinkMessage.parse(buf, NETLINK_INET_DIAG); + if (nlMsg == null) { + // Move to the position where parse started for error log. + buf.position(position); + Log.e(TAG, "Failed to parse netlink message: " + hexify(buf)); + break; + } + + if (nlMsg.getHeader().nlmsg_type == NLMSG_DONE) { + return destroyedSockets; + } + + if (!(nlMsg instanceof InetDiagMessage)) { + Log.wtf(TAG, "Received unexpected netlink message: " + nlMsg); + continue; + } + + final InetDiagMessage diagMsg = (InetDiagMessage) nlMsg; + if (filter.test(diagMsg)) { + try { + sendNetlinkDestroyRequest(destroyFd, proto, diagMsg); + destroyedSockets++; + } catch (InterruptedIOException | ErrnoException e) { + if (!(e instanceof ErrnoException + && ((ErrnoException) e).errno == ENOENT)) { + Log.e(TAG, "Failed to destroy socket: diagMsg=" + diagMsg + ", " + e); + } + } + } + } + } + } + + /** + * Returns whether the InetDiagMessage is for adb socket or not + */ + @VisibleForTesting + public static boolean isAdbSocket(final InetDiagMessage msg) { + // This is inaccurate since adb could run with ROOT_UID or other services can run with + // SHELL_UID. But this check covers most cases and enough. + // Note that getting service.adb.tcp.port system property is prohibited by sepolicy + // TODO: skip the socket only if there is a listen socket owned by SHELL_UID with the same + // source port as this socket + return msg.inetDiagMsg.idiag_uid == Process.SHELL_UID; + } + + /** + * Returns whether the range contains the uid in the InetDiagMessage or not + */ + @VisibleForTesting + public static boolean containsUid(InetDiagMessage msg, Set<Range<Integer>> ranges) { + for (final Range<Integer> range: ranges) { + if (range.contains(msg.inetDiagMsg.idiag_uid)) { + return true; + } + } + return false; + } + + private static boolean isLoopbackAddress(InetAddress addr) { + if (addr.isLoopbackAddress()) return true; + if (!(addr instanceof Inet6Address)) return false; + + // Following check is for v4-mapped v6 address. StructInetDiagSockId contains v4-mapped v6 + // address as Inet6Address, See StructInetDiagSockId#parse + final byte[] addrBytes = addr.getAddress(); + for (int i = 0; i < 10; i++) { + if (addrBytes[i] != 0) return false; + } + return addrBytes[10] == (byte) 0xff + && addrBytes[11] == (byte) 0xff + && addrBytes[12] == 127; + } + + /** + * Returns whether the socket address in the InetDiagMessage is loopback or not + */ + @VisibleForTesting + public static boolean isLoopback(InetDiagMessage msg) { + final InetAddress srcAddr = msg.inetDiagMsg.id.locSocketAddress.getAddress(); + final InetAddress dstAddr = msg.inetDiagMsg.id.remSocketAddress.getAddress(); + return isLoopbackAddress(srcAddr) + || isLoopbackAddress(dstAddr) + || srcAddr.equals(dstAddr); + } + + private static void destroySockets(int proto, int states, Predicate<InetDiagMessage> filter) + throws ErrnoException, SocketException, InterruptedIOException { + FileDescriptor dumpFd = null; + FileDescriptor destroyFd = null; + + try { + dumpFd = NetlinkUtils.createNetLinkInetDiagSocket(); + destroyFd = NetlinkUtils.createNetLinkInetDiagSocket(); + connectSocketToNetlink(dumpFd); + connectSocketToNetlink(destroyFd); + + for (int family : List.of(AF_INET, AF_INET6)) { + try { + sendNetlinkDumpRequest(dumpFd, proto, states, family); + } catch (InterruptedIOException | ErrnoException e) { + Log.e(TAG, "Failed to send netlink dump request: " + e); + continue; + } + final int destroyedSockets = processNetlinkDumpAndDestroySockets( + dumpFd, destroyFd, proto, filter); + Log.d(TAG, "Destroyed " + destroyedSockets + " sockets" + + ", proto=" + stringForProtocol(proto) + + ", family=" + stringForAddressFamily(family) + + ", states=" + states); + } + } finally { + closeSocketQuietly(dumpFd); + closeSocketQuietly(destroyFd); + } + } + + /** + * Close tcp sockets that match the following condition + * 1. TCP status is one of TCP_ESTABLISHED, TCP_SYN_SENT, and TCP_SYN_RECV + * 2. Owner uid of socket is not in the exemptUids + * 3. Owner uid of socket is in the ranges + * 4. Socket is not loopback + * 5. Socket is not adb socket + * + * @param ranges target uid ranges + * @param exemptUids uids to skip close socket + */ + public static void destroyLiveTcpSockets(Set<Range<Integer>> ranges, Set<Integer> exemptUids) + throws SocketException, InterruptedIOException, ErrnoException { + final long startTimeMs = SystemClock.elapsedRealtime(); + destroySockets(IPPROTO_TCP, TCP_ALIVE_STATE_FILTER, + (diagMsg) -> !exemptUids.contains(diagMsg.inetDiagMsg.idiag_uid) + && containsUid(diagMsg, ranges) + && !isLoopback(diagMsg) + && !isAdbSocket(diagMsg)); + final long durationMs = SystemClock.elapsedRealtime() - startTimeMs; + Log.d(TAG, "Destroyed live tcp sockets for uids=" + ranges + " exemptUids=" + exemptUids + + " in " + durationMs + "ms"); + } + + /** + * Close tcp sockets that match the following condition + * 1. TCP status is one of TCP_ESTABLISHED, TCP_SYN_SENT, and TCP_SYN_RECV + * 2. Owner uid of socket is in the targetUids + * 3. Socket is not loopback + * 4. Socket is not adb socket + * + * @param ownerUids target uids to close sockets + */ + public static void destroyLiveTcpSocketsByOwnerUids(Set<Integer> ownerUids) + throws SocketException, InterruptedIOException, ErrnoException { + final long startTimeMs = SystemClock.elapsedRealtime(); + destroySockets(IPPROTO_TCP, TCP_ALIVE_STATE_FILTER, + (diagMsg) -> ownerUids.contains(diagMsg.inetDiagMsg.idiag_uid) + && !isLoopback(diagMsg) + && !isAdbSocket(diagMsg)); + final long durationMs = SystemClock.elapsedRealtime() - startTimeMs; + Log.d(TAG, "Destroyed live tcp sockets for uids=" + ownerUids + " in " + durationMs + "ms"); + } + + @Override + public String toString() { + return "InetDiagMessage{ " + + "nlmsghdr{" + + (mHeader == null ? "" : mHeader.toString(NETLINK_INET_DIAG)) + "}, " + + "inet_diag_msg{" + + (inetDiagMsg == null ? "" : inetDiagMsg.toString()) + "} " + + "}"; + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/NdOption.java b/staticlibs/device/com/android/net/module/util/netlink/NdOption.java new file mode 100644 index 0000000000000000000000000000000000000000..defc88a765be84496b678d8dcc7ab0393d312f4d --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/NdOption.java @@ -0,0 +1,88 @@ +/* + * 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. + */ + +package com.android.net.module.util.netlink; + +import androidx.annotation.NonNull; + +import java.nio.ByteBuffer; + +/** + * Base class for IPv6 neighbour discovery options. + */ +public class NdOption { + public static final int STRUCT_SIZE = 2; + + /** The option type. */ + public final byte type; + /** The length of the option in 8-byte units. Actually an unsigned 8-bit integer */ + public final int length; + + /** Constructs a new NdOption. */ + NdOption(byte type, int length) { + this.type = type; + this.length = length; + } + + /** + * Parses a neighbour discovery option. + * + * Parses (and consumes) the option if it is of a known type. If the option is of an unknown + * type, advances the buffer (so the caller can continue parsing if desired) and returns + * {@link #UNKNOWN}. If the option claims a length of 0, returns null because parsing cannot + * continue. + * + * No checks are performed on the length other than ensuring it is not 0, so if a caller wants + * to deal with options that might overflow the structure that contains them, it must explicitly + * set the buffer's limit to the position at which that structure ends. + * + * @param buf the buffer to parse. + * @return a subclass of {@link NdOption}, or {@code null} for an unknown or malformed option. + */ + public static NdOption parse(@NonNull ByteBuffer buf) { + if (buf.remaining() < STRUCT_SIZE) return null; + + // Peek the type without advancing the buffer. + byte type = buf.get(buf.position()); + int length = Byte.toUnsignedInt(buf.get(buf.position() + 1)); + if (length == 0) return null; + + switch (type) { + case StructNdOptPref64.TYPE: + return StructNdOptPref64.parse(buf); + + case StructNdOptRdnss.TYPE: + return StructNdOptRdnss.parse(buf); + + default: + int newPosition = Math.min(buf.limit(), buf.position() + length * 8); + buf.position(newPosition); + return UNKNOWN; + } + } + + void writeToByteBuffer(ByteBuffer buf) { + buf.put(type); + buf.put((byte) length); + } + + @Override + public String toString() { + return String.format("NdOption(%d, %d)", Byte.toUnsignedInt(type), length); + } + + public static final NdOption UNKNOWN = new NdOption((byte) 0, 0); +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/NduseroptMessage.java b/staticlibs/device/com/android/net/module/util/netlink/NduseroptMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..bdf574db89af911e7c907b3cf99df9c4c9f44c6c --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/NduseroptMessage.java @@ -0,0 +1,148 @@ +/* + * 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. + */ + +package com.android.net.module.util.netlink; + +import static android.system.OsConstants.AF_INET6; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * A NetlinkMessage subclass for RTM_NEWNDUSEROPT messages. + */ +public class NduseroptMessage extends NetlinkMessage { + public static final int STRUCT_SIZE = 16; + + static final int NDUSEROPT_SRCADDR = 1; + + /** The address family. Presumably always AF_INET6. */ + public final byte family; + /** + * The total length in bytes of the options that follow this structure. + * Actually a 16-bit unsigned integer. + */ + public final int opts_len; + /** The interface index on which the options were received. */ + public final int ifindex; + /** The ICMP type of the packet that contained the options. */ + public final byte icmp_type; + /** The ICMP code of the packet that contained the options. */ + public final byte icmp_code; + + /** + * ND option that was in this message. + * Even though the length field is called "opts_len", the kernel only ever sends one option per + * message. It is unlikely that this will ever change as it would break existing userspace code. + * But if it does, we can simply update this code, since userspace is typically newer than the + * kernel. + */ + @Nullable + public final NdOption option; + + /** The IP address that sent the packet containing the option. */ + public final InetAddress srcaddr; + + NduseroptMessage(@NonNull StructNlMsgHdr header, @NonNull ByteBuffer buf) + throws UnknownHostException { + super(header); + + // The structure itself. + buf.order(ByteOrder.nativeOrder()); // Restored in the finally clause inside parse(). + final int start = buf.position(); + family = buf.get(); + buf.get(); // Skip 1 byte of padding. + opts_len = Short.toUnsignedInt(buf.getShort()); + ifindex = buf.getInt(); + icmp_type = buf.get(); + icmp_code = buf.get(); + buf.position(buf.position() + 6); // Skip 6 bytes of padding. + + // The ND option. + // Ensure we don't read past opts_len even if the option length is invalid. + // Note that this check is not really necessary since if the option length is not valid, + // this struct won't be very useful to the caller. + // + // It's safer to pass the slice of original ByteBuffer to just parse the ND option field, + // although parsing ND option might throw exception or return null, it won't break the + // original ByteBuffer position. + buf.order(ByteOrder.BIG_ENDIAN); + try { + final ByteBuffer slice = buf.slice(); + slice.limit(opts_len); + option = NdOption.parse(slice); + } finally { + // Advance buffer position according to opts_len in the header. ND option length might + // be incorrect in the malformed packet. + int newPosition = start + STRUCT_SIZE + opts_len; + if (newPosition >= buf.limit()) { + throw new IllegalArgumentException("ND option extends past end of buffer"); + } + buf.position(newPosition); + } + + // The source address attribute. + StructNlAttr nla = StructNlAttr.parse(buf); + if (nla == null || nla.nla_type != NDUSEROPT_SRCADDR || nla.nla_value == null) { + throw new IllegalArgumentException("Invalid source address in ND useropt"); + } + if (family == AF_INET6) { + // InetAddress.getByAddress only looks at the ifindex if the address type needs one. + srcaddr = Inet6Address.getByAddress(null /* hostname */, nla.nla_value, ifindex); + } else { + srcaddr = InetAddress.getByAddress(nla.nla_value); + } + } + + /** + * Parses a StructNduseroptmsg from a {@link ByteBuffer}. + * + * @param header the netlink message header. + * @param buf The buffer from which to parse the option. The buffer's byte order must be + * {@link java.nio.ByteOrder#BIG_ENDIAN}. + * @return the parsed option, or {@code null} if the option could not be parsed successfully + * (for example, if it was truncated, or if the prefix length code was wrong). + */ + @Nullable + public static NduseroptMessage parse(@NonNull StructNlMsgHdr header, @NonNull ByteBuffer buf) { + if (buf == null || buf.remaining() < STRUCT_SIZE) return null; + ByteOrder oldOrder = buf.order(); + try { + return new NduseroptMessage(header, buf); + } catch (IllegalArgumentException | UnknownHostException | BufferUnderflowException e) { + // Not great, but better than throwing an exception that might crash the caller. + // Convention in this package is that null indicates that the option was truncated, so + // callers must already handle it. + return null; + } finally { + buf.order(oldOrder); + } + } + + @Override + public String toString() { + return String.format("Nduseroptmsg(%d, %d, %d, %d, %d, %s)", + family, opts_len, ifindex, Byte.toUnsignedInt(icmp_type), + Byte.toUnsignedInt(icmp_code), srcaddr.getHostAddress()); + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java b/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java new file mode 100644 index 0000000000000000000000000000000000000000..44c51d8a8c302f9beac8a63e909612e5fb283403 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java @@ -0,0 +1,283 @@ +/* + * 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 com.android.net.module.util.netlink; + +import android.system.OsConstants; + +import androidx.annotation.NonNull; + +import java.nio.ByteBuffer; + +/** + * Various constants and static helper methods for netlink communications. + * + * Values taken from: + * + * include/uapi/linux/netfilter/nfnetlink.h + * include/uapi/linux/netfilter/nfnetlink_conntrack.h + * include/uapi/linux/netlink.h + * include/uapi/linux/rtnetlink.h + * + * @hide + */ +public class NetlinkConstants { + private NetlinkConstants() {} + + public static final int NLA_ALIGNTO = 4; + /** + * Flag for dumping struct tcp_info. + * Corresponding to enum definition in external/strace/linux/inet_diag.h. + */ + public static final int INET_DIAG_MEMINFO = 1; + + public static final int SOCKDIAG_MSG_HEADER_SIZE = + StructNlMsgHdr.STRUCT_SIZE + StructInetDiagMsg.STRUCT_SIZE; + + /** + * Get the aligned length based on a Short type number. + */ + public static final int alignedLengthOf(short length) { + final int intLength = (int) length & 0xffff; + return alignedLengthOf(intLength); + } + + /** + * Get the aligned length based on a Integer type number. + */ + public static final int alignedLengthOf(int length) { + if (length <= 0) return 0; + return (((length + NLA_ALIGNTO - 1) / NLA_ALIGNTO) * NLA_ALIGNTO); + } + + /** + * Convert a address family type to a string. + */ + public static String stringForAddressFamily(int family) { + if (family == OsConstants.AF_INET) return "AF_INET"; + if (family == OsConstants.AF_INET6) return "AF_INET6"; + if (family == OsConstants.AF_NETLINK) return "AF_NETLINK"; + if (family == OsConstants.AF_UNSPEC) return "AF_UNSPEC"; + return String.valueOf(family); + } + + /** + * Convert a protocol type to a string. + */ + public static String stringForProtocol(int protocol) { + if (protocol == OsConstants.IPPROTO_TCP) return "IPPROTO_TCP"; + if (protocol == OsConstants.IPPROTO_UDP) return "IPPROTO_UDP"; + return String.valueOf(protocol); + } + + /** + * Convert a byte array to a hexadecimal string. + */ + public static String hexify(byte[] bytes) { + if (bytes == null) return "(null)"; + return toHexString(bytes, 0, bytes.length); + } + + /** + * Convert a {@link ByteBuffer} to a hexadecimal string. + */ + public static String hexify(ByteBuffer buffer) { + if (buffer == null) return "(null)"; + return toHexString( + buffer.array(), buffer.position(), buffer.remaining()); + } + + // Known values for struct nlmsghdr nlm_type. + public static final short NLMSG_NOOP = 1; // Nothing + public static final short NLMSG_ERROR = 2; // Error + public static final short NLMSG_DONE = 3; // End of a dump + public static final short NLMSG_OVERRUN = 4; // Data lost + public static final short NLMSG_MAX_RESERVED = 15; // Max reserved value + + public static final short RTM_NEWLINK = 16; + public static final short RTM_DELLINK = 17; + public static final short RTM_GETLINK = 18; + public static final short RTM_SETLINK = 19; + public static final short RTM_NEWADDR = 20; + public static final short RTM_DELADDR = 21; + public static final short RTM_GETADDR = 22; + public static final short RTM_NEWROUTE = 24; + public static final short RTM_DELROUTE = 25; + public static final short RTM_GETROUTE = 26; + public static final short RTM_NEWNEIGH = 28; + public static final short RTM_DELNEIGH = 29; + public static final short RTM_GETNEIGH = 30; + public static final short RTM_NEWRULE = 32; + public static final short RTM_DELRULE = 33; + public static final short RTM_GETRULE = 34; + public static final short RTM_NEWNDUSEROPT = 68; + + // Netfilter netlink message types are presented by two bytes: high byte subsystem and + // low byte operation. See the macro NFNL_SUBSYS_ID and NFNL_MSG_TYPE in + // include/uapi/linux/netfilter/nfnetlink.h + public static final short NFNL_SUBSYS_CTNETLINK = 1; + + public static final short IPCTNL_MSG_CT_NEW = 0; + public static final short IPCTNL_MSG_CT_GET = 1; + public static final short IPCTNL_MSG_CT_DELETE = 2; + public static final short IPCTNL_MSG_CT_GET_CTRZERO = 3; + public static final short IPCTNL_MSG_CT_GET_STATS_CPU = 4; + public static final short IPCTNL_MSG_CT_GET_STATS = 5; + public static final short IPCTNL_MSG_CT_GET_DYING = 6; + public static final short IPCTNL_MSG_CT_GET_UNCONFIRMED = 7; + + /* see include/uapi/linux/sock_diag.h */ + public static final short SOCK_DIAG_BY_FAMILY = 20; + public static final short SOCK_DESTROY = 21; + + // Netlink groups. + public static final int RTMGRP_LINK = 1; + public static final int RTMGRP_IPV4_IFADDR = 0x10; + public static final int RTMGRP_IPV6_IFADDR = 0x100; + public static final int RTMGRP_IPV6_ROUTE = 0x400; + public static final int RTNLGRP_ND_USEROPT = 20; + public static final int RTMGRP_ND_USEROPT = 1 << (RTNLGRP_ND_USEROPT - 1); + + // Device flags. + public static final int IFF_UP = 1 << 0; + public static final int IFF_LOWER_UP = 1 << 16; + + // Known values for struct rtmsg rtm_protocol. + public static final short RTPROT_KERNEL = 2; + public static final short RTPROT_RA = 9; + + // Known values for struct rtmsg rtm_scope. + public static final short RT_SCOPE_UNIVERSE = 0; + + // Known values for struct rtmsg rtm_type. + public static final short RTN_UNICAST = 1; + + // Known values for struct rtmsg rtm_flags. + public static final int RTM_F_CLONED = 0x200; + + /** + * Convert a netlink message type to a string for control message. + */ + @NonNull + private static String stringForCtlMsgType(short nlmType) { + switch (nlmType) { + case NLMSG_NOOP: return "NLMSG_NOOP"; + case NLMSG_ERROR: return "NLMSG_ERROR"; + case NLMSG_DONE: return "NLMSG_DONE"; + case NLMSG_OVERRUN: return "NLMSG_OVERRUN"; + default: return "unknown control message type: " + String.valueOf(nlmType); + } + } + + /** + * Convert a netlink message type to a string for NETLINK_ROUTE. + */ + @NonNull + private static String stringForRtMsgType(short nlmType) { + switch (nlmType) { + case RTM_NEWLINK: return "RTM_NEWLINK"; + case RTM_DELLINK: return "RTM_DELLINK"; + case RTM_GETLINK: return "RTM_GETLINK"; + case RTM_SETLINK: return "RTM_SETLINK"; + case RTM_NEWADDR: return "RTM_NEWADDR"; + case RTM_DELADDR: return "RTM_DELADDR"; + case RTM_GETADDR: return "RTM_GETADDR"; + case RTM_NEWROUTE: return "RTM_NEWROUTE"; + case RTM_DELROUTE: return "RTM_DELROUTE"; + case RTM_GETROUTE: return "RTM_GETROUTE"; + case RTM_NEWNEIGH: return "RTM_NEWNEIGH"; + case RTM_DELNEIGH: return "RTM_DELNEIGH"; + case RTM_GETNEIGH: return "RTM_GETNEIGH"; + case RTM_NEWRULE: return "RTM_NEWRULE"; + case RTM_DELRULE: return "RTM_DELRULE"; + case RTM_GETRULE: return "RTM_GETRULE"; + case RTM_NEWNDUSEROPT: return "RTM_NEWNDUSEROPT"; + default: return "unknown RTM type: " + String.valueOf(nlmType); + } + } + + /** + * Convert a netlink message type to a string for NETLINK_INET_DIAG. + */ + @NonNull + private static String stringForInetDiagMsgType(short nlmType) { + switch (nlmType) { + case SOCK_DIAG_BY_FAMILY: return "SOCK_DIAG_BY_FAMILY"; + default: return "unknown SOCK_DIAG type: " + String.valueOf(nlmType); + } + } + + /** + * Convert a netlink message type to a string for NETLINK_NETFILTER. + */ + @NonNull + private static String stringForNfMsgType(short nlmType) { + final byte subsysId = (byte) (nlmType >> 8); + final byte msgType = (byte) nlmType; + switch (subsysId) { + case NFNL_SUBSYS_CTNETLINK: + switch (msgType) { + case IPCTNL_MSG_CT_NEW: return "IPCTNL_MSG_CT_NEW"; + case IPCTNL_MSG_CT_GET: return "IPCTNL_MSG_CT_GET"; + case IPCTNL_MSG_CT_DELETE: return "IPCTNL_MSG_CT_DELETE"; + case IPCTNL_MSG_CT_GET_CTRZERO: return "IPCTNL_MSG_CT_GET_CTRZERO"; + case IPCTNL_MSG_CT_GET_STATS_CPU: return "IPCTNL_MSG_CT_GET_STATS_CPU"; + case IPCTNL_MSG_CT_GET_STATS: return "IPCTNL_MSG_CT_GET_STATS"; + case IPCTNL_MSG_CT_GET_DYING: return "IPCTNL_MSG_CT_GET_DYING"; + case IPCTNL_MSG_CT_GET_UNCONFIRMED: return "IPCTNL_MSG_CT_GET_UNCONFIRMED"; + } + break; + } + return "unknown NETFILTER type: " + String.valueOf(nlmType); + } + + /** + * Convert a netlink message type to a string by netlink family. + */ + @NonNull + public static String stringForNlMsgType(short nlmType, int nlFamily) { + // Reserved control messages. The netlink family is ignored. + // See NLMSG_MIN_TYPE in include/uapi/linux/netlink.h. + if (nlmType <= NLMSG_MAX_RESERVED) return stringForCtlMsgType(nlmType); + + // Netlink family messages. The netlink family is required. Note that the reason for using + // if-statement is that switch-case can't be used because the OsConstants.NETLINK_* are + // not constant. + if (nlFamily == OsConstants.NETLINK_ROUTE) return stringForRtMsgType(nlmType); + if (nlFamily == OsConstants.NETLINK_INET_DIAG) return stringForInetDiagMsgType(nlmType); + if (nlFamily == OsConstants.NETLINK_NETFILTER) return stringForNfMsgType(nlmType); + + return "unknown type: " + String.valueOf(nlmType); + } + + private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F' }; + /** + * Convert a byte array to a hexadecimal string. + */ + public static String toHexString(byte[] array, int offset, int length) { + char[] buf = new char[length * 2]; + + int bufIndex = 0; + for (int i = offset; i < offset + length; i++) { + byte b = array[i]; + buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F]; + buf[bufIndex++] = HEX_DIGITS[b & 0x0F]; + } + + return new String(buf); + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/NetlinkErrorMessage.java b/staticlibs/device/com/android/net/module/util/netlink/NetlinkErrorMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..4831432341f273db45c634856e80c7a3feea4771 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkErrorMessage.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.nio.ByteBuffer; + +/** + * A NetlinkMessage subclass for netlink error messages. + * + * @hide + */ +public class NetlinkErrorMessage extends NetlinkMessage { + + /** + * Parse a netlink error message from a {@link ByteBuffer}. + * + * @param byteBuffer The buffer from which to parse the netlink error message. + * @return the parsed netlink error message, or {@code null} if the netlink error message + * could not be parsed successfully (for example, if it was truncated). + */ + @Nullable + public static NetlinkErrorMessage parse(@NonNull StructNlMsgHdr header, + @NonNull ByteBuffer byteBuffer) { + final NetlinkErrorMessage errorMsg = new NetlinkErrorMessage(header); + + errorMsg.mNlMsgErr = StructNlMsgErr.parse(byteBuffer); + if (errorMsg.mNlMsgErr == null) { + return null; + } + + return errorMsg; + } + + private StructNlMsgErr mNlMsgErr; + + NetlinkErrorMessage(@NonNull StructNlMsgHdr header) { + super(header); + mNlMsgErr = null; + } + + public StructNlMsgErr getNlMsgError() { + return mNlMsgErr; + } + + @Override + public String toString() { + return "NetlinkErrorMessage{ " + + "nlmsghdr{" + (mHeader == null ? "" : mHeader.toString()) + "}, " + + "nlmsgerr{" + (mNlMsgErr == null ? "" : mNlMsgErr.toString()) + "} " + + "}"; + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java b/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..9e1e26e760fddb601de20deca2ed7d606212ee6d --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import android.system.OsConstants; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.nio.ByteBuffer; + +/** + * NetlinkMessage base class for other, more specific netlink message types. + * + * Classes that extend NetlinkMessage should: + * - implement a public static parse(StructNlMsgHdr, ByteBuffer) method + * - returning either null (parse errors) or a new object of the subclass + * type (cast-able to NetlinkMessage) + * + * NetlinkMessage.parse() should be updated to know which nlmsg_type values + * correspond with which message subclasses. + * + * @hide + */ +public class NetlinkMessage { + private static final String TAG = "NetlinkMessage"; + + /** + * Parsing netlink messages for reserved control message or specific netlink message. The + * netlink family is required for parsing specific netlink message. See man-pages/netlink. + */ + @Nullable + public static NetlinkMessage parse(@NonNull ByteBuffer byteBuffer, int nlFamily) { + final int startPosition = (byteBuffer != null) ? byteBuffer.position() : -1; + final StructNlMsgHdr nlmsghdr = StructNlMsgHdr.parse(byteBuffer); + if (nlmsghdr == null) { + return null; + } + + final int messageLength = NetlinkConstants.alignedLengthOf(nlmsghdr.nlmsg_len); + final int payloadLength = messageLength - StructNlMsgHdr.STRUCT_SIZE; + if (payloadLength < 0 || payloadLength > byteBuffer.remaining()) { + // Malformed message or runt buffer. Pretend the buffer was consumed. + byteBuffer.position(byteBuffer.limit()); + return null; + } + + // Reserved control messages. The netlink family is ignored. + // See NLMSG_MIN_TYPE in include/uapi/linux/netlink.h. + if (nlmsghdr.nlmsg_type <= NetlinkConstants.NLMSG_MAX_RESERVED) { + return parseCtlMessage(nlmsghdr, byteBuffer, payloadLength); + } + + // Netlink family messages. The netlink family is required. Note that the reason for using + // if-statement is that switch-case can't be used because the OsConstants.NETLINK_* are + // not constant. + final NetlinkMessage parsed; + if (nlFamily == OsConstants.NETLINK_ROUTE) { + parsed = parseRtMessage(nlmsghdr, byteBuffer); + } else if (nlFamily == OsConstants.NETLINK_INET_DIAG) { + parsed = parseInetDiagMessage(nlmsghdr, byteBuffer); + } else if (nlFamily == OsConstants.NETLINK_NETFILTER) { + parsed = parseNfMessage(nlmsghdr, byteBuffer); + } else { + parsed = null; + } + + // Advance to the end of the message, regardless of whether the parsing code consumed + // all of it or not. + byteBuffer.position(startPosition + messageLength); + + return parsed; + } + + @NonNull + protected final StructNlMsgHdr mHeader; + + public NetlinkMessage(@NonNull StructNlMsgHdr nlmsghdr) { + mHeader = nlmsghdr; + } + + @NonNull + public StructNlMsgHdr getHeader() { + return mHeader; + } + + @Override + public String toString() { + // The netlink family is not provided to StructNlMsgHdr#toString because NetlinkMessage + // doesn't store the information. So the netlink message type can't be transformed into + // a string by StructNlMsgHdr#toString and just keep as an integer. The specific message + // which inherits NetlinkMessage could override NetlinkMessage#toString and provide the + // specific netlink family to StructNlMsgHdr#toString. + return "NetlinkMessage{" + mHeader.toString() + "}"; + } + + @NonNull + private static NetlinkMessage parseCtlMessage(@NonNull StructNlMsgHdr nlmsghdr, + @NonNull ByteBuffer byteBuffer, int payloadLength) { + switch (nlmsghdr.nlmsg_type) { + case NetlinkConstants.NLMSG_ERROR: + return (NetlinkMessage) NetlinkErrorMessage.parse(nlmsghdr, byteBuffer); + default: { + // Other netlink control messages. Just parse the header for now, + // pretending the whole message was consumed. + byteBuffer.position(byteBuffer.position() + payloadLength); + return new NetlinkMessage(nlmsghdr); + } + } + } + + @Nullable + private static NetlinkMessage parseRtMessage(@NonNull StructNlMsgHdr nlmsghdr, + @NonNull ByteBuffer byteBuffer) { + switch (nlmsghdr.nlmsg_type) { + case NetlinkConstants.RTM_NEWLINK: + case NetlinkConstants.RTM_DELLINK: + return (NetlinkMessage) RtNetlinkLinkMessage.parse(nlmsghdr, byteBuffer); + case NetlinkConstants.RTM_NEWADDR: + case NetlinkConstants.RTM_DELADDR: + return (NetlinkMessage) RtNetlinkAddressMessage.parse(nlmsghdr, byteBuffer); + case NetlinkConstants.RTM_NEWROUTE: + case NetlinkConstants.RTM_DELROUTE: + return (NetlinkMessage) RtNetlinkRouteMessage.parse(nlmsghdr, byteBuffer); + case NetlinkConstants.RTM_NEWNEIGH: + case NetlinkConstants.RTM_DELNEIGH: + case NetlinkConstants.RTM_GETNEIGH: + return (NetlinkMessage) RtNetlinkNeighborMessage.parse(nlmsghdr, byteBuffer); + case NetlinkConstants.RTM_NEWNDUSEROPT: + return (NetlinkMessage) NduseroptMessage.parse(nlmsghdr, byteBuffer); + default: return null; + } + } + + @Nullable + private static NetlinkMessage parseInetDiagMessage(@NonNull StructNlMsgHdr nlmsghdr, + @NonNull ByteBuffer byteBuffer) { + switch (nlmsghdr.nlmsg_type) { + case NetlinkConstants.SOCK_DIAG_BY_FAMILY: + return (NetlinkMessage) InetDiagMessage.parse(nlmsghdr, byteBuffer); + default: return null; + } + } + + @Nullable + private static NetlinkMessage parseNfMessage(@NonNull StructNlMsgHdr nlmsghdr, + @NonNull ByteBuffer byteBuffer) { + switch (nlmsghdr.nlmsg_type) { + case NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8 + | NetlinkConstants.IPCTNL_MSG_CT_NEW: + case NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8 + | NetlinkConstants.IPCTNL_MSG_CT_DELETE: + return (NetlinkMessage) ConntrackMessage.parse(nlmsghdr, byteBuffer); + default: return null; + } + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java b/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..33bd36d87521f948fca970477cc392abce119d4d --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import static android.net.util.SocketUtils.makeNetlinkSocketAddress; +import static android.system.OsConstants.AF_NETLINK; +import static android.system.OsConstants.EIO; +import static android.system.OsConstants.EPROTO; +import static android.system.OsConstants.ETIMEDOUT; +import static android.system.OsConstants.NETLINK_INET_DIAG; +import static android.system.OsConstants.NETLINK_ROUTE; +import static android.system.OsConstants.SOCK_CLOEXEC; +import static android.system.OsConstants.SOCK_DGRAM; +import static android.system.OsConstants.SOL_SOCKET; +import static android.system.OsConstants.SO_RCVBUF; +import static android.system.OsConstants.SO_RCVTIMEO; +import static android.system.OsConstants.SO_SNDTIMEO; + +import android.net.util.SocketUtils; +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; +import android.system.StructTimeval; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.Inet6Address; +import java.net.SocketException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Objects; + +/** + * Utilities for netlink related class that may not be able to fit into a specific class. + * @hide + */ +public class NetlinkUtils { + private static final String TAG = "NetlinkUtils"; + /** Corresponds to enum from bionic/libc/include/netinet/tcp.h. */ + private static final int TCP_ESTABLISHED = 1; + private static final int TCP_SYN_SENT = 2; + private static final int TCP_SYN_RECV = 3; + + public static final int TCP_ALIVE_STATE_FILTER = + (1 << TCP_ESTABLISHED) | (1 << TCP_SYN_SENT) | (1 << TCP_SYN_RECV); + + public static final int UNKNOWN_MARK = 0xffffffff; + public static final int NULL_MASK = 0; + + // Initial mark value corresponds to the initValue in system/netd/include/Fwmark.h. + public static final int INIT_MARK_VALUE = 0; + // Corresponds to enum definition in bionic/libc/kernel/uapi/linux/inet_diag.h + public static final int INET_DIAG_INFO = 2; + public static final int INET_DIAG_MARK = 15; + + public static final long IO_TIMEOUT_MS = 300L; + + public static final int DEFAULT_RECV_BUFSIZE = 8 * 1024; + public static final int SOCKET_RECV_BUFSIZE = 64 * 1024; + + /** + * Return whether the input ByteBuffer contains enough remaining bytes for + * {@code StructNlMsgHdr}. + */ + public static boolean enoughBytesRemainForValidNlMsg(@NonNull final ByteBuffer bytes) { + return bytes.remaining() >= StructNlMsgHdr.STRUCT_SIZE; + } + + /** + * Parse netlink error message + * + * @param bytes byteBuffer to parse netlink error message + * @return NetlinkErrorMessage if bytes contains valid NetlinkErrorMessage, else {@code null} + */ + @Nullable + private static NetlinkErrorMessage parseNetlinkErrorMessage(ByteBuffer bytes) { + final StructNlMsgHdr nlmsghdr = StructNlMsgHdr.parse(bytes); + if (nlmsghdr == null || nlmsghdr.nlmsg_type != NetlinkConstants.NLMSG_ERROR) { + return null; + } + + final int messageLength = NetlinkConstants.alignedLengthOf(nlmsghdr.nlmsg_len); + final int payloadLength = messageLength - StructNlMsgHdr.STRUCT_SIZE; + if (payloadLength < 0 || payloadLength > bytes.remaining()) { + // Malformed message or runt buffer. Pretend the buffer was consumed. + bytes.position(bytes.limit()); + return null; + } + + return NetlinkErrorMessage.parse(nlmsghdr, bytes); + } + + /** + * Receive netlink ack message and check error + * + * @param fd fd to read netlink message + */ + public static void receiveNetlinkAck(final FileDescriptor fd) + throws InterruptedIOException, ErrnoException { + final ByteBuffer bytes = recvMessage(fd, DEFAULT_RECV_BUFSIZE, IO_TIMEOUT_MS); + // recvMessage() guaranteed to not return null if it did not throw. + final NetlinkErrorMessage response = parseNetlinkErrorMessage(bytes); + if (response != null && response.getNlMsgError() != null) { + final int errno = response.getNlMsgError().error; + if (errno != 0) { + // TODO: consider ignoring EINVAL (-22), which appears to be + // normal when probing a neighbor for which the kernel does + // not already have / no longer has a link layer address. + Log.e(TAG, "receiveNetlinkAck, errmsg=" + response.toString()); + // Note: convert kernel errnos (negative) into userspace errnos (positive). + throw new ErrnoException(response.toString(), Math.abs(errno)); + } + } else { + final String errmsg; + if (response == null) { + bytes.position(0); + errmsg = "raw bytes: " + NetlinkConstants.hexify(bytes); + } else { + errmsg = response.toString(); + } + Log.e(TAG, "receiveNetlinkAck, errmsg=" + errmsg); + throw new ErrnoException(errmsg, EPROTO); + } + } + + /** + * Send one netlink message to kernel via netlink socket. + * + * @param nlProto netlink protocol type. + * @param msg the raw bytes of netlink message to be sent. + */ + public static void sendOneShotKernelMessage(int nlProto, byte[] msg) throws ErrnoException { + final String errPrefix = "Error in NetlinkSocket.sendOneShotKernelMessage"; + final FileDescriptor fd = netlinkSocketForProto(nlProto); + + try { + connectSocketToNetlink(fd); + sendMessage(fd, msg, 0, msg.length, IO_TIMEOUT_MS); + receiveNetlinkAck(fd); + } catch (InterruptedIOException e) { + Log.e(TAG, errPrefix, e); + throw new ErrnoException(errPrefix, ETIMEDOUT, e); + } catch (SocketException e) { + Log.e(TAG, errPrefix, e); + throw new ErrnoException(errPrefix, EIO, e); + } finally { + try { + SocketUtils.closeSocket(fd); + } catch (IOException e) { + // Nothing we can do here + } + } + } + + /** + * Send an RTM_NEWADDR message to kernel to add or update an IPv6 address. + * + * @param ifIndex interface index. + * @param ip IPv6 address to be added. + * @param prefixlen IPv6 address prefix length. + * @param flags IPv6 address flags. + * @param scope IPv6 address scope. + * @param preferred The preferred lifetime of IPv6 address. + * @param valid The valid lifetime of IPv6 address. + */ + public static boolean sendRtmNewAddressRequest(int ifIndex, @NonNull final Inet6Address ip, + short prefixlen, int flags, byte scope, long preferred, long valid) { + Objects.requireNonNull(ip, "IPv6 address to be added should not be null."); + final byte[] msg = RtNetlinkAddressMessage.newRtmNewAddressMessage(1 /* seqNo*/, ip, + prefixlen, flags, scope, ifIndex, preferred, valid); + try { + NetlinkUtils.sendOneShotKernelMessage(NETLINK_ROUTE, msg); + return true; + } catch (ErrnoException e) { + Log.e(TAG, "Fail to send RTM_NEWADDR to add " + ip.getHostAddress(), e); + return false; + } + } + + /** + * Send an RTM_DELADDR message to kernel to delete an IPv6 address. + * + * @param ifIndex interface index. + * @param ip IPv6 address to be deleted. + * @param prefixlen IPv6 address prefix length. + */ + public static boolean sendRtmDelAddressRequest(int ifIndex, final Inet6Address ip, + short prefixlen) { + Objects.requireNonNull(ip, "IPv6 address to be deleted should not be null."); + final byte[] msg = RtNetlinkAddressMessage.newRtmDelAddressMessage(1 /* seqNo*/, ip, + prefixlen, ifIndex); + try { + NetlinkUtils.sendOneShotKernelMessage(NETLINK_ROUTE, msg); + return true; + } catch (ErrnoException e) { + Log.e(TAG, "Fail to send RTM_DELADDR to delete " + ip.getHostAddress(), e); + return false; + } + } + + /** + * Create netlink socket with the given netlink protocol type. + * + * @return fd the fileDescriptor of the socket. + * @throws ErrnoException if the FileDescriptor not connect to be created successfully + */ + public static FileDescriptor netlinkSocketForProto(int nlProto) throws ErrnoException { + final FileDescriptor fd = Os.socket(AF_NETLINK, SOCK_DGRAM, nlProto); + Os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, SOCKET_RECV_BUFSIZE); + return fd; + } + + /** + * Construct a netlink inet_diag socket. + */ + public static FileDescriptor createNetLinkInetDiagSocket() throws ErrnoException { + return Os.socket(AF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_INET_DIAG); + } + + /** + * Connect the given file descriptor to the Netlink interface to the kernel. + * + * The fd must be a SOCK_DGRAM socket : create it with {@link #netlinkSocketForProto} + * + * @throws ErrnoException if the {@code fd} could not connect to kernel successfully + * @throws SocketException if there is an error accessing a socket. + */ + public static void connectSocketToNetlink(FileDescriptor fd) + throws ErrnoException, SocketException { + Os.connect(fd, makeNetlinkSocketAddress(0, 0)); + } + + private static void checkTimeout(long timeoutMs) { + if (timeoutMs < 0) { + throw new IllegalArgumentException("Negative timeouts not permitted"); + } + } + + /** + * Wait up to |timeoutMs| (or until underlying socket error) for a + * netlink message of at most |bufsize| size. + * + * Multi-threaded calls with different timeouts will cause unexpected results. + */ + public static ByteBuffer recvMessage(FileDescriptor fd, int bufsize, long timeoutMs) + throws ErrnoException, IllegalArgumentException, InterruptedIOException { + checkTimeout(timeoutMs); + + Os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, StructTimeval.fromMillis(timeoutMs)); + + final ByteBuffer byteBuffer = ByteBuffer.allocate(bufsize); + final int length = Os.read(fd, byteBuffer); + if (length == bufsize) { + Log.w(TAG, "maximum read"); + } + byteBuffer.position(0); + byteBuffer.limit(length); + byteBuffer.order(ByteOrder.nativeOrder()); + return byteBuffer; + } + + /** + * Send a message to a peer to which this socket has previously connected. + * + * This waits at most |timeoutMs| milliseconds for the send to complete, will get the exception + * if it times out. + */ + public static int sendMessage( + FileDescriptor fd, byte[] bytes, int offset, int count, long timeoutMs) + throws ErrnoException, IllegalArgumentException, InterruptedIOException { + checkTimeout(timeoutMs); + Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(timeoutMs)); + return Os.write(fd, bytes, offset, count); + } + + private static final long CLOCK_TICKS_PER_SECOND = Os.sysconf(OsConstants._SC_CLK_TCK); + + /** + * Convert the system time in clock ticks(clock_t type in times(), not in clock()) to + * milliseconds. times() clock_t ticks at the kernel's USER_HZ (100) while clock() clock_t + * ticks at CLOCKS_PER_SEC (1000000). + * + * See the NOTES on https://man7.org/linux/man-pages/man2/times.2.html for the difference + * of clock_t used in clock() and times(). + */ + public static long ticksToMilliSeconds(int intClockTicks) { + final long longClockTicks = intClockTicks & 0xffffffffL; + return (longClockTicks * 1000) / CLOCK_TICKS_PER_SECOND; + } + + private NetlinkUtils() {} +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..cbe0ab0e93e50099218cde8c21f72f0ffed5ad5d --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java @@ -0,0 +1,263 @@ +/* + * 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. + */ + +package com.android.net.module.util.netlink; + +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_ACK; +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REPLACE; +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST; + +import android.system.OsConstants; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + +import com.android.net.module.util.HexDump; + +import java.net.Inet6Address; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Objects; + +/** + * A NetlinkMessage subclass for rtnetlink address messages. + * + * RtNetlinkAddressMessage.parse() must be called with a ByteBuffer that contains exactly one + * netlink message. + * + * see also: + * + * include/uapi/linux/rtnetlink.h + * + * @hide + */ +public class RtNetlinkAddressMessage extends NetlinkMessage { + public static final short IFA_ADDRESS = 1; + public static final short IFA_CACHEINFO = 6; + public static final short IFA_FLAGS = 8; + + private int mFlags; + @NonNull + private StructIfaddrMsg mIfaddrmsg; + @NonNull + private InetAddress mIpAddress; + @Nullable + private StructIfacacheInfo mIfacacheInfo; + + @VisibleForTesting + public RtNetlinkAddressMessage(@NonNull final StructNlMsgHdr header, + @NonNull final StructIfaddrMsg ifaddrMsg, + @NonNull final InetAddress ipAddress, + @Nullable final StructIfacacheInfo structIfacacheInfo, + int flags) { + super(header); + mIfaddrmsg = ifaddrMsg; + mIpAddress = ipAddress; + mIfacacheInfo = structIfacacheInfo; + mFlags = flags; + } + private RtNetlinkAddressMessage(@NonNull StructNlMsgHdr header) { + this(header, null, null, null, 0); + } + + public int getFlags() { + return mFlags; + } + + @NonNull + public StructIfaddrMsg getIfaddrHeader() { + return mIfaddrmsg; + } + + @NonNull + public InetAddress getIpAddress() { + return mIpAddress; + } + + @Nullable + public StructIfacacheInfo getIfacacheInfo() { + return mIfacacheInfo; + } + + /** + * Parse rtnetlink address message from {@link ByteBuffer}. This method must be called with a + * ByteBuffer that contains exactly one netlink message. + * + * @param header netlink message header. + * @param byteBuffer the ByteBuffer instance that wraps the raw netlink message bytes. + */ + @Nullable + public static RtNetlinkAddressMessage parse(@NonNull final StructNlMsgHdr header, + @NonNull final ByteBuffer byteBuffer) { + final RtNetlinkAddressMessage addrMsg = new RtNetlinkAddressMessage(header); + + addrMsg.mIfaddrmsg = StructIfaddrMsg.parse(byteBuffer); + if (addrMsg.mIfaddrmsg == null) return null; + + // IFA_ADDRESS + final int baseOffset = byteBuffer.position(); + StructNlAttr nlAttr = StructNlAttr.findNextAttrOfType(IFA_ADDRESS, byteBuffer); + if (nlAttr == null) return null; + addrMsg.mIpAddress = nlAttr.getValueAsInetAddress(); + if (addrMsg.mIpAddress == null) return null; + + // IFA_CACHEINFO + byteBuffer.position(baseOffset); + nlAttr = StructNlAttr.findNextAttrOfType(IFA_CACHEINFO, byteBuffer); + if (nlAttr != null) { + addrMsg.mIfacacheInfo = StructIfacacheInfo.parse(nlAttr.getValueAsByteBuffer()); + } + + // The first 8 bits of flags are in the ifaddrmsg. + addrMsg.mFlags = addrMsg.mIfaddrmsg.flags; + // IFA_FLAGS. All the flags are in the IF_FLAGS attribute. This should always be present, + // and will overwrite the flags set above. + byteBuffer.position(baseOffset); + nlAttr = StructNlAttr.findNextAttrOfType(IFA_FLAGS, byteBuffer); + if (nlAttr == null) return null; + final Integer value = nlAttr.getValueAsInteger(); + if (value == null) return null; + addrMsg.mFlags = value; + + return addrMsg; + } + + /** + * Write a rtnetlink address message to {@link ByteBuffer}. + */ + @VisibleForTesting + protected void pack(ByteBuffer byteBuffer) { + getHeader().pack(byteBuffer); + mIfaddrmsg.pack(byteBuffer); + + final StructNlAttr address = new StructNlAttr(IFA_ADDRESS, mIpAddress); + address.pack(byteBuffer); + + if (mIfacacheInfo != null) { + final StructNlAttr cacheInfo = new StructNlAttr(IFA_CACHEINFO, + mIfacacheInfo.writeToBytes()); + cacheInfo.pack(byteBuffer); + } + + // If IFA_FLAGS attribute isn't present on the wire at parsing netlink message, it will + // still be packed to ByteBuffer even if the flag is 0. + final StructNlAttr flags = new StructNlAttr(IFA_FLAGS, mFlags); + flags.pack(byteBuffer); + } + + /** + * A convenience method to create a RTM_NEWADDR message. + */ + public static byte[] newRtmNewAddressMessage(int seqNo, @NonNull final InetAddress ip, + short prefixlen, int flags, byte scope, int ifIndex, long preferred, long valid) { + Objects.requireNonNull(ip, "IP address to be set via netlink message cannot be null"); + + final StructNlMsgHdr nlmsghdr = new StructNlMsgHdr(); + nlmsghdr.nlmsg_type = NetlinkConstants.RTM_NEWADDR; + nlmsghdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_REPLACE | NLM_F_ACK; + nlmsghdr.nlmsg_seq = seqNo; + + final RtNetlinkAddressMessage msg = new RtNetlinkAddressMessage(nlmsghdr); + final byte family = + (byte) ((ip instanceof Inet6Address) ? OsConstants.AF_INET6 : OsConstants.AF_INET); + // IFA_FLAGS attribute is always present within this method, just set flags from + // ifaddrmsg to 0. kernel will prefer the flags from IFA_FLAGS attribute. + msg.mIfaddrmsg = + new StructIfaddrMsg(family, prefixlen, (short) 0 /* flags */, scope, ifIndex); + msg.mIpAddress = ip; + msg.mIfacacheInfo = new StructIfacacheInfo(preferred, valid, 0 /* cstamp */, + 0 /* tstamp */); + msg.mFlags = flags; + + final byte[] bytes = new byte[msg.getRequiredSpace()]; + nlmsghdr.nlmsg_len = bytes.length; + final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + byteBuffer.order(ByteOrder.nativeOrder()); + msg.pack(byteBuffer); + return bytes; + } + + /** + * A convenience method to create a RTM_DELADDR message. + */ + public static byte[] newRtmDelAddressMessage(int seqNo, @NonNull final InetAddress ip, + short prefixlen, int ifIndex) { + Objects.requireNonNull(ip, "IP address to be deleted via netlink message cannot be null"); + + final int ifaAddrAttrLength = NetlinkConstants.alignedLengthOf( + StructNlAttr.NLA_HEADERLEN + ip.getAddress().length); + final int length = StructNlMsgHdr.STRUCT_SIZE + StructIfaddrMsg.STRUCT_SIZE + + ifaAddrAttrLength; + final byte[] bytes = new byte[length]; + final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + byteBuffer.order(ByteOrder.nativeOrder()); + + final StructNlMsgHdr nlmsghdr = new StructNlMsgHdr(); + nlmsghdr.nlmsg_len = length; + nlmsghdr.nlmsg_type = NetlinkConstants.RTM_DELADDR; + nlmsghdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + nlmsghdr.nlmsg_seq = seqNo; + nlmsghdr.pack(byteBuffer); + + final byte family = + (byte) ((ip instanceof Inet6Address) ? OsConstants.AF_INET6 : OsConstants.AF_INET); + // Actually kernel ignores scope and flags(only deal with IFA_F_MANAGETEMPADDR, it + // indicates that all relevant IPv6 temporary addresses should be deleted as well when + // user space intends to delete a global IPv6 address with IFA_F_MANAGETEMPADDR), so + // far IFA_F_MANAGETEMPADDR flag isn't used in user space, it's fine to ignore it. + // However, we need to add IFA_FLAGS attribute in RTM_DELADDR if flags parsing should + // be supported in the future. + final StructIfaddrMsg ifaddrmsg = new StructIfaddrMsg(family, prefixlen, + (short) 0 /* flags */, (short) 0 /* scope */, ifIndex); + ifaddrmsg.pack(byteBuffer); + + final StructNlAttr address = new StructNlAttr(IFA_ADDRESS, ip); + address.pack(byteBuffer); + + return bytes; + } + + // This function helper gives the required buffer size for IFA_ADDRESS, IFA_CACHEINFO and + // IFA_FLAGS attributes encapsulation. However, that's not a mandatory requirement for all + // RtNetlinkAddressMessage, e.g. RTM_DELADDR sent from user space to kernel to delete an + // IP address only requires IFA_ADDRESS attribute. The caller should check if these attributes + // are necessary to carry when constructing a RtNetlinkAddressMessage. + private int getRequiredSpace() { + int spaceRequired = StructNlMsgHdr.STRUCT_SIZE + StructIfaddrMsg.STRUCT_SIZE; + // IFA_ADDRESS attr + spaceRequired += NetlinkConstants.alignedLengthOf( + StructNlAttr.NLA_HEADERLEN + mIpAddress.getAddress().length); + // IFA_CACHEINFO attr + spaceRequired += NetlinkConstants.alignedLengthOf( + StructNlAttr.NLA_HEADERLEN + StructIfacacheInfo.STRUCT_SIZE); + // IFA_FLAGS "u32" attr + spaceRequired += StructNlAttr.NLA_HEADERLEN + 4; + return spaceRequired; + } + + @Override + public String toString() { + return "RtNetlinkAddressMessage{ " + + "nlmsghdr{" + mHeader.toString(OsConstants.NETLINK_ROUTE) + "}, " + + "Ifaddrmsg{" + mIfaddrmsg.toString() + "}, " + + "IP Address{" + mIpAddress.getHostAddress() + "}, " + + "IfacacheInfo{" + (mIfacacheInfo == null ? "" : mIfacacheInfo.toString()) + "}, " + + "Address Flags{" + HexDump.toHexString(mFlags) + "} " + + "}"; + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..92ec0c4e4c6403b2ba623b79af0204fd4bed8c79 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java @@ -0,0 +1,151 @@ +/* + * 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. + */ + +package com.android.net.module.util.netlink; + +import android.net.MacAddress; +import android.system.OsConstants; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + +import java.nio.ByteBuffer; + +/** + * A NetlinkMessage subclass for rtnetlink link messages. + * + * RtNetlinkLinkMessage.parse() must be called with a ByteBuffer that contains exactly one netlink + * message. + * + * see also: + * + * include/uapi/linux/rtnetlink.h + * + * @hide + */ +public class RtNetlinkLinkMessage extends NetlinkMessage { + public static final short IFLA_ADDRESS = 1; + public static final short IFLA_IFNAME = 3; + public static final short IFLA_MTU = 4; + + private int mMtu; + @NonNull + private StructIfinfoMsg mIfinfomsg; + @Nullable + private MacAddress mHardwareAddress; + @Nullable + private String mInterfaceName; + + private RtNetlinkLinkMessage(@NonNull StructNlMsgHdr header) { + super(header); + mIfinfomsg = null; + mMtu = 0; + mHardwareAddress = null; + mInterfaceName = null; + } + + public int getMtu() { + return mMtu; + } + + @NonNull + public StructIfinfoMsg getIfinfoHeader() { + return mIfinfomsg; + } + + @Nullable + public MacAddress getHardwareAddress() { + return mHardwareAddress; + } + + @Nullable + public String getInterfaceName() { + return mInterfaceName; + } + + /** + * Parse rtnetlink link message from {@link ByteBuffer}. This method must be called with a + * ByteBuffer that contains exactly one netlink message. + * + * @param header netlink message header. + * @param byteBuffer the ByteBuffer instance that wraps the raw netlink message bytes. + */ + @Nullable + public static RtNetlinkLinkMessage parse(@NonNull final StructNlMsgHdr header, + @NonNull final ByteBuffer byteBuffer) { + final RtNetlinkLinkMessage linkMsg = new RtNetlinkLinkMessage(header); + + linkMsg.mIfinfomsg = StructIfinfoMsg.parse(byteBuffer); + if (linkMsg.mIfinfomsg == null) return null; + + // IFLA_MTU + final int baseOffset = byteBuffer.position(); + StructNlAttr nlAttr = StructNlAttr.findNextAttrOfType(IFLA_MTU, byteBuffer); + if (nlAttr != null) { + linkMsg.mMtu = nlAttr.getValueAsInt(0 /* default value */); + } + + // IFLA_ADDRESS + byteBuffer.position(baseOffset); + nlAttr = StructNlAttr.findNextAttrOfType(IFLA_ADDRESS, byteBuffer); + if (nlAttr != null) { + linkMsg.mHardwareAddress = nlAttr.getValueAsMacAddress(); + } + + // IFLA_IFNAME + byteBuffer.position(baseOffset); + nlAttr = StructNlAttr.findNextAttrOfType(IFLA_IFNAME, byteBuffer); + if (nlAttr != null) { + linkMsg.mInterfaceName = nlAttr.getValueAsString(); + } + + return linkMsg; + } + + /** + * Write a rtnetlink link message to {@link ByteBuffer}. + */ + @VisibleForTesting + protected void pack(ByteBuffer byteBuffer) { + getHeader().pack(byteBuffer); + mIfinfomsg.pack(byteBuffer); + + if (mMtu != 0) { + final StructNlAttr mtu = new StructNlAttr(IFLA_MTU, mMtu); + mtu.pack(byteBuffer); + } + if (mHardwareAddress != null) { + final StructNlAttr hardwareAddress = new StructNlAttr(IFLA_ADDRESS, mHardwareAddress); + hardwareAddress.pack(byteBuffer); + } + if (mInterfaceName != null) { + final StructNlAttr ifname = new StructNlAttr(IFLA_IFNAME, mInterfaceName); + ifname.pack(byteBuffer); + } + } + + @Override + public String toString() { + return "RtNetlinkLinkMessage{ " + + "nlmsghdr{" + mHeader.toString(OsConstants.NETLINK_ROUTE) + "}, " + + "Ifinfomsg{" + mIfinfomsg.toString() + "}, " + + "Hardware Address{" + mHardwareAddress + "}, " + + "MTU{" + mMtu + "}, " + + "Ifname{" + mInterfaceName + "} " + + "}"; + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkNeighborMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkNeighborMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..4a090151fbc077acda7c06aed38753974e4cfc81 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkNeighborMessage.java @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_ACK; +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP; +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REPLACE; +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST; + +import android.system.OsConstants; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.net.Inet6Address; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * A NetlinkMessage subclass for rtnetlink neighbor messages. + * + * see also: <linux_src>/include/uapi/linux/neighbour.h + * + * @hide + */ +public class RtNetlinkNeighborMessage extends NetlinkMessage { + public static final short NDA_UNSPEC = 0; + public static final short NDA_DST = 1; + public static final short NDA_LLADDR = 2; + public static final short NDA_CACHEINFO = 3; + public static final short NDA_PROBES = 4; + public static final short NDA_VLAN = 5; + public static final short NDA_PORT = 6; + public static final short NDA_VNI = 7; + public static final short NDA_IFINDEX = 8; + public static final short NDA_MASTER = 9; + + /** + * Parse routing socket netlink neighbor message from ByteBuffer. + * + * @param header netlink message header. + * @param byteBuffer the ByteBuffer instance that wraps the raw netlink message bytes. + */ + @Nullable + public static RtNetlinkNeighborMessage parse(@NonNull StructNlMsgHdr header, + @NonNull ByteBuffer byteBuffer) { + final RtNetlinkNeighborMessage neighMsg = new RtNetlinkNeighborMessage(header); + + neighMsg.mNdmsg = StructNdMsg.parse(byteBuffer); + if (neighMsg.mNdmsg == null) { + return null; + } + + // Some of these are message-type dependent, and not always present. + final int baseOffset = byteBuffer.position(); + StructNlAttr nlAttr = StructNlAttr.findNextAttrOfType(NDA_DST, byteBuffer); + if (nlAttr != null) { + neighMsg.mDestination = nlAttr.getValueAsInetAddress(); + } + + byteBuffer.position(baseOffset); + nlAttr = StructNlAttr.findNextAttrOfType(NDA_LLADDR, byteBuffer); + if (nlAttr != null) { + neighMsg.mLinkLayerAddr = nlAttr.nla_value; + } + + byteBuffer.position(baseOffset); + nlAttr = StructNlAttr.findNextAttrOfType(NDA_PROBES, byteBuffer); + if (nlAttr != null) { + neighMsg.mNumProbes = nlAttr.getValueAsInt(0); + } + + byteBuffer.position(baseOffset); + nlAttr = StructNlAttr.findNextAttrOfType(NDA_CACHEINFO, byteBuffer); + if (nlAttr != null) { + neighMsg.mCacheInfo = StructNdaCacheInfo.parse(nlAttr.getValueAsByteBuffer()); + } + + final int kMinConsumed = StructNlMsgHdr.STRUCT_SIZE + StructNdMsg.STRUCT_SIZE; + final int kAdditionalSpace = NetlinkConstants.alignedLengthOf( + neighMsg.mHeader.nlmsg_len - kMinConsumed); + if (byteBuffer.remaining() < kAdditionalSpace) { + byteBuffer.position(byteBuffer.limit()); + } else { + byteBuffer.position(baseOffset + kAdditionalSpace); + } + + return neighMsg; + } + + /** + * A convenience method to create an RTM_GETNEIGH request message. + */ + public static byte[] newGetNeighborsRequest(int seqNo) { + final int length = StructNlMsgHdr.STRUCT_SIZE + StructNdMsg.STRUCT_SIZE; + final byte[] bytes = new byte[length]; + final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + byteBuffer.order(ByteOrder.nativeOrder()); + + final StructNlMsgHdr nlmsghdr = new StructNlMsgHdr(); + nlmsghdr.nlmsg_len = length; + nlmsghdr.nlmsg_type = NetlinkConstants.RTM_GETNEIGH; + nlmsghdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + nlmsghdr.nlmsg_seq = seqNo; + nlmsghdr.pack(byteBuffer); + + final StructNdMsg ndmsg = new StructNdMsg(); + ndmsg.pack(byteBuffer); + + return bytes; + } + + /** + * A convenience method to create an RTM_NEWNEIGH message, to modify + * the kernel's state information for a specific neighbor. + */ + public static byte[] newNewNeighborMessage( + int seqNo, InetAddress ip, short nudState, int ifIndex, byte[] llAddr) { + final StructNlMsgHdr nlmsghdr = new StructNlMsgHdr(); + nlmsghdr.nlmsg_type = NetlinkConstants.RTM_NEWNEIGH; + nlmsghdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE; + nlmsghdr.nlmsg_seq = seqNo; + + final RtNetlinkNeighborMessage msg = new RtNetlinkNeighborMessage(nlmsghdr); + msg.mNdmsg = new StructNdMsg(); + msg.mNdmsg.ndm_family = + (byte) ((ip instanceof Inet6Address) ? OsConstants.AF_INET6 : OsConstants.AF_INET); + msg.mNdmsg.ndm_ifindex = ifIndex; + msg.mNdmsg.ndm_state = nudState; + msg.mDestination = ip; + msg.mLinkLayerAddr = llAddr; // might be null + + final byte[] bytes = new byte[msg.getRequiredSpace()]; + nlmsghdr.nlmsg_len = bytes.length; + final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + byteBuffer.order(ByteOrder.nativeOrder()); + msg.pack(byteBuffer); + return bytes; + } + + private StructNdMsg mNdmsg; + private InetAddress mDestination; + private byte[] mLinkLayerAddr; + private int mNumProbes; + private StructNdaCacheInfo mCacheInfo; + + private RtNetlinkNeighborMessage(@NonNull StructNlMsgHdr header) { + super(header); + mNdmsg = null; + mDestination = null; + mLinkLayerAddr = null; + mNumProbes = 0; + mCacheInfo = null; + } + + public StructNdMsg getNdHeader() { + return mNdmsg; + } + + public InetAddress getDestination() { + return mDestination; + } + + public byte[] getLinkLayerAddress() { + return mLinkLayerAddr; + } + + public int getProbes() { + return mNumProbes; + } + + public StructNdaCacheInfo getCacheInfo() { + return mCacheInfo; + } + + private int getRequiredSpace() { + int spaceRequired = StructNlMsgHdr.STRUCT_SIZE + StructNdMsg.STRUCT_SIZE; + if (mDestination != null) { + spaceRequired += NetlinkConstants.alignedLengthOf( + StructNlAttr.NLA_HEADERLEN + mDestination.getAddress().length); + } + if (mLinkLayerAddr != null) { + spaceRequired += NetlinkConstants.alignedLengthOf( + StructNlAttr.NLA_HEADERLEN + mLinkLayerAddr.length); + } + // Currently we don't write messages with NDA_PROBES nor NDA_CACHEINFO + // attributes appended. Fix later, if necessary. + return spaceRequired; + } + + private static void packNlAttr(short nlType, byte[] nlValue, ByteBuffer byteBuffer) { + final StructNlAttr nlAttr = new StructNlAttr(); + nlAttr.nla_type = nlType; + nlAttr.nla_value = nlValue; + nlAttr.nla_len = (short) (StructNlAttr.NLA_HEADERLEN + nlAttr.nla_value.length); + nlAttr.pack(byteBuffer); + } + + /** + * Write a neighbor discovery netlink message to {@link ByteBuffer}. + */ + public void pack(ByteBuffer byteBuffer) { + getHeader().pack(byteBuffer); + mNdmsg.pack(byteBuffer); + + if (mDestination != null) { + packNlAttr(NDA_DST, mDestination.getAddress(), byteBuffer); + } + if (mLinkLayerAddr != null) { + packNlAttr(NDA_LLADDR, mLinkLayerAddr, byteBuffer); + } + } + + @Override + public String toString() { + final String ipLiteral = (mDestination == null) ? "" : mDestination.getHostAddress(); + return "RtNetlinkNeighborMessage{ " + + "nlmsghdr{" + + (mHeader == null ? "" : mHeader.toString(OsConstants.NETLINK_ROUTE)) + "}, " + + "ndmsg{" + (mNdmsg == null ? "" : mNdmsg.toString()) + "}, " + + "destination{" + ipLiteral + "} " + + "linklayeraddr{" + NetlinkConstants.hexify(mLinkLayerAddr) + "} " + + "probes{" + mNumProbes + "} " + + "cacheinfo{" + (mCacheInfo == null ? "" : mCacheInfo.toString()) + "} " + + "}"; + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkRouteMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkRouteMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..9acac69cc29fbe01d07533c27e030aa77b7a8e8d --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkRouteMessage.java @@ -0,0 +1,217 @@ +/* + * 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. + */ + +package com.android.net.module.util.netlink; + +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; + +import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ANY; +import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ANY; + +import android.annotation.SuppressLint; +import android.net.IpPrefix; +import android.system.OsConstants; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.nio.ByteBuffer; + +/** + * A NetlinkMessage subclass for rtnetlink route messages. + * + * RtNetlinkRouteMessage.parse() must be called with a ByteBuffer that contains exactly one + * netlink message. + * + * see also: + * + * include/uapi/linux/rtnetlink.h + * + * @hide + */ +public class RtNetlinkRouteMessage extends NetlinkMessage { + public static final short RTA_DST = 1; + public static final short RTA_OIF = 4; + public static final short RTA_GATEWAY = 5; + public static final short RTA_CACHEINFO = 12; + + private int mIfindex; + @NonNull + private StructRtMsg mRtmsg; + @NonNull + private IpPrefix mDestination; + @Nullable + private InetAddress mGateway; + @Nullable + private StructRtaCacheInfo mRtaCacheInfo; + + private RtNetlinkRouteMessage(StructNlMsgHdr header) { + super(header); + mRtmsg = null; + mDestination = null; + mGateway = null; + mIfindex = 0; + mRtaCacheInfo = null; + } + + public int getInterfaceIndex() { + return mIfindex; + } + + @NonNull + public StructRtMsg getRtMsgHeader() { + return mRtmsg; + } + + @NonNull + public IpPrefix getDestination() { + return mDestination; + } + + @Nullable + public InetAddress getGateway() { + return mGateway; + } + + @Nullable + public StructRtaCacheInfo getRtaCacheInfo() { + return mRtaCacheInfo; + } + + /** + * Check whether the address families of destination and gateway match rtm_family in + * StructRtmsg. + * + * For example, IPv4-mapped IPv6 addresses as an IPv6 address will be always converted to IPv4 + * address, that's incorrect when upper layer creates a new {@link RouteInfo} class instance + * for IPv6 route with the converted IPv4 gateway. + */ + private static boolean matchRouteAddressFamily(@NonNull final InetAddress address, + int family) { + return ((address instanceof Inet4Address) && (family == AF_INET)) + || ((address instanceof Inet6Address) && (family == AF_INET6)); + } + + /** + * Parse rtnetlink route message from {@link ByteBuffer}. This method must be called with a + * ByteBuffer that contains exactly one netlink message. + * + * @param header netlink message header. + * @param byteBuffer the ByteBuffer instance that wraps the raw netlink message bytes. + */ + @SuppressLint("NewApi") + @Nullable + public static RtNetlinkRouteMessage parse(@NonNull final StructNlMsgHdr header, + @NonNull final ByteBuffer byteBuffer) { + final RtNetlinkRouteMessage routeMsg = new RtNetlinkRouteMessage(header); + + routeMsg.mRtmsg = StructRtMsg.parse(byteBuffer); + if (routeMsg.mRtmsg == null) return null; + int rtmFamily = routeMsg.mRtmsg.family; + + // RTA_DST + final int baseOffset = byteBuffer.position(); + StructNlAttr nlAttr = StructNlAttr.findNextAttrOfType(RTA_DST, byteBuffer); + if (nlAttr != null) { + final InetAddress destination = nlAttr.getValueAsInetAddress(); + // If the RTA_DST attribute is malformed, return null. + if (destination == null) return null; + // If the address family of destination doesn't match rtm_family, return null. + if (!matchRouteAddressFamily(destination, rtmFamily)) return null; + routeMsg.mDestination = new IpPrefix(destination, routeMsg.mRtmsg.dstLen); + } else if (rtmFamily == AF_INET) { + routeMsg.mDestination = new IpPrefix(IPV4_ADDR_ANY, 0); + } else if (rtmFamily == AF_INET6) { + routeMsg.mDestination = new IpPrefix(IPV6_ADDR_ANY, 0); + } else { + return null; + } + + // RTA_GATEWAY + byteBuffer.position(baseOffset); + nlAttr = StructNlAttr.findNextAttrOfType(RTA_GATEWAY, byteBuffer); + if (nlAttr != null) { + routeMsg.mGateway = nlAttr.getValueAsInetAddress(); + // If the RTA_GATEWAY attribute is malformed, return null. + if (routeMsg.mGateway == null) return null; + // If the address family of gateway doesn't match rtm_family, return null. + if (!matchRouteAddressFamily(routeMsg.mGateway, rtmFamily)) return null; + } + + // RTA_OIF + byteBuffer.position(baseOffset); + nlAttr = StructNlAttr.findNextAttrOfType(RTA_OIF, byteBuffer); + if (nlAttr != null) { + // Any callers that deal with interface names are responsible for converting + // the interface index to a name themselves. This may not succeed or may be + // incorrect, because the interface might have been deleted, or even deleted + // and re-added with a different index, since the netlink message was sent. + routeMsg.mIfindex = nlAttr.getValueAsInt(0 /* 0 isn't a valid ifindex */); + } + + // RTA_CACHEINFO + byteBuffer.position(baseOffset); + nlAttr = StructNlAttr.findNextAttrOfType(RTA_CACHEINFO, byteBuffer); + if (nlAttr != null) { + routeMsg.mRtaCacheInfo = StructRtaCacheInfo.parse(nlAttr.getValueAsByteBuffer()); + } + + return routeMsg; + } + + /** + * Write a rtnetlink address message to {@link ByteBuffer}. + */ + @VisibleForTesting + protected void pack(ByteBuffer byteBuffer) { + getHeader().pack(byteBuffer); + mRtmsg.pack(byteBuffer); + + final StructNlAttr destination = new StructNlAttr(RTA_DST, mDestination.getAddress()); + destination.pack(byteBuffer); + + if (mGateway != null) { + final StructNlAttr gateway = new StructNlAttr(RTA_GATEWAY, mGateway.getAddress()); + gateway.pack(byteBuffer); + } + if (mIfindex != 0) { + final StructNlAttr ifindex = new StructNlAttr(RTA_OIF, mIfindex); + ifindex.pack(byteBuffer); + } + if (mRtaCacheInfo != null) { + final StructNlAttr cacheInfo = new StructNlAttr(RTA_CACHEINFO, + mRtaCacheInfo.writeToBytes()); + cacheInfo.pack(byteBuffer); + } + } + + @Override + public String toString() { + return "RtNetlinkRouteMessage{ " + + "nlmsghdr{" + mHeader.toString(OsConstants.NETLINK_ROUTE) + "}, " + + "Rtmsg{" + mRtmsg.toString() + "}, " + + "destination{" + mDestination.getAddress().getHostAddress() + "}, " + + "gateway{" + (mGateway == null ? "" : mGateway.getHostAddress()) + "}, " + + "ifindex{" + mIfindex + "}, " + + "rta_cacheinfo{" + (mRtaCacheInfo == null ? "" : mRtaCacheInfo.toString()) + "} " + + "}"; + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructIfacacheInfo.java b/staticlibs/device/com/android/net/module/util/netlink/StructIfacacheInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..360f56d268eb2a5bfb0e7dbb0323b4096c2456c4 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/StructIfacacheInfo.java @@ -0,0 +1,79 @@ +/* + * 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. + */ + +package com.android.net.module.util.netlink; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +import java.nio.ByteBuffer; + +/** + * struct ifa_cacheinfo + * + * see also: + * + * include/uapi/linux/if_addr.h + * + * @hide + */ +public class StructIfacacheInfo extends Struct { + // Already aligned. + public static final int STRUCT_SIZE = 16; + + @Field(order = 0, type = Type.U32) + public final long preferred; + @Field(order = 1, type = Type.U32) + public final long valid; + @Field(order = 2, type = Type.U32) + public final long cstamp; // created timestamp, hundredths of seconds. + @Field(order = 3, type = Type.U32) + public final long tstamp; // updated timestamp, hundredths of seconds. + + StructIfacacheInfo(long preferred, long valid, long cstamp, long tstamp) { + this.preferred = preferred; + this.valid = valid; + this.cstamp = cstamp; + this.tstamp = tstamp; + } + + /** + * Parse an ifa_cacheinfo struct from a {@link ByteBuffer}. + * + * @param byteBuffer The buffer from which to parse the ifa_cacheinfo. + * @return the parsed ifa_cacheinfo struct, or {@code null} if the ifa_cacheinfo struct + * could not be parsed successfully (for example, if it was truncated). + */ + @Nullable + public static StructIfacacheInfo parse(@NonNull final ByteBuffer byteBuffer) { + if (byteBuffer.remaining() < STRUCT_SIZE) return null; + + // The ByteOrder must already have been set to native order. + return Struct.parse(StructIfacacheInfo.class, byteBuffer); + } + + /** + * Write an ifa_cacheinfo struct to {@link ByteBuffer}. + */ + public void pack(@NonNull final ByteBuffer byteBuffer) { + // The ByteOrder must already have been set to native order. + this.writeToByteBuffer(byteBuffer); + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructIfaddrMsg.java b/staticlibs/device/com/android/net/module/util/netlink/StructIfaddrMsg.java new file mode 100644 index 0000000000000000000000000000000000000000..f9781a7b2746d1f964de5517f48c3d2acc45bbdb --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/StructIfaddrMsg.java @@ -0,0 +1,82 @@ +/* + * 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. + */ + +package com.android.net.module.util.netlink; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +import java.nio.ByteBuffer; + +/** + * struct ifaddrmsg + * + * see also: + * + * include/uapi/linux/if_addr.h + * + * @hide + */ +public class StructIfaddrMsg extends Struct { + // Already aligned. + public static final int STRUCT_SIZE = 8; + + @Field(order = 0, type = Type.U8) + public final short family; + @Field(order = 1, type = Type.U8) + public final short prefixLen; + @Field(order = 2, type = Type.U8) + public final short flags; + @Field(order = 3, type = Type.U8) + public final short scope; + @Field(order = 4, type = Type.S32) + public final int index; + + public StructIfaddrMsg(short family, short prefixLen, short flags, short scope, int index) { + this.family = family; + this.prefixLen = prefixLen; + this.flags = flags; + this.scope = scope; + this.index = index; + } + + /** + * Parse an ifaddrmsg struct from a {@link ByteBuffer}. + * + * @param byteBuffer The buffer from which to parse the ifaddrmsg. + * @return the parsed ifaddrmsg struct, or {@code null} if the ifaddrmsg struct + * could not be parsed successfully (for example, if it was truncated). + */ + @Nullable + public static StructIfaddrMsg parse(@NonNull final ByteBuffer byteBuffer) { + if (byteBuffer.remaining() < STRUCT_SIZE) return null; + + // The ByteOrder must already have been set to native order. + return Struct.parse(StructIfaddrMsg.class, byteBuffer); + } + + /** + * Write an ifaddrmsg struct to {@link ByteBuffer}. + */ + public void pack(@NonNull final ByteBuffer byteBuffer) { + // The ByteOrder must already have been set to native order. + this.writeToByteBuffer(byteBuffer); + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java b/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java new file mode 100644 index 0000000000000000000000000000000000000000..02d1574bff7770b507370dd7930c473c2e619896 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java @@ -0,0 +1,82 @@ +/* + * 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. + */ + +package com.android.net.module.util.netlink; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +import java.nio.ByteBuffer; + +/** + * struct ifinfomsg + * + * see also: + * + * include/uapi/linux/rtnetlink.h + * + * @hide + */ +public class StructIfinfoMsg extends Struct { + // Already aligned. + public static final int STRUCT_SIZE = 16; + + @Field(order = 0, type = Type.U8, padding = 1) + public final short family; + @Field(order = 1, type = Type.U16) + public final int type; + @Field(order = 2, type = Type.S32) + public final int index; + @Field(order = 3, type = Type.U32) + public final long flags; + @Field(order = 4, type = Type.U32) + public final long change; + + StructIfinfoMsg(short family, int type, int index, long flags, long change) { + this.family = family; + this.type = type; + this.index = index; + this.flags = flags; + this.change = change; + } + + /** + * Parse an ifinfomsg struct from a {@link ByteBuffer}. + * + * @param byteBuffer The buffer from which to parse the ifinfomsg. + * @return the parsed ifinfomsg struct, or {@code null} if the ifinfomsg struct + * could not be parsed successfully (for example, if it was truncated). + */ + @Nullable + public static StructIfinfoMsg parse(@NonNull final ByteBuffer byteBuffer) { + if (byteBuffer.remaining() < STRUCT_SIZE) return null; + + // The ByteOrder must already have been set to native order. + return Struct.parse(StructIfinfoMsg.class, byteBuffer); + } + + /** + * Write an ifinfomsg struct to {@link ByteBuffer}. + */ + public void pack(@NonNull final ByteBuffer byteBuffer) { + // The ByteOrder must already have been set to native order. + this.writeToByteBuffer(byteBuffer); + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagMsg.java b/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagMsg.java new file mode 100644 index 0000000000000000000000000000000000000000..cbd895d66d0d056c6a39b3d5a49fc3c47240d96d --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagMsg.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.nio.ByteBuffer; + +/** + * struct inet_diag_msg + * + * see <linux_src>/include/uapi/linux/inet_diag.h + * + * struct inet_diag_msg { + * __u8 idiag_family; + * __u8 idiag_state; + * __u8 idiag_timer; + * __u8 idiag_retrans; + * struct inet_diag_sockid id; + * __u32 idiag_expires; + * __u32 idiag_rqueue; + * __u32 idiag_wqueue; + * __u32 idiag_uid; + * __u32 idiag_inode; + * }; + * + * @hide + */ +public class StructInetDiagMsg { + public static final int STRUCT_SIZE = 4 + StructInetDiagSockId.STRUCT_SIZE + 20; + public short idiag_family; + public short idiag_state; + public short idiag_timer; + public short idiag_retrans; + @NonNull + public StructInetDiagSockId id; + public long idiag_expires; + public long idiag_rqueue; + public long idiag_wqueue; + // Use int for uid since other code use int for uid and uid fits to int + public int idiag_uid; + public long idiag_inode; + + private static short unsignedByte(byte b) { + return (short) (b & 0xFF); + } + + /** + * Parse inet diag netlink message from buffer. + */ + @Nullable + public static StructInetDiagMsg parse(@NonNull ByteBuffer byteBuffer) { + if (byteBuffer.remaining() < STRUCT_SIZE) { + return null; + } + StructInetDiagMsg struct = new StructInetDiagMsg(); + struct.idiag_family = unsignedByte(byteBuffer.get()); + struct.idiag_state = unsignedByte(byteBuffer.get()); + struct.idiag_timer = unsignedByte(byteBuffer.get()); + struct.idiag_retrans = unsignedByte(byteBuffer.get()); + struct.id = StructInetDiagSockId.parse(byteBuffer, struct.idiag_family); + if (struct.id == null) { + return null; + } + struct.idiag_expires = Integer.toUnsignedLong(byteBuffer.getInt()); + struct.idiag_rqueue = Integer.toUnsignedLong(byteBuffer.getInt()); + struct.idiag_wqueue = Integer.toUnsignedLong(byteBuffer.getInt()); + struct.idiag_uid = byteBuffer.getInt(); + struct.idiag_inode = Integer.toUnsignedLong(byteBuffer.getInt()); + return struct; + } + + @Override + public String toString() { + return "StructInetDiagMsg{ " + + "idiag_family{" + idiag_family + "}, " + + "idiag_state{" + idiag_state + "}, " + + "idiag_timer{" + idiag_timer + "}, " + + "idiag_retrans{" + idiag_retrans + "}, " + + "id{" + id + "}, " + + "idiag_expires{" + idiag_expires + "}, " + + "idiag_rqueue{" + idiag_rqueue + "}, " + + "idiag_wqueue{" + idiag_wqueue + "}, " + + "idiag_uid{" + idiag_uid + "}, " + + "idiag_inode{" + idiag_inode + "}, " + + "}"; + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagReqV2.java b/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagReqV2.java new file mode 100644 index 0000000000000000000000000000000000000000..3b47008acb142a800ac9ea0e222dfaf527afd618 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagReqV2.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import androidx.annotation.Nullable; + +import java.nio.ByteBuffer; + +/** + * struct inet_diag_req_v2 + * + * see <linux_src>/include/uapi/linux/inet_diag.h + * + * struct inet_diag_req_v2 { + * __u8 sdiag_family; + * __u8 sdiag_protocol; + * __u8 idiag_ext; + * __u8 pad; + * __u32 idiag_states; + * struct inet_diag_sockid id; + * }; + * + * @hide + */ +public class StructInetDiagReqV2 { + public static final int STRUCT_SIZE = 8 + StructInetDiagSockId.STRUCT_SIZE; + + private final byte mSdiagFamily; + private final byte mSdiagProtocol; + private final byte mIdiagExt; + private final byte mPad; + private final StructInetDiagSockId mId; + private final int mState; + public static final int INET_DIAG_REQ_V2_ALL_STATES = (int) 0xffffffff; + + public StructInetDiagReqV2(int protocol, @Nullable StructInetDiagSockId id, int family, int pad, + int extension, int state) { + mSdiagFamily = (byte) family; + mSdiagProtocol = (byte) protocol; + mId = id; + mPad = (byte) pad; + mIdiagExt = (byte) extension; + mState = state; + } + + /** + * Write the int diag request v2 message to ByteBuffer. + */ + public void pack(ByteBuffer byteBuffer) { + // The ByteOrder must have already been set by the caller. + byteBuffer.put((byte) mSdiagFamily); + byteBuffer.put((byte) mSdiagProtocol); + byteBuffer.put((byte) mIdiagExt); + byteBuffer.put((byte) mPad); + byteBuffer.putInt(mState); + if (mId != null) mId.pack(byteBuffer); + } + + @Override + public String toString() { + final String familyStr = NetlinkConstants.stringForAddressFamily(mSdiagFamily); + final String protocolStr = NetlinkConstants.stringForAddressFamily(mSdiagProtocol); + + return "StructInetDiagReqV2{ " + + "sdiag_family{" + familyStr + "}, " + + "sdiag_protocol{" + protocolStr + "}, " + + "idiag_ext{" + mIdiagExt + ")}, " + + "pad{" + mPad + "}, " + + "idiag_states{" + Integer.toHexString(mState) + "}, " + + ((mId != null) ? mId.toString() : "inet_diag_sockid=null") + + "}"; + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagSockId.java b/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagSockId.java new file mode 100644 index 0000000000000000000000000000000000000000..dd85934c28916383f56676d428f051ad613050b5 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagSockId.java @@ -0,0 +1,174 @@ +/* + * 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. + */ + +package com.android.net.module.util.netlink; + +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; + +import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_LEN; +import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_LEN; + +import static java.nio.ByteOrder.BIG_ENDIAN; + +import android.util.Log; + +import androidx.annotation.Nullable; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * struct inet_diag_req_v2 + * + * see <linux_src>/include/uapi/linux/inet_diag.h + * + * struct inet_diag_sockid { + * __be16 idiag_sport; + * __be16 idiag_dport; + * __be32 idiag_src[4]; + * __be32 idiag_dst[4]; + * __u32 idiag_if; + * __u32 idiag_cookie[2]; + * #define INET_DIAG_NOCOOKIE (~0U) + * }; + * + * @hide + */ +public class StructInetDiagSockId { + private static final String TAG = StructInetDiagSockId.class.getSimpleName(); + public static final int STRUCT_SIZE = 48; + + private static final long INET_DIAG_NOCOOKIE = ~0L; + private static final byte[] IPV4_PADDING = new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + public final InetSocketAddress locSocketAddress; + public final InetSocketAddress remSocketAddress; + public final int ifIndex; + public final long cookie; + + public StructInetDiagSockId(InetSocketAddress loc, InetSocketAddress rem) { + this(loc, rem, 0 /* ifIndex */, INET_DIAG_NOCOOKIE); + } + + public StructInetDiagSockId(InetSocketAddress loc, InetSocketAddress rem, + int ifIndex, long cookie) { + this.locSocketAddress = loc; + this.remSocketAddress = rem; + this.ifIndex = ifIndex; + this.cookie = cookie; + } + + /** + * Parse inet diag socket id from buffer. + */ + @Nullable + public static StructInetDiagSockId parse(final ByteBuffer byteBuffer, final short family) { + if (byteBuffer.remaining() < STRUCT_SIZE) { + return null; + } + + byteBuffer.order(BIG_ENDIAN); + final int srcPort = Short.toUnsignedInt(byteBuffer.getShort()); + final int dstPort = Short.toUnsignedInt(byteBuffer.getShort()); + + final InetAddress srcAddr; + final InetAddress dstAddr; + if (family == AF_INET) { + final byte[] srcAddrByte = new byte[IPV4_ADDR_LEN]; + final byte[] dstAddrByte = new byte[IPV4_ADDR_LEN]; + byteBuffer.get(srcAddrByte); + // Address always uses IPV6_ADDR_LEN in the buffer. So if the address is IPv4, position + // needs to be advanced to the next field. + byteBuffer.position(byteBuffer.position() + (IPV6_ADDR_LEN - IPV4_ADDR_LEN)); + byteBuffer.get(dstAddrByte); + byteBuffer.position(byteBuffer.position() + (IPV6_ADDR_LEN - IPV4_ADDR_LEN)); + try { + srcAddr = Inet4Address.getByAddress(srcAddrByte); + dstAddr = Inet4Address.getByAddress(dstAddrByte); + } catch (UnknownHostException e) { + Log.wtf(TAG, "Failed to parse address: " + e); + return null; + } + } else if (family == AF_INET6) { + final byte[] srcAddrByte = new byte[IPV6_ADDR_LEN]; + final byte[] dstAddrByte = new byte[IPV6_ADDR_LEN]; + byteBuffer.get(srcAddrByte); + byteBuffer.get(dstAddrByte); + try { + // Using Inet6Address.getByAddress to be consistent with idiag_family field since + // InetAddress.getByAddress returns Inet4Address if the address is v4-mapped v6 + // address. + srcAddr = Inet6Address.getByAddress( + null /* host */, srcAddrByte, -1 /* scope_id */); + dstAddr = Inet6Address.getByAddress( + null /* host */, dstAddrByte, -1 /* scope_id */); + } catch (UnknownHostException e) { + Log.wtf(TAG, "Failed to parse address: " + e); + return null; + } + } else { + Log.wtf(TAG, "Invalid address family: " + family); + return null; + } + + final InetSocketAddress srcSocketAddr = new InetSocketAddress(srcAddr, srcPort); + final InetSocketAddress dstSocketAddr = new InetSocketAddress(dstAddr, dstPort); + + byteBuffer.order(ByteOrder.nativeOrder()); + final int ifIndex = byteBuffer.getInt(); + final long cookie = byteBuffer.getLong(); + return new StructInetDiagSockId(srcSocketAddr, dstSocketAddr, ifIndex, cookie); + } + + /** + * Write inet diag socket id message to ByteBuffer in big endian. + */ + public void pack(ByteBuffer byteBuffer) { + byteBuffer.order(BIG_ENDIAN); + byteBuffer.putShort((short) locSocketAddress.getPort()); + byteBuffer.putShort((short) remSocketAddress.getPort()); + byteBuffer.put(locSocketAddress.getAddress().getAddress()); + if (locSocketAddress.getAddress() instanceof Inet4Address) { + byteBuffer.put(IPV4_PADDING); + } + byteBuffer.put(remSocketAddress.getAddress().getAddress()); + if (remSocketAddress.getAddress() instanceof Inet4Address) { + byteBuffer.put(IPV4_PADDING); + } + byteBuffer.order(ByteOrder.nativeOrder()); + byteBuffer.putInt(ifIndex); + byteBuffer.putLong(cookie); + } + + @Override + public String toString() { + return "StructInetDiagSockId{ " + + "idiag_sport{" + locSocketAddress.getPort() + "}, " + + "idiag_dport{" + remSocketAddress.getPort() + "}, " + + "idiag_src{" + locSocketAddress.getAddress().getHostAddress() + "}, " + + "idiag_dst{" + remSocketAddress.getAddress().getHostAddress() + "}, " + + "idiag_if{" + ifIndex + "}, " + + "idiag_cookie{" + + (cookie == INET_DIAG_NOCOOKIE ? "INET_DIAG_NOCOOKIE" : cookie) + "}" + + "}"; + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNdMsg.java b/staticlibs/device/com/android/net/module/util/netlink/StructNdMsg.java new file mode 100644 index 0000000000000000000000000000000000000000..53ce89918a5c37bd39ccd2ff2c7f7d6df2c947bc --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/StructNdMsg.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import android.system.OsConstants; + +import java.nio.ByteBuffer; + +/** + * struct ndmsg + * + * see: <linux_src>/include/uapi/linux/neighbour.h + * + * @hide + */ +public class StructNdMsg { + // Already aligned. + public static final int STRUCT_SIZE = 12; + + // Neighbor Cache Entry States + public static final short NUD_NONE = 0x00; + public static final short NUD_INCOMPLETE = 0x01; + public static final short NUD_REACHABLE = 0x02; + public static final short NUD_STALE = 0x04; + public static final short NUD_DELAY = 0x08; + public static final short NUD_PROBE = 0x10; + public static final short NUD_FAILED = 0x20; + public static final short NUD_NOARP = 0x40; + public static final short NUD_PERMANENT = 0x80; + + /** + * Convert neighbor cache entry state integer to string. + */ + public static String stringForNudState(short nudState) { + switch (nudState) { + case NUD_NONE: return "NUD_NONE"; + case NUD_INCOMPLETE: return "NUD_INCOMPLETE"; + case NUD_REACHABLE: return "NUD_REACHABLE"; + case NUD_STALE: return "NUD_STALE"; + case NUD_DELAY: return "NUD_DELAY"; + case NUD_PROBE: return "NUD_PROBE"; + case NUD_FAILED: return "NUD_FAILED"; + case NUD_NOARP: return "NUD_NOARP"; + case NUD_PERMANENT: return "NUD_PERMANENT"; + default: + return "unknown NUD state: " + String.valueOf(nudState); + } + } + + /** + * Check whether a neighbor is connected or not. + */ + public static boolean isNudStateConnected(short nudState) { + return ((nudState & (NUD_PERMANENT | NUD_NOARP | NUD_REACHABLE)) != 0); + } + + /** + * Check whether a neighbor is in the valid NUD state or not. + */ + public static boolean isNudStateValid(short nudState) { + return (isNudStateConnected(nudState) + || ((nudState & (NUD_PROBE | NUD_STALE | NUD_DELAY)) != 0)); + } + + // Neighbor Cache Entry Flags + public static byte NTF_USE = (byte) 0x01; + public static byte NTF_SELF = (byte) 0x02; + public static byte NTF_MASTER = (byte) 0x04; + public static byte NTF_PROXY = (byte) 0x08; + public static byte NTF_ROUTER = (byte) 0x80; + + private static String stringForNudFlags(byte flags) { + final StringBuilder sb = new StringBuilder(); + if ((flags & NTF_USE) != 0) { + sb.append("NTF_USE"); + } + if ((flags & NTF_SELF) != 0) { + if (sb.length() > 0) { + sb.append("|"); + } + sb.append("NTF_SELF"); + } + if ((flags & NTF_MASTER) != 0) { + if (sb.length() > 0) { + sb.append("|"); + } + sb.append("NTF_MASTER"); + } + if ((flags & NTF_PROXY) != 0) { + if (sb.length() > 0) { + sb.append("|"); + } + sb.append("NTF_PROXY"); + } + if ((flags & NTF_ROUTER) != 0) { + if (sb.length() > 0) { + sb.append("|"); + } + sb.append("NTF_ROUTER"); + } + return sb.toString(); + } + + private static boolean hasAvailableSpace(ByteBuffer byteBuffer) { + return byteBuffer != null && byteBuffer.remaining() >= STRUCT_SIZE; + } + + /** + * Parse a neighbor discovery netlink message header from a {@link ByteBuffer}. + * + * @param byteBuffer The buffer from which to parse the nd netlink message header. + * @return the parsed nd netlink message header, or {@code null} if the nd netlink message + * header could not be parsed successfully (for example, if it was truncated). + */ + public static StructNdMsg parse(ByteBuffer byteBuffer) { + if (!hasAvailableSpace(byteBuffer)) return null; + + // The ByteOrder must have already been set by the caller. In most + // cases ByteOrder.nativeOrder() is correct, with the possible + // exception of usage within unittests. + final StructNdMsg struct = new StructNdMsg(); + struct.ndm_family = byteBuffer.get(); + final byte pad1 = byteBuffer.get(); + final short pad2 = byteBuffer.getShort(); + struct.ndm_ifindex = byteBuffer.getInt(); + struct.ndm_state = byteBuffer.getShort(); + struct.ndm_flags = byteBuffer.get(); + struct.ndm_type = byteBuffer.get(); + return struct; + } + + public byte ndm_family; + public int ndm_ifindex; + public short ndm_state; + public byte ndm_flags; + public byte ndm_type; + + public StructNdMsg() { + ndm_family = (byte) OsConstants.AF_UNSPEC; + } + + /** + * Write the neighbor discovery message header to {@link ByteBuffer}. + */ + public void pack(ByteBuffer byteBuffer) { + // The ByteOrder must have already been set by the caller. In most + // cases ByteOrder.nativeOrder() is correct, with the exception + // of usage within unittests. + byteBuffer.put(ndm_family); + byteBuffer.put((byte) 0); // pad1 + byteBuffer.putShort((short) 0); // pad2 + byteBuffer.putInt(ndm_ifindex); + byteBuffer.putShort(ndm_state); + byteBuffer.put(ndm_flags); + byteBuffer.put(ndm_type); + } + + /** + * Check whether a neighbor is connected or not. + */ + public boolean nudConnected() { + return isNudStateConnected(ndm_state); + } + + /** + * Check whether a neighbor is in the valid NUD state or not. + */ + public boolean nudValid() { + return isNudStateValid(ndm_state); + } + + @Override + public String toString() { + final String stateStr = "" + ndm_state + " (" + stringForNudState(ndm_state) + ")"; + final String flagsStr = "" + ndm_flags + " (" + stringForNudFlags(ndm_flags) + ")"; + return "StructNdMsg{ " + + "family{" + NetlinkConstants.stringForAddressFamily((int) ndm_family) + "}, " + + "ifindex{" + ndm_ifindex + "}, " + + "state{" + stateStr + "}, " + + "flags{" + flagsStr + "}, " + + "type{" + ndm_type + "} " + + "}"; + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNdOptPref64.java b/staticlibs/device/com/android/net/module/util/netlink/StructNdOptPref64.java new file mode 100644 index 0000000000000000000000000000000000000000..8226346649abe1ae11c16e07da8a16ebbe0080db --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/StructNdOptPref64.java @@ -0,0 +1,171 @@ +/* + * 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. + */ + +package com.android.net.module.util.netlink; + +import android.annotation.SuppressLint; +import android.net.IpPrefix; +import android.util.Log; + +import androidx.annotation.NonNull; + +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.util.Objects; + +/** + * The PREF64 router advertisement option. RFC 8781. + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Type | Length | Scaled Lifetime | PLC | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * + + + * | Highest 96 bits of the Prefix | + * + + + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + */ +public class StructNdOptPref64 extends NdOption { + public static final int STRUCT_SIZE = 16; + public static final int TYPE = 38; + public static final byte LENGTH = 2; + + private static final String TAG = StructNdOptPref64.class.getSimpleName(); + + /** + * How many seconds the prefix is expected to remain valid. + * Valid values are from 0 to 65528 in multiples of 8. + */ + public final int lifetime; + /** The NAT64 prefix. */ + @NonNull public final IpPrefix prefix; + + static int plcToPrefixLength(int plc) { + switch (plc) { + case 0: return 96; + case 1: return 64; + case 2: return 56; + case 3: return 48; + case 4: return 40; + case 5: return 32; + default: + throw new IllegalArgumentException("Invalid prefix length code " + plc); + } + } + + static int prefixLengthToPlc(int prefixLength) { + switch (prefixLength) { + case 96: return 0; + case 64: return 1; + case 56: return 2; + case 48: return 3; + case 40: return 4; + case 32: return 5; + default: + throw new IllegalArgumentException("Invalid prefix length " + prefixLength); + } + } + + /** + * Returns the 2-byte "scaled lifetime and prefix length code" field: 13-bit lifetime, 3-bit PLC + */ + static short getScaledLifetimePlc(int lifetime, int prefixLengthCode) { + return (short) ((lifetime & 0xfff8) | (prefixLengthCode & 0x7)); + } + + public StructNdOptPref64(@NonNull IpPrefix prefix, int lifetime) { + super((byte) TYPE, LENGTH); + + Objects.requireNonNull(prefix, "prefix must not be null"); + if (!(prefix.getAddress() instanceof Inet6Address)) { + throw new IllegalArgumentException("Must be an IPv6 prefix: " + prefix); + } + prefixLengthToPlc(prefix.getPrefixLength()); // Throw if the prefix length is invalid. + this.prefix = prefix; + + if (lifetime < 0 || lifetime > 0xfff8) { + throw new IllegalArgumentException("Invalid lifetime " + lifetime); + } + this.lifetime = lifetime & 0xfff8; + } + + @SuppressLint("NewApi") + private StructNdOptPref64(@NonNull ByteBuffer buf) { + super(buf.get(), Byte.toUnsignedInt(buf.get())); + if (type != TYPE) throw new IllegalArgumentException("Invalid type " + type); + if (length != LENGTH) throw new IllegalArgumentException("Invalid length " + length); + + int scaledLifetimePlc = Short.toUnsignedInt(buf.getShort()); + lifetime = scaledLifetimePlc & 0xfff8; + + byte[] addressBytes = new byte[16]; + buf.get(addressBytes, 0, 12); + InetAddress addr; + try { + addr = InetAddress.getByAddress(addressBytes); + } catch (UnknownHostException e) { + throw new AssertionError("16-byte array not valid InetAddress?"); + } + prefix = new IpPrefix(addr, plcToPrefixLength(scaledLifetimePlc & 7)); + } + + /** + * Parses an option from a {@link ByteBuffer}. + * + * @param buf The buffer from which to parse the option. The buffer's byte order must be + * {@link java.nio.ByteOrder#BIG_ENDIAN}. + * @return the parsed option, or {@code null} if the option could not be parsed successfully + * (for example, if it was truncated, or if the prefix length code was wrong). + */ + public static StructNdOptPref64 parse(@NonNull ByteBuffer buf) { + if (buf.remaining() < STRUCT_SIZE) return null; + try { + return new StructNdOptPref64(buf); + } catch (IllegalArgumentException e) { + // Not great, but better than throwing an exception that might crash the caller. + // Convention in this package is that null indicates that the option was truncated, so + // callers must already handle it. + Log.d(TAG, "Invalid PREF64 option: " + e); + return null; + } + } + + protected void writeToByteBuffer(ByteBuffer buf) { + super.writeToByteBuffer(buf); + buf.putShort(getScaledLifetimePlc(lifetime, prefixLengthToPlc(prefix.getPrefixLength()))); + buf.put(prefix.getRawAddress(), 0, 12); + } + + /** Outputs the wire format of the option to a new big-endian ByteBuffer. */ + public ByteBuffer toByteBuffer() { + ByteBuffer buf = ByteBuffer.allocate(STRUCT_SIZE); + writeToByteBuffer(buf); + buf.flip(); + return buf; + } + + @Override + @NonNull + public String toString() { + return String.format("NdOptPref64(%s, %d)", prefix, lifetime); + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNdOptRdnss.java b/staticlibs/device/com/android/net/module/util/netlink/StructNdOptRdnss.java new file mode 100644 index 0000000000000000000000000000000000000000..6dee0c496bbf0b951a3d9da822c90716a728a086 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/StructNdOptRdnss.java @@ -0,0 +1,134 @@ +/* + * 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. + */ + +package com.android.net.module.util.netlink; + +import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_LEN; + +import android.util.Log; + +import androidx.annotation.NonNull; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.structs.RdnssOption; + +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.util.Objects; +import java.util.StringJoiner; + +/** + * The Recursive DNS Server Option. RFC 8106. + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Type | Length | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Lifetime | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * : Addresses of IPv6 Recursive DNS Servers : + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +public class StructNdOptRdnss extends NdOption { + private static final String TAG = StructNdOptRdnss.class.getSimpleName(); + public static final int TYPE = 25; + // Length in 8-byte units, only if one IPv6 address included. + public static final byte MIN_OPTION_LEN = 3; + + public final RdnssOption header; + @NonNull + public final Inet6Address[] servers; + + public StructNdOptRdnss(@NonNull final Inet6Address[] servers, long lifetime) { + super((byte) TYPE, servers.length * 2 + 1); + + Objects.requireNonNull(servers, "Recursive DNS Servers address array must not be null"); + if (servers.length == 0) { + throw new IllegalArgumentException("DNS server address array must not be empty"); + } + + this.header = new RdnssOption((byte) TYPE, (byte) (servers.length * 2 + 1), + (short) 0 /* reserved */, lifetime); + this.servers = servers.clone(); + } + + /** + * Parses an RDNSS option from a {@link ByteBuffer}. + * + * @param buf The buffer from which to parse the option. The buffer's byte order must be + * {@link java.nio.ByteOrder#BIG_ENDIAN}. + * @return the parsed option, or {@code null} if the option could not be parsed successfully. + */ + public static StructNdOptRdnss parse(@NonNull ByteBuffer buf) { + if (buf == null || buf.remaining() < MIN_OPTION_LEN * 8) return null; + try { + final RdnssOption header = Struct.parse(RdnssOption.class, buf); + if (header.type != TYPE) { + throw new IllegalArgumentException("Invalid type " + header.type); + } + if (header.length < MIN_OPTION_LEN || (header.length % 2 == 0)) { + throw new IllegalArgumentException("Invalid length " + header.length); + } + + final int numOfDnses = (header.length - 1) / 2; + final Inet6Address[] servers = new Inet6Address[numOfDnses]; + for (int i = 0; i < numOfDnses; i++) { + byte[] rawAddress = new byte[IPV6_ADDR_LEN]; + buf.get(rawAddress); + servers[i] = (Inet6Address) InetAddress.getByAddress(rawAddress); + } + return new StructNdOptRdnss(servers, header.lifetime); + } catch (IllegalArgumentException | BufferUnderflowException | UnknownHostException e) { + // Not great, but better than throwing an exception that might crash the caller. + // Convention in this package is that null indicates that the option was truncated + // or malformed, so callers must already handle it. + Log.d(TAG, "Invalid RDNSS option: " + e); + return null; + } + } + + protected void writeToByteBuffer(ByteBuffer buf) { + header.writeToByteBuffer(buf); + for (int i = 0; i < servers.length; i++) { + buf.put(servers[i].getAddress()); + } + } + + /** Outputs the wire format of the option to a new big-endian ByteBuffer. */ + public ByteBuffer toByteBuffer() { + final ByteBuffer buf = ByteBuffer.allocate(Struct.getSize(RdnssOption.class) + + servers.length * IPV6_ADDR_LEN); + writeToByteBuffer(buf); + buf.flip(); + return buf; + } + + @Override + @NonNull + public String toString() { + final StringJoiner sj = new StringJoiner(",", "[", "]"); + for (int i = 0; i < servers.length; i++) { + sj.add(servers[i].getHostAddress()); + } + return String.format("NdOptRdnss(%s,servers:%s)", header.toString(), sj.toString()); + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNdaCacheInfo.java b/staticlibs/device/com/android/net/module/util/netlink/StructNdaCacheInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..1f9bb7eca8b3cd473461a514aa6b23a08ead84b4 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/StructNdaCacheInfo.java @@ -0,0 +1,123 @@ +/* + * 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 com.android.net.module.util.netlink; + +import static com.android.net.module.util.netlink.NetlinkUtils.ticksToMilliSeconds; + +import java.nio.ByteBuffer; + +/** + * struct nda_cacheinfo + * + * see: <linux_src>/include/uapi/linux/neighbour.h + * + * @hide + */ +public class StructNdaCacheInfo { + // Already aligned. + public static final int STRUCT_SIZE = 16; + + private static boolean hasAvailableSpace(ByteBuffer byteBuffer) { + return byteBuffer != null && byteBuffer.remaining() >= STRUCT_SIZE; + } + + /** + * Parse a nd cacheinfo netlink attribute from a {@link ByteBuffer}. + * + * @param byteBuffer The buffer from which to parse the nd cacheinfo attribute. + * @return the parsed nd cacheinfo attribute, or {@code null} if the nd cacheinfo attribute + * could not be parsed successfully (for example, if it was truncated). + */ + public static StructNdaCacheInfo parse(ByteBuffer byteBuffer) { + if (!hasAvailableSpace(byteBuffer)) return null; + + // The ByteOrder must have already been set by the caller. In most + // cases ByteOrder.nativeOrder() is correct, with the possible + // exception of usage within unittests. + final StructNdaCacheInfo struct = new StructNdaCacheInfo(); + struct.ndm_used = byteBuffer.getInt(); + struct.ndm_confirmed = byteBuffer.getInt(); + struct.ndm_updated = byteBuffer.getInt(); + struct.ndm_refcnt = byteBuffer.getInt(); + return struct; + } + + /** + * Explanatory notes, for reference. + * + * Before being returned to user space, the neighbor entry times are + * converted to clock_t's like so: + * + * ndm_used = jiffies_to_clock_t(now - neigh->used); + * ndm_confirmed = jiffies_to_clock_t(now - neigh->confirmed); + * ndm_updated = jiffies_to_clock_t(now - neigh->updated); + * + * meaning that these values are expressed as "clock ticks ago". To + * convert these clock ticks to seconds divide by sysconf(_SC_CLK_TCK). + * When _SC_CLK_TCK is 100, for example, the ndm_* times are expressed + * in centiseconds. + * + * These values are unsigned, but fortunately being expressed as "some + * clock ticks ago", these values are typically very small (and + * 2^31 centiseconds = 248 days). + * + * By observation, it appears that: + * ndm_used: the last time ARP/ND took place for this neighbor + * ndm_confirmed: the last time ARP/ND succeeded for this neighbor OR + * higher layer confirmation (TCP or MSG_CONFIRM) + * was received + * ndm_updated: the time when the current NUD state was entered + */ + public int ndm_used; + public int ndm_confirmed; + public int ndm_updated; + public int ndm_refcnt; + + public StructNdaCacheInfo() {} + + /** + * The last time ARP/ND took place for this neighbor. + */ + public long lastUsed() { + return ticksToMilliSeconds(ndm_used); + } + + /** + * The last time ARP/ND succeeded for this neighbor or higher layer confirmation (TCP or + * MSG_CONFIRM) was received. + */ + public long lastConfirmed() { + return ticksToMilliSeconds(ndm_confirmed); + } + + /** + * The time when the current NUD state was entered. + */ + public long lastUpdated() { + return ticksToMilliSeconds(ndm_updated); + } + + @Override + public String toString() { + return "NdaCacheInfo{ " + + "ndm_used{" + lastUsed() + "}, " + + "ndm_confirmed{" + lastConfirmed() + "}, " + + "ndm_updated{" + lastUpdated() + "}, " + + "ndm_refcnt{" + ndm_refcnt + "} " + + "}"; + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNfGenMsg.java b/staticlibs/device/com/android/net/module/util/netlink/StructNfGenMsg.java new file mode 100644 index 0000000000000000000000000000000000000000..2de5490bcef5ae04e1792e6fa1d3279766bc44bf --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/StructNfGenMsg.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Objects; + +/** + * struct nfgenmsg + * + * see <linux_src>/include/uapi/linux/netfilter/nfnetlink.h + * + * @hide + */ +public class StructNfGenMsg { + public static final int STRUCT_SIZE = 2 + Short.BYTES; + + public static final int NFNETLINK_V0 = 0; + + public final byte nfgen_family; + public final byte version; + public final short res_id; // N.B.: this is big endian in the kernel + + /** + * Parse a netfilter netlink header from a {@link ByteBuffer}. + * + * @param byteBuffer The buffer from which to parse the netfilter netlink header. + * @return the parsed netfilter netlink header, or {@code null} if the netfilter netlink header + * could not be parsed successfully (for example, if it was truncated). + */ + @Nullable + public static StructNfGenMsg parse(@NonNull ByteBuffer byteBuffer) { + Objects.requireNonNull(byteBuffer); + + if (!hasAvailableSpace(byteBuffer)) return null; + + final byte nfgen_family = byteBuffer.get(); + final byte version = byteBuffer.get(); + + final ByteOrder originalOrder = byteBuffer.order(); + byteBuffer.order(ByteOrder.BIG_ENDIAN); + final short res_id = byteBuffer.getShort(); + byteBuffer.order(originalOrder); + + return new StructNfGenMsg(nfgen_family, version, res_id); + } + + public StructNfGenMsg(byte family, byte ver, short id) { + nfgen_family = family; + version = ver; + res_id = id; + } + + public StructNfGenMsg(byte family) { + nfgen_family = family; + version = (byte) NFNETLINK_V0; + res_id = (short) 0; + } + + /** + * Write a netfilter netlink header to a {@link ByteBuffer}. + */ + public void pack(ByteBuffer byteBuffer) { + byteBuffer.put(nfgen_family); + byteBuffer.put(version); + + final ByteOrder originalOrder = byteBuffer.order(); + byteBuffer.order(ByteOrder.BIG_ENDIAN); + byteBuffer.putShort(res_id); + byteBuffer.order(originalOrder); + } + + private static boolean hasAvailableSpace(@NonNull ByteBuffer byteBuffer) { + return byteBuffer.remaining() >= STRUCT_SIZE; + } + + @Override + public String toString() { + final String familyStr = NetlinkConstants.stringForAddressFamily(nfgen_family); + + return "NfGenMsg{ " + + "nfgen_family{" + familyStr + "}, " + + "version{" + Byte.toUnsignedInt(version) + "}, " + + "res_id{" + Short.toUnsignedInt(res_id) + "} " + + "}"; + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNlAttr.java b/staticlibs/device/com/android/net/module/util/netlink/StructNlAttr.java new file mode 100644 index 0000000000000000000000000000000000000000..a9b6495082822538a96c292975d564ee69b38cc6 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/StructNlAttr.java @@ -0,0 +1,394 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import android.net.MacAddress; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.io.UnsupportedEncodingException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; + +/** + * struct nlattr + * + * see: <linux_src>/include/uapi/linux/netlink.h + * + * @hide + */ +public class StructNlAttr { + // Already aligned. + public static final int NLA_HEADERLEN = 4; + public static final int NLA_F_NESTED = (1 << 15); + + /** + * Set carries nested attributes bit. + */ + public static short makeNestedType(short type) { + return (short) (type | NLA_F_NESTED); + } + + /** + * Peek and parse the netlink attribute from {@link ByteBuffer}. + * + * Return a (length, type) object only, without consuming any bytes in + * |byteBuffer| and without copying or interpreting any value bytes. + * This is used for scanning over a packed set of struct nlattr's, + * looking for instances of a particular type. + */ + public static StructNlAttr peek(ByteBuffer byteBuffer) { + if (byteBuffer == null || byteBuffer.remaining() < NLA_HEADERLEN) { + return null; + } + final int baseOffset = byteBuffer.position(); + + final StructNlAttr struct = new StructNlAttr(); + final ByteOrder originalOrder = byteBuffer.order(); + byteBuffer.order(ByteOrder.nativeOrder()); + try { + struct.nla_len = byteBuffer.getShort(); + struct.nla_type = byteBuffer.getShort(); + } finally { + byteBuffer.order(originalOrder); + } + + byteBuffer.position(baseOffset); + if (struct.nla_len < NLA_HEADERLEN) { + // Malformed. + return null; + } + return struct; + } + + /** + * Parse a netlink attribute from a {@link ByteBuffer}. + * + * @param byteBuffer The buffer from which to parse the netlink attriute. + * @return the parsed netlink attribute, or {@code null} if the netlink attribute + * could not be parsed successfully (for example, if it was truncated). + */ + public static StructNlAttr parse(ByteBuffer byteBuffer) { + final StructNlAttr struct = peek(byteBuffer); + if (struct == null || byteBuffer.remaining() < struct.getAlignedLength()) { + return null; + } + + final int baseOffset = byteBuffer.position(); + byteBuffer.position(baseOffset + NLA_HEADERLEN); + + int valueLen = ((int) struct.nla_len) & 0xffff; + valueLen -= NLA_HEADERLEN; + if (valueLen > 0) { + struct.nla_value = new byte[valueLen]; + byteBuffer.get(struct.nla_value, 0, valueLen); + byteBuffer.position(baseOffset + struct.getAlignedLength()); + } + return struct; + } + + /** + * Find next netlink attribute with a given type from {@link ByteBuffer}. + * + * @param attrType The given netlink attribute type is requested for. + * @param byteBuffer The buffer from which to find the netlink attribute. + * @return the found netlink attribute, or {@code null} if the netlink attribute could not be + * found or parsed successfully (for example, if it was truncated). + */ + @Nullable + public static StructNlAttr findNextAttrOfType(short attrType, + @Nullable ByteBuffer byteBuffer) { + while (byteBuffer != null && byteBuffer.remaining() > 0) { + final StructNlAttr nlAttr = StructNlAttr.peek(byteBuffer); + if (nlAttr == null) { + break; + } + if (nlAttr.nla_type == attrType) { + return StructNlAttr.parse(byteBuffer); + } + if (byteBuffer.remaining() < nlAttr.getAlignedLength()) { + break; + } + byteBuffer.position(byteBuffer.position() + nlAttr.getAlignedLength()); + } + return null; + } + + public short nla_len = (short) NLA_HEADERLEN; + public short nla_type; + public byte[] nla_value; + + public StructNlAttr() {} + + public StructNlAttr(short type, byte value) { + nla_type = type; + setValue(new byte[1]); + nla_value[0] = value; + } + + public StructNlAttr(short type, short value) { + this(type, value, ByteOrder.nativeOrder()); + } + + public StructNlAttr(short type, short value, ByteOrder order) { + nla_type = type; + setValue(new byte[Short.BYTES]); + final ByteBuffer buf = getValueAsByteBuffer(); + final ByteOrder originalOrder = buf.order(); + try { + buf.order(order); + buf.putShort(value); + } finally { + buf.order(originalOrder); + } + } + + public StructNlAttr(short type, int value) { + this(type, value, ByteOrder.nativeOrder()); + } + + public StructNlAttr(short type, int value, ByteOrder order) { + nla_type = type; + setValue(new byte[Integer.BYTES]); + final ByteBuffer buf = getValueAsByteBuffer(); + final ByteOrder originalOrder = buf.order(); + try { + buf.order(order); + buf.putInt(value); + } finally { + buf.order(originalOrder); + } + } + + public StructNlAttr(short type, @NonNull final byte[] value) { + nla_type = type; + setValue(value); + } + + public StructNlAttr(short type, @NonNull final InetAddress ip) { + nla_type = type; + setValue(ip.getAddress()); + } + + public StructNlAttr(short type, @NonNull final MacAddress mac) { + nla_type = type; + setValue(mac.toByteArray()); + } + + public StructNlAttr(short type, @NonNull final String string) { + nla_type = type; + byte[] value = null; + try { + final byte[] stringBytes = string.getBytes("UTF-8"); + // Append '\0' at the end of interface name string bytes. + value = Arrays.copyOf(stringBytes, stringBytes.length + 1); + } catch (UnsupportedEncodingException ignored) { + // Do nothing. + } finally { + setValue(value); + } + } + + public StructNlAttr(short type, StructNlAttr... nested) { + this(); + nla_type = makeNestedType(type); + + int payloadLength = 0; + for (StructNlAttr nla : nested) payloadLength += nla.getAlignedLength(); + setValue(new byte[payloadLength]); + + final ByteBuffer buf = getValueAsByteBuffer(); + for (StructNlAttr nla : nested) { + nla.pack(buf); + } + } + + /** + * Get aligned attribute length. + */ + public int getAlignedLength() { + return NetlinkConstants.alignedLengthOf(nla_len); + } + + /** + * Get attribute value as BE16. + */ + public short getValueAsBe16(short defaultValue) { + final ByteBuffer byteBuffer = getValueAsByteBuffer(); + if (byteBuffer == null || byteBuffer.remaining() != Short.BYTES) { + return defaultValue; + } + final ByteOrder originalOrder = byteBuffer.order(); + try { + byteBuffer.order(ByteOrder.BIG_ENDIAN); + return byteBuffer.getShort(); + } finally { + byteBuffer.order(originalOrder); + } + } + + /** + * Get attribute value as BE32. + */ + public int getValueAsBe32(int defaultValue) { + final ByteBuffer byteBuffer = getValueAsByteBuffer(); + if (byteBuffer == null || byteBuffer.remaining() != Integer.BYTES) { + return defaultValue; + } + final ByteOrder originalOrder = byteBuffer.order(); + try { + byteBuffer.order(ByteOrder.BIG_ENDIAN); + return byteBuffer.getInt(); + } finally { + byteBuffer.order(originalOrder); + } + } + + /** + * Get attribute value as ByteBuffer. + */ + public ByteBuffer getValueAsByteBuffer() { + if (nla_value == null) return null; + final ByteBuffer byteBuffer = ByteBuffer.wrap(nla_value); + // By convention, all buffers in this library are in native byte order because netlink is in + // native byte order. It's the order that is used by NetlinkSocket.recvMessage and the only + // order accepted by NetlinkMessage.parse. + byteBuffer.order(ByteOrder.nativeOrder()); + return byteBuffer; + } + + /** + * Get attribute value as byte. + */ + public byte getValueAsByte(byte defaultValue) { + final ByteBuffer byteBuffer = getValueAsByteBuffer(); + if (byteBuffer == null || byteBuffer.remaining() != Byte.BYTES) { + return defaultValue; + } + return getValueAsByteBuffer().get(); + } + + /** + * Get attribute value as Integer, or null if malformed (e.g., length is not 4 bytes). + */ + public Integer getValueAsInteger() { + final ByteBuffer byteBuffer = getValueAsByteBuffer(); + if (byteBuffer == null || byteBuffer.remaining() != Integer.BYTES) { + return null; + } + return byteBuffer.getInt(); + } + + /** + * Get attribute value as Int, default value if malformed. + */ + public int getValueAsInt(int defaultValue) { + final Integer value = getValueAsInteger(); + return (value != null) ? value : defaultValue; + } + + /** + * Get attribute value as InetAddress. + * + * @return the InetAddress instance representation of attribute value or null if IP address + * is of illegal length. + */ + @Nullable + public InetAddress getValueAsInetAddress() { + if (nla_value == null) return null; + + try { + return InetAddress.getByAddress(nla_value); + } catch (UnknownHostException ignored) { + return null; + } + } + + /** + * Get attribute value as MacAddress. + * + * @return the MacAddress instance representation of attribute value or null if the given byte + * array is not a valid representation(e.g, not all link layers have 6-byte link-layer + * addresses) + */ + @Nullable + public MacAddress getValueAsMacAddress() { + if (nla_value == null) return null; + + try { + return MacAddress.fromBytes(nla_value); + } catch (IllegalArgumentException ignored) { + return null; + } + } + + /** + * Get attribute value as a unicode string. + * + * @return a unicode string or null if UTF-8 charset is not supported. + */ + @Nullable + public String getValueAsString() { + if (nla_value == null) return null; + // Check the attribute value length after removing string termination flag '\0'. + // This assumes that all netlink strings are null-terminated. + if (nla_value.length < (nla_len - NLA_HEADERLEN - 1)) return null; + + try { + final byte[] array = Arrays.copyOf(nla_value, nla_len - NLA_HEADERLEN - 1); + return new String(array, "UTF-8"); + } catch (UnsupportedEncodingException | NegativeArraySizeException ignored) { + return null; + } + } + + /** + * Write the netlink attribute to {@link ByteBuffer}. + */ + public void pack(ByteBuffer byteBuffer) { + final ByteOrder originalOrder = byteBuffer.order(); + final int originalPosition = byteBuffer.position(); + + byteBuffer.order(ByteOrder.nativeOrder()); + try { + byteBuffer.putShort(nla_len); + byteBuffer.putShort(nla_type); + if (nla_value != null) byteBuffer.put(nla_value); + } finally { + byteBuffer.order(originalOrder); + } + byteBuffer.position(originalPosition + getAlignedLength()); + } + + private void setValue(byte[] value) { + nla_value = value; + nla_len = (short) (NLA_HEADERLEN + ((nla_value != null) ? nla_value.length : 0)); + } + + @Override + public String toString() { + return "StructNlAttr{ " + + "nla_len{" + nla_len + "}, " + + "nla_type{" + nla_type + "}, " + + "nla_value{" + NetlinkConstants.hexify(nla_value) + "}, " + + "}"; + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgErr.java b/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgErr.java new file mode 100644 index 0000000000000000000000000000000000000000..b6620f3bdc346bf0887164404d24d1c2c79887c7 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgErr.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import java.nio.ByteBuffer; + +/** + * struct nlmsgerr + * + * see <linux_src>/include/uapi/linux/netlink.h + * + * @hide + */ +public class StructNlMsgErr { + public static final int STRUCT_SIZE = Integer.BYTES + StructNlMsgHdr.STRUCT_SIZE; + + private static boolean hasAvailableSpace(ByteBuffer byteBuffer) { + return byteBuffer != null && byteBuffer.remaining() >= STRUCT_SIZE; + } + + /** + * Parse a netlink error message payload from a {@link ByteBuffer}. + * + * @param byteBuffer The buffer from which to parse the netlink error message payload. + * @return the parsed netlink error message payload, or {@code null} if the netlink error + * message payload could not be parsed successfully (for example, if it was truncated). + */ + public static StructNlMsgErr parse(ByteBuffer byteBuffer) { + if (!hasAvailableSpace(byteBuffer)) return null; + + // The ByteOrder must have already been set by the caller. In most + // cases ByteOrder.nativeOrder() is correct, with the exception + // of usage within unittests. + final StructNlMsgErr struct = new StructNlMsgErr(); + struct.error = byteBuffer.getInt(); + struct.msg = StructNlMsgHdr.parse(byteBuffer); + return struct; + } + + public int error; + public StructNlMsgHdr msg; + + /** + * Write the netlink error message payload to {@link ByteBuffer}. + */ + public void pack(ByteBuffer byteBuffer) { + // The ByteOrder must have already been set by the caller. In most + // cases ByteOrder.nativeOrder() is correct, with the possible + // exception of usage within unittests. + byteBuffer.putInt(error); + if (msg != null) { + msg.pack(byteBuffer); + } + } + + @Override + public String toString() { + return "StructNlMsgErr{ " + + "error{" + error + "}, " + + "msg{" + (msg == null ? "" : msg.toString()) + "} " + + "}"; + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgHdr.java b/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgHdr.java new file mode 100644 index 0000000000000000000000000000000000000000..5052cb8a06da1df51c7a7daececc30c0aef075cc --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgHdr.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.nio.ByteBuffer; + +/** + * struct nlmsghdr + * + * see <linux_src>/include/uapi/linux/netlink.h + * + * @hide + */ +public class StructNlMsgHdr { + // Already aligned. + public static final int STRUCT_SIZE = 16; + + public static final short NLM_F_REQUEST = 0x0001; + public static final short NLM_F_MULTI = 0x0002; + public static final short NLM_F_ACK = 0x0004; + public static final short NLM_F_ECHO = 0x0008; + // Flags for a GET request. + public static final short NLM_F_ROOT = 0x0100; + public static final short NLM_F_MATCH = 0x0200; + public static final short NLM_F_DUMP = NLM_F_ROOT | NLM_F_MATCH; + // Flags for a NEW request. + public static final short NLM_F_REPLACE = 0x100; + public static final short NLM_F_EXCL = 0x200; + public static final short NLM_F_CREATE = 0x400; + public static final short NLM_F_APPEND = 0x800; + + // TODO: Probably need to distinguish the flags which have the same value. For example, + // NLM_F_MATCH (0x200) and NLM_F_EXCL (0x200). + private static String stringForNlMsgFlags(short flags) { + final StringBuilder sb = new StringBuilder(); + if ((flags & NLM_F_REQUEST) != 0) { + sb.append("NLM_F_REQUEST"); + } + if ((flags & NLM_F_MULTI) != 0) { + if (sb.length() > 0) { + sb.append("|"); + } + sb.append("NLM_F_MULTI"); + } + if ((flags & NLM_F_ACK) != 0) { + if (sb.length() > 0) { + sb.append("|"); + } + sb.append("NLM_F_ACK"); + } + if ((flags & NLM_F_ECHO) != 0) { + if (sb.length() > 0) { + sb.append("|"); + } + sb.append("NLM_F_ECHO"); + } + if ((flags & NLM_F_ROOT) != 0) { + if (sb.length() > 0) { + sb.append("|"); + } + sb.append("NLM_F_ROOT"); + } + if ((flags & NLM_F_MATCH) != 0) { + if (sb.length() > 0) { + sb.append("|"); + } + sb.append("NLM_F_MATCH"); + } + return sb.toString(); + } + + private static boolean hasAvailableSpace(ByteBuffer byteBuffer) { + return byteBuffer != null && byteBuffer.remaining() >= STRUCT_SIZE; + } + + /** + * Parse netlink message header from buffer. + */ + @Nullable + public static StructNlMsgHdr parse(@NonNull ByteBuffer byteBuffer) { + if (!hasAvailableSpace(byteBuffer)) return null; + + // The ByteOrder must have already been set by the caller. In most + // cases ByteOrder.nativeOrder() is correct, with the exception + // of usage within unittests. + final StructNlMsgHdr struct = new StructNlMsgHdr(); + struct.nlmsg_len = byteBuffer.getInt(); + struct.nlmsg_type = byteBuffer.getShort(); + struct.nlmsg_flags = byteBuffer.getShort(); + struct.nlmsg_seq = byteBuffer.getInt(); + struct.nlmsg_pid = byteBuffer.getInt(); + + if (struct.nlmsg_len < STRUCT_SIZE) { + // Malformed. + return null; + } + return struct; + } + + public int nlmsg_len; + public short nlmsg_type; + public short nlmsg_flags; + public int nlmsg_seq; + public int nlmsg_pid; + + public StructNlMsgHdr() { + nlmsg_len = 0; + nlmsg_type = 0; + nlmsg_flags = 0; + nlmsg_seq = 0; + nlmsg_pid = 0; + } + + /** + * Write netlink message header to ByteBuffer. + */ + public void pack(ByteBuffer byteBuffer) { + // The ByteOrder must have already been set by the caller. In most + // cases ByteOrder.nativeOrder() is correct, with the possible + // exception of usage within unittests. + byteBuffer.putInt(nlmsg_len); + byteBuffer.putShort(nlmsg_type); + byteBuffer.putShort(nlmsg_flags); + byteBuffer.putInt(nlmsg_seq); + byteBuffer.putInt(nlmsg_pid); + } + + @Override + public String toString() { + return toString(null /* unknown netlink family */); + } + + /** + * Transform a netlink header into a string. The netlink family is required for transforming + * a netlink type integer into a string. + * @param nlFamily netlink family. Using Integer will not incur autoboxing penalties because + * family values are small, and all Integer objects between -128 and 127 are + * statically cached. See Integer.IntegerCache. + * @return A list of header elements. + */ + @NonNull + public String toString(@Nullable Integer nlFamily) { + final String typeStr = "" + nlmsg_type + + "(" + (nlFamily == null + ? "" : NetlinkConstants.stringForNlMsgType(nlmsg_type, nlFamily)) + + ")"; + final String flagsStr = "" + nlmsg_flags + + "(" + stringForNlMsgFlags(nlmsg_flags) + ")"; + return "StructNlMsgHdr{ " + + "nlmsg_len{" + nlmsg_len + "}, " + + "nlmsg_type{" + typeStr + "}, " + + "nlmsg_flags{" + flagsStr + "}, " + + "nlmsg_seq{" + nlmsg_seq + "}, " + + "nlmsg_pid{" + nlmsg_pid + "} " + + "}"; + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructRtMsg.java b/staticlibs/device/com/android/net/module/util/netlink/StructRtMsg.java new file mode 100644 index 0000000000000000000000000000000000000000..3cd729222b3556b9240226d6ab1ca08522a273e2 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/StructRtMsg.java @@ -0,0 +1,95 @@ +/* + * 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. + */ + +package com.android.net.module.util.netlink; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +import java.nio.ByteBuffer; + +/** + * struct rtmsg + * + * see also: + * + * include/uapi/linux/rtnetlink.h + * + * @hide + */ +public class StructRtMsg extends Struct { + // Already aligned. + public static final int STRUCT_SIZE = 12; + + @Field(order = 0, type = Type.U8) + public final short family; // Address family of route. + @Field(order = 1, type = Type.U8) + public final short dstLen; // Length of destination. + @Field(order = 2, type = Type.U8) + public final short srcLen; // Length of source. + @Field(order = 3, type = Type.U8) + public final short tos; // TOS filter. + @Field(order = 4, type = Type.U8) + public final short table; // Routing table ID. + @Field(order = 5, type = Type.U8) + public final short protocol; // Routing protocol. + @Field(order = 6, type = Type.U8) + public final short scope; // distance to the destination. + @Field(order = 7, type = Type.U8) + public final short type; // route type + @Field(order = 8, type = Type.U32) + public final long flags; + + StructRtMsg(short family, short dstLen, short srcLen, short tos, short table, short protocol, + short scope, short type, long flags) { + this.family = family; + this.dstLen = dstLen; + this.srcLen = srcLen; + this.tos = tos; + this.table = table; + this.protocol = protocol; + this.scope = scope; + this.type = type; + this.flags = flags; + } + + /** + * Parse a rtmsg struct from a {@link ByteBuffer}. + * + * @param byteBuffer The buffer from which to parse the rtmsg struct. + * @return the parsed rtmsg struct, or {@code null} if the rtmsg struct could not be + * parsed successfully (for example, if it was truncated). + */ + @Nullable + public static StructRtMsg parse(@NonNull final ByteBuffer byteBuffer) { + if (byteBuffer.remaining() < STRUCT_SIZE) return null; + + // The ByteOrder must already have been set to native order. + return Struct.parse(StructRtMsg.class, byteBuffer); + } + + /** + * Write the rtmsg struct to {@link ByteBuffer}. + */ + public void pack(@NonNull final ByteBuffer byteBuffer) { + // The ByteOrder must already have been set to native order. + this.writeToByteBuffer(byteBuffer); + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructRtaCacheInfo.java b/staticlibs/device/com/android/net/module/util/netlink/StructRtaCacheInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..fef1f9e8131f123884e9b7c18b204a6e32f32ff3 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/StructRtaCacheInfo.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +import java.nio.ByteBuffer; + +/** + * struct rta_cacheinfo + * + * see also: + * + * include/uapi/linux/rtnetlink.h + * + * @hide + */ +public class StructRtaCacheInfo extends Struct { + // Already aligned. + public static final int STRUCT_SIZE = 32; + + @Field(order = 0, type = Type.U32) + public final long clntref; + @Field(order = 1, type = Type.U32) + public final long lastuse; + @Field(order = 2, type = Type.S32) + public final int expires; + @Field(order = 3, type = Type.U32) + public final long error; + @Field(order = 4, type = Type.U32) + public final long used; + @Field(order = 5, type = Type.U32) + public final long id; + @Field(order = 6, type = Type.U32) + public final long ts; + @Field(order = 7, type = Type.U32) + public final long tsage; + + StructRtaCacheInfo(long clntref, long lastuse, int expires, long error, long used, long id, + long ts, long tsage) { + this.clntref = clntref; + this.lastuse = lastuse; + this.expires = expires; + this.error = error; + this.used = used; + this.id = id; + this.ts = ts; + this.tsage = tsage; + } + + /** + * Parse an rta_cacheinfo struct from a {@link ByteBuffer}. + * + * @param byteBuffer The buffer from which to parse the rta_cacheinfo. + * @return the parsed rta_cacheinfo struct, or {@code null} if the rta_cacheinfo struct + * could not be parsed successfully (for example, if it was truncated). + */ + @Nullable + public static StructRtaCacheInfo parse(@NonNull final ByteBuffer byteBuffer) { + if (byteBuffer.remaining() < STRUCT_SIZE) return null; + + // The ByteOrder must already have been set to native order. + return Struct.parse(StructRtaCacheInfo.class, byteBuffer); + } + + /** + * Write a rta_cacheinfo struct to {@link ByteBuffer}. + */ + public void pack(@NonNull final ByteBuffer byteBuffer) { + // The ByteOrder must already have been set to native order. + this.writeToByteBuffer(byteBuffer); + } +} diff --git a/staticlibs/device/com/android/net/module/util/structs/EthernetHeader.java b/staticlibs/device/com/android/net/module/util/structs/EthernetHeader.java new file mode 100644 index 0000000000000000000000000000000000000000..92ef8a7e16524a09ca4758c26f52426386bb6092 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/structs/EthernetHeader.java @@ -0,0 +1,60 @@ +/* + * 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. + */ + +package com.android.net.module.util.structs; + +import android.net.MacAddress; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +/** + * L2 ethernet header as per IEEE 802.3. Does not include a 802.1Q tag. + * + * 0 1 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Destination | + * +- -+ + * | Ethernet | + * +- -+ + * | Address | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Source | + * +- -+ + * | Ethernet | + * +- -+ + * | Address | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | EtherType | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +public class EthernetHeader extends Struct { + @Field(order = 0, type = Type.EUI48) + public final MacAddress dstMac; + @Field(order = 1, type = Type.EUI48) + public final MacAddress srcMac; + @Field(order = 2, type = Type.U16) + public final int etherType; + + public EthernetHeader(final MacAddress dstMac, final MacAddress srcMac, + final int etherType) { + this.dstMac = dstMac; + this.srcMac = srcMac; + this.etherType = etherType; + } +} diff --git a/staticlibs/device/com/android/net/module/util/structs/IaAddressOption.java b/staticlibs/device/com/android/net/module/util/structs/IaAddressOption.java new file mode 100644 index 0000000000000000000000000000000000000000..575a1f367f5b47c52a62bafeddb453fce871ce56 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/structs/IaAddressOption.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.structs; + +import static com.android.net.module.util.NetworkStackConstants.DHCP6_OPTION_IA_ADDR; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +import java.net.Inet6Address; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * DHCPv6 IA Address option. + * https://tools.ietf.org/html/rfc8415. This does not contain any option. + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | OPTION_IAADDR | option-len | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * | IPv6-address | + * | | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | preferred-lifetime | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | valid-lifetime | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * . . + * . IAaddr-options . + * . . + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +public class IaAddressOption extends Struct { + public static final int LENGTH = 24; // option length excluding IAaddr-options + + @Field(order = 0, type = Type.S16) + public final short code; + @Field(order = 1, type = Type.S16) + public final short length; + @Field(order = 2, type = Type.Ipv6Address) + public final Inet6Address address; + @Field(order = 3, type = Type.U32) + public final long preferred; + @Field(order = 4, type = Type.U32) + public final long valid; + + IaAddressOption(final short code, final short length, final Inet6Address address, + final long preferred, final long valid) { + this.code = code; + this.length = length; + this.address = address; + this.preferred = preferred; + this.valid = valid; + } + + /** + * Build an IA Address option from the required specific parameters. + */ + public static ByteBuffer build(final short length, final long id, final Inet6Address address, + final long preferred, final long valid) { + final IaAddressOption option = new IaAddressOption((short) DHCP6_OPTION_IA_ADDR, + length /* 24 + IAaddr-options length */, address, preferred, valid); + return ByteBuffer.wrap(option.writeToBytes(ByteOrder.BIG_ENDIAN)); + } +} diff --git a/staticlibs/device/com/android/net/module/util/structs/IaPdOption.java b/staticlibs/device/com/android/net/module/util/structs/IaPdOption.java new file mode 100644 index 0000000000000000000000000000000000000000..dbf79dcbaff5a56d82958df483cfba7811254e49 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/structs/IaPdOption.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.structs; + +import static com.android.net.module.util.NetworkStackConstants.DHCP6_OPTION_IA_PD; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * DHCPv6 IA_PD option. + * https://tools.ietf.org/html/rfc8415. This does not contain any option. + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | OPTION_IA_PD | option-len | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | IAID (4 octets) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | T1 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | T2 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * . . + * . IA_PD-options . + * . . + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + */ +public class IaPdOption extends Struct { + public static final int LENGTH = 12; // option length excluding IA_PD options + + @Field(order = 0, type = Type.S16) + public final short code; + @Field(order = 1, type = Type.S16) + public final short length; + @Field(order = 2, type = Type.U32) + public final long id; + @Field(order = 3, type = Type.U32) + public final long t1; + @Field(order = 4, type = Type.U32) + public final long t2; + + IaPdOption(final short code, final short length, final long id, final long t1, + final long t2) { + this.code = code; + this.length = length; + this.id = id; + this.t1 = t1; + this.t2 = t2; + } + + /** + * Build an IA_PD option from the required specific parameters. + */ + public static ByteBuffer build(final short length, final long id, final long t1, + final long t2) { + final IaPdOption option = new IaPdOption((short) DHCP6_OPTION_IA_PD, + length /* 12 + IA_PD options length */, id, t1, t2); + return ByteBuffer.wrap(option.writeToBytes(ByteOrder.BIG_ENDIAN)); + } +} diff --git a/staticlibs/device/com/android/net/module/util/structs/IaPrefixOption.java b/staticlibs/device/com/android/net/module/util/structs/IaPrefixOption.java new file mode 100644 index 0000000000000000000000000000000000000000..59d655ca8be48a9a20a978b540220c498b127cbd --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/structs/IaPrefixOption.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.structs; + +import static com.android.net.module.util.NetworkStackConstants.DHCP6_OPTION_IAPREFIX; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * DHCPv6 IA Prefix Option. + * https://tools.ietf.org/html/rfc8415. This does not contain any option. + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | OPTION_IAPREFIX | option-len | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | preferred-lifetime | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | valid-lifetime | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | prefix-length | | + * +-+-+-+-+-+-+-+-+ IPv6-prefix | + * | (16 octets) | + * | | + * | | + * | | + * | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | . + * +-+-+-+-+-+-+-+-+ . + * . IAprefix-options . + * . . + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +public class IaPrefixOption extends Struct { + public static final int LENGTH = 25; // option length excluding IAprefix-options + + @Field(order = 0, type = Type.S16) + public final short code; + @Field(order = 1, type = Type.S16) + public final short length; + @Field(order = 2, type = Type.U32) + public final long preferred; + @Field(order = 3, type = Type.U32) + public final long valid; + @Field(order = 4, type = Type.S8) + public final byte prefixLen; + @Field(order = 5, type = Type.ByteArray, arraysize = 16) + public final byte[] prefix; + + public IaPrefixOption(final short code, final short length, final long preferred, + final long valid, final byte prefixLen, final byte[] prefix) { + this.code = code; + this.length = length; + this.preferred = preferred; + this.valid = valid; + this.prefixLen = prefixLen; + this.prefix = prefix.clone(); + } + + /** + * Build an IA_PD prefix option with given specific parameters. + */ + public static ByteBuffer build(final short length, final long preferred, final long valid, + final byte prefixLen, final byte[] prefix) { + final IaPrefixOption option = new IaPrefixOption((byte) DHCP6_OPTION_IAPREFIX, + length /* 25 + IAPrefix options length */, preferred, valid, prefixLen, prefix); + return ByteBuffer.wrap(option.writeToBytes(ByteOrder.BIG_ENDIAN)); + } +} diff --git a/staticlibs/device/com/android/net/module/util/structs/Icmpv4Header.java b/staticlibs/device/com/android/net/module/util/structs/Icmpv4Header.java new file mode 100644 index 0000000000000000000000000000000000000000..454bb0747a10aa938ca06d47fa58fbd2617acd0d --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/structs/Icmpv4Header.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.net.module.util.structs; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +/** + * ICMPv4 header as per https://tools.ietf.org/html/rfc792. + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Type | Code | Checksum | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +public class Icmpv4Header extends Struct { + @Field(order = 0, type = Type.U8) + public short type; + @Field(order = 1, type = Type.U8) + public short code; + @Field(order = 2, type = Type.S16) + public short checksum; + public Icmpv4Header(final short type, final short code, final short checksum) { + this.type = type; + this.code = code; + this.checksum = checksum; + } +} diff --git a/staticlibs/device/com/android/net/module/util/structs/Icmpv6Header.java b/staticlibs/device/com/android/net/module/util/structs/Icmpv6Header.java new file mode 100644 index 0000000000000000000000000000000000000000..c82ae02b0dae60201afa8500c70f6c6de968ca7c --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/structs/Icmpv6Header.java @@ -0,0 +1,45 @@ +/* + * 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. + */ + +package com.android.net.module.util.structs; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +/** + * ICMPv6 header as per https://tools.ietf.org/html/rfc4443. + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Type | Code | Checksum | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +public class Icmpv6Header extends Struct { + @Field(order = 0, type = Type.U8) + public short type; + @Field(order = 1, type = Type.U8) + public short code; + @Field(order = 2, type = Type.S16) + public short checksum; + + public Icmpv6Header(final short type, final short code, final short checksum) { + this.type = type; + this.code = code; + this.checksum = checksum; + } +} diff --git a/staticlibs/device/com/android/net/module/util/structs/Ipv4Header.java b/staticlibs/device/com/android/net/module/util/structs/Ipv4Header.java new file mode 100644 index 0000000000000000000000000000000000000000..5249454c44605bd32bd6e7a6579a4e611b23b3a7 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/structs/Ipv4Header.java @@ -0,0 +1,94 @@ +/* + * 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. + */ + +package com.android.net.module.util.structs; + +import androidx.annotation.VisibleForTesting; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +import java.net.Inet4Address; + +/** + * L3 IPv4 header as per https://tools.ietf.org/html/rfc791. + * This class doesn't contain options field. + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |Version| IHL |Type of Service| Total Length | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Identification |Flags| Fragment Offset | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Time to Live | Protocol | Header Checksum | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Source Address | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Destination Address | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +public class Ipv4Header extends Struct { + // IP Version=IPv4, IHL is always 5(*4bytes) because options are not supported. + @VisibleForTesting + public static final byte IPHDR_VERSION_IHL = 0x45; + + @Field(order = 0, type = Type.S8) + // version (4 bits), IHL (4 bits) + public final byte vi; + @Field(order = 1, type = Type.S8) + public final byte tos; + @Field(order = 2, type = Type.U16) + public final int totalLength; + @Field(order = 3, type = Type.S16) + public final short id; + @Field(order = 4, type = Type.S16) + // flags (3 bits), fragment offset (13 bits) + public final short flagsAndFragmentOffset; + @Field(order = 5, type = Type.U8) + public final short ttl; + @Field(order = 6, type = Type.S8) + public final byte protocol; + @Field(order = 7, type = Type.S16) + public final short checksum; + @Field(order = 8, type = Type.Ipv4Address) + public final Inet4Address srcIp; + @Field(order = 9, type = Type.Ipv4Address) + public final Inet4Address dstIp; + + public Ipv4Header(final byte tos, final int totalLength, final short id, + final short flagsAndFragmentOffset, final short ttl, final byte protocol, + final short checksum, final Inet4Address srcIp, final Inet4Address dstIp) { + this(IPHDR_VERSION_IHL, tos, totalLength, id, flagsAndFragmentOffset, ttl, + protocol, checksum, srcIp, dstIp); + } + + private Ipv4Header(final byte vi, final byte tos, final int totalLength, final short id, + final short flagsAndFragmentOffset, final short ttl, final byte protocol, + final short checksum, final Inet4Address srcIp, final Inet4Address dstIp) { + this.vi = vi; + this.tos = tos; + this.totalLength = totalLength; + this.id = id; + this.flagsAndFragmentOffset = flagsAndFragmentOffset; + this.ttl = ttl; + this.protocol = protocol; + this.checksum = checksum; + this.srcIp = srcIp; + this.dstIp = dstIp; + } +} diff --git a/staticlibs/device/com/android/net/module/util/structs/Ipv6Header.java b/staticlibs/device/com/android/net/module/util/structs/Ipv6Header.java new file mode 100644 index 0000000000000000000000000000000000000000..a14e064d10d30c7a2714a4926536aa95391a46e0 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/structs/Ipv6Header.java @@ -0,0 +1,75 @@ +/* + * 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. + */ + +package com.android.net.module.util.structs; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +import java.net.Inet6Address; + +/** + * L3 IPv6 header as per https://tools.ietf.org/html/rfc8200. + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |Version| Traffic Class | Flow Label | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Payload Length | Next Header | Hop Limit | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * + + + * | | + * + Source Address + + * | | + * + + + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * + + + * | | + * + Destination Address + + * | | + * + + + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +public class Ipv6Header extends Struct { + @Field(order = 0, type = Type.S32) + public int vtf; + @Field(order = 1, type = Type.U16) + public int payloadLength; + @Field(order = 2, type = Type.S8) + public byte nextHeader; + @Field(order = 3, type = Type.U8) + public short hopLimit; + @Field(order = 4, type = Type.Ipv6Address) + public Inet6Address srcIp; + @Field(order = 5, type = Type.Ipv6Address) + public Inet6Address dstIp; + + public Ipv6Header(final int vtf, final int payloadLength, final byte nextHeader, + final short hopLimit, final Inet6Address srcIp, final Inet6Address dstIp) { + this.vtf = vtf; + this.payloadLength = payloadLength; + this.nextHeader = nextHeader; + this.hopLimit = hopLimit; + this.srcIp = srcIp; + this.dstIp = dstIp; + } +} diff --git a/staticlibs/device/com/android/net/module/util/structs/Ipv6PktInfo.java b/staticlibs/device/com/android/net/module/util/structs/Ipv6PktInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..0dccb72723fd451702a1cf342d03f0d042ae95ee --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/structs/Ipv6PktInfo.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.structs; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +import java.net.Inet6Address; + +/** + * structure in6_pktinfo + * + * see also: + * + * include/uapi/linux/ipv6.h + */ +public class Ipv6PktInfo extends Struct { + @Field(order = 0, type = Type.Ipv6Address) + public final Inet6Address addr; // IPv6 source or destination address + @Field(order = 1, type = Type.S32) + public final int ifindex; // interface index + + public Ipv6PktInfo(final Inet6Address addr, final int ifindex) { + this.addr = addr; + this.ifindex = ifindex; + } +} diff --git a/staticlibs/device/com/android/net/module/util/structs/LlaOption.java b/staticlibs/device/com/android/net/module/util/structs/LlaOption.java new file mode 100644 index 0000000000000000000000000000000000000000..fbaccabcca8fa1d98a76d22eebd6ed77a410b5b7 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/structs/LlaOption.java @@ -0,0 +1,61 @@ +/* + * 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. + */ + +package com.android.net.module.util.structs; + +import android.net.MacAddress; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * ICMPv6 source/target link-layer address option, as per https://tools.ietf.org/html/rfc4861. + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Type | Length | Link-Layer Address ... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +public class LlaOption extends Struct { + @Field(order = 0, type = Type.S8) + public final byte type; + @Field(order = 1, type = Type.S8) + public final byte length; // Length in 8-byte units + @Field(order = 2, type = Type.EUI48) + // Link layer address length and format varies on different link layers, which is not + // guaranteed to be a 6-byte MAC address. However, Struct only supports 6-byte MAC + // addresses type(EUI-48) for now. + public final MacAddress linkLayerAddress; + + LlaOption(final byte type, final byte length, final MacAddress linkLayerAddress) { + this.type = type; + this.length = length; + this.linkLayerAddress = linkLayerAddress; + } + + /** + * Build a target link-layer address option from the required specified parameters. + */ + public static ByteBuffer build(final byte type, final MacAddress linkLayerAddress) { + final LlaOption option = new LlaOption(type, (byte) 1 /* option len */, linkLayerAddress); + return ByteBuffer.wrap(option.writeToBytes(ByteOrder.BIG_ENDIAN)); + } +} diff --git a/staticlibs/device/com/android/net/module/util/structs/MtuOption.java b/staticlibs/device/com/android/net/module/util/structs/MtuOption.java new file mode 100644 index 0000000000000000000000000000000000000000..34bc21c140f4af0a924668e3dd8e131257333548 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/structs/MtuOption.java @@ -0,0 +1,65 @@ +/* + * 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. + */ + +package com.android.net.module.util.structs; + +import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_MTU; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * ICMPv6 MTU option, as per https://tools.ietf.org/html/rfc4861. + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Type | Length | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | MTU | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +public class MtuOption extends Struct { + @Field(order = 0, type = Type.S8) + public final byte type; + @Field(order = 1, type = Type.S8) + public final byte length; // Length in 8-byte units + @Field(order = 2, type = Type.S16) + public final short reserved; + @Field(order = 3, type = Type.U32) + public final long mtu; + + MtuOption(final byte type, final byte length, final short reserved, + final long mtu) { + this.type = type; + this.length = length; + this.reserved = reserved; + this.mtu = mtu; + } + + /** + * Build a MTU option from the required specified parameters. + */ + public static ByteBuffer build(final long mtu) { + final MtuOption option = new MtuOption((byte) ICMPV6_ND_OPTION_MTU, + (byte) 1 /* option len */, (short) 0 /* reserved */, mtu); + return ByteBuffer.wrap(option.writeToBytes(ByteOrder.BIG_ENDIAN)); + } +} diff --git a/staticlibs/device/com/android/net/module/util/structs/NaHeader.java b/staticlibs/device/com/android/net/module/util/structs/NaHeader.java new file mode 100644 index 0000000000000000000000000000000000000000..90c078e8c6f516a72bc42a5178a02f1a8efc5c99 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/structs/NaHeader.java @@ -0,0 +1,57 @@ +/* + * 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. + */ + +package com.android.net.module.util.structs; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +import java.net.Inet6Address; + +/** + * ICMPv6 Neighbor Advertisement header, follow {@link Icmpv6Header}, as per + * https://tools.ietf.org/html/rfc4861. This does not contain any option. + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Type | Code | Checksum | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |R|S|O| Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * + + + * | | + * + Target Address + + * | | + * + + + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Options ... + * +-+-+-+-+-+-+-+-+-+-+-+- + */ +public class NaHeader extends Struct { + @Field(order = 0, type = Type.S32) + public int flags; // Router flag, Solicited flag, Override flag and 29 Reserved bits. + @Field(order = 1, type = Type.Ipv6Address) + public Inet6Address target; + + public NaHeader(final int flags, final Inet6Address target) { + this.flags = flags; + this.target = target; + } +} diff --git a/staticlibs/device/com/android/net/module/util/structs/NsHeader.java b/staticlibs/device/com/android/net/module/util/structs/NsHeader.java new file mode 100644 index 0000000000000000000000000000000000000000..2e8b77ba11597f6a4b1a212dbc13d87ce5a3ce8a --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/structs/NsHeader.java @@ -0,0 +1,61 @@ +/* + * 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. + */ + +package com.android.net.module.util.structs; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +import java.net.Inet6Address; + +/** + * ICMPv6 Neighbor Solicitation header, follow {@link Icmpv6Header}, as per + * https://tools.ietf.org/html/rfc4861. This does not contain any option. + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Type | Code | Checksum | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * + + + * | | + * + Target Address + + * | | + * + + + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Options ... + * +-+-+-+-+-+-+-+-+-+-+-+- + */ +public class NsHeader extends Struct { + @Field(order = 0, type = Type.S32) + public int reserved; // 32 Reserved bits. + @Field(order = 1, type = Type.Ipv6Address) + public Inet6Address target; + + NsHeader(int reserved, final Inet6Address target) { + this.reserved = reserved; + this.target = target; + } + + public NsHeader(final Inet6Address target) { + this(0, target); + } +} diff --git a/staticlibs/device/com/android/net/module/util/structs/PrefixInformationOption.java b/staticlibs/device/com/android/net/module/util/structs/PrefixInformationOption.java new file mode 100644 index 0000000000000000000000000000000000000000..49d7654d3a843fdae35f59a94313dadd4973fb93 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/structs/PrefixInformationOption.java @@ -0,0 +1,98 @@ +/* + * 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. + */ + +package com.android.net.module.util.structs; + +import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_PIO; + +import android.net.IpPrefix; + +import androidx.annotation.NonNull; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * ICMPv6 prefix information option, as per https://tools.ietf.org/html/rfc4861. + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Type | Length | Prefix Length |L|A| Reserved1 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Valid Lifetime | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Preferred Lifetime | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Reserved2 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * + + + * | | + * + Prefix + + * | | + * + + + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +public class PrefixInformationOption extends Struct { + @Field(order = 0, type = Type.S8) + public final byte type; + @Field(order = 1, type = Type.S8) + public final byte length; // Length in 8-byte units + @Field(order = 2, type = Type.S8) + public final byte prefixLen; + @Field(order = 3, type = Type.S8) + // On-link flag, Autonomous address configuration flag, 6-reserved bits + public final byte flags; + @Field(order = 4, type = Type.U32) + public final long validLifetime; + @Field(order = 5, type = Type.U32) + public final long preferredLifetime; + @Field(order = 6, type = Type.S32) + public final int reserved; + @Field(order = 7, type = Type.ByteArray, arraysize = 16) + public final byte[] prefix; + + PrefixInformationOption(final byte type, final byte length, final byte prefixLen, + final byte flags, final long validLifetime, final long preferredLifetime, + final int reserved, @NonNull final byte[] prefix) { + this.type = type; + this.length = length; + this.prefixLen = prefixLen; + this.flags = flags; + this.validLifetime = validLifetime; + this.preferredLifetime = preferredLifetime; + this.reserved = reserved; + this.prefix = prefix; + } + + /** + * Build a Prefix Information option from the required specified parameters. + */ + public static ByteBuffer build(final IpPrefix prefix, final byte flags, + final long validLifetime, final long preferredLifetime) { + final PrefixInformationOption option = new PrefixInformationOption( + (byte) ICMPV6_ND_OPTION_PIO, (byte) 4 /* option len */, + (byte) prefix.getPrefixLength(), flags, validLifetime, preferredLifetime, + (int) 0, prefix.getRawAddress()); + return ByteBuffer.wrap(option.writeToBytes(ByteOrder.BIG_ENDIAN)); + } +} diff --git a/staticlibs/device/com/android/net/module/util/structs/RaHeader.java b/staticlibs/device/com/android/net/module/util/structs/RaHeader.java new file mode 100644 index 0000000000000000000000000000000000000000..31a5cb76fce8c36b62d63a6cc1a82ce240fb0bb6 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/structs/RaHeader.java @@ -0,0 +1,62 @@ +/* + * 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. + */ + +package com.android.net.module.util.structs; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +/** + * ICMPv6 Router Advertisement header, follow [Icmpv6Header], as per + * https://tools.ietf.org/html/rfc4861. This does not contain any option. + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Type | Code | Checksum | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Cur Hop Limit |M|O| Reserved | Router Lifetime | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Reachable Time | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Retrans Timer | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Options ... + * +-+-+-+-+-+-+-+-+-+-+-+- + */ +public class RaHeader extends Struct { + @Field(order = 0, type = Type.S8) + public final byte hopLimit; + // "Managed address configuration", "Other configuration" bits, and 6 reserved bits + @Field(order = 1, type = Type.S8) + public final byte flags; + @Field(order = 2, type = Type.U16) + public final int lifetime; + @Field(order = 3, type = Type.U32) + public final long reachableTime; + @Field(order = 4, type = Type.U32) + public final long retransTimer; + + public RaHeader(final byte hopLimit, final byte flags, final int lifetime, + final long reachableTime, final long retransTimer) { + this.hopLimit = hopLimit; + this.flags = flags; + this.lifetime = lifetime; + this.reachableTime = reachableTime; + this.retransTimer = retransTimer; + } +} diff --git a/staticlibs/device/com/android/net/module/util/structs/RdnssOption.java b/staticlibs/device/com/android/net/module/util/structs/RdnssOption.java new file mode 100644 index 0000000000000000000000000000000000000000..4a5bd7e9f82cd350da91eaea85b96514b604748a --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/structs/RdnssOption.java @@ -0,0 +1,92 @@ +/* + * 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. + */ + +package com.android.net.module.util.structs; + +import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_RDNSS; + +import android.net.InetAddresses; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +import java.net.Inet6Address; +import java.nio.ByteBuffer; + +/** + * IPv6 RA recursive DNS server option, as per https://tools.ietf.org/html/rfc8106. + * This should be followed by a series of DNSv6 server addresses. + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Type | Length | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Lifetime | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * : Addresses of IPv6 Recursive DNS Servers : + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +public class RdnssOption extends Struct { + @Field(order = 0, type = Type.S8) + public final byte type; + @Field(order = 1, type = Type.S8) + public final byte length; // Length in 8-byte units + @Field(order = 2, type = Type.S16) + public final short reserved; + @Field(order = 3, type = Type.U32) + public final long lifetime; + + public RdnssOption(final byte type, final byte length, final short reserved, + final long lifetime) { + this.type = type; + this.length = length; + this.reserved = reserved; + this.lifetime = lifetime; + } + + /** + * Build a RDNSS option from the required specified Inet6Address parameters. + */ + public static ByteBuffer build(final long lifetime, final Inet6Address... servers) { + final byte length = (byte) (1 + 2 * servers.length); + final RdnssOption option = new RdnssOption((byte) ICMPV6_ND_OPTION_RDNSS, + length, (short) 0, lifetime); + final ByteBuffer buffer = ByteBuffer.allocate(length * 8); + option.writeToByteBuffer(buffer); + for (Inet6Address server : servers) { + buffer.put(server.getAddress()); + } + buffer.flip(); + return buffer; + } + + /** + * Build a RDNSS option from the required specified String parameters. + * + * @throws IllegalArgumentException if {@code servers} does not contain only numeric addresses. + */ + public static ByteBuffer build(final long lifetime, final String... servers) { + final Inet6Address[] serverArray = new Inet6Address[servers.length]; + for (int i = 0; i < servers.length; i++) { + serverArray[i] = (Inet6Address) InetAddresses.parseNumericAddress(servers[i]); + } + return build(lifetime, serverArray); + } +} diff --git a/staticlibs/device/com/android/net/module/util/structs/RouteInformationOption.java b/staticlibs/device/com/android/net/module/util/structs/RouteInformationOption.java new file mode 100644 index 0000000000000000000000000000000000000000..49bafedbedc2922281bfcb92484b8b6fd9f2a3c8 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/structs/RouteInformationOption.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.structs; + +import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_RIO; + +import android.net.IpPrefix; + +import androidx.annotation.NonNull; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * ICMPv6 route information option, as per https://tools.ietf.org/html/rfc4191. + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Type | Length | Prefix Length |Resvd|Prf|Resvd| + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Route Lifetime | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Prefix (Variable Length) | + * . . + * . . + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +public class RouteInformationOption extends Struct { + public enum Preference { + HIGH((byte) 0x1), + MEDIUM((byte) 0x0), + LOW((byte) 0x3), + RESERVED((byte) 0x2); + + final byte mValue; + Preference(byte value) { + this.mValue = value; + } + } + + @Field(order = 0, type = Type.S8) + public final byte type; + @Field(order = 1, type = Type.S8) + public final byte length; // Length in 8-byte octets + @Field(order = 2, type = Type.U8) + public final short prefixLen; + @Field(order = 3, type = Type.S8) + public final byte prf; + @Field(order = 4, type = Type.U32) + public final long routeLifetime; + @Field(order = 5, type = Type.ByteArray, arraysize = 16) + public final byte[] prefix; + + RouteInformationOption(final byte type, final byte length, final short prefixLen, + final byte prf, final long routeLifetime, @NonNull final byte[] prefix) { + this.type = type; + this.length = length; + this.prefixLen = prefixLen; + this.prf = prf; + this.routeLifetime = routeLifetime; + this.prefix = prefix; + } + + /** + * Build a Route Information option from the required specified parameters. + */ + public static ByteBuffer build(final IpPrefix prefix, final Preference prf, + final long routeLifetime) { + // The prefix field is always assumed to have 16 bytes, but the number of leading + // bits in this prefix depends on IpPrefix#prefixLength, then we can simply set the + // option length to 3. + final RouteInformationOption option = new RouteInformationOption( + (byte) ICMPV6_ND_OPTION_RIO, (byte) 3 /* option length */, + (short) prefix.getPrefixLength(), (byte) (prf.mValue << 3), routeLifetime, + prefix.getRawAddress()); + return ByteBuffer.wrap(option.writeToBytes(ByteOrder.BIG_ENDIAN)); + } +} diff --git a/staticlibs/device/com/android/net/module/util/structs/RsHeader.java b/staticlibs/device/com/android/net/module/util/structs/RsHeader.java new file mode 100644 index 0000000000000000000000000000000000000000..0b51ff25fdbeb36121a83c972d2a5844cb57f19a --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/structs/RsHeader.java @@ -0,0 +1,44 @@ +/* + * 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. + */ + +package com.android.net.module.util.structs; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +/** + * ICMPv6 Router Solicitation header, follow [Icmpv6Header], as per + * https://tools.ietf.org/html/rfc4861. This does not contain any option. + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Type | Code | Checksum | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Options ... + * +-+-+-+-+-+-+-+-+-+-+-+- + */ +public class RsHeader extends Struct { + @Field(order = 0, type = Type.S32) + public final int reserved; + + public RsHeader(final int reserved) { + this.reserved = reserved; + } +} diff --git a/staticlibs/device/com/android/net/module/util/structs/TcpHeader.java b/staticlibs/device/com/android/net/module/util/structs/TcpHeader.java new file mode 100644 index 0000000000000000000000000000000000000000..0c97401c785819805c4fe1b2b2ef9de65f2cb943 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/structs/TcpHeader.java @@ -0,0 +1,79 @@ +/* + * 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. + */ + +package com.android.net.module.util.structs; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +/** + * L4 TCP header as per https://tools.ietf.org/html/rfc793. + * This class does not contain option and data fields. + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Source Port | Destination Port | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Sequence Number | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Acknowledgment Number | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Data | |U|A|P|R|S|F| | + * | Offset| Reserved |R|C|S|S|Y|I| Window | + * | | |G|K|H|T|N|N| | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Checksum | Urgent Pointer | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Options | Padding | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | data | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +public class TcpHeader extends Struct { + @Field(order = 0, type = Type.U16) + public final int srcPort; + @Field(order = 1, type = Type.U16) + public final int dstPort; + @Field(order = 2, type = Type.U32) + public final long seq; + @Field(order = 3, type = Type.U32) + public final long ack; + @Field(order = 4, type = Type.S16) + // data Offset (4 bits), reserved (6 bits), control bits (6 bits) + // TODO: update with bitfields once class Struct supports it + public final short dataOffsetAndControlBits; + @Field(order = 5, type = Type.U16) + public final int window; + @Field(order = 6, type = Type.S16) + public final short checksum; + @Field(order = 7, type = Type.U16) + public final int urgentPointer; + + public TcpHeader(final int srcPort, final int dstPort, final long seq, final long ack, + final short dataOffsetAndControlBits, final int window, final short checksum, + final int urgentPointer) { + this.srcPort = srcPort; + this.dstPort = dstPort; + this.seq = seq; + this.ack = ack; + this.dataOffsetAndControlBits = dataOffsetAndControlBits; + this.window = window; + this.checksum = checksum; + this.urgentPointer = urgentPointer; + } +} diff --git a/staticlibs/device/com/android/net/module/util/structs/UdpHeader.java b/staticlibs/device/com/android/net/module/util/structs/UdpHeader.java new file mode 100644 index 0000000000000000000000000000000000000000..8b0316bf66a90b45bcff90e6039b37b08526f113 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/structs/UdpHeader.java @@ -0,0 +1,53 @@ +/* + * 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. + */ + +package com.android.net.module.util.structs; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +/** + * L4 UDP header as per https://tools.ietf.org/html/rfc768. + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Source Port | Destination Port | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Length | Checksum | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | data octets ... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ... + */ +public class UdpHeader extends Struct { + @Field(order = 0, type = Type.U16) + public final int srcPort; + @Field(order = 1, type = Type.U16) + public final int dstPort; + @Field(order = 2, type = Type.U16) + public final int length; + @Field(order = 3, type = Type.S16) + public final short checksum; + + public UdpHeader(final int srcPort, final int dstPort, final int length, + final short checksum) { + this.srcPort = srcPort; + this.dstPort = dstPort; + this.length = length; + this.checksum = checksum; + } +} diff --git a/staticlibs/device/com/android/net/module/util/wear/NetPacketHelpers.java b/staticlibs/device/com/android/net/module/util/wear/NetPacketHelpers.java new file mode 100644 index 0000000000000000000000000000000000000000..341c44be59d99001fd81938422b034188d9048fc --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/wear/NetPacketHelpers.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.wear; + +import com.android.net.module.util.async.ReadableByteBuffer; + +/** + * Implements utilities for decoding parts of TCP/UDP/IP headers. + * + * @hide + */ +final class NetPacketHelpers { + static void encodeNetworkUnsignedInt16(int value, byte[] dst, final int dstPos) { + dst[dstPos] = (byte) ((value >> 8) & 0xFF); + dst[dstPos + 1] = (byte) (value & 0xFF); + } + + static int decodeNetworkUnsignedInt16(byte[] data, final int pos) { + return ((data[pos] & 0xFF) << 8) | (data[pos + 1] & 0xFF); + } + + static int decodeNetworkUnsignedInt16(ReadableByteBuffer data, final int pos) { + return ((data.peek(pos) & 0xFF) << 8) | (data.peek(pos + 1) & 0xFF); + } + + private NetPacketHelpers() {} +} diff --git a/staticlibs/device/com/android/net/module/util/wear/PacketFile.java b/staticlibs/device/com/android/net/module/util/wear/PacketFile.java new file mode 100644 index 0000000000000000000000000000000000000000..7f5ed785b20b562e53adbd52bc3cf3c468329606 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/wear/PacketFile.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.wear; + +/** + * Defines bidirectional file where all transmissions are made as complete packets. + * + * Automatically manages all readability and writeability events in EventManager: + * - When read buffer has more space - asks EventManager to notify on more data + * - When write buffer has more space - asks the user to provide more data + * - When underlying file cannot accept more data - registers EventManager callback + * + * @hide + */ +public interface PacketFile { + /** @hide */ + public enum ErrorCode { + UNEXPECTED_ERROR, + IO_ERROR, + INBOUND_PACKET_TOO_LARGE, + OUTBOUND_PACKET_TOO_LARGE, + } + + /** + * Receives notifications when new data or output space is available. + * + * @hide + */ + public interface Listener { + /** + * Handles the initial part of the stream, which on some systems provides lower-level + * configuration data. + * + * Returns the number of bytes consumed, or zero if the preamble has been fully read. + */ + int onPreambleData(byte[] data, int pos, int len); + + /** Handles one extracted packet. */ + void onInboundPacket(byte[] data, int pos, int len); + + /** Notifies on new data being added to the buffer. */ + void onInboundBuffered(int newByteCount, int totalBufferedSize); + + /** Notifies on data being flushed from output buffer. */ + void onOutboundPacketSpace(); + + /** Notifies on unrecoverable error in the packet processing. */ + void onPacketFileError(ErrorCode error, String message); + } + + /** Requests this file to be closed. */ + void close(); + + /** Permanently disables reading of this file, and clears all buffered data. */ + void shutdownReading(); + + /** Starts or resumes async read operations on this file. */ + void continueReading(); + + /** Returns the number of bytes currently buffered as input. */ + int getInboundBufferSize(); + + /** Returns the number of bytes currently available for buffering for output. */ + int getOutboundFreeSize(); + + /** + * Queues the given data for output. + * Throws runtime exception if there is not enough space. + */ + boolean enqueueOutboundPacket(byte[] data, int pos, int len); +} diff --git a/staticlibs/device/com/android/net/module/util/wear/StreamingPacketFile.java b/staticlibs/device/com/android/net/module/util/wear/StreamingPacketFile.java new file mode 100644 index 0000000000000000000000000000000000000000..52dbee47d7c324fd0bab8728991f88b4b59642e0 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/wear/StreamingPacketFile.java @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.wear; + +import com.android.net.module.util.async.BufferedFile; +import com.android.net.module.util.async.EventManager; +import com.android.net.module.util.async.FileHandle; +import com.android.net.module.util.async.Assertions; +import com.android.net.module.util.async.ReadableByteBuffer; + +import java.io.IOException; + +/** + * Implements PacketFile based on a streaming file descriptor. + * + * Packets are delineated using network-order 2-byte length indicators. + * + * @hide + */ +public final class StreamingPacketFile implements PacketFile, BufferedFile.Listener { + private static final int HEADER_SIZE = 2; + + private final EventManager mEventManager; + private final Listener mListener; + private final BufferedFile mFile; + private final int mMaxPacketSize; + private final ReadableByteBuffer mInboundBuffer; + private boolean mIsInPreamble = true; + + private final byte[] mTempPacketReadBuffer; + private final byte[] mTempHeaderWriteBuffer; + + public StreamingPacketFile( + EventManager eventManager, + FileHandle fileHandle, + Listener listener, + int maxPacketSize, + int maxBufferedInboundPackets, + int maxBufferedOutboundPackets) throws IOException { + if (eventManager == null || fileHandle == null || listener == null) { + throw new NullPointerException(); + } + + mEventManager = eventManager; + mListener = listener; + mMaxPacketSize = maxPacketSize; + + final int maxTotalLength = HEADER_SIZE + maxPacketSize; + + mFile = BufferedFile.create(eventManager, fileHandle, this, + maxTotalLength * maxBufferedInboundPackets, + maxTotalLength * maxBufferedOutboundPackets); + mInboundBuffer = mFile.getInboundBuffer(); + + mTempPacketReadBuffer = new byte[maxTotalLength]; + mTempHeaderWriteBuffer = new byte[HEADER_SIZE]; + } + + @Override + public void close() { + mFile.close(); + } + + public BufferedFile getUnderlyingFileForTest() { + return mFile; + } + + @Override + public void shutdownReading() { + mFile.shutdownReading(); + } + + @Override + public void continueReading() { + mFile.continueReading(); + } + + @Override + public int getInboundBufferSize() { + return mInboundBuffer.size(); + } + + @Override + public void onBufferedFileClosed() { + } + + @Override + public void onBufferedFileInboundData(int readByteCount) { + if (mFile.isReadingShutdown()) { + return; + } + + if (readByteCount > 0) { + mListener.onInboundBuffered(readByteCount, mInboundBuffer.size()); + } + + if (extractOnePacket() && !mFile.isReadingShutdown()) { + // There could be more packets already buffered, continue parsing next + // packet even before another read event comes + mEventManager.execute(() -> { + onBufferedFileInboundData(0); + }); + } else { + continueReading(); + } + } + + private boolean extractOnePacket() { + while (mIsInPreamble) { + final int directReadSize = Math.min( + mInboundBuffer.getDirectReadSize(), mTempPacketReadBuffer.length); + if (directReadSize == 0) { + return false; + } + + // Copy for safety, so higher-level callback cannot modify the data. + System.arraycopy(mInboundBuffer.getDirectReadBuffer(), + mInboundBuffer.getDirectReadPos(), mTempPacketReadBuffer, 0, directReadSize); + + final int preambleConsumedBytes = mListener.onPreambleData( + mTempPacketReadBuffer, 0, directReadSize); + if (mFile.isReadingShutdown()) { + return false; // The callback has called shutdownReading(). + } + + if (preambleConsumedBytes == 0) { + mIsInPreamble = false; + break; + } + + mInboundBuffer.accountForDirectRead(preambleConsumedBytes); + } + + final int bufferedSize = mInboundBuffer.size(); + if (bufferedSize < HEADER_SIZE) { + return false; + } + + final int dataLength = NetPacketHelpers.decodeNetworkUnsignedInt16(mInboundBuffer, 0); + if (dataLength > mMaxPacketSize) { + mListener.onPacketFileError( + PacketFile.ErrorCode.INBOUND_PACKET_TOO_LARGE, + "Inbound packet length: " + dataLength); + return false; + } + + final int totalLength = HEADER_SIZE + dataLength; + if (bufferedSize < totalLength) { + return false; + } + + mInboundBuffer.readBytes(mTempPacketReadBuffer, 0, totalLength); + + mListener.onInboundPacket(mTempPacketReadBuffer, HEADER_SIZE, dataLength); + return true; + } + + @Override + public int getOutboundFreeSize() { + final int freeSize = mFile.getOutboundBufferFreeSize(); + return (freeSize > HEADER_SIZE ? freeSize - HEADER_SIZE : 0); + } + + @Override + public boolean enqueueOutboundPacket(byte[] buffer, int pos, int len) { + Assertions.throwsIfOutOfBounds(buffer, pos, len); + + if (len == 0) { + return true; + } + + if (len > mMaxPacketSize) { + mListener.onPacketFileError( + PacketFile.ErrorCode.OUTBOUND_PACKET_TOO_LARGE, + "Outbound packet length: " + len); + return false; + } + + NetPacketHelpers.encodeNetworkUnsignedInt16(len, mTempHeaderWriteBuffer, 0); + + mFile.enqueueOutboundData( + mTempHeaderWriteBuffer, 0, mTempHeaderWriteBuffer.length, + buffer, pos, len); + return true; + } + + @Override + public void onBufferedFileOutboundSpace() { + mListener.onOutboundPacketSpace(); + } + + @Override + public void onBufferedFileIoError(String message) { + mListener.onPacketFileError(PacketFile.ErrorCode.IO_ERROR, message); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("maxPacket="); + sb.append(mMaxPacketSize); + sb.append(", file={"); + sb.append(mFile); + sb.append("}"); + return sb.toString(); + } +} diff --git a/staticlibs/framework/com/android/net/module/util/BestClock.java b/staticlibs/framework/com/android/net/module/util/BestClock.java new file mode 100644 index 0000000000000000000000000000000000000000..35391add013d865b1e7a0c3e4b186fff8a2448b5 --- /dev/null +++ b/staticlibs/framework/com/android/net/module/util/BestClock.java @@ -0,0 +1,78 @@ +/* + * 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. + */ + +package com.android.net.module.util; + +import android.util.Log; + +import java.time.Clock; +import java.time.DateTimeException; +import java.time.Instant; +import java.time.ZoneId; +import java.util.Arrays; + +/** + * Single {@link Clock} that will return the best available time from a set of + * prioritized {@link Clock} instances. + * <p> + * For example, when {@link SystemClock#currentNetworkTimeClock()} isn't able to + * provide the time, this class could use {@link Clock#systemUTC()} instead. + * + * Note that this is re-implemented based on {@code android.os.BestClock} to be used inside + * the mainline module. And the class does NOT support serialization. + * + * @hide + */ +final public class BestClock extends Clock { + private static final String TAG = "BestClock"; + private final ZoneId mZone; + private final Clock[] mClocks; + + public BestClock(ZoneId zone, Clock... clocks) { + super(); + this.mZone = zone; + this.mClocks = clocks; + } + + @Override + public long millis() { + for (Clock clock : mClocks) { + try { + return clock.millis(); + } catch (DateTimeException e) { + // Ignore and attempt the next clock + Log.w(TAG, e.toString()); + } + } + throw new DateTimeException( + "No clocks in " + Arrays.toString(mClocks) + " were able to provide time"); + } + + @Override + public ZoneId getZone() { + return mZone; + } + + @Override + public Clock withZone(ZoneId zone) { + return new BestClock(zone, mClocks); + } + + @Override + public Instant instant() { + return Instant.ofEpochMilli(millis()); + } +} diff --git a/staticlibs/framework/com/android/net/module/util/BinderUtils.java b/staticlibs/framework/com/android/net/module/util/BinderUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..e4d14ea8d63f93a8345dbcb86f0ca13f544d8964 --- /dev/null +++ b/staticlibs/framework/com/android/net/module/util/BinderUtils.java @@ -0,0 +1,96 @@ +/* + * 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. + */ + +package com.android.net.module.util; + +import android.annotation.NonNull; +import android.os.Binder; + +import java.util.function.Supplier; + +/** + * Collection of utilities for {@link Binder} and related classes. + * @hide + */ +public class BinderUtils { + /** + * Convenience method for running the provided action enclosed in + * {@link Binder#clearCallingIdentity}/{@link Binder#restoreCallingIdentity} + * + * Any exception thrown by the given action will be caught and rethrown after the call to + * {@link Binder#restoreCallingIdentity} + * + * Note that this is copied from Binder#withCleanCallingIdentity with minor changes + * since it is not public. + * + * @hide + */ + public static final <T extends Exception> void withCleanCallingIdentity( + @NonNull ThrowingRunnable<T> action) throws T { + final long callingIdentity = Binder.clearCallingIdentity(); + try { + action.run(); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + } + + /** + * Like a Runnable, but declared to throw an exception. + * + * @param <T> The exception class which is declared to be thrown. + */ + @FunctionalInterface + public interface ThrowingRunnable<T extends Exception> { + /** @see java.lang.Runnable */ + void run() throws T; + } + + /** + * Convenience method for running the provided action enclosed in + * {@link Binder#clearCallingIdentity}/{@link Binder#restoreCallingIdentity} returning the + * result. + * + * <p>Any exception thrown by the given action will be caught and rethrown after + * the call to {@link Binder#restoreCallingIdentity}. + * + * Note that this is copied from Binder#withCleanCallingIdentity with minor changes + * since it is not public. + * + * @hide + */ + public static final <T, E extends Exception> T withCleanCallingIdentity( + @NonNull ThrowingSupplier<T, E> action) throws E { + final long callingIdentity = Binder.clearCallingIdentity(); + try { + return action.get(); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + } + + /** + * An equivalent of {@link Supplier} + * + * @param <T> The class which is declared to be returned. + * @param <E> The exception class which is declared to be thrown. + */ + @FunctionalInterface + public interface ThrowingSupplier<T, E extends Exception> { + /** @see java.util.function.Supplier */ + T get() throws E; + } +} diff --git a/staticlibs/framework/com/android/net/module/util/BitUtils.java b/staticlibs/framework/com/android/net/module/util/BitUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..3062d8cf31b6c17bc78815a82cee822285ade55c --- /dev/null +++ b/staticlibs/framework/com/android/net/module/util/BitUtils.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +/** + * @hide + */ +public class BitUtils { + /** + * Unpacks long value into an array of bits. + */ + public static int[] unpackBits(long val) { + int size = Long.bitCount(val); + int[] result = new int[size]; + int index = 0; + int bitPos = 0; + while (val != 0) { + if ((val & 1) == 1) result[index++] = bitPos; + val = val >>> 1; + bitPos++; + } + return result; + } + + /** + * Packs a list of ints in the same way as packBits() + * + * Each passed int is the rank of a bit that should be set in the returned long. + * Example : passing (1,3) will return in 0b00001010 and passing (5,6,0) will return 0b01100001 + * + * @param bits bits to pack + * @return a long with the specified bits set. + */ + public static long packBitList(int... bits) { + return packBits(bits); + } + + /** + * Packs array of bits into a long value. + * + * Each passed int is the rank of a bit that should be set in the returned long. + * Example : passing [1,3] will return in 0b00001010 and passing [5,6,0] will return 0b01100001 + * + * @param bits bits to pack + * @return a long with the specified bits set. + */ + public static long packBits(int[] bits) { + long packed = 0; + for (int b : bits) { + packed |= (1L << b); + } + return packed; + } + + /** + * An interface for a function that can retrieve a name associated with an int. + * + * This is useful for bitfields like network capabilities or network score policies. + */ + @FunctionalInterface + public interface NameOf { + /** Retrieve the name associated with the passed value */ + String nameOf(int value); + } + + /** + * Given a bitmask and a name fetcher, append names of all set bits to the builder + * + * This method takes all bit sets in the passed bitmask, will figure out the name associated + * with the weight of each bit with the passed name fetcher, and append each name to the + * passed StringBuilder, separated by the passed separator. + * + * For example, if the bitmask is 0110, and the name fetcher return "BIT_1" to "BIT_4" for + * numbers from 1 to 4, and the separator is "&", this method appends "BIT_2&BIT3" to the + * StringBuilder. + */ + public static void appendStringRepresentationOfBitMaskToStringBuilder(@NonNull StringBuilder sb, + long bitMask, @NonNull NameOf nameFetcher, @NonNull String separator) { + int bitPos = 0; + boolean firstElementAdded = false; + while (bitMask != 0) { + if ((bitMask & 1) != 0) { + if (firstElementAdded) { + sb.append(separator); + } else { + firstElementAdded = true; + } + sb.append(nameFetcher.nameOf(bitPos)); + } + bitMask >>>= 1; + ++bitPos; + } + } + + /** + * Returns a short but human-readable string of updates between an old and a new bit fields. + * + * @param oldVal the old bit field to diff from + * @param newVal the new bit field to diff to + * @return a string fit for logging differences, or null if no differences. + * this method cannot return the empty string. + */ + @Nullable + public static String describeDifferences(final long oldVal, final long newVal, + @NonNull final NameOf nameFetcher) { + final long changed = oldVal ^ newVal; + if (0 == changed) return null; + // If the control reaches here, there are changes (additions, removals, or both) so + // the code below is guaranteed to add something to the string and can't return "". + final long removed = oldVal & changed; + final long added = newVal & changed; + final StringBuilder sb = new StringBuilder(); + if (0 != removed) { + sb.append("-"); + appendStringRepresentationOfBitMaskToStringBuilder(sb, removed, nameFetcher, "-"); + } + if (0 != added) { + sb.append("+"); + appendStringRepresentationOfBitMaskToStringBuilder(sb, added, nameFetcher, "+"); + } + return sb.toString(); + } +} diff --git a/staticlibs/framework/com/android/net/module/util/ByteUtils.java b/staticlibs/framework/com/android/net/module/util/ByteUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..290ed465c135581cc72513b4eec7e4c5176b41af --- /dev/null +++ b/staticlibs/framework/com/android/net/module/util/ByteUtils.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util; + +import android.annotation.NonNull; + +/** + * Byte utility functions. + * @hide + */ +public class ByteUtils { + /** + * Returns the index of the first appearance of the value {@code target} in {@code array}. + * + * @param array an array of {@code byte} values, possibly empty + * @param target a primitive {@code byte} value + * @return the least index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int indexOf(@NonNull byte[] array, byte target) { + return indexOf(array, target, 0, array.length); + } + + private static int indexOf(byte[] array, byte target, int start, int end) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the values from each provided array combined into a single array. For example, {@code + * concat(new byte[] {a, b}, new byte[] {}, new byte[] {c}} returns the array {@code {a, b, c}}. + * + * @param arrays zero or more {@code byte} arrays + * @return a single array containing all the values from the source arrays, in order + */ + public static byte[] concat(@NonNull byte[]... arrays) { + int length = 0; + for (byte[] array : arrays) { + length += array.length; + } + byte[] result = new byte[length]; + int pos = 0; + for (byte[] array : arrays) { + System.arraycopy(array, 0, result, pos, array.length); + pos += array.length; + } + return result; + } +} diff --git a/staticlibs/framework/com/android/net/module/util/CollectionUtils.java b/staticlibs/framework/com/android/net/module/util/CollectionUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..39e7ce9456f92b34ed43c1e611c3fd9450a21c47 --- /dev/null +++ b/staticlibs/framework/com/android/net/module/util/CollectionUtils.java @@ -0,0 +1,392 @@ +/* + * 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. + */ + +package com.android.net.module.util; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.ArrayMap; +import android.util.Pair; +import android.util.SparseArray; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * Utilities for {@link Collection} and arrays. + * @hide + */ +public final class CollectionUtils { + private CollectionUtils() {} + + /** + * @return True if the array is null or 0-length. + */ + public static <T> boolean isEmpty(@Nullable T[] array) { + return array == null || array.length == 0; + } + + /** + * @return True if the collection is null or 0-length. + */ + public static <T> boolean isEmpty(@Nullable Collection<T> collection) { + return collection == null || collection.isEmpty(); + } + + /** + * Returns an int array from the given Integer list. + */ + @NonNull + public static int[] toIntArray(@NonNull Collection<Integer> list) { + int[] array = new int[list.size()]; + int i = 0; + for (Integer item : list) { + array[i] = item; + i++; + } + return array; + } + + /** + * Returns a long array from the given long list. + */ + @NonNull + public static long[] toLongArray(@NonNull Collection<Long> list) { + long[] array = new long[list.size()]; + int i = 0; + for (Long item : list) { + array[i] = item; + i++; + } + return array; + } + + /** + * @return True if all elements satisfy the predicate, false otherwise. + * Note that means this always returns true for empty collections. + */ + public static <T> boolean all(@NonNull Collection<T> elem, @NonNull Predicate<T> predicate) { + for (final T e : elem) { + if (!predicate.test(e)) return false; + } + return true; + + } + + /** + * @return True if any element satisfies the predicate, false otherwise. + * Note that means this always returns false for empty collections. + */ + public static <T> boolean any(@NonNull Collection<T> elem, @NonNull Predicate<T> predicate) { + return indexOf(elem, predicate) >= 0; + } + + /** + * @return The index of the first element that matches the predicate, or -1 if none. + */ + public static <T> int indexOf(@NonNull final Collection<T> elem, + @NonNull final Predicate<? super T> predicate) { + int idx = 0; + for (final T e : elem) { + if (predicate.test(e)) return idx; + idx++; + } + return -1; + } + + /** + * @return True if there exists at least one element in the sparse array for which + * condition {@code predicate} + */ + public static <T> boolean any(@NonNull SparseArray<T> array, @NonNull Predicate<T> predicate) { + for (int i = 0; i < array.size(); ++i) { + if (predicate.test(array.valueAt(i))) { + return true; + } + } + return false; + } + + /** + * @return true if the array contains the specified value. + */ + public static boolean contains(@Nullable short[] array, short value) { + if (array == null) return false; + for (int element : array) { + if (element == value) { + return true; + } + } + return false; + } + + /** + * @return true if the array contains the specified value. + */ + public static boolean contains(@Nullable int[] array, int value) { + if (array == null) return false; + for (int element : array) { + if (element == value) { + return true; + } + } + return false; + } + + /** + * @return true if the array contains the specified value. + */ + public static <T> boolean contains(@Nullable T[] array, @Nullable T value) { + return indexOf(array, value) != -1; + } + + /** + * Return first index of value in given array, or -1 if not found. + */ + public static <T> int indexOf(@Nullable T[] array, @Nullable T value) { + if (array == null) return -1; + for (int i = 0; i < array.length; i++) { + if (Objects.equals(array[i], value)) return i; + } + return -1; + } + + /** + * Returns the index of the needle array in the haystack array, or -1 if it can't be found. + * This is a byte array equivalent of Collections.indexOfSubList(). + */ + public static int indexOfSubArray(@NonNull byte[] haystack, @NonNull byte[] needle) { + for (int i = 0; i < haystack.length - needle.length + 1; i++) { + boolean found = true; + for (int j = 0; j < needle.length; j++) { + if (haystack[i + j] != needle[j]) { + found = false; + break; + } + } + if (found) { + return i; + } + } + return -1; + } + + /** + * Returns a new collection of elements that match the passed predicate. + * @param source the elements to filter. + * @param test the predicate to test for. + * @return a new collection containing only the source elements that satisfy the predicate. + */ + @NonNull public static <T> ArrayList<T> filter(@NonNull final Collection<T> source, + @NonNull final Predicate<T> test) { + final ArrayList<T> matches = new ArrayList<>(); + for (final T e : source) { + if (test.test(e)) { + matches.add(e); + } + } + return matches; + } + + /** + * Return sum of the given long array. + */ + public static long total(@Nullable long[] array) { + long total = 0; + if (array != null) { + for (long value : array) { + total += value; + } + } + return total; + } + + /** + * Returns true if the first collection contains any of the elements of the second. + * @param haystack where to search + * @param needles what to search for + * @param <T> type of elements + * @return true if |haystack| contains any of the |needles|, false otherwise + */ + public static <T> boolean containsAny(@NonNull final Collection<T> haystack, + @NonNull final Collection<? extends T> needles) { + for (T needle : needles) { + if (haystack.contains(needle)) return true; + } + return false; + } + + /** + * Returns true if the first collection contains all of the elements of the second. + * @param haystack where to search + * @param needles what to search for + * @param <T> type of elements + * @return true if |haystack| contains all of the |needles|, false otherwise + */ + public static <T> boolean containsAll(@NonNull final Collection<T> haystack, + @NonNull final Collection<? extends T> needles) { + return haystack.containsAll(needles); + } + + /** + * Returns the first item of a collection that matches the predicate. + * @param haystack The collection to search. + * @param condition The predicate to match. + * @param <T> The type of element in the collection. + * @return The first element matching the predicate, or null if none. + */ + @Nullable + public static <T> T findFirst(@NonNull final Collection<T> haystack, + @NonNull final Predicate<? super T> condition) { + for (T needle : haystack) { + if (condition.test(needle)) return needle; + } + return null; + } + + /** + * Returns the last item of a List that matches the predicate. + * @param haystack The List to search. + * @param condition The predicate to match. + * @param <T> The type of element in the list. + * @return The last element matching the predicate, or null if none. + */ + // There is no way to reverse iterate a Collection in Java (e.g. the collection may + // be a single-linked list), so implementing this on Collection is necessarily very + // wasteful (store and reverse a copy, test all elements, or recurse to the end of the + // list to test on the up path and possibly blow the call stack) + @Nullable + public static <T> T findLast(@NonNull final List<T> haystack, + @NonNull final Predicate<? super T> condition) { + for (int i = haystack.size() - 1; i >= 0; --i) { + final T needle = haystack.get(i); + if (condition.test(needle)) return needle; + } + return null; + } + + /** + * Returns whether a collection contains an element matching a condition + * @param haystack The collection to search. + * @param condition The predicate to match. + * @param <T> The type of element in the collection. + * @return Whether the collection contains any element matching the condition. + */ + public static <T> boolean contains(@NonNull final Collection<T> haystack, + @NonNull final Predicate<? super T> condition) { + return -1 != indexOf(haystack, condition); + } + + /** + * Standard map function, but returns a new modifiable ArrayList + * + * This returns a new list that contains, for each element of the source collection, its + * image through the passed transform. + * Elements in the source can be null if the transform accepts null inputs. + * Elements in the output can be null if the transform ever returns null. + * This function never returns null. If the source collection is empty, it returns the + * empty list. + * Contract : this method calls the transform function exactly once for each element in the + * list, in iteration order. + * + * @param source the source collection + * @param transform the function to transform the elements + * @param <T> type of source elements + * @param <R> type of destination elements + * @return an unmodifiable list of transformed elements + */ + @NonNull + public static <T, R> ArrayList<R> map(@NonNull final Collection<T> source, + @NonNull final Function<? super T, ? extends R> transform) { + final ArrayList<R> dest = new ArrayList<>(source.size()); + for (final T e : source) { + dest.add(transform.apply(e)); + } + return dest; + } + + /** + * Standard zip function, but returns a new modifiable ArrayList + * + * This returns a list of pairs containing, at each position, a pair of the element from the + * first list at that index and the element from the second list at that index. + * Both lists must be the same size. They may contain null. + * + * The easiest way to visualize what's happening is to think of two lists being laid out next + * to each other and stitched together with a zipper. + * + * Contract : this method will read each element of each list exactly once, in some unspecified + * order. If it throws, it will not read any element. + * + * @param first the first list of elements + * @param second the second list of elements + * @param <T> the type of first elements + * @param <R> the type of second elements + * @return the zipped list + */ + @NonNull + public static <T, R> ArrayList<Pair<T, R>> zip(@NonNull final List<T> first, + @NonNull final List<R> second) { + final int size = first.size(); + if (size != second.size()) { + throw new IllegalArgumentException("zip : collections must be the same size"); + } + final ArrayList<Pair<T, R>> dest = new ArrayList<>(size); + for (int i = 0; i < size; ++i) { + dest.add(new Pair<>(first.get(i), second.get(i))); + } + return dest; + } + + /** + * Returns a new ArrayMap that associates each key with the value at the same index. + * + * Both lists must be the same size. + * Both keys and values may contain null. + * Keys may not contain the same value twice. + * + * Contract : this method will read each element of each list exactly once, but does not + * specify the order, except if it throws in which case the number of reads is undefined. + * + * @param keys The list of keys + * @param values The list of values + * @param <T> The type of keys + * @param <R> The type of values + * @return The associated map + */ + @NonNull + public static <T, R> ArrayMap<T, R> assoc( + @NonNull final List<T> keys, @NonNull final List<R> values) { + final int size = keys.size(); + if (size != values.size()) { + throw new IllegalArgumentException("assoc : collections must be the same size"); + } + final ArrayMap<T, R> dest = new ArrayMap<>(size); + for (int i = 0; i < size; ++i) { + final T key = keys.get(i); + if (dest.containsKey(key)) { + throw new IllegalArgumentException( + "assoc : keys may not contain the same value twice"); + } + dest.put(key, values.get(i)); + } + return dest; + } +} diff --git a/staticlibs/framework/com/android/net/module/util/ConnectivitySettingsUtils.java b/staticlibs/framework/com/android/net/module/util/ConnectivitySettingsUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..f4856b344fd47300fb72586c9734ba784e46c70b --- /dev/null +++ b/staticlibs/framework/com/android/net/module/util/ConnectivitySettingsUtils.java @@ -0,0 +1,131 @@ +/* + * 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. + */ + +package com.android.net.module.util; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ContentResolver; +import android.content.Context; +import android.provider.Settings; +import android.text.TextUtils; + +/** + * Collection of connectivity settings utilities. + * + * @hide + */ +public class ConnectivitySettingsUtils { + public static final int PRIVATE_DNS_MODE_OFF = 1; + public static final int PRIVATE_DNS_MODE_OPPORTUNISTIC = 2; + public static final int PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = 3; + + public static final String PRIVATE_DNS_DEFAULT_MODE = "private_dns_default_mode"; + public static final String PRIVATE_DNS_MODE = "private_dns_mode"; + public static final String PRIVATE_DNS_MODE_OFF_STRING = "off"; + public static final String PRIVATE_DNS_MODE_OPPORTUNISTIC_STRING = "opportunistic"; + public static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME_STRING = "hostname"; + public static final String PRIVATE_DNS_SPECIFIER = "private_dns_specifier"; + + /** + * Get private DNS mode as string. + * + * @param mode One of the private DNS values. + * @return A string of private DNS mode. + */ + public static String getPrivateDnsModeAsString(int mode) { + switch (mode) { + case PRIVATE_DNS_MODE_OFF: + return PRIVATE_DNS_MODE_OFF_STRING; + case PRIVATE_DNS_MODE_OPPORTUNISTIC: + return PRIVATE_DNS_MODE_OPPORTUNISTIC_STRING; + case PRIVATE_DNS_MODE_PROVIDER_HOSTNAME: + return PRIVATE_DNS_MODE_PROVIDER_HOSTNAME_STRING; + default: + throw new IllegalArgumentException("Invalid private dns mode: " + mode); + } + } + + private static int getPrivateDnsModeAsInt(String mode) { + // If both PRIVATE_DNS_MODE and PRIVATE_DNS_DEFAULT_MODE are not set, choose + // PRIVATE_DNS_MODE_OPPORTUNISTIC as default mode. + if (TextUtils.isEmpty(mode)) + return PRIVATE_DNS_MODE_OPPORTUNISTIC; + switch (mode) { + case "off": + return PRIVATE_DNS_MODE_OFF; + case "hostname": + return PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; + case "opportunistic": + return PRIVATE_DNS_MODE_OPPORTUNISTIC; + default: + // b/260211513: adb shell settings put global private_dns_mode foo + // can result in arbitrary strings - treat any unknown value as empty string. + // throw new IllegalArgumentException("Invalid private dns mode: " + mode); + return PRIVATE_DNS_MODE_OPPORTUNISTIC; + } + } + + /** + * Get private DNS mode from settings. + * + * @param context The Context to query the private DNS mode from settings. + * @return An integer of private DNS mode. + */ + public static int getPrivateDnsMode(@NonNull Context context) { + final ContentResolver cr = context.getContentResolver(); + String mode = Settings.Global.getString(cr, PRIVATE_DNS_MODE); + if (TextUtils.isEmpty(mode)) mode = Settings.Global.getString(cr, PRIVATE_DNS_DEFAULT_MODE); + return getPrivateDnsModeAsInt(mode); + } + + /** + * Set private DNS mode to settings. + * + * @param context The {@link Context} to set the private DNS mode. + * @param mode The private dns mode. This should be one of the PRIVATE_DNS_MODE_* constants. + */ + public static void setPrivateDnsMode(@NonNull Context context, int mode) { + if (!(mode == PRIVATE_DNS_MODE_OFF + || mode == PRIVATE_DNS_MODE_OPPORTUNISTIC + || mode == PRIVATE_DNS_MODE_PROVIDER_HOSTNAME)) { + throw new IllegalArgumentException("Invalid private dns mode: " + mode); + } + Settings.Global.putString(context.getContentResolver(), PRIVATE_DNS_MODE, + getPrivateDnsModeAsString(mode)); + } + + /** + * Get specific private dns provider name from {@link Settings}. + * + * @param context The {@link Context} to query the setting. + * @return The specific private dns provider name, or null if no setting value. + */ + @Nullable + public static String getPrivateDnsHostname(@NonNull Context context) { + return Settings.Global.getString(context.getContentResolver(), PRIVATE_DNS_SPECIFIER); + } + + /** + * Set specific private dns provider name to {@link Settings}. + * + * @param context The {@link Context} to set the setting. + * @param specifier The specific private dns provider name. + */ + public static void setPrivateDnsHostname(@NonNull Context context, @Nullable String specifier) { + Settings.Global.putString(context.getContentResolver(), PRIVATE_DNS_SPECIFIER, specifier); + } +} diff --git a/staticlibs/framework/com/android/net/module/util/ConnectivityUtils.java b/staticlibs/framework/com/android/net/module/util/ConnectivityUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..c135e4664368c9a4df011ea155940d0830d9ae4d --- /dev/null +++ b/staticlibs/framework/com/android/net/module/util/ConnectivityUtils.java @@ -0,0 +1,67 @@ +/* + * 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. + */ + +package com.android.net.module.util; + + +import android.annotation.Nullable; + +import java.net.Inet6Address; +import java.net.InetAddress; + +/** + * Various utilities used in connectivity code. + * @hide + */ +public final class ConnectivityUtils { + private ConnectivityUtils() {} + + + /** + * Return IP address and port in a string format. + */ + public static String addressAndPortToString(InetAddress address, int port) { + return String.format( + (address instanceof Inet6Address) ? "[%s]:%d" : "%s:%d", + address.getHostAddress(), port); + } + + /** + * Return true if the provided address is non-null and an IPv6 Unique Local Address (RFC4193). + */ + public static boolean isIPv6ULA(@Nullable InetAddress addr) { + return addr instanceof Inet6Address + && ((addr.getAddress()[0] & 0xfe) == 0xfc); + } + + /** + * Returns the {@code int} nearest in value to {@code value}. + * + * @param value any {@code long} value + * @return the same value cast to {@code int} if it is in the range of the {@code int} + * type, {@link Integer#MAX_VALUE} if it is too large, or {@link Integer#MIN_VALUE} if + * it is too small + */ + public static int saturatedCast(long value) { + if (value > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + if (value < Integer.MIN_VALUE) { + return Integer.MIN_VALUE; + } + return (int) value; + } +} diff --git a/staticlibs/framework/com/android/net/module/util/DnsPacket.java b/staticlibs/framework/com/android/net/module/util/DnsPacket.java new file mode 100644 index 0000000000000000000000000000000000000000..0dcdf1e6598278becb28fbcfd9b68a2c3f27cfee --- /dev/null +++ b/staticlibs/framework/com/android/net/module/util/DnsPacket.java @@ -0,0 +1,599 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util; + +import static android.net.DnsResolver.TYPE_A; +import static android.net.DnsResolver.TYPE_AAAA; + +import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; +import static com.android.net.module.util.DnsPacketUtils.DnsRecordParser.domainNameToLabels; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.text.TextUtils; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.net.module.util.DnsPacketUtils.DnsRecordParser; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.net.InetAddress; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * Defines basic data for DNS protocol based on RFC 1035. + * Subclasses create the specific format used in DNS packet. + * + * @hide + */ +public abstract class DnsPacket { + /** + * Type of the canonical name for an alias. Refer to RFC 1035 section 3.2.2. + */ + // TODO: Define the constant as a public constant in DnsResolver since it can never change. + private static final int TYPE_CNAME = 5; + + /** + * Thrown when parsing packet failed. + */ + public static class ParseException extends RuntimeException { + public String reason; + public ParseException(@NonNull String reason) { + super(reason); + this.reason = reason; + } + + public ParseException(@NonNull String reason, @NonNull Throwable cause) { + super(reason, cause); + this.reason = reason; + } + } + + /** + * DNS header for DNS protocol based on RFC 1035 section 4.1.1. + * + * 1 1 1 1 1 1 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | ID | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * |QR| Opcode |AA|TC|RD|RA| Z | RCODE | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | QDCOUNT | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | ANCOUNT | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | NSCOUNT | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | ARCOUNT | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + */ + public static class DnsHeader { + private static final String TAG = "DnsHeader"; + private static final int SIZE_IN_BYTES = 12; + private final int mId; + private final int mFlags; + private final int[] mRecordCount; + + /* If this bit in the 'flags' field is set to 0, the DNS message corresponding to this + * header is a query; otherwise, it is a response. + */ + private static final int FLAGS_SECTION_QR_BIT = 15; + + /** + * Create a new DnsHeader from a positioned ByteBuffer. + * + * The ByteBuffer must be in network byte order (which is the default). + * Reads the passed ByteBuffer from its current position and decodes a DNS header. + * When this constructor returns, the reading position of the ByteBuffer has been + * advanced to the end of the DNS header record. + * This is meant to chain with other methods reading a DNS response in sequence. + */ + @VisibleForTesting + public DnsHeader(@NonNull ByteBuffer buf) throws BufferUnderflowException { + Objects.requireNonNull(buf); + mId = Short.toUnsignedInt(buf.getShort()); + mFlags = Short.toUnsignedInt(buf.getShort()); + mRecordCount = new int[NUM_SECTIONS]; + for (int i = 0; i < NUM_SECTIONS; ++i) { + mRecordCount[i] = Short.toUnsignedInt(buf.getShort()); + } + } + + /** + * Determines if the DNS message corresponding to this header is a response, as defined in + * RFC 1035 Section 4.1.1. + */ + public boolean isResponse() { + return (mFlags & (1 << FLAGS_SECTION_QR_BIT)) != 0; + } + + /** + * Create a new DnsHeader from specified parameters. + * + * This constructor only builds the question and answer sections. Authority + * and additional sections are not supported. Useful when synthesizing dns + * responses from query or reply packets. + */ + @VisibleForTesting + public DnsHeader(int id, int flags, int qdcount, int ancount) { + this.mId = id; + this.mFlags = flags; + mRecordCount = new int[NUM_SECTIONS]; + mRecordCount[QDSECTION] = qdcount; + mRecordCount[ANSECTION] = ancount; + } + + /** + * Get record count by type. + */ + public int getRecordCount(int type) { + return mRecordCount[type]; + } + + /** + * Get flags of this instance. + */ + public int getFlags() { + return mFlags; + } + + /** + * Get id of this instance. + */ + public int getId() { + return mId; + } + + @Override + public String toString() { + return "DnsHeader{" + "id=" + mId + ", flags=" + mFlags + + ", recordCounts=" + Arrays.toString(mRecordCount) + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o.getClass() != getClass()) return false; + final DnsHeader other = (DnsHeader) o; + return mId == other.mId + && mFlags == other.mFlags + && Arrays.equals(mRecordCount, other.mRecordCount); + } + + @Override + public int hashCode() { + return 31 * mId + 37 * mFlags + Arrays.hashCode(mRecordCount); + } + + /** + * Get DnsHeader as byte array. + */ + @NonNull + public byte[] getBytes() { + // TODO: if this is called often, optimize the ByteBuffer out and write to the + // array directly. + final ByteBuffer buf = ByteBuffer.allocate(SIZE_IN_BYTES); + buf.putShort((short) mId); + buf.putShort((short) mFlags); + for (int i = 0; i < NUM_SECTIONS; ++i) { + buf.putShort((short) mRecordCount[i]); + } + return buf.array(); + } + } + + /** + * Superclass for DNS questions and DNS resource records. + * + * DNS questions (No TTL/RDLENGTH/RDATA) based on RFC 1035 section 4.1.2. + * 1 1 1 1 1 1 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | | + * / QNAME / + * / / + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | QTYPE | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | QCLASS | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * + * DNS resource records (With TTL/RDLENGTH/RDATA) based on RFC 1035 section 4.1.3. + * 1 1 1 1 1 1 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | | + * / / + * / NAME / + * | | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | TYPE | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | CLASS | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | TTL | + * | | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | RDLENGTH | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--| + * / RDATA / + * / / + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * + * Note that this class is meant to be used by composition and not inheritance, and + * that classes implementing more specific DNS records should call #parse. + */ + // TODO: Make DnsResourceRecord and DnsQuestion subclasses of DnsRecord. + public static class DnsRecord { + // Refer to RFC 1035 section 2.3.4 for MAXNAMESIZE. + // NAME_NORMAL and NAME_COMPRESSION are used for checking name compression, + // refer to rfc 1035 section 4.1.4. + public static final int MAXNAMESIZE = 255; + public static final int NAME_NORMAL = 0; + public static final int NAME_COMPRESSION = 0xC0; + + private static final String TAG = "DnsRecord"; + + public final String dName; + public final int nsType; + public final int nsClass; + public final long ttl; + private final byte[] mRdata; + /** + * Type of this DNS record. + */ + @RecordType + public final int rType; + + /** + * Create a new DnsRecord from a positioned ByteBuffer. + * + * Reads the passed ByteBuffer from its current position and decodes a DNS record. + * When this constructor returns, the reading position of the ByteBuffer has been + * advanced to the end of the DNS resource record. + * This is meant to chain with other methods reading a DNS response in sequence. + * + * @param rType Type of the record. + * @param buf ByteBuffer input of record, must be in network byte order + * (which is the default). + */ + private DnsRecord(@RecordType int rType, @NonNull ByteBuffer buf) + throws BufferUnderflowException, ParseException { + Objects.requireNonNull(buf); + this.rType = rType; + dName = DnsRecordParser.parseName(buf, 0 /* Parse depth */, + true /* isNameCompressionSupported */); + if (dName.length() > MAXNAMESIZE) { + throw new ParseException( + "Parse name fail, name size is too long: " + dName.length()); + } + nsType = Short.toUnsignedInt(buf.getShort()); + nsClass = Short.toUnsignedInt(buf.getShort()); + + if (rType != QDSECTION) { + ttl = Integer.toUnsignedLong(buf.getInt()); + final int length = Short.toUnsignedInt(buf.getShort()); + mRdata = new byte[length]; + buf.get(mRdata); + } else { + ttl = 0; + mRdata = null; + } + } + + /** + * Create a new DnsRecord or subclass of DnsRecord instance from a positioned ByteBuffer. + * + * Peek the nsType, sending the buffer to corresponding DnsRecord subclass constructors + * to allow constructing the corresponding object. + */ + @VisibleForTesting(visibility = PRIVATE) + public static DnsRecord parse(@RecordType int rType, @NonNull ByteBuffer buf) + throws BufferUnderflowException, ParseException { + Objects.requireNonNull(buf); + final int oldPos = buf.position(); + // Parsed name not used, just for jumping to nsType position. + DnsRecordParser.parseName(buf, 0 /* Parse depth */, + true /* isNameCompressionSupported */); + // Peek the nsType. + final int nsType = Short.toUnsignedInt(buf.getShort()); + buf.position(oldPos); + // Return a DnsRecord instance by default for backward compatibility, this is useful + // when a partner supports new type of DnsRecord but does not inherit DnsRecord. + switch (nsType) { + default: + return new DnsRecord(rType, buf); + } + } + + /** + * Make an A or AAAA record based on the specified parameters. + * + * @param rType Type of the record, can be {@link #ANSECTION}, {@link #ARSECTION} + * or {@link #NSSECTION}. + * @param dName Domain name of the record. + * @param nsClass Class of the record. See RFC 1035 section 3.2.4. + * @param ttl time interval (in seconds) that the resource record may be + * cached before it should be discarded. Zero values are + * interpreted to mean that the RR can only be used for the + * transaction in progress, and should not be cached. + * @param address Instance of {@link InetAddress} + * @return A record if the {@code address} is an IPv4 address, or AAAA record if the + * {@code address} is an IPv6 address. + */ + public static DnsRecord makeAOrAAAARecord(int rType, @NonNull String dName, + int nsClass, long ttl, @NonNull InetAddress address) throws IOException { + final int nsType = (address.getAddress().length == 4) ? TYPE_A : TYPE_AAAA; + return new DnsRecord(rType, dName, nsType, nsClass, ttl, address, null /* rDataStr */); + } + + /** + * Make an CNAME record based on the specified parameters. + * + * @param rType Type of the record, can be {@link #ANSECTION}, {@link #ARSECTION} + * or {@link #NSSECTION}. + * @param dName Domain name of the record. + * @param nsClass Class of the record. See RFC 1035 section 3.2.4. + * @param ttl time interval (in seconds) that the resource record may be + * cached before it should be discarded. Zero values are + * interpreted to mean that the RR can only be used for the + * transaction in progress, and should not be cached. + * @param domainName Canonical name of the {@code dName}. + * @return A record if the {@code address} is an IPv4 address, or AAAA record if the + * {@code address} is an IPv6 address. + */ + public static DnsRecord makeCNameRecord(int rType, @NonNull String dName, int nsClass, + long ttl, @NonNull String domainName) throws IOException { + return new DnsRecord(rType, dName, TYPE_CNAME, nsClass, ttl, null /* address */, + domainName); + } + + /** + * Make a DNS question based on the specified parameters. + */ + public static DnsRecord makeQuestion(@NonNull String dName, int nsType, int nsClass) { + return new DnsRecord(dName, nsType, nsClass); + } + + private static String requireHostName(@NonNull String name) { + if (!DnsRecordParser.isHostName(name)) { + throw new IllegalArgumentException("Expected domain name but got " + name); + } + return name; + } + + /** + * Create a new query DnsRecord from specified parameters, useful when synthesizing + * dns response. + */ + private DnsRecord(@NonNull String dName, int nsType, int nsClass) { + this.rType = QDSECTION; + this.dName = requireHostName(dName); + this.nsType = nsType; + this.nsClass = nsClass; + mRdata = null; + this.ttl = 0; + } + + /** + * Create a new CNAME/A/AAAA DnsRecord from specified parameters. + * + * @param address The address only used when synthesizing A or AAAA record. + * @param rDataStr The alias of the domain, only used when synthesizing CNAME record. + */ + private DnsRecord(@RecordType int rType, @NonNull String dName, int nsType, int nsClass, + long ttl, @Nullable InetAddress address, @Nullable String rDataStr) + throws IOException { + this.rType = rType; + this.dName = requireHostName(dName); + this.nsType = nsType; + this.nsClass = nsClass; + if (rType < 0 || rType >= NUM_SECTIONS || rType == QDSECTION) { + throw new IllegalArgumentException("Unexpected record type: " + rType); + } + mRdata = nsType == TYPE_CNAME ? domainNameToLabels(rDataStr) : address.getAddress(); + this.ttl = ttl; + } + + /** + * Get a copy of rdata. + */ + @Nullable + public byte[] getRR() { + return (mRdata == null) ? null : mRdata.clone(); + } + + /** + * Get DnsRecord as byte array. + */ + @NonNull + public byte[] getBytes() throws IOException { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final DataOutputStream dos = new DataOutputStream(baos); + dos.write(domainNameToLabels(dName)); + dos.writeShort(nsType); + dos.writeShort(nsClass); + if (rType != QDSECTION) { + dos.writeInt((int) ttl); + if (mRdata == null) { + dos.writeShort(0); + } else { + dos.writeShort(mRdata.length); + dos.write(mRdata); + } + } + return baos.toByteArray(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o.getClass() != getClass()) return false; + final DnsRecord other = (DnsRecord) o; + return rType == other.rType + && nsType == other.nsType + && nsClass == other.nsClass + && ttl == other.ttl + && TextUtils.equals(dName, other.dName) + && Arrays.equals(mRdata, other.mRdata); + } + + @Override + public int hashCode() { + return 31 * Objects.hash(dName) + + 37 * ((int) (ttl & 0xFFFFFFFF)) + + 41 * ((int) (ttl >> 32)) + + 43 * nsType + + 47 * nsClass + + 53 * rType + + Arrays.hashCode(mRdata); + } + + @Override + public String toString() { + return "DnsRecord{" + + "rType=" + rType + + ", dName='" + dName + '\'' + + ", nsType=" + nsType + + ", nsClass=" + nsClass + + ", ttl=" + ttl + + ", mRdata=" + Arrays.toString(mRdata) + + '}'; + } + } + + /** + * Header section types, refer to RFC 1035 section 4.1.1. + */ + public static final int QDSECTION = 0; + public static final int ANSECTION = 1; + public static final int NSSECTION = 2; + public static final int ARSECTION = 3; + @VisibleForTesting(visibility = PRIVATE) + static final int NUM_SECTIONS = ARSECTION + 1; + + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + QDSECTION, + ANSECTION, + NSSECTION, + ARSECTION, + }) + public @interface RecordType {} + + + private static final String TAG = DnsPacket.class.getSimpleName(); + + protected final DnsHeader mHeader; + protected final List<DnsRecord>[] mRecords; + + protected DnsPacket(@NonNull byte[] data) throws ParseException { + if (null == data) { + throw new ParseException("Parse header failed, null input data"); + } + + final ByteBuffer buffer; + try { + buffer = ByteBuffer.wrap(data); + mHeader = new DnsHeader(buffer); + } catch (BufferUnderflowException e) { + throw new ParseException("Parse Header fail, bad input data", e); + } + + mRecords = new ArrayList[NUM_SECTIONS]; + + for (int i = 0; i < NUM_SECTIONS; ++i) { + final int count = mHeader.getRecordCount(i); + mRecords[i] = new ArrayList(count); + for (int j = 0; j < count; ++j) { + try { + mRecords[i].add(DnsRecord.parse(i, buffer)); + } catch (BufferUnderflowException e) { + throw new ParseException("Parse record fail", e); + } + } + } + } + + /** + * Create a new {@link #DnsPacket} from specified parameters. + * + * Note that authority records section and additional records section is not supported. + */ + protected DnsPacket(@NonNull DnsHeader header, @NonNull List<DnsRecord> qd, + @NonNull List<DnsRecord> an) { + mHeader = Objects.requireNonNull(header); + mRecords = new List[NUM_SECTIONS]; + mRecords[QDSECTION] = Collections.unmodifiableList(new ArrayList<>(qd)); + mRecords[ANSECTION] = Collections.unmodifiableList(new ArrayList<>(an)); + mRecords[NSSECTION] = new ArrayList<>(); + mRecords[ARSECTION] = new ArrayList<>(); + for (int i = 0; i < NUM_SECTIONS; i++) { + if (mHeader.mRecordCount[i] != mRecords[i].size()) { + throw new IllegalArgumentException("Record count mismatch: expected " + + mHeader.mRecordCount[i] + " but was " + mRecords[i]); + } + } + } + + /** + * Get DnsPacket as byte array. + */ + public @NonNull byte[] getBytes() throws IOException { + final ByteArrayOutputStream buf = new ByteArrayOutputStream(); + buf.write(mHeader.getBytes()); + + for (int i = 0; i < NUM_SECTIONS; ++i) { + for (final DnsRecord record : mRecords[i]) { + buf.write(record.getBytes()); + } + } + return buf.toByteArray(); + } + + @Override + public String toString() { + return "DnsPacket{" + "header=" + mHeader + ", records='" + Arrays.toString(mRecords) + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o.getClass() != getClass()) return false; + final DnsPacket other = (DnsPacket) o; + return Objects.equals(mHeader, other.mHeader) + && Arrays.deepEquals(mRecords, other.mRecords); + } + + @Override + public int hashCode() { + int result = Objects.hash(mHeader); + result = 31 * result + Arrays.hashCode(mRecords); + return result; + } +} diff --git a/staticlibs/framework/com/android/net/module/util/DnsPacketUtils.java b/staticlibs/framework/com/android/net/module/util/DnsPacketUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..105d783765e5bfb04e80756db77c6d9e3e253923 --- /dev/null +++ b/staticlibs/framework/com/android/net/module/util/DnsPacketUtils.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util; + +import static com.android.net.module.util.DnsPacket.DnsRecord.NAME_COMPRESSION; +import static com.android.net.module.util.DnsPacket.DnsRecord.NAME_NORMAL; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.InetAddresses; +import android.net.ParseException; +import android.text.TextUtils; +import android.util.Patterns; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.text.DecimalFormat; +import java.text.FieldPosition; + +/** + * Utilities for decoding the contents of a DnsPacket. + * + * @hide + */ +public final class DnsPacketUtils { + /** + * Reads the passed ByteBuffer from its current position and decodes a DNS record. + */ + public static class DnsRecordParser { + private static final int MAXLABELSIZE = 63; + private static final int MAXNAMESIZE = 255; + private static final int MAXLABELCOUNT = 128; + + private static final DecimalFormat sByteFormat = new DecimalFormat(); + private static final FieldPosition sPos = new FieldPosition(0); + + /** + * Convert label from {@code byte[]} to {@code String} + * + * <p>Follows the same conversion rules of the native code (ns_name.c in libc). + */ + @VisibleForTesting + static String labelToString(@NonNull byte[] label) { + final StringBuffer sb = new StringBuffer(); + + for (int i = 0; i < label.length; ++i) { + int b = Byte.toUnsignedInt(label[i]); + // Control characters and non-ASCII characters. + if (b <= 0x20 || b >= 0x7f) { + // Append the byte as an escaped decimal number, e.g., "\19" for 0x13. + sb.append('\\'); + sByteFormat.format(b, sb, sPos); + } else if (b == '"' || b == '.' || b == ';' || b == '\\' || b == '(' || b == ')' + || b == '@' || b == '$') { + // Append the byte as an escaped character, e.g., "\:" for 0x3a. + sb.append('\\'); + sb.append((char) b); + } else { + // Append the byte as a character, e.g., "a" for 0x61. + sb.append((char) b); + } + } + return sb.toString(); + } + + /** + * Converts domain name to labels according to RFC 1035. + * + * @param name Domain name as String that needs to be converted to labels. + * @return An encoded byte array that is constructed out of labels, + * and ends with zero-length label. + * @throws ParseException if failed to parse the given domain name or + * IOException if failed to output labels. + */ + public static @NonNull byte[] domainNameToLabels(@NonNull String name) throws + IOException, ParseException { + if (name.length() > MAXNAMESIZE) { + throw new ParseException("Domain name exceeds max length: " + name.length()); + } + if (!isHostName(name)) { + throw new ParseException("Failed to parse domain name: " + name); + } + final ByteArrayOutputStream buf = new ByteArrayOutputStream(); + final String[] labels = name.split("\\."); + for (final String label : labels) { + if (label.length() > MAXLABELSIZE) { + throw new ParseException("label is too long: " + label); + } + buf.write(label.length()); + // Encode as UTF-8 as suggested in RFC 6055 section 3. + buf.write(label.getBytes(StandardCharsets.UTF_8)); + } + buf.write(0x00); // end with zero-length label + return buf.toByteArray(); + } + + /** + * Check whether the input is a valid hostname based on rfc 1035 section 3.3. + * + * @param hostName the target host name. + * @return true if the input is a valid hostname. + */ + public static boolean isHostName(@Nullable String hostName) { + // TODO: Use {@code Patterns.HOST_NAME} if available. + // Patterns.DOMAIN_NAME accepts host names or IP addresses, so reject + // IP addresses. + return hostName != null + && Patterns.DOMAIN_NAME.matcher(hostName).matches() + && !InetAddresses.isNumericAddress(hostName); + } + + /** + * Parses the domain / target name of a DNS record. + */ + public static String parseName(final ByteBuffer buf, int depth, + boolean isNameCompressionSupported) throws + BufferUnderflowException, DnsPacket.ParseException { + return parseName(buf, depth, MAXLABELCOUNT, isNameCompressionSupported); + } + + /** + * Parses the domain / target name of a DNS record. + * + * As described in RFC 1035 Section 4.1.3, the NAME field of a DNS Resource Record always + * supports Name Compression, whereas domain names contained in the RDATA payload of a DNS + * record may or may not support Name Compression, depending on the record TYPE. Moreover, + * even if Name Compression is supported, its usage is left to the implementation. + */ + public static String parseName(final ByteBuffer buf, int depth, int maxLabelCount, + boolean isNameCompressionSupported) throws + BufferUnderflowException, DnsPacket.ParseException { + if (depth > maxLabelCount) { + throw new DnsPacket.ParseException("Failed to parse name, too many labels"); + } + final int len = Byte.toUnsignedInt(buf.get()); + final int mask = len & NAME_COMPRESSION; + if (0 == len) { + return ""; + } else if (mask != NAME_NORMAL && mask != NAME_COMPRESSION + || (!isNameCompressionSupported && mask == NAME_COMPRESSION)) { + throw new DnsPacket.ParseException("Parse name fail, bad label type: " + mask); + } else if (mask == NAME_COMPRESSION) { + // Name compression based on RFC 1035 - 4.1.4 Message compression + final int offset = ((len & ~NAME_COMPRESSION) << 8) + Byte.toUnsignedInt(buf.get()); + final int oldPos = buf.position(); + if (offset >= oldPos - 2) { + throw new DnsPacket.ParseException( + "Parse compression name fail, invalid compression"); + } + buf.position(offset); + final String pointed = parseName(buf, depth + 1, maxLabelCount, + isNameCompressionSupported); + buf.position(oldPos); + return pointed; + } else { + final byte[] label = new byte[len]; + buf.get(label); + final String head = labelToString(label); + if (head.length() > MAXLABELSIZE) { + throw new DnsPacket.ParseException("Parse name fail, invalid label length"); + } + final String tail = parseName(buf, depth + 1, maxLabelCount, + isNameCompressionSupported); + return TextUtils.isEmpty(tail) ? head : head + "." + tail; + } + } + + private DnsRecordParser() {} + } + + private DnsPacketUtils() {} +} diff --git a/staticlibs/framework/com/android/net/module/util/DnsSdTxtRecord.java b/staticlibs/framework/com/android/net/module/util/DnsSdTxtRecord.java new file mode 100644 index 0000000000000000000000000000000000000000..760891b9fca570ccff5a390046252b23738a109b --- /dev/null +++ b/staticlibs/framework/com/android/net/module/util/DnsSdTxtRecord.java @@ -0,0 +1,325 @@ +/* -*- Mode: Java; tab-width: 4 -*- + * + * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. + * + * 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. + + To do: + - implement remove() + - fix set() to replace existing values + */ + +package com.android.net.module.util; + +import android.os.Parcelable; +import android.os.Parcel; + +import java.util.Arrays; + +/** + * This class handles TXT record data for DNS based service discovery as specified at + * http://tools.ietf.org/html/draft-cheshire-dnsext-dns-sd-11 + * + * DNS-SD specifies that a TXT record corresponding to an SRV record consist of + * a packed array of bytes, each preceded by a length byte. Each string + * is an attribute-value pair. + * + * The DnsSdTxtRecord object stores the entire TXT data as a single byte array, traversing it + * as need be to implement its various methods. + * @hide + * + */ +public class DnsSdTxtRecord implements Parcelable { + private static final byte mSeparator = '='; + + private byte[] mData; + + /** Constructs a new, empty TXT record. */ + public DnsSdTxtRecord() { + mData = new byte[0]; + } + + /** Constructs a new TXT record from a byte array in the standard format. */ + public DnsSdTxtRecord(byte[] data) { + mData = (byte[]) data.clone(); + } + + /** Copy constructor */ + public DnsSdTxtRecord(DnsSdTxtRecord src) { + if (src != null && src.mData != null) { + mData = (byte[]) src.mData.clone(); + } + } + + /** + * Set a key/value pair. Setting an existing key will replace its value. + * @param key Must be ascii with no '=' + * @param value matching value to key + */ + public void set(String key, String value) { + byte[] keyBytes; + byte[] valBytes; + int valLen; + + if (value != null) { + valBytes = value.getBytes(); + valLen = valBytes.length; + } else { + valBytes = null; + valLen = 0; + } + + try { + keyBytes = key.getBytes("US-ASCII"); + } + catch (java.io.UnsupportedEncodingException e) { + throw new IllegalArgumentException("key should be US-ASCII"); + } + + for (int i = 0; i < keyBytes.length; i++) { + if (keyBytes[i] == '=') { + throw new IllegalArgumentException("= is not a valid character in key"); + } + } + + if (keyBytes.length + valLen >= 255) { + throw new IllegalArgumentException("Key and Value length cannot exceed 255 bytes"); + } + + int currentLoc = remove(key); + if (currentLoc == -1) + currentLoc = keyCount(); + + insert(keyBytes, valBytes, currentLoc); + } + + /** + * Get a value for a key + * + * @param key + * @return The value associated with the key + */ + public String get(String key) { + byte[] val = this.getValue(key); + return val != null ? new String(val) : null; + } + + /** Remove a key/value pair. If found, returns the index or -1 if not found */ + public int remove(String key) { + int avStart = 0; + + for (int i=0; avStart < mData.length; i++) { + int avLen = mData[avStart]; + if (key.length() <= avLen && + (key.length() == avLen || mData[avStart + key.length() + 1] == mSeparator)) { + String s = new String(mData, avStart + 1, key.length()); + if (0 == key.compareToIgnoreCase(s)) { + byte[] oldBytes = mData; + mData = new byte[oldBytes.length - avLen - 1]; + System.arraycopy(oldBytes, 0, mData, 0, avStart); + System.arraycopy(oldBytes, avStart + avLen + 1, mData, avStart, + oldBytes.length - avStart - avLen - 1); + return i; + } + } + avStart += (0xFF & (avLen + 1)); + } + return -1; + } + + /** Return the count of keys */ + public int keyCount() { + int count = 0, nextKey; + for (nextKey = 0; nextKey < mData.length; count++) { + nextKey += (0xFF & (mData[nextKey] + 1)); + } + return count; + } + + /** Return true if key is present, false if not. */ + public boolean contains(String key) { + String s = null; + for (int i = 0; null != (s = this.getKey(i)); i++) { + if (0 == key.compareToIgnoreCase(s)) return true; + } + return false; + } + + /* Gets the size in bytes */ + public int size() { + return mData.length; + } + + /* Gets the raw data in bytes */ + public byte[] getRawData() { + return (byte[]) mData.clone(); + } + + private void insert(byte[] keyBytes, byte[] value, int index) { + byte[] oldBytes = mData; + int valLen = (value != null) ? value.length : 0; + int insertion = 0; + int newLen, avLen; + + for (int i = 0; i < index && insertion < mData.length; i++) { + insertion += (0xFF & (mData[insertion] + 1)); + } + + avLen = keyBytes.length + valLen + (value != null ? 1 : 0); + newLen = avLen + oldBytes.length + 1; + + mData = new byte[newLen]; + System.arraycopy(oldBytes, 0, mData, 0, insertion); + int secondHalfLen = oldBytes.length - insertion; + System.arraycopy(oldBytes, insertion, mData, newLen - secondHalfLen, secondHalfLen); + mData[insertion] = (byte) avLen; + System.arraycopy(keyBytes, 0, mData, insertion + 1, keyBytes.length); + if (value != null) { + mData[insertion + 1 + keyBytes.length] = mSeparator; + System.arraycopy(value, 0, mData, insertion + keyBytes.length + 2, valLen); + } + } + + /** Return a key in the TXT record by zero-based index. Returns null if index exceeds the total number of keys. */ + private String getKey(int index) { + int avStart = 0; + + for (int i=0; i < index && avStart < mData.length; i++) { + avStart += mData[avStart] + 1; + } + + if (avStart < mData.length) { + int avLen = mData[avStart]; + int aLen = 0; + + for (aLen=0; aLen < avLen; aLen++) { + if (mData[avStart + aLen + 1] == mSeparator) break; + } + return new String(mData, avStart + 1, aLen); + } + return null; + } + + /** + * Look up a key in the TXT record by zero-based index and return its value. + * Returns null if index exceeds the total number of keys. + * Returns null if the key is present with no value. + */ + private byte[] getValue(int index) { + int avStart = 0; + byte[] value = null; + + for (int i=0; i < index && avStart < mData.length; i++) { + avStart += mData[avStart] + 1; + } + + if (avStart < mData.length) { + int avLen = mData[avStart]; + int aLen = 0; + + for (aLen=0; aLen < avLen; aLen++) { + if (mData[avStart + aLen + 1] == mSeparator) { + value = new byte[avLen - aLen - 1]; + System.arraycopy(mData, avStart + aLen + 2, value, 0, avLen - aLen - 1); + break; + } + } + } + return value; + } + + private String getValueAsString(int index) { + byte[] value = this.getValue(index); + return value != null ? new String(value) : null; + } + + private byte[] getValue(String forKey) { + String s = null; + int i; + + for (i = 0; null != (s = this.getKey(i)); i++) { + if (0 == forKey.compareToIgnoreCase(s)) { + return this.getValue(i); + } + } + + return null; + } + + /** + * Return a string representation. + * Example : {key1=value1},{key2=value2}.. + * + * For a key say like "key3" with null value + * {key1=value1},{key2=value2}{key3} + */ + public String toString() { + String a, result = null; + + for (int i = 0; null != (a = this.getKey(i)); i++) { + String av = "{" + a; + String val = this.getValueAsString(i); + if (val != null) + av += "=" + val + "}"; + else + av += "}"; + if (result == null) + result = av; + else + result = result + ", " + av; + } + return result != null ? result : ""; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof DnsSdTxtRecord)) { + return false; + } + + DnsSdTxtRecord record = (DnsSdTxtRecord)o; + return Arrays.equals(record.mData, mData); + } + + @Override + public int hashCode() { + return Arrays.hashCode(mData); + } + + /** Implement the Parcelable interface */ + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeByteArray(mData); + } + + /** Implement the Parcelable interface */ + public static final @android.annotation.NonNull Creator<DnsSdTxtRecord> CREATOR = + new Creator<DnsSdTxtRecord>() { + public DnsSdTxtRecord createFromParcel(Parcel in) { + DnsSdTxtRecord info = new DnsSdTxtRecord(); + in.readByteArray(info.mData); + return info; + } + + public DnsSdTxtRecord[] newArray(int size) { + return new DnsSdTxtRecord[size]; + } + }; +} diff --git a/staticlibs/framework/com/android/net/module/util/HexDump.java b/staticlibs/framework/com/android/net/module/util/HexDump.java new file mode 100644 index 0000000000000000000000000000000000000000..a22c258d0a9dcff9aadd4d0bc7a457b726c6e9fe --- /dev/null +++ b/staticlibs/framework/com/android/net/module/util/HexDump.java @@ -0,0 +1,238 @@ +/* + * 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. + */ + +package com.android.net.module.util; + +import androidx.annotation.Nullable; + +/** + * Hex utility functions. + * + * @hide + */ +public class HexDump { + private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F' }; + private static final char[] HEX_LOWER_CASE_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + + /** + * Dump the hex string corresponding to the specified byte array. + * + * @param array byte array to be dumped. + */ + public static String dumpHexString(@Nullable byte[] array) { + if (array == null) return "(null)"; + return dumpHexString(array, 0, array.length); + } + + /** + * Dump the hex string corresponding to the specified byte array. + * + * @param array byte array to be dumped. + * @param offset the offset in array where dump should start. + * @param length the length of bytes to be dumped. + */ + public static String dumpHexString(@Nullable byte[] array, int offset, int length) { + if (array == null) return "(null)"; + StringBuilder result = new StringBuilder(); + + byte[] line = new byte[16]; + int lineIndex = 0; + + result.append("\n0x"); + result.append(toHexString(offset)); + + for (int i = offset; i < offset + length; i++) { + if (lineIndex == 16) { + result.append(" "); + + for (int j = 0; j < 16; j++) { + if (line[j] > ' ' && line[j] < '~') { + result.append(new String(line, j, 1)); + } else { + result.append("."); + } + } + + result.append("\n0x"); + result.append(toHexString(i)); + lineIndex = 0; + } + + byte b = array[i]; + result.append(" "); + result.append(HEX_DIGITS[(b >>> 4) & 0x0F]); + result.append(HEX_DIGITS[b & 0x0F]); + + line[lineIndex++] = b; + } + + if (lineIndex != 16) { + int count = (16 - lineIndex) * 3; + count++; + for (int i = 0; i < count; i++) { + result.append(" "); + } + + for (int i = 0; i < lineIndex; i++) { + if (line[i] > ' ' && line[i] < '~') { + result.append(new String(line, i, 1)); + } else { + result.append("."); + } + } + } + + return result.toString(); + } + + /** + * Convert a byte to an uppercase hex string. + * + * @param b the byte to be converted. + */ + public static String toHexString(byte b) { + return toHexString(toByteArray(b)); + } + + /** + * Convert a byte array to an uppercase hex string. + * + * @param array the byte array to be converted. + */ + public static String toHexString(byte[] array) { + return toHexString(array, 0, array.length, true); + } + + /** + * Convert a byte array to a hex string. + * + * @param array the byte array to be converted. + * @param upperCase whether the converted hex string should be uppercase or not. + */ + public static String toHexString(byte[] array, boolean upperCase) { + return toHexString(array, 0, array.length, upperCase); + } + + /** + * Convert a byte array to hex string. + * + * @param array the byte array to be converted. + * @param offset the offset in array where conversion should start. + * @param length the length of bytes to be converted. + */ + public static String toHexString(byte[] array, int offset, int length) { + return toHexString(array, offset, length, true); + } + + /** + * Convert a byte array to hex string. + * + * @param array the byte array to be converted. + * @param offset the offset in array where conversion should start. + * @param length the length of bytes to be converted. + * @param upperCase whether the converted hex string should be uppercase or not. + */ + public static String toHexString(byte[] array, int offset, int length, boolean upperCase) { + char[] digits = upperCase ? HEX_DIGITS : HEX_LOWER_CASE_DIGITS; + char[] buf = new char[length * 2]; + + int bufIndex = 0; + for (int i = offset; i < offset + length; i++) { + byte b = array[i]; + buf[bufIndex++] = digits[(b >>> 4) & 0x0F]; + buf[bufIndex++] = digits[b & 0x0F]; + } + + return new String(buf); + } + + /** + * Convert an integer to hex string. + * + * @param i the integer to be converted. + */ + public static String toHexString(int i) { + return toHexString(toByteArray(i)); + } + + /** + * Convert a byte to byte array. + * + * @param b the byte to be converted. + */ + public static byte[] toByteArray(byte b) { + byte[] array = new byte[1]; + array[0] = b; + return array; + } + + /** + * Convert an integer to byte array. + * + * @param i the integer to be converted. + */ + public static byte[] toByteArray(int i) { + byte[] array = new byte[4]; + + array[3] = (byte) (i & 0xFF); + array[2] = (byte) ((i >> 8) & 0xFF); + array[1] = (byte) ((i >> 16) & 0xFF); + array[0] = (byte) ((i >> 24) & 0xFF); + + return array; + } + + private static int toByte(char c) { + if (c >= '0' && c <= '9') return (c - '0'); + if (c >= 'A' && c <= 'F') return (c - 'A' + 10); + if (c >= 'a' && c <= 'f') return (c - 'a' + 10); + + throw new RuntimeException("Invalid hex char '" + c + "'"); + } + + /** + * Convert a hex string to a byte array. + * + * @param hexString the string to be converted. + */ + public static byte[] hexStringToByteArray(String hexString) { + int length = hexString.length(); + byte[] buffer = new byte[length / 2]; + + for (int i = 0; i < length; i += 2) { + buffer[i / 2] = + (byte) ((toByte(hexString.charAt(i)) << 4) | toByte(hexString.charAt(i + 1))); + } + + return buffer; + } + + /** + * Convert a byte to hex string and append it to StringBuilder. + * + * @param sb StringBuilder instance. + * @param b the byte to be converted. + * @param upperCase whether the converted hex string should be uppercase or not. + */ + public static StringBuilder appendByteAsHex(StringBuilder sb, byte b, boolean upperCase) { + char[] digits = upperCase ? HEX_DIGITS : HEX_LOWER_CASE_DIGITS; + sb.append(digits[(b >> 4) & 0xf]); + sb.append(digits[b & 0xf]); + return sb; + } +} diff --git a/staticlibs/framework/com/android/net/module/util/Inet4AddressUtils.java b/staticlibs/framework/com/android/net/module/util/Inet4AddressUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..87f43d518261db3964a5dfea03b072bc958c5c9f --- /dev/null +++ b/staticlibs/framework/com/android/net/module/util/Inet4AddressUtils.java @@ -0,0 +1,192 @@ +/* + * 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. + */ + +package com.android.net.module.util; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * Collection of utilities to work with IPv4 addresses. + * @hide + */ +public class Inet4AddressUtils { + + /** + * Convert a IPv4 address from an integer to an InetAddress (0x04030201 -> 1.2.3.4) + * + * <p>This method uses the higher-order int bytes as the lower-order IPv4 address bytes, + * which is an unusual convention. Consider {@link #intToInet4AddressHTH(int)} instead. + * @param hostAddress an int coding for an IPv4 address, where higher-order int byte is + * lower-order IPv4 address byte + */ + public static Inet4Address intToInet4AddressHTL(int hostAddress) { + return intToInet4AddressHTH(Integer.reverseBytes(hostAddress)); + } + + /** + * Convert a IPv4 address from an integer to an InetAddress (0x01020304 -> 1.2.3.4) + * @param hostAddress an int coding for an IPv4 address + */ + public static Inet4Address intToInet4AddressHTH(int hostAddress) { + byte[] addressBytes = { (byte) (0xff & (hostAddress >> 24)), + (byte) (0xff & (hostAddress >> 16)), + (byte) (0xff & (hostAddress >> 8)), + (byte) (0xff & hostAddress) }; + + try { + return (Inet4Address) InetAddress.getByAddress(addressBytes); + } catch (UnknownHostException e) { + throw new AssertionError(); + } + } + + /** + * Convert an IPv4 address from an InetAddress to an integer (1.2.3.4 -> 0x01020304) + * + * <p>This conversion can help order IP addresses: considering the ordering + * 192.0.2.1 < 192.0.2.2 < ..., resulting ints will follow that ordering if read as unsigned + * integers with {@link Integer#toUnsignedLong}. + * @param inetAddr is an InetAddress corresponding to the IPv4 address + * @return the IP address as integer + */ + public static int inet4AddressToIntHTH(Inet4Address inetAddr) + throws IllegalArgumentException { + byte [] addr = inetAddr.getAddress(); + return ((addr[0] & 0xff) << 24) | ((addr[1] & 0xff) << 16) + | ((addr[2] & 0xff) << 8) | (addr[3] & 0xff); + } + + /** + * Convert a IPv4 address from an InetAddress to an integer (1.2.3.4 -> 0x04030201) + * + * <p>This method stores the higher-order IPv4 address bytes in the lower-order int bytes, + * which is an unusual convention. Consider {@link #inet4AddressToIntHTH(Inet4Address)} instead. + * @param inetAddr is an InetAddress corresponding to the IPv4 address + * @return the IP address as integer + */ + public static int inet4AddressToIntHTL(Inet4Address inetAddr) { + return Integer.reverseBytes(inet4AddressToIntHTH(inetAddr)); + } + + /** + * Convert a network prefix length to an IPv4 netmask integer (prefixLength 17 -> 0xffff8000) + * @return the IPv4 netmask as an integer + */ + public static int prefixLengthToV4NetmaskIntHTH(int prefixLength) + throws IllegalArgumentException { + if (prefixLength < 0 || prefixLength > 32) { + throw new IllegalArgumentException("Invalid prefix length (0 <= prefix <= 32)"); + } + // (int)a << b is equivalent to a << (b & 0x1f): can't shift by 32 (-1 << 32 == -1) + return prefixLength == 0 ? 0 : 0xffffffff << (32 - prefixLength); + } + + /** + * Convert a network prefix length to an IPv4 netmask integer (prefixLength 17 -> 0x0080ffff). + * + * <p>This method stores the higher-order IPv4 address bytes in the lower-order int bytes, + * which is an unusual convention. Consider {@link #prefixLengthToV4NetmaskIntHTH(int)} instead. + * @return the IPv4 netmask as an integer + */ + public static int prefixLengthToV4NetmaskIntHTL(int prefixLength) + throws IllegalArgumentException { + return Integer.reverseBytes(prefixLengthToV4NetmaskIntHTH(prefixLength)); + } + + /** + * Convert an IPv4 netmask to a prefix length, checking that the netmask is contiguous. + * @param netmask as a {@code Inet4Address}. + * @return the network prefix length + * @throws IllegalArgumentException the specified netmask was not contiguous. + * @hide + */ + public static int netmaskToPrefixLength(Inet4Address netmask) { + // inetAddressToInt returns an int in *network* byte order. + int i = inet4AddressToIntHTH(netmask); + int prefixLength = Integer.bitCount(i); + int trailingZeros = Integer.numberOfTrailingZeros(i); + if (trailingZeros != 32 - prefixLength) { + throw new IllegalArgumentException("Non-contiguous netmask: " + Integer.toHexString(i)); + } + return prefixLength; + } + + /** + * Returns the implicit netmask of an IPv4 address, as was the custom before 1993. + */ + public static int getImplicitNetmask(Inet4Address address) { + int firstByte = address.getAddress()[0] & 0xff; // Convert to an unsigned value. + if (firstByte < 128) { + return 8; + } else if (firstByte < 192) { + return 16; + } else if (firstByte < 224) { + return 24; + } else { + return 32; // Will likely not end well for other reasons. + } + } + + /** + * Get the broadcast address for a given prefix. + * + * <p>For example 192.168.0.1/24 -> 192.168.0.255 + */ + public static Inet4Address getBroadcastAddress(Inet4Address addr, int prefixLength) + throws IllegalArgumentException { + final int intBroadcastAddr = inet4AddressToIntHTH(addr) + | ~prefixLengthToV4NetmaskIntHTH(prefixLength); + return intToInet4AddressHTH(intBroadcastAddr); + } + + /** + * Get a prefix mask as Inet4Address for a given prefix length. + * + * <p>For example 20 -> 255.255.240.0 + */ + public static Inet4Address getPrefixMaskAsInet4Address(int prefixLength) + throws IllegalArgumentException { + return intToInet4AddressHTH(prefixLengthToV4NetmaskIntHTH(prefixLength)); + } + + /** + * Trim leading zeros from IPv4 address strings + * Non-v4 addresses and host names remain unchanged. + * For example, 192.168.000.010 -> 192.168.0.10 + * @param addr a string representing an ip address + * @return a string properly trimmed + */ + public static String trimAddressZeros(String addr) { + if (addr == null) return null; + String[] octets = addr.split("\\."); + if (octets.length != 4) return addr; + StringBuilder builder = new StringBuilder(16); + String result = null; + for (int i = 0; i < 4; i++) { + try { + if (octets[i].length() > 3) return addr; + builder.append(Integer.parseInt(octets[i])); + } catch (NumberFormatException e) { + return addr; + } + if (i < 3) builder.append('.'); + } + result = builder.toString(); + return result; + } +} diff --git a/staticlibs/framework/com/android/net/module/util/InetAddressUtils.java b/staticlibs/framework/com/android/net/module/util/InetAddressUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..40fc59fb6d4f88167de4de4a5c6bc27659307733 --- /dev/null +++ b/staticlibs/framework/com/android/net/module/util/InetAddressUtils.java @@ -0,0 +1,97 @@ +/* + * 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. + */ + +package com.android.net.module.util; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.util.Log; + + +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * Collection of utilities to interact with {@link InetAddress} + * @hide + */ +public class InetAddressUtils { + + private static final String TAG = InetAddressUtils.class.getSimpleName(); + private static final int INET6_ADDR_LENGTH = 16; + + /** + * Writes an InetAddress to a parcel. The address may be null. This is likely faster than + * calling writeSerializable. + * @hide + */ + public static void parcelInetAddress(Parcel parcel, InetAddress address, int flags) { + byte[] addressArray = (address != null) ? address.getAddress() : null; + parcel.writeByteArray(addressArray); + if (address instanceof Inet6Address) { + final Inet6Address v6Address = (Inet6Address) address; + final boolean hasScopeId = v6Address.getScopeId() != 0; + parcel.writeBoolean(hasScopeId); + if (hasScopeId) parcel.writeInt(v6Address.getScopeId()); + } + + } + + /** + * Reads an InetAddress from a parcel. Returns null if the address that was written was null + * or if the data is invalid. + * @hide + */ + public static InetAddress unparcelInetAddress(Parcel in) { + byte[] addressArray = in.createByteArray(); + if (addressArray == null) { + return null; + } + + try { + if (addressArray.length == INET6_ADDR_LENGTH) { + final boolean hasScopeId = in.readBoolean(); + final int scopeId = hasScopeId ? in.readInt() : 0; + return Inet6Address.getByAddress(null /* host */, addressArray, scopeId); + } + + return InetAddress.getByAddress(addressArray); + } catch (UnknownHostException e) { + return null; + } + } + + /** + * Create a Inet6Address with scope id if it is a link local address. Otherwise, returns the + * original address. + */ + public static Inet6Address withScopeId(@NonNull final Inet6Address addr, int scopeid) { + if (!addr.isLinkLocalAddress()) { + return addr; + } + try { + return Inet6Address.getByAddress(null /* host */, addr.getAddress(), + scopeid); + } catch (UnknownHostException impossible) { + Log.wtf(TAG, "Cannot construct scoped Inet6Address with Inet6Address.getAddress(" + + addr.getHostAddress() + "): ", impossible); + return null; + } + } + + private InetAddressUtils() {} +} diff --git a/staticlibs/framework/com/android/net/module/util/InterfaceParams.java b/staticlibs/framework/com/android/net/module/util/InterfaceParams.java new file mode 100644 index 0000000000000000000000000000000000000000..30762ebb5bcabdc83c5ea64761e94a7f211dc1de --- /dev/null +++ b/staticlibs/framework/com/android/net/module/util/InterfaceParams.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util; + +import android.net.MacAddress; +import android.text.TextUtils; + +import java.net.NetworkInterface; +import java.net.SocketException; + + +/** + * Encapsulate the interface parameters common to IpClient/IpServer components. + * + * Basically all java.net.NetworkInterface methods throw Exceptions. IpClient + * and IpServer (sub)components need most or all of this information at some + * point during their lifecycles, so pass only this simplified object around + * which can be created once when IpClient/IpServer are told to start. + * + * @hide + */ +public class InterfaceParams { + public final String name; + public final int index; + public final boolean hasMacAddress; + public final MacAddress macAddr; + public final int defaultMtu; + + // TODO: move the below to NetworkStackConstants when this class is moved to the NetworkStack. + private static final int ETHER_MTU = 1500; + private static final int IPV6_MIN_MTU = 1280; + + + /** + * Return InterfaceParams corresponding with an interface name + * @param name the interface name + */ + public static InterfaceParams getByName(String name) { + final NetworkInterface netif = getNetworkInterfaceByName(name); + if (netif == null) return null; + + // Not all interfaces have MAC addresses, e.g. rmnet_data0. + final MacAddress macAddr = getMacAddress(netif); + + try { + return new InterfaceParams(name, netif.getIndex(), macAddr, netif.getMTU()); + } catch (IllegalArgumentException | SocketException e) { + return null; + } + } + + public InterfaceParams(String name, int index, MacAddress macAddr) { + this(name, index, macAddr, ETHER_MTU); + } + + public InterfaceParams(String name, int index, MacAddress macAddr, int defaultMtu) { + if (TextUtils.isEmpty(name)) { + throw new IllegalArgumentException("impossible interface name"); + } + + if (index <= 0) throw new IllegalArgumentException("invalid interface index"); + + this.name = name; + this.index = index; + this.hasMacAddress = (macAddr != null); + this.macAddr = hasMacAddress ? macAddr : MacAddress.fromBytes(new byte[] { + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00 }); + this.defaultMtu = (defaultMtu > IPV6_MIN_MTU) ? defaultMtu : IPV6_MIN_MTU; + } + + @Override + public String toString() { + return String.format("%s/%d/%s/%d", name, index, macAddr, defaultMtu); + } + + private static NetworkInterface getNetworkInterfaceByName(String name) { + try { + return NetworkInterface.getByName(name); + } catch (NullPointerException | SocketException e) { + return null; + } + } + + private static MacAddress getMacAddress(NetworkInterface netif) { + try { + return MacAddress.fromBytes(netif.getHardwareAddress()); + } catch (IllegalArgumentException | NullPointerException | SocketException e) { + return null; + } + } +} diff --git a/staticlibs/framework/com/android/net/module/util/IpRange.java b/staticlibs/framework/com/android/net/module/util/IpRange.java new file mode 100644 index 0000000000000000000000000000000000000000..0a3f95bee5fd1cca5e7f3c6f6aa14cdaa339612e --- /dev/null +++ b/staticlibs/framework/com/android/net/module/util/IpRange.java @@ -0,0 +1,223 @@ +/* + * 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. + */ + +package com.android.net.module.util; + +import static com.android.internal.annotations.VisibleForTesting.Visibility; + +import android.annotation.NonNull; +import android.net.IpPrefix; + +import com.android.internal.annotations.VisibleForTesting; + +import java.math.BigInteger; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.Queue; + +/** + * This class represents an IP range, i.e., a contiguous block of IP addresses defined by a starting + * and ending IP address. These addresses may not be power-of-two aligned. + * + * <p>Conversion to prefixes are deterministic, and will always return the same set of {@link + * IpPrefix}(es). Ordering of IpPrefix instances is not guaranteed. + * + * @hide + */ +public final class IpRange { + private static final int SIGNUM_POSITIVE = 1; + + private final byte[] mStartAddr; + private final byte[] mEndAddr; + + public IpRange(@NonNull InetAddress startAddr, @NonNull InetAddress endAddr) { + Objects.requireNonNull(startAddr, "startAddr must not be null"); + Objects.requireNonNull(endAddr, "endAddr must not be null"); + + if (!startAddr.getClass().equals(endAddr.getClass())) { + throw new IllegalArgumentException("Invalid range: Address family mismatch"); + } + if (addrToBigInteger(startAddr.getAddress()).compareTo( + addrToBigInteger(endAddr.getAddress())) >= 0) { + throw new IllegalArgumentException( + "Invalid range; start address must be before end address"); + } + + mStartAddr = startAddr.getAddress(); + mEndAddr = endAddr.getAddress(); + } + + @VisibleForTesting(visibility = Visibility.PRIVATE) + public IpRange(@NonNull IpPrefix prefix) { + Objects.requireNonNull(prefix, "prefix must not be null"); + + // Use masked address from IpPrefix to zero out lower order bits. + mStartAddr = prefix.getRawAddress(); + + // Set all non-prefix bits to max. + mEndAddr = prefix.getRawAddress(); + for (int bitIndex = prefix.getPrefixLength(); bitIndex < 8 * mEndAddr.length; ++bitIndex) { + mEndAddr[bitIndex / 8] |= (byte) (0x80 >> (bitIndex % 8)); + } + } + + private static InetAddress getAsInetAddress(byte[] address) { + try { + return InetAddress.getByAddress(address); + } catch (UnknownHostException e) { + // Cannot happen. InetAddress.getByAddress can only throw an exception if the byte + // array is the wrong length, but are always generated from InetAddress(es). + throw new IllegalArgumentException("Address is invalid"); + } + } + + @VisibleForTesting(visibility = Visibility.PRIVATE) + public InetAddress getStartAddr() { + return getAsInetAddress(mStartAddr); + } + + @VisibleForTesting(visibility = Visibility.PRIVATE) + public InetAddress getEndAddr() { + return getAsInetAddress(mEndAddr); + } + + /** + * Converts this IP range to a list of IpPrefix instances. + * + * <p>This method outputs the IpPrefix instances for use in the routing architecture. + * + * <p>For example, the range 192.0.2.4 - 192.0.3.1 converts to the following prefixes: + * + * <ul> + * <li>192.0.2.128/25 + * <li>192.0.2.64/26 + * <li>192.0.2.32/27 + * <li>192.0.2.16/28 + * <li>192.0.2.8/29 + * <li>192.0.2.4/30 + * <li>192.0.3.0/31 + * </ul> + */ + public List<IpPrefix> asIpPrefixes() { + final boolean isIpv6 = (mStartAddr.length == 16); + final List<IpPrefix> result = new ArrayList<>(); + final Queue<IpPrefix> workingSet = new LinkedList<>(); + + // Start with the any-address. Inet4/6Address.ANY requires compiling against + // core-platform-api. + workingSet.add(new IpPrefix(isIpv6 ? getAsInetAddress(new byte[16]) /* IPv6_ANY */ + : getAsInetAddress(new byte[4]) /* IPv4_ANY */, 0)); + + // While items are still in the queue, test and narrow to subsets. + while (!workingSet.isEmpty()) { + final IpPrefix workingPrefix = workingSet.poll(); + final IpRange workingRange = new IpRange(workingPrefix); + + // If the other range is contained within, it's part of the output. Do not test subsets, + // or we will end up with duplicates. + if (containsRange(workingRange)) { + result.add(workingPrefix); + continue; + } + + // If there is any overlap, split the working range into it's two subsets, and + // reevaluate those. + if (overlapsRange(workingRange)) { + workingSet.addAll(getSubsetPrefixes(workingPrefix)); + } + } + + return result; + } + + /** + * Returns the two prefixes that comprise the given prefix. + * + * <p>For example, for the prefix 192.0.2.0/24, this will return the two prefixes that combined + * make up the current prefix: + * + * <ul> + * <li>192.0.2.0/25 + * <li>192.0.2.128/25 + * </ul> + */ + private static List<IpPrefix> getSubsetPrefixes(IpPrefix prefix) { + final List<IpPrefix> result = new ArrayList<>(); + + final int currentPrefixLen = prefix.getPrefixLength(); + result.add(new IpPrefix(prefix.getAddress(), currentPrefixLen + 1)); + + final byte[] other = prefix.getRawAddress(); + other[currentPrefixLen / 8] = + (byte) (other[currentPrefixLen / 8] ^ (0x80 >> (currentPrefixLen % 8))); + result.add(new IpPrefix(getAsInetAddress(other), currentPrefixLen + 1)); + + return result; + } + + /** + * Checks if the other IP range is contained within this one + * + * <p>Checks based on byte values. For other to be contained within this IP range, other's + * starting address must be greater or equal to the current IpRange's starting address, and the + * other's ending address must be less than or equal to the current IP range's ending address. + */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + public boolean containsRange(IpRange other) { + return addrToBigInteger(mStartAddr).compareTo(addrToBigInteger(other.mStartAddr)) <= 0 + && addrToBigInteger(mEndAddr).compareTo(addrToBigInteger(other.mEndAddr)) >= 0; + } + + /** + * Checks if the other IP range overlaps with this one + * + * <p>Checks based on byte values. For there to be overlap, this IpRange's starting address must + * be less than the other's ending address, and vice versa. + */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + public boolean overlapsRange(IpRange other) { + return addrToBigInteger(mStartAddr).compareTo(addrToBigInteger(other.mEndAddr)) <= 0 + && addrToBigInteger(other.mStartAddr).compareTo(addrToBigInteger(mEndAddr)) <= 0; + } + + @Override + public int hashCode() { + return Objects.hash(Arrays.hashCode(mStartAddr), Arrays.hashCode(mEndAddr)); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof IpRange)) { + return false; + } + + final IpRange other = (IpRange) obj; + return Arrays.equals(mStartAddr, other.mStartAddr) + && Arrays.equals(mEndAddr, other.mEndAddr); + } + + /** Gets the InetAddress in BigInteger form */ + private static BigInteger addrToBigInteger(byte[] addr) { + // Since addr.getAddress() returns network byte order (big-endian), it is compatible with + // the BigInteger constructor (which assumes big-endian). + return new BigInteger(SIGNUM_POSITIVE, addr); + } +} diff --git a/staticlibs/framework/com/android/net/module/util/IpUtils.java b/staticlibs/framework/com/android/net/module/util/IpUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..18d96f30a0b88ce9629346fdade60abe0fb869ac --- /dev/null +++ b/staticlibs/framework/com/android/net/module/util/IpUtils.java @@ -0,0 +1,174 @@ +/* + * 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 com.android.net.module.util; + +import com.android.internal.annotations.VisibleForTesting; + +import static android.system.OsConstants.IPPROTO_ICMPV6; +import static android.system.OsConstants.IPPROTO_TCP; +import static android.system.OsConstants.IPPROTO_UDP; + +import java.net.Inet6Address; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.nio.ShortBuffer; + +/** + * @hide + */ +public class IpUtils { + /** + * Converts a signed short value to an unsigned int value. Needed + * because Java does not have unsigned types. + */ + private static int intAbs(short v) { + return v & 0xFFFF; + } + + /** + * Performs an IP checksum (used in IP header and across UDP + * payload) or ICMP checksum on the specified portion of a ByteBuffer. The seed + * allows the checksum to commence with a specified value. + */ + @VisibleForTesting + public static int checksum(ByteBuffer buf, int seed, int start, int end) { + int sum = seed + 0xFFFF; // to make things work with empty / zero-filled buffer + final int bufPosition = buf.position(); + + // set position of original ByteBuffer, so that the ShortBuffer + // will be correctly initialized + buf.position(start); + ShortBuffer shortBuf = buf.asShortBuffer(); + + // re-set ByteBuffer position + buf.position(bufPosition); + + final int numShorts = (end - start) / 2; + for (int i = 0; i < numShorts; i++) { + sum += intAbs(shortBuf.get(i)); + } + start += numShorts * 2; + + // see if a singleton byte remains + if (end != start) { + short b = buf.get(start); + + // make it unsigned + if (b < 0) { + b += 256; + } + + sum += b * 256; // assumes bytebuffer is network order (ie. big endian) + } + + sum = ((sum >> 16) & 0xFFFF) + (sum & 0xFFFF); // max sum is 0x1FFFE + sum = ((sum >> 16) & 0xFFFF) + (sum & 0xFFFF); // max sum is 0xFFFF + return sum ^ 0xFFFF; // u16 bitwise negation + } + + private static int pseudoChecksumIPv4( + ByteBuffer buf, int headerOffset, int protocol, int transportLen) { + int partial = protocol + transportLen; + partial += intAbs(buf.getShort(headerOffset + 12)); + partial += intAbs(buf.getShort(headerOffset + 14)); + partial += intAbs(buf.getShort(headerOffset + 16)); + partial += intAbs(buf.getShort(headerOffset + 18)); + return partial; + } + + private static int pseudoChecksumIPv6( + ByteBuffer buf, int headerOffset, int protocol, int transportLen) { + int partial = protocol + transportLen; + for (int offset = 8; offset < 40; offset += 2) { + partial += intAbs(buf.getShort(headerOffset + offset)); + } + return partial; + } + + private static byte ipversion(ByteBuffer buf, int headerOffset) { + return (byte) ((buf.get(headerOffset) & (byte) 0xf0) >> 4); + } + + public static short ipChecksum(ByteBuffer buf, int headerOffset) { + byte ihl = (byte) (buf.get(headerOffset) & 0x0f); + return (short) checksum(buf, 0, headerOffset, headerOffset + ihl * 4); + } + + private static short transportChecksum(ByteBuffer buf, int protocol, + int ipOffset, int transportOffset, int transportLen) { + if (transportLen < 0) { + throw new IllegalArgumentException("Transport length < 0: " + transportLen); + } + int sum; + byte ver = ipversion(buf, ipOffset); + if (ver == 4) { + sum = pseudoChecksumIPv4(buf, ipOffset, protocol, transportLen); + } else if (ver == 6) { + sum = pseudoChecksumIPv6(buf, ipOffset, protocol, transportLen); + } else { + throw new UnsupportedOperationException("Checksum must be IPv4 or IPv6"); + } + + sum = checksum(buf, sum, transportOffset, transportOffset + transportLen); + if (protocol == IPPROTO_UDP && sum == 0) { + sum = (short) 0xffff; + } + return (short) sum; + } + + /** + * Calculate the UDP checksum for an UDP packet. + */ + public static short udpChecksum(ByteBuffer buf, int ipOffset, int transportOffset) { + int transportLen = intAbs(buf.getShort(transportOffset + 4)); + return transportChecksum(buf, IPPROTO_UDP, ipOffset, transportOffset, transportLen); + } + + /** + * Calculate the TCP checksum for a TCP packet. + */ + public static short tcpChecksum(ByteBuffer buf, int ipOffset, int transportOffset, + int transportLen) { + return transportChecksum(buf, IPPROTO_TCP, ipOffset, transportOffset, transportLen); + } + + /** + * Calculate the ICMP checksum for an ICMPv4 packet. + */ + public static short icmpChecksum(ByteBuffer buf, int transportOffset, int transportLen) { + // ICMP checksum doesn't include pseudo-header. See RFC 792. + return (short) checksum(buf, 0, transportOffset, transportOffset + transportLen); + } + + /** + * Calculate the ICMPv6 checksum for an ICMPv6 packet. + */ + public static short icmpv6Checksum(ByteBuffer buf, int ipOffset, int transportOffset, + int transportLen) { + return transportChecksum(buf, IPPROTO_ICMPV6, ipOffset, transportOffset, transportLen); + } + + public static String addressAndPortToString(InetAddress address, int port) { + return String.format( + (address instanceof Inet6Address) ? "[%s]:%d" : "%s:%d", + address.getHostAddress(), port); + } + + public static boolean isValidUdpOrTcpPort(int port) { + return port > 0 && port < 65536; + } +} diff --git a/staticlibs/framework/com/android/net/module/util/LinkPropertiesUtils.java b/staticlibs/framework/com/android/net/module/util/LinkPropertiesUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..e271f6490291258fa8016a7933a83d735da05639 --- /dev/null +++ b/staticlibs/framework/com/android/net/module/util/LinkPropertiesUtils.java @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.RouteInfo; +import android.text.TextUtils; + +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; + +/** + * Collection of link properties utilities. + * @hide + */ +public final class LinkPropertiesUtils { + + /** + * @param <T> The type of data to compare. + */ + public static class CompareResult<T> { + public final List<T> removed = new ArrayList<>(); + public final List<T> added = new ArrayList<>(); + + public CompareResult() {} + + public CompareResult(@Nullable Collection<T> oldItems, @Nullable Collection<T> newItems) { + if (oldItems != null) { + removed.addAll(oldItems); + } + if (newItems != null) { + for (T newItem : newItems) { + if (!removed.remove(newItem)) { + added.add(newItem); + } + } + } + } + + @Override + public String toString() { + return "removed=[" + TextUtils.join(",", removed) + + "] added=[" + TextUtils.join(",", added) + + "]"; + } + } + + /** + * Generic class to compare two lists of items of type {@code T} whose properties can change. + * The items to be compared must provide a way to calculate a corresponding key of type + * {@code K} such that if (and only if) an old and a new item have the same key, then the new + * item is an update of the old item. Both the old list and the new list may not contain more + * than one item with the same key, and may not contain any null items. + * + * @param <K> A class that represents the key of the items to be compared. + * @param <T> The class that represents the object to be compared. + */ + public static class CompareOrUpdateResult<K, T> { + public final List<T> added = new ArrayList<>(); + public final List<T> removed = new ArrayList<>(); + public final List<T> updated = new ArrayList<>(); + + /** + * Compares two lists of items. + * @param oldItems the old list of items. + * @param newItems the new list of items. + * @param keyCalculator a {@link Function} that calculates an item's key. + */ + public CompareOrUpdateResult(Collection<T> oldItems, Collection<T> newItems, + Function<T, K> keyCalculator) { + HashMap<K, T> updateTracker = new HashMap<>(); + + if (oldItems != null) { + for (T oldItem : oldItems) { + updateTracker.put(keyCalculator.apply(oldItem), oldItem); + } + } + + if (newItems != null) { + for (T newItem : newItems) { + T oldItem = updateTracker.remove(keyCalculator.apply(newItem)); + if (oldItem != null) { + if (!oldItem.equals(newItem)) { + // Update of existing item. + updated.add(newItem); + } + } else { + // New item. + added.add(newItem); + } + } + } + + removed.addAll(updateTracker.values()); + } + + @Override + public String toString() { + return "removed=[" + TextUtils.join(",", removed) + + "] added=[" + TextUtils.join(",", added) + + "] updated=[" + TextUtils.join(",", updated) + + "]"; + } + } + + /** + * Compares the addresses in {@code left} LinkProperties with {@code right} + * LinkProperties, examining only addresses on the base link. + * + * @param left A LinkProperties with the old list of addresses. + * @param right A LinkProperties with the new list of addresses. + * @return the differences between the addresses. + */ + public static @NonNull CompareResult<LinkAddress> compareAddresses( + @Nullable LinkProperties left, @Nullable LinkProperties right) { + /* + * Duplicate the LinkAddresses into removed, we will be removing + * address which are common between mLinkAddresses and target + * leaving the addresses that are different. And address which + * are in target but not in mLinkAddresses are placed in the + * addedAddresses. + */ + return new CompareResult<>(left != null ? left.getLinkAddresses() : null, + right != null ? right.getLinkAddresses() : null); + } + + /** + * Compares {@code left} {@code LinkProperties} allLinkAddresses against the {@code right}. + * + * @param left A LinkProperties or null + * @param right A LinkProperties or null + * @return {@code true} if both are identical, {@code false} otherwise. + * @see LinkProperties#getAllLinkAddresses() + */ + public static boolean isIdenticalAllLinkAddresses(@Nullable LinkProperties left, + @Nullable LinkProperties right) { + if (left == right) return true; + if (left == null || right == null) return false; + final List<LinkAddress> leftAddresses = left.getAllLinkAddresses(); + final List<LinkAddress> rightAddresses = right.getAllLinkAddresses(); + if (leftAddresses.size() != rightAddresses.size()) return false; + return leftAddresses.containsAll(rightAddresses); + } + + /** + * Compares {@code left} {@code LinkProperties} interface addresses against the {@code right}. + * + * @param left A LinkProperties. + * @param right LinkProperties to be compared with {@code left}. + * @return {@code true} if both are identical, {@code false} otherwise. + */ + public static boolean isIdenticalAddresses(@NonNull LinkProperties left, + @NonNull LinkProperties right) { + final Collection<InetAddress> leftAddresses = left.getAddresses(); + final Collection<InetAddress> rightAddresses = right.getAddresses(); + return (leftAddresses.size() == rightAddresses.size()) + ? leftAddresses.containsAll(rightAddresses) : false; + } + + /** + * Compares {@code left} {@code LinkProperties} DNS addresses against the {@code right}. + * + * @param left A LinkProperties. + * @param right A LinkProperties to be compared with {@code left}. + * @return {@code true} if both are identical, {@code false} otherwise. + */ + public static boolean isIdenticalDnses(@NonNull LinkProperties left, + @NonNull LinkProperties right) { + final Collection<InetAddress> leftDnses = left.getDnsServers(); + final Collection<InetAddress> rightDnses = right.getDnsServers(); + + final String leftDomains = left.getDomains(); + final String rightDomains = right.getDomains(); + if (leftDomains == null) { + if (rightDomains != null) return false; + } else { + if (!leftDomains.equals(rightDomains)) return false; + } + return (leftDnses.size() == rightDnses.size()) + ? leftDnses.containsAll(rightDnses) : false; + } + + /** + * Compares {@code left} {@code LinkProperties} HttpProxy against the {@code right}. + * + * @param left A LinkProperties. + * @param right A LinkProperties to be compared with {@code left}. + * @return {@code true} if both are identical, {@code false} otherwise. + */ + public static boolean isIdenticalHttpProxy(@NonNull LinkProperties left, + @NonNull LinkProperties right) { + return Objects.equals(left.getHttpProxy(), right.getHttpProxy()); + } + + /** + * Compares {@code left} {@code LinkProperties} interface name against the {@code right}. + * + * @param left A LinkProperties. + * @param right A LinkProperties to be compared with {@code left}. + * @return {@code true} if both are identical, {@code false} otherwise. + */ + public static boolean isIdenticalInterfaceName(@NonNull LinkProperties left, + @NonNull LinkProperties right) { + return TextUtils.equals(left.getInterfaceName(), right.getInterfaceName()); + } + + /** + * Compares {@code left} {@code LinkProperties} Routes against the {@code right}. + * + * @param left A LinkProperties. + * @param right A LinkProperties to be compared with {@code left}. + * @return {@code true} if both are identical, {@code false} otherwise. + */ + public static boolean isIdenticalRoutes(@NonNull LinkProperties left, + @NonNull LinkProperties right) { + final Collection<RouteInfo> leftRoutes = left.getRoutes(); + final Collection<RouteInfo> rightRoutes = right.getRoutes(); + return (leftRoutes.size() == rightRoutes.size()) + ? leftRoutes.containsAll(rightRoutes) : false; + } +} diff --git a/staticlibs/framework/com/android/net/module/util/LocationPermissionChecker.java b/staticlibs/framework/com/android/net/module/util/LocationPermissionChecker.java new file mode 100644 index 0000000000000000000000000000000000000000..cd1f31cbc5273208c344bb7544ff880228ad91a0 --- /dev/null +++ b/staticlibs/framework/com/android/net/module/util/LocationPermissionChecker.java @@ -0,0 +1,309 @@ +/* + * 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. + */ + +package com.android.net.module.util; + +import android.Manifest; +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.AppOpsManager; +import android.content.Context; +import android.content.pm.PackageManager; +import android.location.LocationManager; +import android.net.NetworkStack; +import android.os.Binder; +import android.os.Build; +import android.os.UserHandle; +import android.util.Log; + +import androidx.annotation.RequiresApi; + +import com.android.internal.annotations.VisibleForTesting; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + + +/** + * The methods used for location permission and location mode checking. + * + * @hide + */ +@RequiresApi(Build.VERSION_CODES.R) +public class LocationPermissionChecker { + + private static final String TAG = "LocationPermissionChecker"; + + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"LOCATION_PERMISSION_CHECK_STATUS_"}, value = { + SUCCEEDED, + ERROR_LOCATION_MODE_OFF, + ERROR_LOCATION_PERMISSION_MISSING, + }) + public @interface LocationPermissionCheckStatus{} + + // The location permission check succeeded. + public static final int SUCCEEDED = 0; + // The location mode turns off for the caller. + public static final int ERROR_LOCATION_MODE_OFF = 1; + // The location permission isn't granted for the caller. + public static final int ERROR_LOCATION_PERMISSION_MISSING = 2; + + private final Context mContext; + private final AppOpsManager mAppOpsManager; + + public LocationPermissionChecker(Context context) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + throw new UnsupportedOperationException("This utility is not supported before R"); + } + + mContext = context; + mAppOpsManager = mContext.getSystemService(AppOpsManager.class); + } + + /** + * Check location permission granted by the caller. + * + * This API check if the location mode enabled for the caller and the caller has + * ACCESS_COARSE_LOCATION permission is targetSDK<29, otherwise, has ACCESS_FINE_LOCATION. + * + * @param pkgName package name of the application requesting access + * @param featureId The feature in the package + * @param uid The uid of the package + * @param message A message describing why the permission was checked. Only needed if this is + * not inside of a two-way binder call from the data receiver + * + * @return {@code true} returns if the caller has location permission and the location mode is + * enabled. + */ + public boolean checkLocationPermission(String pkgName, @Nullable String featureId, + int uid, @Nullable String message) { + return checkLocationPermissionInternal(pkgName, featureId, uid, message) == SUCCEEDED; + } + + /** + * Check location permission granted by the caller. + * + * This API check if the location mode enabled for the caller and the caller has + * ACCESS_COARSE_LOCATION permission is targetSDK<29, otherwise, has ACCESS_FINE_LOCATION. + * Compared with {@link #checkLocationPermission(String, String, int, String)}, this API returns + * the detail information about the checking result, including the reason why it's failed and + * logs the error for the caller. + * + * @param pkgName package name of the application requesting access + * @param featureId The feature in the package + * @param uid The uid of the package + * @param message A message describing why the permission was checked. Only needed if this is + * not inside of a two-way binder call from the data receiver + * + * @return {@link LocationPermissionCheckStatus} the result of the location permission check. + */ + public @LocationPermissionCheckStatus int checkLocationPermissionWithDetailInfo( + String pkgName, @Nullable String featureId, int uid, @Nullable String message) { + final int result = checkLocationPermissionInternal(pkgName, featureId, uid, message); + switch (result) { + case ERROR_LOCATION_MODE_OFF: + Log.e(TAG, "Location mode is disabled for the device"); + break; + case ERROR_LOCATION_PERMISSION_MISSING: + Log.e(TAG, "UID " + uid + " has no location permission"); + break; + } + return result; + } + + /** + * Enforce the caller has location permission. + * + * This API determines if the location mode enabled for the caller and the caller has + * ACCESS_COARSE_LOCATION permission is targetSDK<29, otherwise, has ACCESS_FINE_LOCATION. + * SecurityException is thrown if the caller has no permission or the location mode is disabled. + * + * @param pkgName package name of the application requesting access + * @param featureId The feature in the package + * @param uid The uid of the package + * @param message A message describing why the permission was checked. Only needed if this is + * not inside of a two-way binder call from the data receiver + */ + public void enforceLocationPermission(String pkgName, @Nullable String featureId, int uid, + @Nullable String message) throws SecurityException { + final int result = checkLocationPermissionInternal(pkgName, featureId, uid, message); + + switch (result) { + case ERROR_LOCATION_MODE_OFF: + throw new SecurityException("Location mode is disabled for the device"); + case ERROR_LOCATION_PERMISSION_MISSING: + throw new SecurityException("UID " + uid + " has no location permission"); + } + } + + private int checkLocationPermissionInternal(String pkgName, @Nullable String featureId, + int uid, @Nullable String message) { + checkPackage(uid, pkgName); + + // Apps with NETWORK_SETTINGS, NETWORK_SETUP_WIZARD, NETWORK_STACK & MAINLINE_NETWORK_STACK + // are granted a bypass. + if (checkNetworkSettingsPermission(uid) || checkNetworkSetupWizardPermission(uid) + || checkNetworkStackPermission(uid) || checkMainlineNetworkStackPermission(uid)) { + return SUCCEEDED; + } + + // Location mode must be enabled + if (!isLocationModeEnabled()) { + return ERROR_LOCATION_MODE_OFF; + } + + // LocationAccess by App: caller must have Coarse/Fine Location permission to have access to + // location information. + if (!checkCallersLocationPermission(pkgName, featureId, uid, + true /* coarseForTargetSdkLessThanQ */, message)) { + return ERROR_LOCATION_PERMISSION_MISSING; + } + return SUCCEEDED; + } + + /** + * Checks that calling process has android.Manifest.permission.ACCESS_FINE_LOCATION or + * android.Manifest.permission.ACCESS_COARSE_LOCATION (depending on config/targetSDK level) + * and a corresponding app op is allowed for this package and uid. + * + * @param pkgName PackageName of the application requesting access + * @param featureId The feature in the package + * @param uid The uid of the package + * @param coarseForTargetSdkLessThanQ If true and the targetSDK < Q then will check for COARSE + * else (false or targetSDK >= Q) then will check for FINE + * @param message A message describing why the permission was checked. Only needed if this is + * not inside of a two-way binder call from the data receiver + */ + public boolean checkCallersLocationPermission(String pkgName, @Nullable String featureId, + int uid, boolean coarseForTargetSdkLessThanQ, @Nullable String message) { + + boolean isTargetSdkLessThanQ = isTargetSdkLessThan(pkgName, Build.VERSION_CODES.Q, uid); + + String permissionType = Manifest.permission.ACCESS_FINE_LOCATION; + if (coarseForTargetSdkLessThanQ && isTargetSdkLessThanQ) { + // Having FINE permission implies having COARSE permission (but not the reverse) + permissionType = Manifest.permission.ACCESS_COARSE_LOCATION; + } + if (getUidPermission(permissionType, uid) == PackageManager.PERMISSION_DENIED) { + return false; + } + + // Always checking FINE - even if will not enforce. This will record the request for FINE + // so that a location request by the app is surfaced to the user. + boolean isFineLocationAllowed = noteAppOpAllowed( + AppOpsManager.OPSTR_FINE_LOCATION, pkgName, featureId, uid, message); + if (isFineLocationAllowed) { + return true; + } + if (coarseForTargetSdkLessThanQ && isTargetSdkLessThanQ) { + return noteAppOpAllowed(AppOpsManager.OPSTR_COARSE_LOCATION, pkgName, featureId, uid, + message); + } + return false; + } + + /** + * Retrieves a handle to LocationManager (if not already done) and check if location is enabled. + */ + public boolean isLocationModeEnabled() { + final LocationManager LocationManager = mContext.getSystemService(LocationManager.class); + try { + return LocationManager.isLocationEnabledForUser(UserHandle.of( + getCurrentUser())); + } catch (Exception e) { + Log.e(TAG, "Failure to get location mode via API, falling back to settings", e); + return false; + } + } + + private boolean isTargetSdkLessThan(String packageName, int versionCode, int callingUid) { + final long ident = Binder.clearCallingIdentity(); + try { + if (mContext.getPackageManager().getApplicationInfoAsUser( + packageName, 0, + UserHandle.getUserHandleForUid(callingUid)).targetSdkVersion + < versionCode) { + return true; + } + } catch (PackageManager.NameNotFoundException e) { + // In case of exception, assume unknown app (more strict checking) + // Note: This case will never happen since checkPackage is + // called to verify validity before checking App's version. + } finally { + Binder.restoreCallingIdentity(ident); + } + return false; + } + + private boolean noteAppOpAllowed(String op, String pkgName, @Nullable String featureId, + int uid, @Nullable String message) { + return mAppOpsManager.noteOp(op, uid, pkgName, featureId, message) + == AppOpsManager.MODE_ALLOWED; + } + + private void checkPackage(int uid, String pkgName) + throws SecurityException { + if (pkgName == null) { + throw new SecurityException("Checking UID " + uid + " but Package Name is Null"); + } + mAppOpsManager.checkPackage(uid, pkgName); + } + + @VisibleForTesting + protected int getCurrentUser() { + return ActivityManager.getCurrentUser(); + } + + private int getUidPermission(String permissionType, int uid) { + // We don't care about pid, pass in -1 + return mContext.checkPermission(permissionType, -1, uid); + } + + /** + * Returns true if the |uid| holds NETWORK_SETTINGS permission. + */ + public boolean checkNetworkSettingsPermission(int uid) { + return getUidPermission(android.Manifest.permission.NETWORK_SETTINGS, uid) + == PackageManager.PERMISSION_GRANTED; + } + + /** + * Returns true if the |uid| holds NETWORK_SETUP_WIZARD permission. + */ + public boolean checkNetworkSetupWizardPermission(int uid) { + return getUidPermission(android.Manifest.permission.NETWORK_SETUP_WIZARD, uid) + == PackageManager.PERMISSION_GRANTED; + } + + /** + * Returns true if the |uid| holds NETWORK_STACK permission. + */ + public boolean checkNetworkStackPermission(int uid) { + return getUidPermission(android.Manifest.permission.NETWORK_STACK, uid) + == PackageManager.PERMISSION_GRANTED; + } + + /** + * Returns true if the |uid| holds MAINLINE_NETWORK_STACK permission. + */ + public boolean checkMainlineNetworkStackPermission(int uid) { + return getUidPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, uid) + == PackageManager.PERMISSION_GRANTED; + } + +} diff --git a/staticlibs/framework/com/android/net/module/util/MacAddressUtils.java b/staticlibs/framework/com/android/net/module/util/MacAddressUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..ab0040c01e21a1afdd569b213dfc98eaf1e5927d --- /dev/null +++ b/staticlibs/framework/com/android/net/module/util/MacAddressUtils.java @@ -0,0 +1,138 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.MacAddress; + +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Objects; +import java.util.Random; + +/** + * Collection of MAC address utilities. + * @hide + */ +public final class MacAddressUtils { + + private static final long VALID_LONG_MASK = (1L << 48) - 1; + private static final long LOCALLY_ASSIGNED_MASK = longAddrFromByteAddr( + MacAddress.fromString("2:0:0:0:0:0").toByteArray()); + private static final long MULTICAST_MASK = longAddrFromByteAddr( + MacAddress.fromString("1:0:0:0:0:0").toByteArray()); + private static final long OUI_MASK = longAddrFromByteAddr( + MacAddress.fromString("ff:ff:ff:0:0:0").toByteArray()); + private static final long NIC_MASK = longAddrFromByteAddr( + MacAddress.fromString("0:0:0:ff:ff:ff").toByteArray()); + // Matches WifiInfo.DEFAULT_MAC_ADDRESS + private static final MacAddress DEFAULT_MAC_ADDRESS = + MacAddress.fromString("02:00:00:00:00:00"); + private static final int ETHER_ADDR_LEN = 6; + + /** + * @return true if this MacAddress is a multicast address. + */ + public static boolean isMulticastAddress(@NonNull MacAddress address) { + return (longAddrFromByteAddr(address.toByteArray()) & MULTICAST_MASK) != 0; + } + + /** + * Returns a generated MAC address whose 46 bits, excluding the locally assigned bit and the + * unicast bit, are randomly selected. + * + * The locally assigned bit is always set to 1. The multicast bit is always set to 0. + * + * @return a random locally assigned, unicast MacAddress. + */ + public static @NonNull MacAddress createRandomUnicastAddress() { + return createRandomUnicastAddress(null, new SecureRandom()); + } + + /** + * Returns a randomly generated MAC address using the given Random object and the same + * OUI values as the given MacAddress. + * + * The locally assigned bit is always set to 1. The multicast bit is always set to 0. + * + * @param base a base MacAddress whose OUI is used for generating the random address. + * If base == null then the OUI will also be randomized. + * @param r a standard Java Random object used for generating the random address. + * @return a random locally assigned MacAddress. + */ + public static @NonNull MacAddress createRandomUnicastAddress(@Nullable MacAddress base, + @NonNull Random r) { + long addr; + + if (base == null) { + addr = r.nextLong() & VALID_LONG_MASK; + } else { + addr = (longAddrFromByteAddr(base.toByteArray()) & OUI_MASK) + | (NIC_MASK & r.nextLong()); + } + addr |= LOCALLY_ASSIGNED_MASK; + addr &= ~MULTICAST_MASK; + MacAddress mac = MacAddress.fromBytes(byteAddrFromLongAddr(addr)); + if (mac.equals(DEFAULT_MAC_ADDRESS)) { + return createRandomUnicastAddress(base, r); + } + return mac; + } + + /** + * Convert a byte address to long address. + */ + public static long longAddrFromByteAddr(byte[] addr) { + Objects.requireNonNull(addr); + if (!isMacAddress(addr)) { + throw new IllegalArgumentException( + Arrays.toString(addr) + " was not a valid MAC address"); + } + long longAddr = 0; + for (byte b : addr) { + final int uint8Byte = b & 0xff; + longAddr = (longAddr << 8) + uint8Byte; + } + return longAddr; + } + + /** + * Convert a long address to byte address. + */ + public static byte[] byteAddrFromLongAddr(long addr) { + byte[] bytes = new byte[ETHER_ADDR_LEN]; + int index = ETHER_ADDR_LEN; + while (index-- > 0) { + bytes[index] = (byte) addr; + addr = addr >> 8; + } + return bytes; + } + + /** + * Returns true if the given byte array is a valid MAC address. + * A valid byte array representation for a MacAddress is a non-null array of length 6. + * + * @param addr a byte array. + * @return true if the given byte array is not null and has the length of a MAC address. + * + */ + public static boolean isMacAddress(byte[] addr) { + return addr != null && addr.length == ETHER_ADDR_LEN; + } +} diff --git a/staticlibs/framework/com/android/net/module/util/NetUtils.java b/staticlibs/framework/com/android/net/module/util/NetUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..ae1b9bbc89a3dac95fd5ee25d213b7a9122fed12 --- /dev/null +++ b/staticlibs/framework/com/android/net/module/util/NetUtils.java @@ -0,0 +1,119 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.RouteInfo; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Collection; + +/** + * Collection of network common utilities. + * @hide + */ +public final class NetUtils { + + /** + * Check if IP address type is consistent between two InetAddress. + * @return true if both are the same type. False otherwise. + */ + public static boolean addressTypeMatches(@NonNull InetAddress left, + @NonNull InetAddress right) { + return (((left instanceof Inet4Address) && (right instanceof Inet4Address)) + || ((left instanceof Inet6Address) && (right instanceof Inet6Address))); + } + + /** + * Find the route from a Collection of routes that best matches a given address. + * May return null if no routes are applicable, or the best route is not a route of + * {@link RouteInfo.RTN_UNICAST} type. + * + * @param routes a Collection of RouteInfos to chose from + * @param dest the InetAddress your trying to get to + * @return the RouteInfo from the Collection that best fits the given address + */ + @Nullable + public static RouteInfo selectBestRoute(@Nullable Collection<RouteInfo> routes, + @Nullable InetAddress dest) { + if ((routes == null) || (dest == null)) return null; + + RouteInfo bestRoute = null; + + // pick a longest prefix match under same address type + for (RouteInfo route : routes) { + if (addressTypeMatches(route.getDestination().getAddress(), dest)) { + if ((bestRoute != null) + && (bestRoute.getDestination().getPrefixLength() + >= route.getDestination().getPrefixLength())) { + continue; + } + if (route.matches(dest)) bestRoute = route; + } + } + + if (bestRoute != null && bestRoute.getType() == RouteInfo.RTN_UNICAST) { + return bestRoute; + } else { + return null; + } + } + + /** + * Get InetAddress masked with prefixLength. Will never return null. + * @param address the IP address to mask with + * @param prefixLength the prefixLength used to mask the IP + */ + public static InetAddress getNetworkPart(InetAddress address, int prefixLength) { + byte[] array = address.getAddress(); + maskRawAddress(array, prefixLength); + + InetAddress netPart = null; + try { + netPart = InetAddress.getByAddress(array); + } catch (UnknownHostException e) { + throw new RuntimeException("getNetworkPart error - " + e.toString()); + } + return netPart; + } + + /** + * Masks a raw IP address byte array with the specified prefix length. + */ + public static void maskRawAddress(byte[] array, int prefixLength) { + if (prefixLength < 0 || prefixLength > array.length * 8) { + throw new RuntimeException("IP address with " + array.length + + " bytes has invalid prefix length " + prefixLength); + } + + int offset = prefixLength / 8; + int remainder = prefixLength % 8; + byte mask = (byte) (0xFF << (8 - remainder)); + + if (offset < array.length) array[offset] = (byte) (array[offset] & mask); + + offset++; + + for (; offset < array.length; offset++) { + array[offset] = 0; + } + } +} diff --git a/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java b/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..54ce01ecbdecffc4847f2b1e9f915ac0642e7aea --- /dev/null +++ b/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java @@ -0,0 +1,187 @@ +/* + * 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. + */ + +package com.android.net.module.util; + +import static android.net.NetworkCapabilities.NET_CAPABILITY_BIP; +import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS; +import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS; +import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE; +import static android.net.NetworkCapabilities.NET_CAPABILITY_FOTA; +import static android.net.NetworkCapabilities.NET_CAPABILITY_IA; +import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.NET_CAPABILITY_MCX; +import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS; +import static android.net.NetworkCapabilities.NET_CAPABILITY_MMTEL; +import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID; +import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE; +import static android.net.NetworkCapabilities.NET_CAPABILITY_RCS; +import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL; +import static android.net.NetworkCapabilities.NET_CAPABILITY_VEHICLE_INTERNAL; +import static android.net.NetworkCapabilities.NET_CAPABILITY_VSIM; +import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P; +import static android.net.NetworkCapabilities.NET_CAPABILITY_XCAP; +import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; +import static android.net.NetworkCapabilities.TRANSPORT_USB; +import static android.net.NetworkCapabilities.TRANSPORT_VPN; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE; + +import static com.android.net.module.util.BitUtils.packBitList; +import static com.android.net.module.util.BitUtils.unpackBits; + +import android.annotation.NonNull; +import android.net.NetworkCapabilities; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * Utilities to examine {@link android.net.NetworkCapabilities}. + * @hide + */ +public final class NetworkCapabilitiesUtils { + // Transports considered to classify networks in UI, in order of which transport should be + // surfaced when there are multiple transports. Transports not in this list do not have + // an ordering preference (in practice they will have a deterministic order based on the + // transport int itself). + private static final int[] DISPLAY_TRANSPORT_PRIORITIES = new int[] { + // Users think of their VPNs as VPNs, not as any of the underlying nets + TRANSPORT_VPN, + // If the network has cell, prefer showing that because it's usually metered. + TRANSPORT_CELLULAR, + // If the network has WiFi aware, prefer showing that as it's a more specific use case. + // Ethernet can masquerade as other transports, where the device uses ethernet to connect to + // a box providing cell or wifi. Today this is represented by only the masqueraded type for + // backward compatibility, but these networks should morally have Ethernet & the masqueraded + // type. Because of this, prefer other transports instead of Ethernet. + TRANSPORT_WIFI_AWARE, + TRANSPORT_BLUETOOTH, + TRANSPORT_WIFI, + TRANSPORT_ETHERNET, + TRANSPORT_USB + + // Notably, TRANSPORT_TEST is not in this list as any network that has TRANSPORT_TEST and + // one of the above transports should be counted as that transport, to keep tests as + // realistic as possible. + }; + + /** + * Capabilities that suggest that a network is restricted. + * See {@code NetworkCapabilities#maybeMarkCapabilitiesRestricted}, + * and {@code FORCE_RESTRICTED_CAPABILITIES}. + */ + @VisibleForTesting + public static final long RESTRICTED_CAPABILITIES = packBitList( + NET_CAPABILITY_BIP, + NET_CAPABILITY_CBS, + NET_CAPABILITY_DUN, + NET_CAPABILITY_EIMS, + NET_CAPABILITY_ENTERPRISE, + NET_CAPABILITY_FOTA, + NET_CAPABILITY_IA, + NET_CAPABILITY_IMS, + NET_CAPABILITY_MCX, + NET_CAPABILITY_RCS, + NET_CAPABILITY_VEHICLE_INTERNAL, + NET_CAPABILITY_VSIM, + NET_CAPABILITY_XCAP, + NET_CAPABILITY_MMTEL); + + /** + * Capabilities that force network to be restricted. + * See {@code NetworkCapabilities#maybeMarkCapabilitiesRestricted}. + */ + private static final long FORCE_RESTRICTED_CAPABILITIES = packBitList( + NET_CAPABILITY_ENTERPRISE, + NET_CAPABILITY_OEM_PAID, + NET_CAPABILITY_OEM_PRIVATE); + + /** + * Capabilities that suggest that a network is unrestricted. + * See {@code NetworkCapabilities#maybeMarkCapabilitiesRestricted}. + */ + @VisibleForTesting + public static final long UNRESTRICTED_CAPABILITIES = packBitList( + NET_CAPABILITY_INTERNET, + NET_CAPABILITY_MMS, + NET_CAPABILITY_SUPL, + NET_CAPABILITY_WIFI_P2P); + + /** + * Get a transport that can be used to classify a network when displaying its info to users. + * + * While networks can have multiple transports, users generally think of them as "wifi", + * "mobile data", "vpn" and expect them to be classified as such in UI such as settings. + * @param transports Non-empty array of transports on a network + * @return A single transport + * @throws IllegalArgumentException The array is empty + */ + public static int getDisplayTransport(@NonNull int[] transports) { + for (int transport : DISPLAY_TRANSPORT_PRIORITIES) { + if (CollectionUtils.contains(transports, transport)) { + return transport; + } + } + + if (transports.length < 1) { + // All NetworkCapabilities representing a network have at least one transport, so an + // empty transport array would be created by the caller instead of extracted from + // NetworkCapabilities. + throw new IllegalArgumentException("No transport in the provided array"); + } + return transports[0]; + } + + + /** + * Infers that all the capabilities it provides are typically provided by restricted networks + * or not. + * + * @param nc the {@link NetworkCapabilities} to infer the restricted capabilities. + * + * @return {@code true} if the network should be restricted. + */ + // TODO: Use packBits(nc.getCapabilities()) to check more easily using bit masks. + public static boolean inferRestrictedCapability(NetworkCapabilities nc) { + // Check if we have any capability that forces the network to be restricted. + for (int capability : unpackBits(FORCE_RESTRICTED_CAPABILITIES)) { + if (nc.hasCapability(capability)) { + return true; + } + } + + // Verify there aren't any unrestricted capabilities. If there are we say + // the whole thing is unrestricted unless it is forced to be restricted. + for (int capability : unpackBits(UNRESTRICTED_CAPABILITIES)) { + if (nc.hasCapability(capability)) { + return false; + } + } + + // Must have at least some restricted capabilities. + for (int capability : unpackBits(RESTRICTED_CAPABILITIES)) { + if (nc.hasCapability(capability)) { + return true; + } + } + return false; + } + +} diff --git a/staticlibs/framework/com/android/net/module/util/NetworkIdentityUtils.java b/staticlibs/framework/com/android/net/module/util/NetworkIdentityUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..b641753c032bf00da2067e5bad9bcc72058b06d0 --- /dev/null +++ b/staticlibs/framework/com/android/net/module/util/NetworkIdentityUtils.java @@ -0,0 +1,52 @@ +/* + * 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. + */ + +package com.android.net.module.util; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +/** + * Utilities to examine {@link android.net.NetworkIdentity}. + * @hide + */ +public class NetworkIdentityUtils { + /** + * Scrub given IMSI on production builds. + */ + @NonNull + public static String scrubSubscriberId(@Nullable String subscriberId) { + if (subscriberId != null) { + // TODO: parse this as MCC+MNC instead of hard-coding + return subscriberId.substring(0, Math.min(6, subscriberId.length())) + "..."; + } else { + return "null"; + } + } + + /** + * Scrub given IMSI on production builds. + */ + @Nullable + public static String[] scrubSubscriberIds(@Nullable String[] subscriberIds) { + if (subscriberIds == null) return null; + final String[] res = new String[subscriberIds.length]; + for (int i = 0; i < res.length; i++) { + res[i] = scrubSubscriberId(subscriberIds[i]); + } + return res; + } +} diff --git a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java new file mode 100644 index 0000000000000000000000000000000000000000..f9895c608ce69e870707ed7eb649fc70de656d1e --- /dev/null +++ b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java @@ -0,0 +1,342 @@ +/* + * 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. + */ + +package com.android.net.module.util; + +import android.net.InetAddresses; +import android.net.IpPrefix; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * Network constants used by the network stack. + * @hide + */ +public final class NetworkStackConstants { + + /** + * Ethernet constants. + * + * See also: + * - https://tools.ietf.org/html/rfc894 + * - https://tools.ietf.org/html/rfc2464 + * - https://tools.ietf.org/html/rfc7042 + * - http://www.iana.org/assignments/ethernet-numbers/ethernet-numbers.xhtml + * - http://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml + */ + public static final int ETHER_DST_ADDR_OFFSET = 0; + public static final int ETHER_SRC_ADDR_OFFSET = 6; + public static final int ETHER_ADDR_LEN = 6; + public static final int ETHER_TYPE_OFFSET = 12; + public static final int ETHER_TYPE_LENGTH = 2; + public static final int ETHER_TYPE_ARP = 0x0806; + public static final int ETHER_TYPE_IPV4 = 0x0800; + public static final int ETHER_TYPE_IPV6 = 0x86dd; + public static final int ETHER_HEADER_LEN = 14; + public static final int ETHER_MTU = 1500; + public static final byte[] ETHER_BROADCAST = new byte[] { + (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, + }; + + /** + * ARP constants. + * + * See also: + * - https://tools.ietf.org/html/rfc826 + * - http://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml + */ + public static final int ARP_PAYLOAD_LEN = 28; // For Ethernet+IPv4. + public static final int ARP_ETHER_IPV4_LEN = ARP_PAYLOAD_LEN + ETHER_HEADER_LEN; + public static final int ARP_REQUEST = 1; + public static final int ARP_REPLY = 2; + public static final int ARP_HWTYPE_RESERVED_LO = 0; + public static final int ARP_HWTYPE_ETHER = 1; + public static final int ARP_HWTYPE_RESERVED_HI = 0xffff; + + /** + * IPv4 Address Conflict Detection constants. + * + * See also: + * - https://tools.ietf.org/html/rfc5227 + */ + public static final int IPV4_CONFLICT_PROBE_NUM = 3; + public static final int IPV4_CONFLICT_ANNOUNCE_NUM = 2; + + /** + * IPv4 constants. + * + * See also: + * - https://tools.ietf.org/html/rfc791 + */ + public static final int IPV4_ADDR_BITS = 32; + public static final int IPV4_MIN_MTU = 68; + public static final int IPV4_MAX_MTU = 65_535; + public static final int IPV4_HEADER_MIN_LEN = 20; + public static final int IPV4_IHL_MASK = 0xf; + public static final int IPV4_LENGTH_OFFSET = 2; + public static final int IPV4_FLAGS_OFFSET = 6; + public static final int IPV4_FRAGMENT_MASK = 0x1fff; + public static final int IPV4_PROTOCOL_OFFSET = 9; + public static final int IPV4_CHECKSUM_OFFSET = 10; + public static final int IPV4_SRC_ADDR_OFFSET = 12; + public static final int IPV4_DST_ADDR_OFFSET = 16; + public static final int IPV4_ADDR_LEN = 4; + public static final int IPV4_FLAG_MF = 0x2000; + public static final int IPV4_FLAG_DF = 0x4000; + // getSockOpt() for v4 MTU + public static final int IP_MTU = 14; + public static final Inet4Address IPV4_ADDR_ALL = makeInet4Address( + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff); + public static final Inet4Address IPV4_ADDR_ANY = makeInet4Address( + (byte) 0, (byte) 0, (byte) 0, (byte) 0); + public static final Inet6Address IPV6_ADDR_ANY = makeInet6Address(new byte[]{ + (byte) 0, (byte) 0, (byte) 0, (byte) 0, + (byte) 0, (byte) 0, (byte) 0, (byte) 0, + (byte) 0, (byte) 0, (byte) 0, (byte) 0, + (byte) 0, (byte) 0, (byte) 0, (byte) 0 }); + + /** + * CLAT constants + */ + public static final IpPrefix CLAT_PREFIX = new IpPrefix("192.0.0.0/29"); + + /** + * IPv6 constants. + * + * See also: + * - https://tools.ietf.org/html/rfc2460 + */ + public static final int IPV6_ADDR_LEN = 16; + public static final int IPV6_HEADER_LEN = 40; + public static final int IPV6_LEN_OFFSET = 4; + public static final int IPV6_PROTOCOL_OFFSET = 6; + public static final int IPV6_SRC_ADDR_OFFSET = 8; + public static final int IPV6_DST_ADDR_OFFSET = 24; + public static final int IPV6_MIN_MTU = 1280; + public static final int IPV6_FRAGMENT_HEADER_LEN = 8; + public static final int RFC7421_PREFIX_LENGTH = 64; + // getSockOpt() for v6 MTU + public static final int IPV6_MTU = 24; + public static final Inet6Address IPV6_ADDR_ALL_NODES_MULTICAST = + (Inet6Address) InetAddresses.parseNumericAddress("ff02::1"); + public static final Inet6Address IPV6_ADDR_ALL_ROUTERS_MULTICAST = + (Inet6Address) InetAddresses.parseNumericAddress("ff02::2"); + public static final Inet6Address IPV6_ADDR_ALL_HOSTS_MULTICAST = + (Inet6Address) InetAddresses.parseNumericAddress("ff02::3"); + + /** + * ICMP constants. + * + * See also: + * - https://tools.ietf.org/html/rfc792 + */ + public static final int ICMP_CHECKSUM_OFFSET = 2; + public static final int ICMP_HEADER_LEN = 8; + /** + * ICMPv6 constants. + * + * See also: + * - https://tools.ietf.org/html/rfc4191 + * - https://tools.ietf.org/html/rfc4443 + * - https://tools.ietf.org/html/rfc4861 + */ + public static final int ICMPV6_HEADER_MIN_LEN = 4; + public static final int ICMPV6_CHECKSUM_OFFSET = 2; + public static final int ICMPV6_ECHO_REPLY_TYPE = 129; + public static final int ICMPV6_ECHO_REQUEST_TYPE = 128; + public static final int ICMPV6_ROUTER_SOLICITATION = 133; + public static final int ICMPV6_ROUTER_ADVERTISEMENT = 134; + public static final int ICMPV6_NEIGHBOR_SOLICITATION = 135; + public static final int ICMPV6_NEIGHBOR_ADVERTISEMENT = 136; + public static final int ICMPV6_ND_OPTION_MIN_LENGTH = 8; + public static final int ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR = 8; + public static final int ICMPV6_ND_OPTION_SLLA = 1; + public static final int ICMPV6_ND_OPTION_TLLA = 2; + public static final int ICMPV6_ND_OPTION_PIO = 3; + public static final int ICMPV6_ND_OPTION_MTU = 5; + public static final int ICMPV6_ND_OPTION_RIO = 24; + public static final int ICMPV6_ND_OPTION_RDNSS = 25; + public static final int ICMPV6_ND_OPTION_PREF64 = 38; + + public static final int ICMPV6_RS_HEADER_LEN = 8; + public static final int ICMPV6_RA_HEADER_LEN = 16; + public static final int ICMPV6_NS_HEADER_LEN = 24; + public static final int ICMPV6_NA_HEADER_LEN = 24; + + public static final int NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER = 1 << 31; + public static final int NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED = 1 << 30; + public static final int NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE = 1 << 29; + + public static final byte ROUTER_ADVERTISEMENT_FLAG_MANAGED_ADDRESS = (byte) (1 << 7); + public static final byte ROUTER_ADVERTISEMENT_FLAG_OTHER = (byte) (1 << 6); + + public static final byte PIO_FLAG_ON_LINK = (byte) (1 << 7); + public static final byte PIO_FLAG_AUTONOMOUS = (byte) (1 << 6); + + /** + * TCP constants. + * + * See also: + * - https://tools.ietf.org/html/rfc793 + */ + public static final int TCP_HEADER_MIN_LEN = 20; + public static final int TCP_CHECKSUM_OFFSET = 16; + public static final byte TCPHDR_FIN = (byte) (1 << 0); + public static final byte TCPHDR_SYN = (byte) (1 << 1); + public static final byte TCPHDR_RST = (byte) (1 << 2); + public static final byte TCPHDR_PSH = (byte) (1 << 3); + public static final byte TCPHDR_ACK = (byte) (1 << 4); + public static final byte TCPHDR_URG = (byte) (1 << 5); + + /** + * UDP constants. + * + * See also: + * - https://tools.ietf.org/html/rfc768 + */ + public static final int UDP_HEADER_LEN = 8; + public static final int UDP_SRCPORT_OFFSET = 0; + public static final int UDP_DSTPORT_OFFSET = 2; + public static final int UDP_LENGTH_OFFSET = 4; + public static final int UDP_CHECKSUM_OFFSET = 6; + + /** + * DHCP constants. + * + * See also: + * - https://tools.ietf.org/html/rfc2131 + */ + public static final int INFINITE_LEASE = 0xffffffff; + public static final int DHCP4_CLIENT_PORT = 68; + // The maximum length of a DHCP packet that can be constructed. + public static final int DHCP_MAX_LENGTH = 1500; + public static final int DHCP_MAX_OPTION_LEN = 255; + + /** + * DHCPv6 constants. + * + * See also: + * - https://datatracker.ietf.org/doc/html/rfc8415 + * - https://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml + */ + public static final int DHCP6_CLIENT_PORT = 546; + public static final int DHCP6_SERVER_PORT = 547; + public static final Inet6Address ALL_DHCP_RELAY_AGENTS_AND_SERVERS = + (Inet6Address) InetAddresses.parseNumericAddress("ff02::1:2"); + public static final int DHCP6_OPTION_IA_ADDR = 5; + public static final int DHCP6_OPTION_IA_PD = 25; + public static final int DHCP6_OPTION_IAPREFIX = 26; + + /** + * DNS constants. + * + * See also: + * - https://datatracker.ietf.org/doc/html/rfc7858#section-3.1 + */ + public static final short DNS_OVER_TLS_PORT = 853; + + /** + * IEEE802.11 standard constants. + * + * See also: + * - https://ieeexplore.ieee.org/document/7786995 + */ + public static final int VENDOR_SPECIFIC_IE_ID = 0xdd; + + + /** + * TrafficStats constants. + */ + // These tags are used by the network stack to do traffic for its own purposes. Traffic + // tagged with these will be counted toward the network stack and must stay inside the + // range defined by + // {@link android.net.TrafficStats#TAG_NETWORK_STACK_RANGE_START} and + // {@link android.net.TrafficStats#TAG_NETWORK_STACK_RANGE_END}. + public static final int TAG_SYSTEM_DHCP = 0xFFFFFE01; + public static final int TAG_SYSTEM_NEIGHBOR = 0xFFFFFE02; + public static final int TAG_SYSTEM_DHCP_SERVER = 0xFFFFFE03; + + // These tags are used by the network stack to do traffic on behalf of apps. Traffic + // tagged with these will be counted toward the app on behalf of which the network + // stack is doing this traffic. These values must stay inside the range defined by + // {@link android.net.TrafficStats#TAG_NETWORK_STACK_IMPERSONATION_RANGE_START} and + // {@link android.net.TrafficStats#TAG_NETWORK_STACK_IMPERSONATION_RANGE_END}. + public static final int TAG_SYSTEM_PROBE = 0xFFFFFF81; + public static final int TAG_SYSTEM_DNS = 0xFFFFFF82; + + /** + * A test URL used to override configuration settings and overlays for the network validation + * HTTPS URL, when set in {@link android.provider.DeviceConfig} configuration. + * + * <p>This URL will be ignored if the host is not "localhost" (it can only be used to test with + * a local test server), and must not be set in production scenarios (as enforced by CTS tests). + * + * <p>{@link #TEST_URL_EXPIRATION_TIME} must also be set to use this setting. + */ + public static final String TEST_CAPTIVE_PORTAL_HTTPS_URL = "test_captive_portal_https_url"; + /** + * A test URL used to override configuration settings and overlays for the network validation + * HTTP URL, when set in {@link android.provider.DeviceConfig} configuration. + * + * <p>This URL will be ignored if the host is not "localhost" (it can only be used to test with + * a local test server), and must not be set in production scenarios (as enforced by CTS tests). + * + * <p>{@link #TEST_URL_EXPIRATION_TIME} must also be set to use this setting. + */ + public static final String TEST_CAPTIVE_PORTAL_HTTP_URL = "test_captive_portal_http_url"; + /** + * Expiration time of the test URL, in ms, relative to {@link System#currentTimeMillis()}. + * + * <p>After this expiration time, test URLs will be ignored. They will also be ignored if + * the expiration time is more than 10 minutes in the future, to avoid misconfiguration + * following test runs. + */ + public static final String TEST_URL_EXPIRATION_TIME = "test_url_expiration_time"; + + // TODO: Move to Inet4AddressUtils + // See aosp/1455936: NetworkStackConstants can't depend on it as it causes jarjar-related issues + // for users of both the net-utils-device-common and net-utils-framework-common libraries. + // Jarjar rule management needs to be simplified for that: b/170445871 + + /** + * Make an Inet4Address from 4 bytes in network byte order. + */ + private static Inet4Address makeInet4Address(byte b1, byte b2, byte b3, byte b4) { + try { + return (Inet4Address) InetAddress.getByAddress(new byte[] { b1, b2, b3, b4 }); + } catch (UnknownHostException e) { + throw new IllegalArgumentException("addr must be 4 bytes: this should never happen"); + } + } + + /** + * Make an Inet6Address from 16 bytes in network byte order. + */ + private static Inet6Address makeInet6Address(byte[] bytes) { + try { + return (Inet6Address) InetAddress.getByAddress(bytes); + } catch (UnknownHostException e) { + throw new IllegalArgumentException("addr must be 16 bytes: this should never happen"); + } + } + private NetworkStackConstants() { + throw new UnsupportedOperationException("This class is not to be instantiated"); + } +} diff --git a/staticlibs/framework/com/android/net/module/util/NetworkStatsUtils.java b/staticlibs/framework/com/android/net/module/util/NetworkStatsUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..41a9428a0083e6d1f962ca3552b52b762111b93e --- /dev/null +++ b/staticlibs/framework/com/android/net/module/util/NetworkStatsUtils.java @@ -0,0 +1,175 @@ +/* + * 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. + */ + +package com.android.net.module.util; + +import android.app.usage.NetworkStats; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * Various utilities used for NetworkStats related code. + * + * @hide + */ +public class NetworkStatsUtils { + // These constants must be synced with the definition in android.net.NetworkStats. + // TODO: update to formal APIs once all downstreams have these APIs. + private static final int SET_ALL = -1; + private static final int METERED_ALL = -1; + private static final int ROAMING_ALL = -1; + private static final int DEFAULT_NETWORK_ALL = -1; + + /** + * Safely multiple a value by a rational. + * <p> + * Internally it uses integer-based math whenever possible, but switches + * over to double-based math if values would overflow. + * @hide + */ + public static long multiplySafeByRational(long value, long num, long den) { + if (den == 0) { + throw new ArithmeticException("Invalid Denominator"); + } + long x = value; + long y = num; + + // Logic shamelessly borrowed from Math.multiplyExact() + long r = x * y; + long ax = Math.abs(x); + long ay = Math.abs(y); + if (((ax | ay) >>> 31 != 0)) { + // Some bits greater than 2^31 that might cause overflow + // Check the result using the divide operator + // and check for the special case of Long.MIN_VALUE * -1 + if (((y != 0) && (r / y != x)) + || (x == Long.MIN_VALUE && y == -1)) { + // Use double math to avoid overflowing + return (long) (((double) num / den) * value); + } + } + return r / den; + } + + /** + * Value of the match rule of the subscriberId to match networks with specific subscriberId. + * + * @hide + */ + public static final int SUBSCRIBER_ID_MATCH_RULE_EXACT = 0; + /** + * Value of the match rule of the subscriberId to match networks with any subscriberId which + * includes null and non-null. + * + * @hide + */ + public static final int SUBSCRIBER_ID_MATCH_RULE_ALL = 1; + + /** + * Name representing {@link #bandwidthSetGlobalAlert(long)} limit when delivered to + * {@link AlertObserver#onQuotaLimitReached(String, String)}. + */ + public static final String LIMIT_GLOBAL_ALERT = "globalAlert"; + + /** + * Return the constrained value by given the lower and upper bounds. + */ + public static int constrain(int amount, int low, int high) { + if (low > high) throw new IllegalArgumentException("low(" + low + ") > high(" + high + ")"); + return amount < low ? low : (amount > high ? high : amount); + } + + /** + * Return the constrained value by given the lower and upper bounds. + */ + public static long constrain(long amount, long low, long high) { + if (low > high) throw new IllegalArgumentException("low(" + low + ") > high(" + high + ")"); + return amount < low ? low : (amount > high ? high : amount); + } + + /** + * Convert structure from android.app.usage.NetworkStats to android.net.NetworkStats. + */ + public static android.net.NetworkStats fromPublicNetworkStats( + NetworkStats publiceNetworkStats) { + android.net.NetworkStats stats = new android.net.NetworkStats(0L, 0); + while (publiceNetworkStats.hasNextBucket()) { + NetworkStats.Bucket bucket = new NetworkStats.Bucket(); + publiceNetworkStats.getNextBucket(bucket); + final android.net.NetworkStats.Entry entry = fromBucket(bucket); + stats = stats.addEntry(entry); + } + return stats; + } + + @VisibleForTesting + public static android.net.NetworkStats.Entry fromBucket(NetworkStats.Bucket bucket) { + return new android.net.NetworkStats.Entry( + null /* IFACE_ALL */, bucket.getUid(), convertBucketState(bucket.getState()), + convertBucketTag(bucket.getTag()), convertBucketMetered(bucket.getMetered()), + convertBucketRoaming(bucket.getRoaming()), + convertBucketDefaultNetworkStatus(bucket.getDefaultNetworkStatus()), + bucket.getRxBytes(), bucket.getRxPackets(), + bucket.getTxBytes(), bucket.getTxPackets(), 0 /* operations */); + } + + private static int convertBucketState(int networkStatsSet) { + switch (networkStatsSet) { + case NetworkStats.Bucket.STATE_ALL: return SET_ALL; + case NetworkStats.Bucket.STATE_DEFAULT: return android.net.NetworkStats.SET_DEFAULT; + case NetworkStats.Bucket.STATE_FOREGROUND: + return android.net.NetworkStats.SET_FOREGROUND; + } + return 0; + } + + private static int convertBucketTag(int tag) { + switch (tag) { + case NetworkStats.Bucket.TAG_NONE: return android.net.NetworkStats.TAG_NONE; + } + return tag; + } + + private static int convertBucketMetered(int metered) { + switch (metered) { + case NetworkStats.Bucket.METERED_ALL: return METERED_ALL; + case NetworkStats.Bucket.METERED_NO: return android.net.NetworkStats.METERED_NO; + case NetworkStats.Bucket.METERED_YES: return android.net.NetworkStats.METERED_YES; + } + return 0; + } + + private static int convertBucketRoaming(int roaming) { + switch (roaming) { + case NetworkStats.Bucket.ROAMING_ALL: return ROAMING_ALL; + case NetworkStats.Bucket.ROAMING_NO: return android.net.NetworkStats.ROAMING_NO; + case NetworkStats.Bucket.ROAMING_YES: return android.net.NetworkStats.ROAMING_YES; + } + return 0; + } + + private static int convertBucketDefaultNetworkStatus(int defaultNetworkStatus) { + switch (defaultNetworkStatus) { + case NetworkStats.Bucket.DEFAULT_NETWORK_ALL: + return DEFAULT_NETWORK_ALL; + case NetworkStats.Bucket.DEFAULT_NETWORK_NO: + return android.net.NetworkStats.DEFAULT_NETWORK_NO; + case NetworkStats.Bucket.DEFAULT_NETWORK_YES: + return android.net.NetworkStats.DEFAULT_NETWORK_YES; + } + return 0; + } +} diff --git a/staticlibs/framework/com/android/net/module/util/PerUidCounter.java b/staticlibs/framework/com/android/net/module/util/PerUidCounter.java new file mode 100644 index 0000000000000000000000000000000000000000..463b0c42032c4151339518bcd93f989928d5c4ba --- /dev/null +++ b/staticlibs/framework/com/android/net/module/util/PerUidCounter.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util; + +import android.util.SparseIntArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +/** + * Keeps track of the counters under different uid, fire exception if the counter + * exceeded the specified maximum value. + * + * @hide + */ +public class PerUidCounter { + private final int mMaxCountPerUid; + + // Map from UID to count that UID has filed. + @VisibleForTesting + @GuardedBy("this") + final SparseIntArray mUidToCount = new SparseIntArray(); + + /** + * Constructor + * + * @param maxCountPerUid the maximum count per uid allowed + */ + public PerUidCounter(final int maxCountPerUid) { + if (maxCountPerUid <= 0) { + throw new IllegalArgumentException("Maximum counter value must be positive"); + } + mMaxCountPerUid = maxCountPerUid; + } + + /** + * Increments the count of the given uid. Throws an exception if the number + * of the counter for the uid exceeds the value of maxCounterPerUid which is the value + * passed into the constructor. see: {@link #PerUidCounter(int)}. + * + * @throws IllegalStateException if the number of counter for the uid exceed + * the allowed number. + * + * @param uid the uid that the counter was made under + */ + public synchronized void incrementCountOrThrow(final int uid) { + final long newCount = ((long) mUidToCount.get(uid, 0)) + 1; + if (newCount > mMaxCountPerUid) { + throw new IllegalStateException("Uid " + uid + " exceeded its allowed limit"); + } + // Since the count cannot be greater than Integer.MAX_VALUE here since mMaxCountPerUid + // is an integer, it is safe to cast to int. + mUidToCount.put(uid, (int) newCount); + } + + /** + * Decrements the count of the given uid. Throws an exception if the number + * of the counter goes below zero. + * + * @throws IllegalStateException if the number of counter for the uid goes below + * zero. + * + * @param uid the uid that the count was made under + */ + public synchronized void decrementCountOrThrow(final int uid) { + final int newCount = mUidToCount.get(uid, 0) - 1; + if (newCount < 0) { + throw new IllegalStateException("BUG: too small count " + newCount + " for UID " + uid); + } else if (newCount == 0) { + mUidToCount.delete(uid); + } else { + mUidToCount.put(uid, newCount); + } + } + + @VisibleForTesting + public synchronized int get(int uid) { + return mUidToCount.get(uid, 0); + } +} diff --git a/staticlibs/framework/com/android/net/module/util/PermissionUtils.java b/staticlibs/framework/com/android/net/module/util/PermissionUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..d5b4c90a9ccd82eeca97b73a4d48c122507b6bcb --- /dev/null +++ b/staticlibs/framework/com/android/net/module/util/PermissionUtils.java @@ -0,0 +1,208 @@ +/* + * Copyright 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. + */ + +package com.android.net.module.util; + +import static android.Manifest.permission.ACCESS_NETWORK_STATE; +import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS; +import static android.Manifest.permission.NETWORK_STACK; +import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.content.pm.PermissionInfo.PROTECTION_SIGNATURE; +import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PermissionInfo; +import android.os.Binder; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Collection of permission utilities. + * @hide + */ +public final class PermissionUtils { + /** + * Return true if the context has one of given permission. + */ + public static boolean checkAnyPermissionOf(@NonNull Context context, + @NonNull String... permissions) { + for (String permission : permissions) { + if (context.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) { + return true; + } + } + return false; + } + + /** + * Return true if the permission has system signature. + */ + public static boolean isSystemSignaturePermission(@NonNull Context context, + @NonNull String permission) { + try { + PermissionInfo permissionInfo = context.getPackageManager().getPermissionInfo( + permission, 0 /* flags */); + if (permissionInfo == null) { + return false; + } + return "android".equals(permissionInfo.packageName) + && permissionInfo.getProtection() == PROTECTION_SIGNATURE; + } catch (PackageManager.NameNotFoundException ignored) { + // Ignored the NameNotFoundException and return false + } + return false; + } + + /** + * Return true if the context has one of give permission that is allowed + * for a particular process and user ID running in the system. + */ + public static boolean checkAnyPermissionOf(@NonNull Context context, + int pid, int uid, @NonNull String... permissions) { + for (String permission : permissions) { + if (context.checkPermission(permission, pid, uid) == PERMISSION_GRANTED) { + return true; + } + } + return false; + } + + /** + * Enforce permission check on the context that should have one of given permission. + */ + public static void enforceAnyPermissionOf(@NonNull Context context, + @NonNull String... permissions) { + if (!checkAnyPermissionOf(context, permissions)) { + throw new SecurityException("Requires one of the following permissions: " + + String.join(", ", permissions) + "."); + } + } + + /** + * If the NetworkStack, MAINLINE_NETWORK_STACK are not allowed for a particular process, throw a + * {@link SecurityException}. + * + * @param context {@link android.content.Context} for the process. + */ + public static void enforceNetworkStackPermission(final @NonNull Context context) { + enforceNetworkStackPermissionOr(context); + } + + /** + * If the NetworkStack, MAINLINE_NETWORK_STACK or other specified permissions are not allowed + * for a particular process, throw a {@link SecurityException}. + * + * @param context {@link android.content.Context} for the process. + * @param otherPermissions The set of permissions that could be the candidate permissions , or + * empty string if none of other permissions needed. + */ + public static void enforceNetworkStackPermissionOr(final @NonNull Context context, + final @NonNull String... otherPermissions) { + ArrayList<String> permissions = new ArrayList<String>(Arrays.asList(otherPermissions)); + permissions.add(NETWORK_STACK); + permissions.add(PERMISSION_MAINLINE_NETWORK_STACK); + enforceAnyPermissionOf(context, permissions.toArray(new String[0])); + } + + /** + * If the CONNECTIVITY_USE_RESTRICTED_NETWORKS is not allowed for a particular process, throw a + * {@link SecurityException}. + * + * @param context {@link android.content.Context} for the process. + * @param message A message to include in the exception if it is thrown. + */ + public static void enforceRestrictedNetworkPermission( + final @NonNull Context context, final @Nullable String message) { + context.enforceCallingOrSelfPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, message); + } + + /** + * If the ACCESS_NETWORK_STATE is not allowed for a particular process, throw a + * {@link SecurityException}. + * + * @param context {@link android.content.Context} for the process. + * @param message A message to include in the exception if it is thrown. + */ + public static void enforceAccessNetworkStatePermission( + final @NonNull Context context, final @Nullable String message) { + context.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, message); + } + + /** + * Return true if the context has DUMP permission. + */ + public static boolean checkDumpPermission(Context context, String tag, PrintWriter pw) { + if (context.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump " + tag + " from from pid=" + + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + + " due to missing android.permission.DUMP permission"); + return false; + } else { + return true; + } + } + + /** + * Enforce that a given feature is available and if not, throw an + * {@link UnsupportedOperationException}. + * + * @param context {@link android.content.Context} for the process. + * @param feature the feature name to enforce. + * @param errorMessage an optional error message to include. + */ + public static void enforceSystemFeature(final @NonNull Context context, + final @NonNull String feature, final @Nullable String errorMessage) { + final boolean hasSystemFeature = + context.getPackageManager().hasSystemFeature(feature); + if (!hasSystemFeature) { + if (null == errorMessage) { + throw new UnsupportedOperationException(); + } + throw new UnsupportedOperationException(errorMessage); + } + } + + /** + * Get the list of granted permissions for a package info. + * + * PackageInfo contains the list of requested permissions, and their state (whether they + * were granted or not, in particular) as a parallel array. Most users care only about + * granted permissions. This method returns the list of them. + * + * @param packageInfo the package info for the relevant uid. + * @return the list of granted permissions. + */ + public static List<String> getGrantedPermissions(final @NonNull PackageInfo packageInfo) { + if (null == packageInfo.requestedPermissions) return Collections.emptyList(); + final ArrayList<String> result = new ArrayList<>(packageInfo.requestedPermissions.length); + for (int i = 0; i < packageInfo.requestedPermissions.length; ++i) { + if (0 != (REQUESTED_PERMISSION_GRANTED & packageInfo.requestedPermissionsFlags[i])) { + result.add(packageInfo.requestedPermissions[i]); + } + } + return result; + } +} diff --git a/staticlibs/framework/com/android/net/module/util/ProxyUtils.java b/staticlibs/framework/com/android/net/module/util/ProxyUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..fdd7dca1ce8ae30353ee27912f5f82b5e36c4b49 --- /dev/null +++ b/staticlibs/framework/com/android/net/module/util/ProxyUtils.java @@ -0,0 +1,98 @@ +/* + * Copyright 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. + */ + +package com.android.net.module.util; + +import android.text.TextUtils; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Collection of network common utilities. + * + * @hide + */ +public final class ProxyUtils { + + public static final int PROXY_VALID = 0; + public static final int PROXY_HOSTNAME_EMPTY = 1; + public static final int PROXY_HOSTNAME_INVALID = 2; + public static final int PROXY_PORT_EMPTY = 3; + public static final int PROXY_PORT_INVALID = 4; + public static final int PROXY_EXCLLIST_INVALID = 5; + + // Hostname / IP REGEX validation + // Matches blank input, ips, and domain names + private static final String NAME_IP_REGEX = + "[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*(\\.[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*)*"; + private static final Pattern HOSTNAME_PATTERN; + private static final String HOSTNAME_REGEXP = "^$|^" + NAME_IP_REGEX + "$"; + private static final Pattern EXCLLIST_PATTERN; + private static final String EXCL_REGEX = + "[a-zA-Z0-9*]+(\\-[a-zA-Z0-9*]+)*(\\.[a-zA-Z0-9*]+(\\-[a-zA-Z0-9*]+)*)*"; + private static final String EXCLLIST_REGEXP = "^$|^" + EXCL_REGEX + "(," + EXCL_REGEX + ")*$"; + static { + HOSTNAME_PATTERN = Pattern.compile(HOSTNAME_REGEXP); + EXCLLIST_PATTERN = Pattern.compile(EXCLLIST_REGEXP); + } + + /** Converts exclusion list from String to List. */ + public static List<String> exclusionStringAsList(String exclusionList) { + if (exclusionList == null) { + return Collections.emptyList(); + } + return Arrays.asList(exclusionList.toLowerCase(Locale.ROOT).split(",")); + } + + /** Converts exclusion list from List to string */ + public static String exclusionListAsString(String[] exclusionList) { + if (exclusionList == null) { + return ""; + } + return TextUtils.join(",", exclusionList); + } + + /** + * Validate syntax of hostname, port and exclusion list entries + */ + public static int validate(String hostname, String port, String exclList) { + Matcher match = HOSTNAME_PATTERN.matcher(hostname); + Matcher listMatch = EXCLLIST_PATTERN.matcher(exclList); + + if (!match.matches()) return PROXY_HOSTNAME_INVALID; + + if (!listMatch.matches()) return PROXY_EXCLLIST_INVALID; + + if (hostname.length() > 0 && port.length() == 0) return PROXY_PORT_EMPTY; + + if (port.length() > 0) { + if (hostname.length() == 0) return PROXY_HOSTNAME_EMPTY; + int portVal = -1; + try { + portVal = Integer.parseInt(port); + } catch (NumberFormatException ex) { + return PROXY_PORT_INVALID; + } + if (portVal <= 0 || portVal > 0xFFFF) return PROXY_PORT_INVALID; + } + return PROXY_VALID; + } +} diff --git a/staticlibs/framework/com/android/net/module/util/RouteUtils.java b/staticlibs/framework/com/android/net/module/util/RouteUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..c2416802a79d257f6eb6160511eb43cd2eba7b8c --- /dev/null +++ b/staticlibs/framework/com/android/net/module/util/RouteUtils.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util; + +/** @hide */ +// RouteUtils is now empty, because some new methods will be added to it soon and it is less +// expensive to keep it empty than to remove it now and add it again later. +public class RouteUtils { +} diff --git a/staticlibs/jarjar-rules-shared.txt b/staticlibs/jarjar-rules-shared.txt new file mode 100644 index 0000000000000000000000000000000000000000..e26999dd0f2b80aea6e058ae331ffbdbf0ef124a --- /dev/null +++ b/staticlibs/jarjar-rules-shared.txt @@ -0,0 +1,3 @@ +rule android.annotation.** com.android.net.module.annotation.@1 +rule com.android.internal.annotations.** com.android.net.module.annotation.@1 +rule com.android.modules.utils.build.** com.android.net.module.util.build.$1 diff --git a/staticlibs/lint-baseline.xml b/staticlibs/lint-baseline.xml new file mode 100644 index 0000000000000000000000000000000000000000..d413b2a7769b760652529ffc04b06867963baac3 --- /dev/null +++ b/staticlibs/lint-baseline.xml @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="UTF-8"?> +<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev"> + + <issue + id="NewApi" + message="Call requires API level 30 (current min is 29): `android.net.LinkProperties#getAddresses`" + errorLine1=" final Collection<InetAddress> leftAddresses = left.getAddresses();" + errorLine2=" ~~~~~~~~~~~~"> + <location + file="frameworks/libs/net/common/framework/com/android/net/module/util/LinkPropertiesUtils.java" + line="158" + column="60"/> + </issue> + + <issue + id="NewApi" + message="Call requires API level 30 (current min is 29): `android.net.LinkProperties#getAddresses`" + errorLine1=" final Collection<InetAddress> rightAddresses = right.getAddresses();" + errorLine2=" ~~~~~~~~~~~~"> + <location + file="frameworks/libs/net/common/framework/com/android/net/module/util/LinkPropertiesUtils.java" + line="159" + column="62"/> + </issue> + + <issue + id="NewApi" + message="Call requires API level 30 (current min is 29): `android.net.NetworkStats#addEntry`" + errorLine1=" stats = stats.addEntry(entry);" + errorLine2=" ~~~~~~~~"> + <location + file="frameworks/libs/net/common/framework/com/android/net/module/util/NetworkStatsUtils.java" + line="113" + column="27"/> + </issue> + + <issue + id="NewApi" + message="Call requires API level 30 (current min is 29): `new android.net.NetworkStats.Entry`" + errorLine1=" return new android.net.NetworkStats.Entry(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/libs/net/common/framework/com/android/net/module/util/NetworkStatsUtils.java" + line="120" + column="16"/> + </issue> + + <issue + id="NewApi" + message="Call requires API level 30 (current min is 29): `new android.net.NetworkStats`" + errorLine1=" android.net.NetworkStats stats = new android.net.NetworkStats(0L, 0);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/libs/net/common/framework/com/android/net/module/util/NetworkStatsUtils.java" + line="108" + column="42"/> + </issue> + + <issue + id="NewApi" + message="Call requires API level 31 (current min is 29): `new android.system.NetlinkSocketAddress`" + errorLine1=" return new NetlinkSocketAddress(portId, groupsMask);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/libs/net/common/device/com/android/net/module/util/SocketUtils.java" + line="44" + column="16"/> + </issue> + +</issues> \ No newline at end of file diff --git a/staticlibs/native/README.md b/staticlibs/native/README.md new file mode 100644 index 0000000000000000000000000000000000000000..1f505c4de138555f46ceb285fd16a838a5072545 --- /dev/null +++ b/staticlibs/native/README.md @@ -0,0 +1,30 @@ +# JNI +As a general rule, jarjar every static library dependency used in a mainline module into the +modules's namespace (especially if it is also used by other modules) + +Fully-qualified name of java class needs to be hard-coded into the JNI .so, because JNI_OnLoad +does not take any parameters. This means that there needs to be a different .so target for each +post-jarjared package, so for each module. + +This is the guideline to provide JNI library shared with modules: + +* provide a common java library in frameworks/libs/net with the Java class (e.g. BpfMap.java). + +* provide a common native library in frameworks/libs/net with the JNI and provide the native + register function with class_name parameter. See register_com_android_net_module_util_BpfMap + function in frameworks/libs/net/common/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp + as an example. + +When you want to use JNI library from frameworks/lib/net: + +* Each module includes the java library (e.g. net-utils-device-common-bpf) and applies its jarjar + rules after build. + +* Each module creates a native library in their directory, which statically links against the + common native library (e.g. libnet_utils_device_common_bpf), and calls the native registered + function by hardcoding the post-jarjar class_name. Linkage *MUST* be static because common + functions in the file (e.g., `register_com_android_net_module_util_BpfMap`) will appear in the + library (`.so`) file, and different versions of the library loaded in the same process by + different modules will in general have different versions. It's important that each of these + libraries loads the common function from its own library. Static linkage should guarantee this + because static linkage resolves symbols at build time, not runtime. \ No newline at end of file diff --git a/staticlibs/native/bpf_headers/Android.bp b/staticlibs/native/bpf_headers/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..41184ea48ec83795b154a6dfcd45503839dfe8f0 --- /dev/null +++ b/staticlibs/native/bpf_headers/Android.bp @@ -0,0 +1,65 @@ +// 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. + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_library_headers { + name: "bpf_headers", + vendor_available: true, + recovery_available: true, + host_supported: true, + native_bridge_supported: true, + header_libs: ["bpf_syscall_wrappers"], + export_header_lib_headers: ["bpf_syscall_wrappers"], + export_include_dirs: ["include"], + cflags: [ + "-Wall", + "-Werror", + ], + sdk_version: "30", + min_sdk_version: "30", + apex_available: [ + "//apex_available:platform", + "com.android.art.debug", + "com.android.os.statsd", + "com.android.resolv", + "com.android.tethering", + ], +} + +cc_test { + // TODO: Rename to bpf_map_test and modify .gcls as well. + name: "libbpf_android_test", + srcs: [ + "BpfMapTest.cpp", + "BpfRingbufTest.cpp", + ], + defaults: ["bpf_defaults"], + cflags: [ + "-Wall", + "-Werror", + "-Wno-error=unused-variable", + ], + header_libs: ["bpf_headers"], + static_libs: ["libgmock"], + shared_libs: [ + "libbase", + "liblog", + "libutils", + ], + require_root: true, + test_suites: ["general-tests"], +} diff --git a/staticlibs/native/bpf_headers/BpfMapTest.cpp b/staticlibs/native/bpf_headers/BpfMapTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..10afe8603f312f39971cfab55e5677450877ae2d --- /dev/null +++ b/staticlibs/native/bpf_headers/BpfMapTest.cpp @@ -0,0 +1,245 @@ +/* + * 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. + */ + +#include <fstream> +#include <iostream> +#include <string> +#include <vector> + +#include <fcntl.h> +#include <inttypes.h> +#include <linux/inet_diag.h> +#include <linux/sock_diag.h> +#include <net/if.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +#include <gtest/gtest.h> + +#include <android-base/stringprintf.h> +#include <android-base/strings.h> + +#define BPF_MAP_MAKE_VISIBLE_FOR_TESTING +#include "bpf/BpfMap.h" +#include "bpf/BpfUtils.h" + +using ::testing::Test; + +namespace android { +namespace bpf { + +using base::Result; +using base::unique_fd; + +constexpr uint32_t TEST_MAP_SIZE = 10; +constexpr uint32_t TEST_KEY1 = 1; +constexpr uint32_t TEST_VALUE1 = 10; +constexpr const char PINNED_MAP_PATH[] = "/sys/fs/bpf/testMap"; + +class BpfMapTest : public testing::Test { + protected: + BpfMapTest() {} + + void SetUp() { + EXPECT_EQ(0, setrlimitForTest()); + if (!access(PINNED_MAP_PATH, R_OK)) { + EXPECT_EQ(0, remove(PINNED_MAP_PATH)); + } + } + + void TearDown() { + if (!access(PINNED_MAP_PATH, R_OK)) { + EXPECT_EQ(0, remove(PINNED_MAP_PATH)); + } + } + + void checkMapInvalid(BpfMap<uint32_t, uint32_t>& map) { + EXPECT_FALSE(map.isValid()); + EXPECT_EQ(-1, map.getMap().get()); + } + + void checkMapValid(BpfMap<uint32_t, uint32_t>& map) { + EXPECT_LE(0, map.getMap().get()); + EXPECT_TRUE(map.isValid()); + } + + void writeToMapAndCheck(BpfMap<uint32_t, uint32_t>& map, uint32_t key, uint32_t value) { + ASSERT_RESULT_OK(map.writeValue(key, value, BPF_ANY)); + uint32_t value_read; + ASSERT_EQ(0, findMapEntry(map.getMap(), &key, &value_read)); + checkValueAndStatus(value, value_read); + } + + void checkValueAndStatus(uint32_t refValue, Result<uint32_t> value) { + ASSERT_RESULT_OK(value); + ASSERT_EQ(refValue, value.value()); + } + + void populateMap(uint32_t total, BpfMap<uint32_t, uint32_t>& map) { + for (uint32_t key = 0; key < total; key++) { + uint32_t value = key * 10; + EXPECT_RESULT_OK(map.writeValue(key, value, BPF_ANY)); + } + } + + void expectMapEmpty(BpfMap<uint32_t, uint32_t>& map) { + Result<bool> isEmpty = map.isEmpty(); + ASSERT_RESULT_OK(isEmpty); + ASSERT_TRUE(isEmpty.value()); + } +}; + +TEST_F(BpfMapTest, constructor) { + BpfMap<uint32_t, uint32_t> testMap1; + checkMapInvalid(testMap1); + + BpfMap<uint32_t, uint32_t> testMap2(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC); + checkMapValid(testMap2); +} + +TEST_F(BpfMapTest, basicHelpers) { + BpfMap<uint32_t, uint32_t> testMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC); + uint32_t key = TEST_KEY1; + uint32_t value_write = TEST_VALUE1; + writeToMapAndCheck(testMap, key, value_write); + Result<uint32_t> value_read = testMap.readValue(key); + checkValueAndStatus(value_write, value_read); + Result<uint32_t> key_read = testMap.getFirstKey(); + checkValueAndStatus(key, key_read); + ASSERT_RESULT_OK(testMap.deleteValue(key)); + ASSERT_GT(0, findMapEntry(testMap.getMap(), &key, &value_read)); + ASSERT_EQ(ENOENT, errno); +} + +TEST_F(BpfMapTest, reset) { + BpfMap<uint32_t, uint32_t> testMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC); + uint32_t key = TEST_KEY1; + uint32_t value_write = TEST_VALUE1; + writeToMapAndCheck(testMap, key, value_write); + + testMap.reset(-1); + checkMapInvalid(testMap); + ASSERT_GT(0, findMapEntry(testMap.getMap(), &key, &value_write)); + ASSERT_EQ(EBADF, errno); +} + +TEST_F(BpfMapTest, moveConstructor) { + BpfMap<uint32_t, uint32_t> testMap1(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC); + BpfMap<uint32_t, uint32_t> testMap2; + testMap2 = std::move(testMap1); + uint32_t key = TEST_KEY1; + checkMapInvalid(testMap1); + uint32_t value = TEST_VALUE1; + writeToMapAndCheck(testMap2, key, value); +} + +TEST_F(BpfMapTest, SetUpMap) { + EXPECT_NE(0, access(PINNED_MAP_PATH, R_OK)); + BpfMap<uint32_t, uint32_t> testMap1(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC); + ASSERT_EQ(0, bpfFdPin(testMap1.getMap(), PINNED_MAP_PATH)); + EXPECT_EQ(0, access(PINNED_MAP_PATH, R_OK)); + checkMapValid(testMap1); + BpfMap<uint32_t, uint32_t> testMap2; + EXPECT_RESULT_OK(testMap2.init(PINNED_MAP_PATH)); + checkMapValid(testMap2); + uint32_t key = TEST_KEY1; + uint32_t value = TEST_VALUE1; + writeToMapAndCheck(testMap1, key, value); + Result<uint32_t> value_read = testMap2.readValue(key); + checkValueAndStatus(value, value_read); +} + +TEST_F(BpfMapTest, iterate) { + BpfMap<uint32_t, uint32_t> testMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC); + populateMap(TEST_MAP_SIZE, testMap); + int totalCount = 0; + int totalSum = 0; + const auto iterateWithDeletion = [&totalCount, &totalSum](const uint32_t& key, + BpfMap<uint32_t, uint32_t>& map) { + EXPECT_GE((uint32_t)TEST_MAP_SIZE, key); + totalCount++; + totalSum += key; + return map.deleteValue(key); + }; + EXPECT_RESULT_OK(testMap.iterate(iterateWithDeletion)); + EXPECT_EQ((int)TEST_MAP_SIZE, totalCount); + EXPECT_EQ(((1 + TEST_MAP_SIZE - 1) * (TEST_MAP_SIZE - 1)) / 2, (uint32_t)totalSum); + expectMapEmpty(testMap); +} + +TEST_F(BpfMapTest, iterateWithValue) { + BpfMap<uint32_t, uint32_t> testMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC); + populateMap(TEST_MAP_SIZE, testMap); + int totalCount = 0; + int totalSum = 0; + const auto iterateWithDeletion = [&totalCount, &totalSum](const uint32_t& key, + const uint32_t& value, + BpfMap<uint32_t, uint32_t>& map) { + EXPECT_GE((uint32_t)TEST_MAP_SIZE, key); + EXPECT_EQ(value, key * 10); + totalCount++; + totalSum += value; + return map.deleteValue(key); + }; + EXPECT_RESULT_OK(testMap.iterateWithValue(iterateWithDeletion)); + EXPECT_EQ((int)TEST_MAP_SIZE, totalCount); + EXPECT_EQ(((1 + TEST_MAP_SIZE - 1) * (TEST_MAP_SIZE - 1)) * 5, (uint32_t)totalSum); + expectMapEmpty(testMap); +} + +TEST_F(BpfMapTest, mapIsEmpty) { + BpfMap<uint32_t, uint32_t> testMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC); + expectMapEmpty(testMap); + uint32_t key = TEST_KEY1; + uint32_t value_write = TEST_VALUE1; + writeToMapAndCheck(testMap, key, value_write); + Result<bool> isEmpty = testMap.isEmpty(); + ASSERT_RESULT_OK(isEmpty); + ASSERT_FALSE(isEmpty.value()); + ASSERT_RESULT_OK(testMap.deleteValue(key)); + ASSERT_GT(0, findMapEntry(testMap.getMap(), &key, &value_write)); + ASSERT_EQ(ENOENT, errno); + expectMapEmpty(testMap); + int entriesSeen = 0; + EXPECT_RESULT_OK(testMap.iterate( + [&entriesSeen](const unsigned int&, + const BpfMap<unsigned int, unsigned int>&) -> Result<void> { + entriesSeen++; + return {}; + })); + EXPECT_EQ(0, entriesSeen); + EXPECT_RESULT_OK(testMap.iterateWithValue( + [&entriesSeen](const unsigned int&, const unsigned int&, + const BpfMap<unsigned int, unsigned int>&) -> Result<void> { + entriesSeen++; + return {}; + })); + EXPECT_EQ(0, entriesSeen); +} + +TEST_F(BpfMapTest, mapClear) { + BpfMap<uint32_t, uint32_t> testMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC); + populateMap(TEST_MAP_SIZE, testMap); + Result<bool> isEmpty = testMap.isEmpty(); + ASSERT_RESULT_OK(isEmpty); + ASSERT_FALSE(*isEmpty); + ASSERT_RESULT_OK(testMap.clear()); + expectMapEmpty(testMap); +} + +} // namespace bpf +} // namespace android diff --git a/staticlibs/native/bpf_headers/BpfRingbufTest.cpp b/staticlibs/native/bpf_headers/BpfRingbufTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6c0841c614762e7cf4cc002006cdf54d90e0f560 --- /dev/null +++ b/staticlibs/native/bpf_headers/BpfRingbufTest.cpp @@ -0,0 +1,138 @@ +/* + * 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. + */ + +#include <android-base/file.h> +#include <android-base/macros.h> +#include <android-base/result-gmock.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <stdlib.h> +#include <unistd.h> + +#include "BpfSyscallWrappers.h" +#include "bpf/BpfRingbuf.h" +#include "bpf/BpfUtils.h" +#include "bpf/KernelUtils.h" + +#define TEST_RINGBUF_MAGIC_NUM 12345 + +namespace android { +namespace bpf { +using ::android::base::testing::HasError; +using ::android::base::testing::HasValue; +using ::android::base::testing::WithCode; +using ::testing::AllOf; +using ::testing::Gt; +using ::testing::HasSubstr; +using ::testing::Lt; + +class BpfRingbufTest : public ::testing::Test { + protected: + BpfRingbufTest() + : mProgPath("/sys/fs/bpf/prog_bpfRingbufProg_skfilter_ringbuf_test"), + mRingbufPath("/sys/fs/bpf/map_bpfRingbufProg_test_ringbuf") {} + + void SetUp() { + if (!android::bpf::isAtLeastKernelVersion(5, 8, 0)) { + GTEST_SKIP() << "BPF ring buffers not supported below 5.8"; + } + + errno = 0; + mProgram.reset(retrieveProgram(mProgPath.c_str())); + EXPECT_EQ(errno, 0); + ASSERT_GE(mProgram.get(), 0) + << mProgPath << " was either not found or inaccessible."; + } + + void RunProgram() { + char fake_skb[128] = {}; + EXPECT_EQ(runProgram(mProgram, fake_skb, sizeof(fake_skb)), 0); + } + + void RunTestN(int n) { + int run_count = 0; + uint64_t output = 0; + auto callback = [&](const uint64_t& value) { + output = value; + run_count++; + }; + + auto result = BpfRingbuf<uint64_t>::Create(mRingbufPath.c_str()); + ASSERT_RESULT_OK(result); + + for (int i = 0; i < n; i++) { + RunProgram(); + } + + EXPECT_THAT(result.value()->ConsumeAll(callback), HasValue(n)); + EXPECT_EQ(output, TEST_RINGBUF_MAGIC_NUM); + EXPECT_EQ(run_count, n); + } + + std::string mProgPath; + std::string mRingbufPath; + android::base::unique_fd mProgram; +}; + +TEST_F(BpfRingbufTest, ConsumeSingle) { RunTestN(1); } +TEST_F(BpfRingbufTest, ConsumeMultiple) { RunTestN(3); } + +TEST_F(BpfRingbufTest, FillAndWrap) { + int run_count = 0; + auto callback = [&](const uint64_t&) { run_count++; }; + + auto result = BpfRingbuf<uint64_t>::Create(mRingbufPath.c_str()); + ASSERT_RESULT_OK(result); + + // 4kb buffer with 16 byte payloads (8 byte data, 8 byte header) should fill + // after 255 iterations. Exceed that so that some events are dropped. + constexpr int iterations = 300; + for (int i = 0; i < iterations; i++) { + RunProgram(); + } + + // Some events were dropped, but consume all that succeeded. + EXPECT_THAT(result.value()->ConsumeAll(callback), + HasValue(AllOf(Gt(250), Lt(260)))); + EXPECT_THAT(run_count, AllOf(Gt(250), Lt(260))); + + // After consuming everything, we should be able to use the ring buffer again. + run_count = 0; + RunProgram(); + EXPECT_THAT(result.value()->ConsumeAll(callback), HasValue(1)); + EXPECT_EQ(run_count, 1); +} + +TEST_F(BpfRingbufTest, WrongTypeSize) { + // The program under test writes 8-byte uint64_t values so a ringbuffer for + // 1-byte uint8_t values will fail to read from it. Note that the map_def does + // not specify the value size, so we fail on read, not creation. + auto result = BpfRingbuf<uint8_t>::Create(mRingbufPath.c_str()); + ASSERT_RESULT_OK(result); + + RunProgram(); + + EXPECT_THAT(result.value()->ConsumeAll([](const uint8_t&) {}), + HasError(WithCode(EMSGSIZE))); +} + +TEST_F(BpfRingbufTest, InvalidPath) { + EXPECT_THAT(BpfRingbuf<int>::Create("/sys/fs/bpf/bad_path"), + HasError(WithCode(ENOENT))); +} + +} // namespace bpf +} // namespace android diff --git a/staticlibs/native/bpf_headers/TEST_MAPPING b/staticlibs/native/bpf_headers/TEST_MAPPING new file mode 100644 index 0000000000000000000000000000000000000000..9ec8a4085391452b8e17ce26bbd8d1b529d163b5 --- /dev/null +++ b/staticlibs/native/bpf_headers/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "libbpf_android_test" + } + ] +} diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfClassic.h b/staticlibs/native/bpf_headers/include/bpf/BpfClassic.h new file mode 100644 index 0000000000000000000000000000000000000000..dd0804c364eedeceee109db2d484d94cac0cb22a --- /dev/null +++ b/staticlibs/native/bpf_headers/include/bpf/BpfClassic.h @@ -0,0 +1,110 @@ +/* + * 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. + */ + +#pragma once + +// Accept the full packet +#define BPF_ACCEPT BPF_STMT(BPF_RET | BPF_K, 0xFFFFFFFF) + +// Reject the packet +#define BPF_REJECT BPF_STMT(BPF_RET | BPF_K, 0) + +// *TWO* instructions: compare and if not equal jump over the accept statement +#define BPF2_ACCEPT_IF_EQUAL(v) \ + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, (v), 0, 1), \ + BPF_ACCEPT + +// *TWO* instructions: compare and if equal jump over the reject statement +#define BPF2_REJECT_IF_NOT_EQUAL(v) \ + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, (v), 1, 0), \ + BPF_REJECT + +// *TWO* instructions: compare and if none of the bits are set jump over the reject statement +#define BPF2_REJECT_IF_ANY_BITS_SET(v) \ + BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, (v), 0, 1), \ + BPF_REJECT + +// loads skb->protocol +#define BPF_LOAD_SKB_PROTOCOL \ + BPF_STMT(BPF_LD | BPF_H | BPF_ABS, (__u32)SKF_AD_OFF + SKF_AD_PROTOCOL) + +// 8-bit load relative to start of link layer (mac/ethernet) header. +#define BPF_LOAD_MAC_RELATIVE_U8(ofs) \ + BPF_STMT(BPF_LD | BPF_B | BPF_ABS, (__u32)SKF_LL_OFF + (ofs)) + +// Big/Network Endian 16-bit load relative to start of link layer (mac/ethernet) header. +#define BPF_LOAD_MAC_RELATIVE_BE16(ofs) \ + BPF_STMT(BPF_LD | BPF_H | BPF_ABS, (__u32)SKF_LL_OFF + (ofs)) + +// Big/Network Endian 32-bit load relative to start of link layer (mac/ethernet) header. +#define BPF_LOAD_MAC_RELATIVE_BE32(ofs) \ + BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (__u32)SKF_LL_OFF + (ofs)) + +// 8-bit load relative to start of network (IPv4/IPv6) header. +#define BPF_LOAD_NET_RELATIVE_U8(ofs) \ + BPF_STMT(BPF_LD | BPF_B | BPF_ABS, (__u32)SKF_NET_OFF + (ofs)) + +// Big/Network Endian 16-bit load relative to start of network (IPv4/IPv6) header. +#define BPF_LOAD_NET_RELATIVE_BE16(ofs) \ + BPF_STMT(BPF_LD | BPF_H | BPF_ABS, (__u32)SKF_NET_OFF + (ofs)) + +// Big/Network Endian 32-bit load relative to start of network (IPv4/IPv6) header. +#define BPF_LOAD_NET_RELATIVE_BE32(ofs) \ + BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (__u32)SKF_NET_OFF + (ofs)) + +#define field_sizeof(struct_type,field) sizeof(((struct_type *)0)->field) + +// 8-bit load from IPv4 header field. +#define BPF_LOAD_IPV4_U8(field) \ + BPF_LOAD_NET_RELATIVE_U8(({ \ + _Static_assert(field_sizeof(struct iphdr, field) == 1, "field of wrong size"); \ + offsetof(iphdr, field); \ + })) + +// Big/Network Endian 16-bit load from IPv4 header field. +#define BPF_LOAD_IPV4_BE16(field) \ + BPF_LOAD_NET_RELATIVE_BE16(({ \ + _Static_assert(field_sizeof(struct iphdr, field) == 2, "field of wrong size"); \ + offsetof(iphdr, field); \ + })) + +// Big/Network Endian 32-bit load from IPv4 header field. +#define BPF_LOAD_IPV4_BE32(field) \ + BPF_LOAD_NET_RELATIVE_BE32(({ \ + _Static_assert(field_sizeof(struct iphdr, field) == 4, "field of wrong size"); \ + offsetof(iphdr, field); \ + })) + +// 8-bit load from IPv6 header field. +#define BPF_LOAD_IPV6_U8(field) \ + BPF_LOAD_NET_RELATIVE_U8(({ \ + _Static_assert(field_sizeof(struct ipv6hdr, field) == 1, "field of wrong size"); \ + offsetof(ipv6hdr, field); \ + })) + +// Big/Network Endian 16-bit load from IPv6 header field. +#define BPF_LOAD_IPV6_BE16(field) \ + BPF_LOAD_NET_RELATIVE_BE16(({ \ + _Static_assert(field_sizeof(struct ipv6hdr, field) == 2, "field of wrong size"); \ + offsetof(ipv6hdr, field); \ + })) + +// Big/Network Endian 32-bit load from IPv6 header field. +#define BPF_LOAD_IPV6_BE32(field) \ + BPF_LOAD_NET_RELATIVE_BE32(({ \ + _Static_assert(field_sizeof(struct ipv6hdr, field) == 4, "field of wrong size"); \ + offsetof(ipv6hdr, field); \ + })) diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfMap.h b/staticlibs/native/bpf_headers/include/bpf/BpfMap.h new file mode 100644 index 0000000000000000000000000000000000000000..847083e5ea24afc9f1f530dedc0abd42acd1bbc6 --- /dev/null +++ b/staticlibs/native/bpf_headers/include/bpf/BpfMap.h @@ -0,0 +1,330 @@ +/* + * 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. + */ + +#pragma once + +#include <linux/bpf.h> + +#include <android-base/result.h> +#include <android-base/stringprintf.h> +#include <android-base/unique_fd.h> +#include <utils/Log.h> + +#include "BpfSyscallWrappers.h" +#include "bpf/BpfUtils.h" + +namespace android { +namespace bpf { + +using base::Result; +using base::unique_fd; +using std::function; + +// This is a class wrapper for eBPF maps. The eBPF map is a special in-kernel +// data structure that stores data in <Key, Value> pairs. It can be read/write +// from userspace by passing syscalls with the map file descriptor. This class +// is used to generalize the procedure of interacting with eBPF maps and hide +// the implementation detail from other process. Besides the basic syscalls +// wrapper, it also provides some useful helper functions as well as an iterator +// nested class to iterate the map more easily. +// +// NOTE: A kernel eBPF map may be accessed by both kernel and userspace +// processes at the same time. Or if the map is pinned as a virtual file, it can +// be obtained by multiple eBPF map class object and accessed concurrently. +// Though the map class object and the underlying kernel map are thread safe, it +// is not safe to iterate over a map while another thread or process is deleting +// from it. In this case the iteration can return duplicate entries. +template <class Key, class Value> +class BpfMap { + public: + BpfMap<Key, Value>() {}; + + // explicitly force no copy constructor, since it would need to dup the fd + // (later on, for testing, we still make available a copy assignment operator) + BpfMap<Key, Value>(const BpfMap<Key, Value>&) = delete; + + private: + void abortOnKeyOrValueSizeMismatch() { + if (!mMapFd.ok()) abort(); + if (isAtLeastKernelVersion(4, 14, 0)) { + if (bpfGetFdKeySize(mMapFd) != sizeof(Key)) abort(); + if (bpfGetFdValueSize(mMapFd) != sizeof(Value)) abort(); + } + } + + protected: + // flag must be within BPF_OBJ_FLAG_MASK, ie. 0, BPF_F_RDONLY, BPF_F_WRONLY + BpfMap<Key, Value>(const char* pathname, uint32_t flags) { + mMapFd.reset(mapRetrieve(pathname, flags)); + abortOnKeyOrValueSizeMismatch(); + } + + public: + explicit BpfMap<Key, Value>(const char* pathname) : BpfMap<Key, Value>(pathname, 0) {} + +#ifdef BPF_MAP_MAKE_VISIBLE_FOR_TESTING + // All bpf maps should be created by the bpfloader, so this constructor + // is reserved for tests + BpfMap<Key, Value>(bpf_map_type map_type, uint32_t max_entries, uint32_t map_flags = 0) { + mMapFd.reset(createMap(map_type, sizeof(Key), sizeof(Value), max_entries, map_flags)); + if (!mMapFd.ok()) abort(); + } +#endif + + Result<Key> getFirstKey() const { + Key firstKey; + if (getFirstMapKey(mMapFd, &firstKey)) { + return ErrnoErrorf("Get firstKey map {} failed", mMapFd.get()); + } + return firstKey; + } + + Result<Key> getNextKey(const Key& key) const { + Key nextKey; + if (getNextMapKey(mMapFd, &key, &nextKey)) { + return ErrnoErrorf("Get next key of map {} failed", mMapFd.get()); + } + return nextKey; + } + + Result<void> writeValue(const Key& key, const Value& value, uint64_t flags) { + if (writeToMapEntry(mMapFd, &key, &value, flags)) { + return ErrnoErrorf("Write to map {} failed", mMapFd.get()); + } + return {}; + } + + Result<Value> readValue(const Key key) const { + Value value; + if (findMapEntry(mMapFd, &key, &value)) { + return ErrnoErrorf("Read value of map {} failed", mMapFd.get()); + } + return value; + } + + Result<void> deleteValue(const Key& key) { + if (deleteMapEntry(mMapFd, &key)) { + return ErrnoErrorf("Delete entry from map {} failed", mMapFd.get()); + } + return {}; + } + + protected: + [[clang::reinitializes]] Result<void> init(const char* path, int fd) { + mMapFd.reset(fd); + if (!mMapFd.ok()) { + return ErrnoErrorf("Pinned map not accessible or does not exist: ({})", path); + } + // Normally we should return an error here instead of calling abort, + // but this cannot happen at runtime without a massive code bug (K/V type mismatch) + // and as such it's better to just blow the system up and let the developer fix it. + // Crashes are much more likely to be noticed than logs and missing functionality. + abortOnKeyOrValueSizeMismatch(); + return {}; + } + + public: + // Function that tries to get map from a pinned path. + [[clang::reinitializes]] Result<void> init(const char* path) { + return init(path, mapRetrieveRW(path)); + } + + +#ifdef BPF_MAP_MAKE_VISIBLE_FOR_TESTING + // due to Android SELinux limitations which prevent map creation by anyone besides the bpfloader + // this should only ever be used by test code, it is equivalent to: + // .reset(createMap(type, keysize, valuesize, max_entries, map_flags) + // TODO: derive map_flags from BpfMap vs BpfMapRO + [[clang::reinitializes]] Result<void> resetMap(bpf_map_type map_type, + uint32_t max_entries, + uint32_t map_flags = 0) { + mMapFd.reset(createMap(map_type, sizeof(Key), sizeof(Value), max_entries, map_flags)); + if (!mMapFd.ok()) return ErrnoErrorf("Unable to create map."); + return {}; + } +#endif + + // Iterate through the map and handle each key retrieved based on the filter + // without modification of map content. + Result<void> iterate( + const function<Result<void>(const Key& key, + const BpfMap<Key, Value>& map)>& filter) const; + + // Iterate through the map and get each <key, value> pair, handle each <key, + // value> pair based on the filter without modification of map content. + Result<void> iterateWithValue( + const function<Result<void>(const Key& key, const Value& value, + const BpfMap<Key, Value>& map)>& filter) const; + + // Iterate through the map and handle each key retrieved based on the filter + Result<void> iterate( + const function<Result<void>(const Key& key, + BpfMap<Key, Value>& map)>& filter); + + // Iterate through the map and get each <key, value> pair, handle each <key, + // value> pair based on the filter. + Result<void> iterateWithValue( + const function<Result<void>(const Key& key, const Value& value, + BpfMap<Key, Value>& map)>& filter); + +#ifdef BPF_MAP_MAKE_VISIBLE_FOR_TESTING + const unique_fd& getMap() const { return mMapFd; }; + + // Copy assignment operator - due to need for fd duping, should not be used in non-test code. + BpfMap<Key, Value>& operator=(const BpfMap<Key, Value>& other) { + if (this != &other) mMapFd.reset(fcntl(other.mMapFd.get(), F_DUPFD_CLOEXEC, 0)); + return *this; + } +#else + BpfMap<Key, Value>& operator=(const BpfMap<Key, Value>&) = delete; +#endif + + // Move assignment operator + BpfMap<Key, Value>& operator=(BpfMap<Key, Value>&& other) noexcept { + if (this != &other) { + mMapFd = std::move(other.mMapFd); + other.reset(); + } + return *this; + } + + void reset(unique_fd fd) = delete; + +#ifdef BPF_MAP_MAKE_VISIBLE_FOR_TESTING + // Note that unique_fd.reset() carefully saves and restores the errno, + // and BpfMap.reset() won't touch the errno if passed in fd is negative either, + // hence you can do something like BpfMap.reset(systemcall()) and then + // check BpfMap.isValid() and look at errno and see why systemcall() failed. + [[clang::reinitializes]] void reset(int fd) { + mMapFd.reset(fd); + if (mMapFd.ok()) abortOnKeyOrValueSizeMismatch(); + } +#endif + + [[clang::reinitializes]] void reset() { + mMapFd.reset(); + } + + bool isValid() const { return mMapFd.ok(); } + + Result<void> clear() { + while (true) { + auto key = getFirstKey(); + if (!key.ok()) { + if (key.error().code() == ENOENT) return {}; // empty: success + return key.error(); // Anything else is an error + } + auto res = deleteValue(key.value()); + if (!res.ok()) { + // Someone else could have deleted the key, so ignore ENOENT + if (res.error().code() == ENOENT) continue; + ALOGE("Failed to delete data %s", strerror(res.error().code())); + return res.error(); + } + } + } + + Result<bool> isEmpty() const { + auto key = getFirstKey(); + if (key.ok()) return false; + if (key.error().code() == ENOENT) return true; + return key.error(); + } + + private: + unique_fd mMapFd; +}; + +template <class Key, class Value> +Result<void> BpfMap<Key, Value>::iterate( + const function<Result<void>(const Key& key, + const BpfMap<Key, Value>& map)>& filter) const { + Result<Key> curKey = getFirstKey(); + while (curKey.ok()) { + const Result<Key>& nextKey = getNextKey(curKey.value()); + Result<void> status = filter(curKey.value(), *this); + if (!status.ok()) return status; + curKey = nextKey; + } + if (curKey.error().code() == ENOENT) return {}; + return curKey.error(); +} + +template <class Key, class Value> +Result<void> BpfMap<Key, Value>::iterateWithValue( + const function<Result<void>(const Key& key, const Value& value, + const BpfMap<Key, Value>& map)>& filter) const { + Result<Key> curKey = getFirstKey(); + while (curKey.ok()) { + const Result<Key>& nextKey = getNextKey(curKey.value()); + Result<Value> curValue = readValue(curKey.value()); + if (!curValue.ok()) return curValue.error(); + Result<void> status = filter(curKey.value(), curValue.value(), *this); + if (!status.ok()) return status; + curKey = nextKey; + } + if (curKey.error().code() == ENOENT) return {}; + return curKey.error(); +} + +template <class Key, class Value> +Result<void> BpfMap<Key, Value>::iterate( + const function<Result<void>(const Key& key, + BpfMap<Key, Value>& map)>& filter) { + Result<Key> curKey = getFirstKey(); + while (curKey.ok()) { + const Result<Key>& nextKey = getNextKey(curKey.value()); + Result<void> status = filter(curKey.value(), *this); + if (!status.ok()) return status; + curKey = nextKey; + } + if (curKey.error().code() == ENOENT) return {}; + return curKey.error(); +} + +template <class Key, class Value> +Result<void> BpfMap<Key, Value>::iterateWithValue( + const function<Result<void>(const Key& key, const Value& value, + BpfMap<Key, Value>& map)>& filter) { + Result<Key> curKey = getFirstKey(); + while (curKey.ok()) { + const Result<Key>& nextKey = getNextKey(curKey.value()); + Result<Value> curValue = readValue(curKey.value()); + if (!curValue.ok()) return curValue.error(); + Result<void> status = filter(curKey.value(), curValue.value(), *this); + if (!status.ok()) return status; + curKey = nextKey; + } + if (curKey.error().code() == ENOENT) return {}; + return curKey.error(); +} + +template <class Key, class Value> +class BpfMapRO : public BpfMap<Key, Value> { + public: + BpfMapRO<Key, Value>() {}; + + explicit BpfMapRO<Key, Value>(const char* pathname) + : BpfMap<Key, Value>(pathname, BPF_F_RDONLY) {} + + // Function that tries to get map from a pinned path. + [[clang::reinitializes]] Result<void> init(const char* path) { + return BpfMap<Key,Value>::init(path, mapRetrieveRO(path)); + } +}; + +} // namespace bpf +} // namespace android diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h b/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h new file mode 100644 index 0000000000000000000000000000000000000000..dd1504c92bc6049346f48fccce4fa972b6275eb5 --- /dev/null +++ b/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h @@ -0,0 +1,257 @@ +/* + * 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. + */ + +#pragma once + +#include <android-base/result.h> +#include <android-base/unique_fd.h> +#include <linux/bpf.h> +#include <sys/mman.h> +#include <utils/Log.h> + +#include "bpf/BpfUtils.h" + +#include <atomic> + +namespace android { +namespace bpf { + +// BpfRingbufBase contains the non-templated functionality of BPF ring buffers. +class BpfRingbufBase { + public: + ~BpfRingbufBase() { + if (mConsumerPos) munmap(mConsumerPos, mConsumerSize); + if (mProducerPos) munmap(mProducerPos, mProducerSize); + mConsumerPos = nullptr; + mProducerPos = nullptr; + } + + protected: + // Non-initializing constructor, used by Create. + BpfRingbufBase(size_t value_size) : mValueSize(value_size) {} + + // Full construction that aborts on error (use Create/Init to handle errors). + BpfRingbufBase(const char* path, size_t value_size) : mValueSize(value_size) { + if (auto status = Init(path); !status.ok()) { + ALOGE("BpfRingbuf init failed: %s", status.error().message().c_str()); + abort(); + } + } + + // Delete copy constructor (class owns raw pointers). + BpfRingbufBase(const BpfRingbufBase&) = delete; + + // Initialize the base ringbuffer components. Must be called exactly once. + base::Result<void> Init(const char* path); + + // Consumes all messages from the ring buffer, passing them to the callback. + base::Result<int> ConsumeAll( + const std::function<void(const void*)>& callback); + + // Replicates c-style void* "byte-wise" pointer addition. + template <typename Ptr> + static Ptr pointerAddBytes(void* base, ssize_t offset_bytes) { + return reinterpret_cast<Ptr>(reinterpret_cast<char*>(base) + offset_bytes); + } + + // Rounds len by clearing bitmask, adding header, and aligning to 8 bytes. + static uint32_t roundLength(uint32_t len) { + len &= ~(BPF_RINGBUF_BUSY_BIT | BPF_RINGBUF_DISCARD_BIT); + len += BPF_RINGBUF_HDR_SZ; + return (len + 7) & ~7; + } + + const size_t mValueSize; + + size_t mConsumerSize; + size_t mProducerSize; + unsigned long mPosMask; + android::base::unique_fd mRingFd; + + void* mDataPos = nullptr; + // The kernel uses an "unsigned long" type for both consumer and producer position. + // Unsigned long is a 4 byte value on a 32-bit kernel, and an 8 byte value on a 64-bit kernel. + // To support 32-bit kernels, producer pos is capped at 4 bytes (despite it being 8 bytes on + // 64-bit kernels) and all comparisons of consumer and producer pos only compare the low-order 4 + // bytes (an inequality comparison is performed to support overflow). + // This solution is bitness agnostic. The consumer only increments the 8 byte consumer pos, which, + // in a little-endian architecture, is safe since the entire page is mapped into memory and a + // 32-bit kernel will just ignore the high-order bits. + std::atomic_uint64_t* mConsumerPos = nullptr; + std::atomic_uint32_t* mProducerPos = nullptr; + + // In order to guarantee atomic access in a 32 bit userspace environment, atomic_uint64_t is used + // in addition to std::atomic<T>::is_always_lock_free that guarantees that read / write operations + // are indeed atomic. + // Since std::atomic does not support wrapping preallocated memory, an additional static assert on + // the size of the atomic and the underlying type is added to ensure a reinterpret_cast from type + // to its atomic version is safe (is_always_lock_free being true should provide additional + // confidence). + static_assert(std::atomic_uint64_t::is_always_lock_free); + static_assert(std::atomic_uint32_t::is_always_lock_free); + static_assert(sizeof(std::atomic_uint64_t) == sizeof(uint64_t)); + static_assert(sizeof(std::atomic_uint32_t) == sizeof(uint32_t)); +}; + +// This is a class wrapper for eBPF ring buffers. An eBPF ring buffer is a +// special type of eBPF map used for sending messages from eBPF to userspace. +// The implementation relies on fast shared memory and atomics for the producer +// and consumer management. Ring buffers are a faster alternative to eBPF perf +// buffers. +// +// This class is thread compatible, but not thread safe. +// +// Note: A kernel eBPF ring buffer may be accessed by both kernel and userspace +// processes at the same time. However, the userspace consumers of a given ring +// buffer all share a single read pointer. There is no guarantee which readers +// will read which messages. +template <typename Value> +class BpfRingbuf : public BpfRingbufBase { + public: + using MessageCallback = std::function<void(const Value&)>; + + // Creates a ringbuffer wrapper from a pinned path. This initialization will + // abort on error. To handle errors, initialize with Create instead. + BpfRingbuf(const char* path) : BpfRingbufBase(path, sizeof(Value)) {} + + // Creates a ringbuffer wrapper from a pinned path. There are no guarantees + // that the ringbuf outputs messaged of type `Value`, only that they are the + // same size. Size is only checked in ConsumeAll. + static base::Result<std::unique_ptr<BpfRingbuf<Value>>> Create( + const char* path); + + // Consumes all messages from the ring buffer, passing them to the callback. + // Returns the number of messages consumed or a non-ok result on error. If the + // ring buffer has no pending messages an OK result with count 0 is returned. + base::Result<int> ConsumeAll(const MessageCallback& callback); + + private: + // Empty ctor for use by Create. + BpfRingbuf() : BpfRingbufBase(sizeof(Value)) {} +}; + + +inline base::Result<void> BpfRingbufBase::Init(const char* path) { + mRingFd.reset(mapRetrieveRW(path)); + if (!mRingFd.ok()) { + return android::base::ErrnoError() + << "failed to retrieve ringbuffer at " << path; + } + + int map_type = android::bpf::bpfGetFdMapType(mRingFd); + if (map_type != BPF_MAP_TYPE_RINGBUF) { + errno = EINVAL; + return android::base::ErrnoError() + << "bpf map has wrong type: want BPF_MAP_TYPE_RINGBUF (" + << BPF_MAP_TYPE_RINGBUF << ") got " << map_type; + } + + int max_entries = android::bpf::bpfGetFdMaxEntries(mRingFd); + if (max_entries < 0) { + return android::base::ErrnoError() + << "failed to read max_entries from ringbuf"; + } + if (max_entries == 0) { + errno = EINVAL; + return android::base::ErrnoError() << "max_entries must be non-zero"; + } + + mPosMask = max_entries - 1; + mConsumerSize = getpagesize(); + mProducerSize = getpagesize() + 2 * max_entries; + + { + void* ptr = mmap(NULL, mConsumerSize, PROT_READ | PROT_WRITE, MAP_SHARED, + mRingFd, 0); + if (ptr == MAP_FAILED) { + return android::base::ErrnoError() + << "failed to mmap ringbuf consumer pages"; + } + mConsumerPos = reinterpret_cast<decltype(mConsumerPos)>(ptr); + } + + { + void* ptr = mmap(NULL, mProducerSize, PROT_READ, MAP_SHARED, mRingFd, + mConsumerSize); + if (ptr == MAP_FAILED) { + return android::base::ErrnoError() + << "failed to mmap ringbuf producer page"; + } + mProducerPos = reinterpret_cast<decltype(mProducerPos)>(ptr); + } + + mDataPos = pointerAddBytes<void*>(mProducerPos, getpagesize()); + return {}; +} + +inline base::Result<int> BpfRingbufBase::ConsumeAll( + const std::function<void(const void*)>& callback) { + int64_t count = 0; + uint32_t prod_pos = mProducerPos->load(std::memory_order_acquire); + // Only userspace writes to mConsumerPos, so no need to use std::memory_order_acquire + uint64_t cons_pos = mConsumerPos->load(std::memory_order_relaxed); + while ((cons_pos & 0xFFFFFFFF) != prod_pos) { + // Find the start of the entry for this read (wrapping is done here). + void* start_ptr = pointerAddBytes<void*>(mDataPos, cons_pos & mPosMask); + + // The entry has an 8 byte header containing the sample length. + // struct bpf_ringbuf_hdr { + // u32 len; + // u32 pg_off; + // }; + uint32_t length = *reinterpret_cast<volatile uint32_t*>(start_ptr); + + // If the sample isn't committed, we're caught up with the producer. + if (length & BPF_RINGBUF_BUSY_BIT) return count; + + cons_pos += roundLength(length); + + if ((length & BPF_RINGBUF_DISCARD_BIT) == 0) { + if (length != mValueSize) { + mConsumerPos->store(cons_pos, std::memory_order_release); + errno = EMSGSIZE; + return android::base::ErrnoError() + << "BPF ring buffer message has unexpected size (want " + << mValueSize << " bytes, got " << length << " bytes)"; + } + callback(pointerAddBytes<const void*>(start_ptr, BPF_RINGBUF_HDR_SZ)); + count++; + } + + mConsumerPos->store(cons_pos, std::memory_order_release); + } + + return count; +} + +template <typename Value> +inline base::Result<std::unique_ptr<BpfRingbuf<Value>>> +BpfRingbuf<Value>::Create(const char* path) { + auto rb = std::unique_ptr<BpfRingbuf>(new BpfRingbuf); + if (auto status = rb->Init(path); !status.ok()) return status.error(); + return rb; +} + +template <typename Value> +inline base::Result<int> BpfRingbuf<Value>::ConsumeAll( + const MessageCallback& callback) { + return BpfRingbufBase::ConsumeAll([&](const void* value) { + callback(*reinterpret_cast<const Value*>(value)); + }); +} + +} // namespace bpf +} // namespace android diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfUtils.h b/staticlibs/native/bpf_headers/include/bpf/BpfUtils.h new file mode 100644 index 0000000000000000000000000000000000000000..9dd5822a46fd4bf8f56db86af5bd0be01612479d --- /dev/null +++ b/staticlibs/native/bpf_headers/include/bpf/BpfUtils.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2017 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. + */ + +#pragma once + +#include <errno.h> +#include <linux/if_ether.h> +#include <linux/pfkeyv2.h> +#include <net/if.h> +#include <stdlib.h> +#include <string.h> +#include <sys/resource.h> +#include <sys/socket.h> +#include <sys/utsname.h> + +#include <log/log.h> + +#include "KernelUtils.h" + +namespace android { +namespace bpf { + +// See kernel's net/core/sock_diag.c __sock_gen_cookie() +// the implementation of which guarantees 0 will never be returned, +// primarily because 0 is used to mean not yet initialized, +// and socket cookies are only assigned on first fetch. +constexpr const uint64_t NONEXISTENT_COOKIE = 0; + +static inline uint64_t getSocketCookie(int sockFd) { + uint64_t sock_cookie; + socklen_t cookie_len = sizeof(sock_cookie); + if (getsockopt(sockFd, SOL_SOCKET, SO_COOKIE, &sock_cookie, &cookie_len)) { + // Failure is almost certainly either EBADF or ENOTSOCK + const int err = errno; + ALOGE("Failed to get socket cookie: %s\n", strerror(err)); + errno = err; + return NONEXISTENT_COOKIE; + } + if (cookie_len != sizeof(sock_cookie)) { + // This probably cannot actually happen, but... + ALOGE("Failed to get socket cookie: len %d != 8\n", cookie_len); + errno = 523; // EBADCOOKIE: kernel internal, seems reasonable enough... + return NONEXISTENT_COOKIE; + } + return sock_cookie; +} + +static inline int synchronizeKernelRCU() { + // This is a temporary hack for network stats map swap on devices running + // 4.9 kernels. The kernel code of socket release on pf_key socket will + // explicitly call synchronize_rcu() which is exactly what we need. + // + // Linux 4.14/4.19/5.4/5.10/5.15/6.1 (and 6.3-rc5) still have this same behaviour. + // see net/key/af_key.c: pfkey_release() -> synchronize_rcu() + // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/key/af_key.c?h=v6.3-rc5#n185 + const int pfSocket = socket(AF_KEY, SOCK_RAW | SOCK_CLOEXEC, PF_KEY_V2); + + if (pfSocket < 0) { + const int err = errno; + ALOGE("create PF_KEY socket failed: %s", strerror(err)); + return -err; + } + + // When closing socket, synchronize_rcu() gets called in sock_release(). + if (close(pfSocket)) { + const int err = errno; + ALOGE("failed to close the PF_KEY socket: %s", strerror(err)); + return -err; + } + return 0; +} + +static inline int setrlimitForTest() { + // Set the memory rlimit for the test process if the default MEMLOCK rlimit is not enough. + struct rlimit limit = { + .rlim_cur = 1073741824, // 1 GiB + .rlim_max = 1073741824, // 1 GiB + }; + const int res = setrlimit(RLIMIT_MEMLOCK, &limit); + if (res) ALOGE("Failed to set the default MEMLOCK rlimit: %s", strerror(errno)); + return res; +} + +} // namespace bpf +} // namespace android diff --git a/staticlibs/native/bpf_headers/include/bpf/KernelUtils.h b/staticlibs/native/bpf_headers/include/bpf/KernelUtils.h new file mode 100644 index 0000000000000000000000000000000000000000..59257b8b823287b345b7c5d75fb1f20533b6495b --- /dev/null +++ b/staticlibs/native/bpf_headers/include/bpf/KernelUtils.h @@ -0,0 +1,173 @@ +/* + * 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. + */ + +#pragma once + +#include <stdio.h> +#include <string.h> +#include <sys/personality.h> +#include <sys/utsname.h> + +namespace android { +namespace bpf { + +#define KVER(a, b, c) (((a) << 24) + ((b) << 16) + (c)) + +static inline unsigned uncachedKernelVersion() { + struct utsname buf; + if (uname(&buf)) return 0; + + unsigned kver_major = 0; + unsigned kver_minor = 0; + unsigned kver_sub = 0; + (void)sscanf(buf.release, "%u.%u.%u", &kver_major, &kver_minor, &kver_sub); + return KVER(kver_major, kver_minor, kver_sub); +} + +static inline unsigned kernelVersion() { + static unsigned kver = uncachedKernelVersion(); + return kver; +} + +static inline __unused bool isAtLeastKernelVersion(unsigned major, unsigned minor, unsigned sub) { + return kernelVersion() >= KVER(major, minor, sub); +} + +// Figure out the bitness of userspace. +// Trivial and known at compile time. +static constexpr bool isUserspace32bit() { + return sizeof(void*) == 4; +} + +static constexpr bool isUserspace64bit() { + return sizeof(void*) == 8; +} + +#if defined(__LP64__) +static_assert(isUserspace64bit(), "huh? LP64 must have 64-bit userspace"); +#elif defined(__ILP32__) +static_assert(isUserspace32bit(), "huh? ILP32 must have 32-bit userspace"); +#else +#error "huh? must be either LP64 (64-bit userspace) or ILP32 (32-bit userspace)" +#endif + +static_assert(isUserspace32bit() || isUserspace64bit(), "must be either 32 or 64 bit"); + +// Figure out the bitness of the kernel. +static inline bool isKernel64Bit() { + // a 64-bit userspace requires a 64-bit kernel + if (isUserspace64bit()) return true; + + static bool init = false; + static bool cache = false; + if (init) return cache; + + // Retrieve current personality - on Linux this system call *cannot* fail. + int p = personality(0xffffffff); + // But if it does just assume kernel and userspace (which is 32-bit) match... + if (p == -1) return false; + + // This will effectively mask out the bottom 8 bits, and switch to 'native' + // personality, and then return the previous personality of this thread + // (likely PER_LINUX or PER_LINUX32) with any extra options unmodified. + int q = personality((p & ~PER_MASK) | PER_LINUX); + // Per man page this theoretically could error out with EINVAL, + // but kernel code analysis suggests setting PER_LINUX cannot fail. + // Either way, assume kernel and userspace (which is 32-bit) match... + if (q != p) return false; + + struct utsname u; + (void)uname(&u); // only possible failure is EFAULT, but u is on stack. + + // Switch back to previous personality. + // Theoretically could fail with EINVAL on arm64 with no 32-bit support, + // but then we wouldn't have fetched 'p' from the kernel in the first place. + // Either way there's nothing meaningful we can do in case of error. + // Since PER_LINUX32 vs PER_LINUX only affects uname.machine it doesn't + // really hurt us either. We're really just switching back to be 'clean'. + (void)personality(p); + + // Possible values of utsname.machine observed on x86_64 desktop (arm via qemu): + // x86_64 i686 aarch64 armv7l + // additionally observed on arm device: + // armv8l + // presumably also might just be possible: + // i386 i486 i586 + // and there might be other weird arm32 cases. + // We note that the 64 is present in both 64-bit archs, + // and in general is likely to be present in only 64-bit archs. + cache = !!strstr(u.machine, "64"); + init = true; + return cache; +} + +static inline __unused bool isKernel32Bit() { + return !isKernel64Bit(); +} + +static constexpr bool isArm() { +#if defined(__arm__) + static_assert(isUserspace32bit(), "huh? arm must be 32 bit"); + return true; +#elif defined(__aarch64__) + static_assert(isUserspace64bit(), "aarch64 must be LP64 - no support for ILP32"); + return true; +#else + return false; +#endif +} + +static constexpr bool isX86() { +#if defined(__i386__) + static_assert(isUserspace32bit(), "huh? i386 must be 32 bit"); + return true; +#elif defined(__x86_64__) + static_assert(isUserspace64bit(), "x86_64 must be LP64 - no support for ILP32 (x32)"); + return true; +#else + return false; +#endif +} + +static constexpr bool isRiscV() { +#if defined(__riscv) + static_assert(isUserspace64bit(), "riscv must be 64 bit"); + return true; +#else + return false; +#endif +} + +static_assert(isArm() || isX86() || isRiscV(), "Unknown architecture"); + +static __unused const char * describeArch() { + // ordered so as to make it easier to compile time optimize, + // only thing not known at compile time is isKernel64Bit() + if (isUserspace64bit()) { + if (isArm()) return "64-on-aarch64"; + if (isX86()) return "64-on-x86-64"; + if (isRiscV()) return "64-on-riscv64"; + } else if (isKernel64Bit()) { + if (isArm()) return "32-on-aarch64"; + if (isX86()) return "32-on-x86-64"; + } else { + if (isArm()) return "32-on-arm32"; + if (isX86()) return "32-on-x86-32"; + } +} + +} // namespace bpf +} // namespace android diff --git a/staticlibs/native/bpf_headers/include/bpf/WaitForProgsLoaded.h b/staticlibs/native/bpf_headers/include/bpf/WaitForProgsLoaded.h new file mode 100644 index 0000000000000000000000000000000000000000..bc4168eed4c5a06f6ebc16ebdaaa5bf820d17576 --- /dev/null +++ b/staticlibs/native/bpf_headers/include/bpf/WaitForProgsLoaded.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * Android BPF library - public API + * + * 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. + */ + +#pragma once + +#include <log/log.h> + +#include <android-base/properties.h> + +namespace android { +namespace bpf { + +// Wait for bpfloader to load BPF programs. +static inline void waitForProgsLoaded() { + // infinite loop until success with 5/10/20/40/60/60/60... delay + for (int delay = 5;; delay *= 2) { + if (delay > 60) delay = 60; + if (android::base::WaitForProperty("bpf.progs_loaded", "1", std::chrono::seconds(delay))) + return; + ALOGW("Waited %ds for bpf.progs_loaded, still waiting...", delay); + } +} + +} // namespace bpf +} // namespace android diff --git a/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h new file mode 100644 index 0000000000000000000000000000000000000000..67ac0e46e958c129baa5cbfed995977093c1487b --- /dev/null +++ b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h @@ -0,0 +1,425 @@ +/* Common BPF helpers to be used by all BPF programs loaded by Android */ + +#include <linux/bpf.h> +#include <stdbool.h> +#include <stdint.h> + +#include "bpf_map_def.h" + +/****************************************************************************** + * WARNING: CHANGES TO THIS FILE OUTSIDE OF AOSP/MASTER ARE LIKELY TO BREAK * + * DEVICE COMPATIBILITY WITH MAINLINE MODULES SHIPPING EBPF CODE. * + * * + * THIS WILL LIKELY RESULT IN BRICKED DEVICES AT SOME ARBITRARY FUTURE TIME * + * * + * THAT GOES ESPECIALLY FOR THE 'SECTION' 'LICENSE' AND 'CRITICAL' MACROS * + * * + * We strongly suggest that if you need changes to bpfloader functionality * + * you get your changes reviewed and accepted into aosp/master. * + * * + ******************************************************************************/ + +// The actual versions of the bpfloader that shipped in various Android releases + +// Android P/Q/R: BpfLoader was initially part of netd, +// this was later split out into a standalone binary, but was unversioned. + +// Android S / 12 (api level 31) - added 'tethering' mainline eBPF support +#define BPFLOADER_S_VERSION 2u + +// Android T / 13 (api level 33) - support for shared/selinux_context/pindir +#define BPFLOADER_T_VERSION 19u + +// BpfLoader v0.25+ support obj@ver.o files +#define BPFLOADER_OBJ_AT_VER_VERSION 25u + +// Bpfloader v0.33+ supports {map,prog}.ignore_on_{eng,user,userdebug} +#define BPFLOADER_IGNORED_ON_VERSION 33u + +// Android U / 14 (api level 34) - various new program types added +#define BPFLOADER_U_VERSION 37u + +/* For mainline module use, you can #define BPFLOADER_{MIN/MAX}_VER + * before #include "bpf_helpers.h" to change which bpfloaders will + * process the resulting .o file. + * + * While this will work outside of mainline too, there just is no point to + * using it when the .o and the bpfloader ship in sync with each other. + * In which case it's just best to use the default. + */ +#ifndef BPFLOADER_MIN_VER +#define BPFLOADER_MIN_VER COMPILE_FOR_BPFLOADER_VERSION +#endif + +#ifndef BPFLOADER_MAX_VER +#define BPFLOADER_MAX_VER DEFAULT_BPFLOADER_MAX_VER +#endif + +/* place things in different elf sections */ +#define SECTION(NAME) __attribute__((section(NAME), used)) + +/* Must be present in every program, example usage: + * LICENSE("GPL"); or LICENSE("Apache 2.0"); + * + * We also take this opportunity to embed a bunch of other useful values in + * the resulting .o (This is to enable some limited forward compatibility + * with mainline module shipped ebpf programs) + * + * The bpfloader_{min/max}_ver defines the [min, max) range of bpfloader + * versions that should load this .o file (bpfloaders outside of this range + * will simply ignore/skip this *entire* .o) + * The [inclusive,exclusive) matches what we do for kernel ver dependencies. + * + * The size_of_bpf_{map,prog}_def allow the bpfloader to load programs where + * these structures have been extended with additional fields (they will of + * course simply be ignored then). + * + * If missing, bpfloader_{min/max}_ver default to 0/0x10000 ie. [v0.0, v1.0), + * while size_of_bpf_{map/prog}_def default to 32/20 which are the v0.0 sizes. + */ +#define LICENSE(NAME) \ + unsigned int _bpfloader_min_ver SECTION("bpfloader_min_ver") = BPFLOADER_MIN_VER; \ + unsigned int _bpfloader_max_ver SECTION("bpfloader_max_ver") = BPFLOADER_MAX_VER; \ + size_t _size_of_bpf_map_def SECTION("size_of_bpf_map_def") = sizeof(struct bpf_map_def); \ + size_t _size_of_bpf_prog_def SECTION("size_of_bpf_prog_def") = sizeof(struct bpf_prog_def); \ + char _license[] SECTION("license") = (NAME) + +/* This macro disables loading BTF map debug information on Android <=U *and* all user builds. + * + * Note: Bpfloader v0.39+ honours 'btf_user_min_bpfloader_ver' on user builds, + * and 'btf_min_bpfloader_ver' on non-user builds. + * Older BTF capable versions unconditionally honour 'btf_min_bpfloader_ver' + */ +#define DISABLE_BTF_ON_USER_BUILDS() \ + unsigned _btf_min_bpfloader_ver SECTION("btf_min_bpfloader_ver") = 39u; \ + unsigned _btf_user_min_bpfloader_ver SECTION("btf_user_min_bpfloader_ver") = 0xFFFFFFFFu + +/* flag the resulting bpf .o file as critical to system functionality, + * loading all kernel version appropriate programs in it must succeed + * for bpfloader success + */ +#define CRITICAL(REASON) char _critical[] SECTION("critical") = (REASON) + +/* + * Helper functions called from eBPF programs written in C. These are + * implemented in the kernel sources. + */ + +#define KVER_NONE 0 +#define KVER(a, b, c) (((a) << 24) + ((b) << 16) + (c)) +#define KVER_INF 0xFFFFFFFFu + +/* + * BPFFS (ie. /sys/fs/bpf) labelling is as follows: + * subdirectory selinux context mainline usecase / usable by + * / fs_bpf no [*] core operating system (ie. platform) + * /loader fs_bpf_loader no, U+ (as yet unused) + * /net_private fs_bpf_net_private yes, T+ network_stack + * /net_shared fs_bpf_net_shared yes, T+ network_stack & system_server + * /netd_readonly fs_bpf_netd_readonly yes, T+ network_stack & system_server & r/o to netd + * /netd_shared fs_bpf_netd_shared yes, T+ network_stack & system_server & netd [**] + * /tethering fs_bpf_tethering yes, S+ network_stack + * /vendor fs_bpf_vendor no, T+ vendor + * + * [*] initial support for bpf was added back in P, + * but things worked differently back then with no bpfloader, + * and instead netd doing stuff by hand, + * bpfloader with pinning into /sys/fs/bpf was (I believe) added in Q + * (and was definitely there in R). + * + * [**] additionally bpf programs are accessible to netutils_wrapper + * for use by iptables xt_bpf extensions. + * + * See cs/p:aosp-master%20-file:prebuilts/%20file:genfs_contexts%20"genfscon%20bpf" + */ + +/* generic functions */ + +/* + * Type-unsafe bpf map functions - avoid if possible. + * + * Using these it is possible to pass in keys/values of the wrong type/size, + * or, for 'bpf_map_lookup_elem_unsafe' receive into a pointer to the wrong type. + * You will not get a compile time failure, and for certain types of errors you + * might not even get a failure from the kernel's ebpf verifier during program load, + * instead stuff might just not work right at runtime. + * + * Instead please use: + * DEFINE_BPF_MAP(foo_map, TYPE, KeyType, ValueType, num_entries) + * where TYPE can be something like HASH or ARRAY, and num_entries is an integer. + * + * This defines the map (hence this should not be used in a header file included + * from multiple locations) and provides type safe accessors: + * ValueType * bpf_foo_map_lookup_elem(const KeyType *) + * int bpf_foo_map_update_elem(const KeyType *, const ValueType *, flags) + * int bpf_foo_map_delete_elem(const KeyType *) + * + * This will make sure that if you change the type of a map you'll get compile + * errors at any spots you forget to update with the new type. + * + * Note: these all take pointers to const map because from the C/eBPF point of view + * the map struct is really just a readonly map definition of the in kernel object. + * Runtime modification of the map defining struct is meaningless, since + * the contents is only ever used during bpf program loading & map creation + * by the bpf loader, and not by the eBPF program itself. + */ +static void* (*bpf_map_lookup_elem_unsafe)(const struct bpf_map_def* map, + const void* key) = (void*)BPF_FUNC_map_lookup_elem; +static int (*bpf_map_update_elem_unsafe)(const struct bpf_map_def* map, const void* key, + const void* value, unsigned long long flags) = (void*) + BPF_FUNC_map_update_elem; +static int (*bpf_map_delete_elem_unsafe)(const struct bpf_map_def* map, + const void* key) = (void*)BPF_FUNC_map_delete_elem; +static int (*bpf_ringbuf_output_unsafe)(const struct bpf_map_def* ringbuf, + const void* data, __u64 size, __u64 flags) = (void*) + BPF_FUNC_ringbuf_output; +static void* (*bpf_ringbuf_reserve_unsafe)(const struct bpf_map_def* ringbuf, + __u64 size, __u64 flags) = (void*) + BPF_FUNC_ringbuf_reserve; +static void (*bpf_ringbuf_submit_unsafe)(const void* data, __u64 flags) = (void*) + BPF_FUNC_ringbuf_submit; + +#define BPF_ANNOTATE_KV_PAIR(name, type_key, type_val) \ + struct ____btf_map_##name { \ + type_key key; \ + type_val value; \ + }; \ + struct ____btf_map_##name \ + __attribute__ ((section(".maps." #name), used)) \ + ____btf_map_##name = { } + +#define BPF_ASSERT_LOADER_VERSION(min_loader, ignore_eng, ignore_user, ignore_userdebug) \ + _Static_assert( \ + (min_loader) >= BPFLOADER_IGNORED_ON_VERSION || \ + !((ignore_eng) || (ignore_user) || (ignore_userdebug)), \ + "bpfloader min version must be >= 0.33 in order to use ignored_on"); + +#define DEFINE_BPF_MAP_BASE(the_map, TYPE, keysize, valuesize, num_entries, \ + usr, grp, md, selinux, pindir, share, minkver, \ + maxkver, minloader, maxloader, ignore_eng, \ + ignore_user, ignore_userdebug) \ + const struct bpf_map_def SECTION("maps") the_map = { \ + .type = BPF_MAP_TYPE_##TYPE, \ + .key_size = (keysize), \ + .value_size = (valuesize), \ + .max_entries = (num_entries), \ + .map_flags = 0, \ + .uid = (usr), \ + .gid = (grp), \ + .mode = (md), \ + .bpfloader_min_ver = (minloader), \ + .bpfloader_max_ver = (maxloader), \ + .min_kver = (minkver), \ + .max_kver = (maxkver), \ + .selinux_context = (selinux), \ + .pin_subdir = (pindir), \ + .shared = (share), \ + .ignore_on_eng = (ignore_eng), \ + .ignore_on_user = (ignore_user), \ + .ignore_on_userdebug = (ignore_userdebug), \ + }; \ + BPF_ASSERT_LOADER_VERSION(minloader, ignore_eng, ignore_user, ignore_userdebug); + +// Type safe macro to declare a ring buffer and related output functions. +// Compatibility: +// * BPF ring buffers are only available kernels 5.8 and above. Any program +// accessing the ring buffer should set a program level min_kver >= 5.8. +// * The definition below sets a map min_kver of 5.8 which requires targeting +// a BPFLOADER_MIN_VER >= BPFLOADER_S_VERSION. +#define DEFINE_BPF_RINGBUF_EXT(the_map, ValueType, size_bytes, usr, grp, md, \ + selinux, pindir, share, min_loader, max_loader, \ + ignore_eng, ignore_user, ignore_userdebug) \ + DEFINE_BPF_MAP_BASE(the_map, RINGBUF, 0, 0, size_bytes, usr, grp, md, \ + selinux, pindir, share, KVER(5, 8, 0), KVER_INF, \ + min_loader, max_loader, ignore_eng, ignore_user, \ + ignore_userdebug); \ + \ + _Static_assert((size_bytes) >= 4096, "min 4 kiB ringbuffer size"); \ + _Static_assert((size_bytes) <= 0x10000000, "max 256 MiB ringbuffer size"); \ + _Static_assert(((size_bytes) & ((size_bytes) - 1)) == 0, \ + "ring buffer size must be a power of two"); \ + \ + static inline __always_inline __unused int bpf_##the_map##_output( \ + const ValueType* v) { \ + return bpf_ringbuf_output_unsafe(&the_map, v, sizeof(*v), 0); \ + } \ + \ + static inline __always_inline __unused \ + ValueType* bpf_##the_map##_reserve() { \ + return bpf_ringbuf_reserve_unsafe(&the_map, sizeof(ValueType), 0); \ + } \ + \ + static inline __always_inline __unused void bpf_##the_map##_submit( \ + const ValueType* v) { \ + bpf_ringbuf_submit_unsafe(v, 0); \ + } + +/* There exist buggy kernels with pre-T OS, that due to + * kernel patch "[ALPS05162612] bpf: fix ubsan error" + * do not support userspace writes into non-zero index of bpf map arrays. + * + * We use this assert to prevent us from being able to define such a map. + */ + +#ifdef THIS_BPF_PROGRAM_IS_FOR_TEST_PURPOSES_ONLY +#define BPF_MAP_ASSERT_OK(type, entries, mode) +#elif BPFLOADER_MIN_VER >= BPFLOADER_T_VERSION +#define BPF_MAP_ASSERT_OK(type, entries, mode) +#else +#define BPF_MAP_ASSERT_OK(type, entries, mode) \ + _Static_assert(((type) != BPF_MAP_TYPE_ARRAY) || ((entries) <= 1) || !((mode) & 0222), \ + "Writable arrays with more than 1 element not supported on pre-T devices.") +#endif + +/* type safe macro to declare a map and related accessor functions */ +#define DEFINE_BPF_MAP_EXT(the_map, TYPE, KeyType, ValueType, num_entries, usr, grp, md, \ + selinux, pindir, share, min_loader, max_loader, ignore_eng, \ + ignore_user, ignore_userdebug) \ + DEFINE_BPF_MAP_BASE(the_map, TYPE, sizeof(KeyType), sizeof(ValueType), \ + num_entries, usr, grp, md, selinux, pindir, share, \ + KVER_NONE, KVER_INF, min_loader, max_loader, \ + ignore_eng, ignore_user, ignore_userdebug); \ + BPF_MAP_ASSERT_OK(BPF_MAP_TYPE_##TYPE, (num_entries), (md)); \ + _Static_assert(sizeof(KeyType) < 1024, "aosp/2370288 requires < 1024 byte keys"); \ + _Static_assert(sizeof(ValueType) < 65536, "aosp/2370288 requires < 65536 byte values"); \ + BPF_ANNOTATE_KV_PAIR(the_map, KeyType, ValueType); \ + \ + static inline __always_inline __unused ValueType* bpf_##the_map##_lookup_elem( \ + const KeyType* k) { \ + return bpf_map_lookup_elem_unsafe(&the_map, k); \ + }; \ + \ + static inline __always_inline __unused int bpf_##the_map##_update_elem( \ + const KeyType* k, const ValueType* v, unsigned long long flags) { \ + return bpf_map_update_elem_unsafe(&the_map, k, v, flags); \ + }; \ + \ + static inline __always_inline __unused int bpf_##the_map##_delete_elem(const KeyType* k) { \ + return bpf_map_delete_elem_unsafe(&the_map, k); \ + }; + +#ifndef DEFAULT_BPF_MAP_SELINUX_CONTEXT +#define DEFAULT_BPF_MAP_SELINUX_CONTEXT "" +#endif + +#ifndef DEFAULT_BPF_MAP_PIN_SUBDIR +#define DEFAULT_BPF_MAP_PIN_SUBDIR "" +#endif + +#ifndef DEFAULT_BPF_MAP_UID +#define DEFAULT_BPF_MAP_UID AID_ROOT +#elif BPFLOADER_MIN_VER < 28u +#error "Bpf Map UID must be left at default of AID_ROOT for BpfLoader prior to v0.28" +#endif + +#define DEFINE_BPF_MAP_UGM(the_map, TYPE, KeyType, ValueType, num_entries, usr, grp, md) \ + DEFINE_BPF_MAP_EXT(the_map, TYPE, KeyType, ValueType, num_entries, usr, grp, md, \ + DEFAULT_BPF_MAP_SELINUX_CONTEXT, DEFAULT_BPF_MAP_PIN_SUBDIR, false, \ + BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, /*ignore_on_eng*/false, \ + /*ignore_on_user*/false, /*ignore_on_userdebug*/false) + +#define DEFINE_BPF_MAP(the_map, TYPE, KeyType, ValueType, num_entries) \ + DEFINE_BPF_MAP_UGM(the_map, TYPE, KeyType, ValueType, num_entries, \ + DEFAULT_BPF_MAP_UID, AID_ROOT, 0600) + +#define DEFINE_BPF_MAP_RO(the_map, TYPE, KeyType, ValueType, num_entries, gid) \ + DEFINE_BPF_MAP_UGM(the_map, TYPE, KeyType, ValueType, num_entries, \ + DEFAULT_BPF_MAP_UID, gid, 0440) + +#define DEFINE_BPF_MAP_GWO(the_map, TYPE, KeyType, ValueType, num_entries, gid) \ + DEFINE_BPF_MAP_UGM(the_map, TYPE, KeyType, ValueType, num_entries, \ + DEFAULT_BPF_MAP_UID, gid, 0620) + +#define DEFINE_BPF_MAP_GRO(the_map, TYPE, KeyType, ValueType, num_entries, gid) \ + DEFINE_BPF_MAP_UGM(the_map, TYPE, KeyType, ValueType, num_entries, \ + DEFAULT_BPF_MAP_UID, gid, 0640) + +#define DEFINE_BPF_MAP_GRW(the_map, TYPE, KeyType, ValueType, num_entries, gid) \ + DEFINE_BPF_MAP_UGM(the_map, TYPE, KeyType, ValueType, num_entries, \ + DEFAULT_BPF_MAP_UID, gid, 0660) + +// LLVM eBPF builtins: they directly generate BPF_LD_ABS/BPF_LD_IND (skb may be ignored?) +unsigned long long load_byte(void* skb, unsigned long long off) asm("llvm.bpf.load.byte"); +unsigned long long load_half(void* skb, unsigned long long off) asm("llvm.bpf.load.half"); +unsigned long long load_word(void* skb, unsigned long long off) asm("llvm.bpf.load.word"); + +static int (*bpf_probe_read)(void* dst, int size, void* unsafe_ptr) = (void*) BPF_FUNC_probe_read; +static int (*bpf_probe_read_str)(void* dst, int size, void* unsafe_ptr) = (void*) BPF_FUNC_probe_read_str; +static int (*bpf_probe_read_user)(void* dst, int size, const void* unsafe_ptr) = (void*)BPF_FUNC_probe_read_user; +static int (*bpf_probe_read_user_str)(void* dst, int size, const void* unsafe_ptr) = (void*) BPF_FUNC_probe_read_user_str; +static unsigned long long (*bpf_ktime_get_ns)(void) = (void*) BPF_FUNC_ktime_get_ns; +static unsigned long long (*bpf_ktime_get_boot_ns)(void) = (void*)BPF_FUNC_ktime_get_boot_ns; +static int (*bpf_trace_printk)(const char* fmt, int fmt_size, ...) = (void*) BPF_FUNC_trace_printk; +static unsigned long long (*bpf_get_current_pid_tgid)(void) = (void*) BPF_FUNC_get_current_pid_tgid; +static unsigned long long (*bpf_get_current_uid_gid)(void) = (void*) BPF_FUNC_get_current_uid_gid; +static unsigned long long (*bpf_get_smp_processor_id)(void) = (void*) BPF_FUNC_get_smp_processor_id; +static long (*bpf_get_stackid)(void* ctx, void* map, uint64_t flags) = (void*) BPF_FUNC_get_stackid; +static long (*bpf_get_current_comm)(void* buf, uint32_t buf_size) = (void*) BPF_FUNC_get_current_comm; + +#define DEFINE_BPF_PROG_EXT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, max_kv, \ + min_loader, max_loader, opt, selinux, pindir, ignore_eng, \ + ignore_user, ignore_userdebug) \ + const struct bpf_prog_def SECTION("progs") the_prog##_def = { \ + .uid = (prog_uid), \ + .gid = (prog_gid), \ + .min_kver = (min_kv), \ + .max_kver = (max_kv), \ + .optional = (opt), \ + .bpfloader_min_ver = (min_loader), \ + .bpfloader_max_ver = (max_loader), \ + .selinux_context = (selinux), \ + .pin_subdir = (pindir), \ + .ignore_on_eng = (ignore_eng), \ + .ignore_on_user = (ignore_user), \ + .ignore_on_userdebug = (ignore_userdebug), \ + }; \ + SECTION(SECTION_NAME) \ + int the_prog + +#ifndef DEFAULT_BPF_PROG_SELINUX_CONTEXT +#define DEFAULT_BPF_PROG_SELINUX_CONTEXT "" +#endif + +#ifndef DEFAULT_BPF_PROG_PIN_SUBDIR +#define DEFAULT_BPF_PROG_PIN_SUBDIR "" +#endif + +#define DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, max_kv, \ + opt) \ + DEFINE_BPF_PROG_EXT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, max_kv, \ + BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, opt, \ + DEFAULT_BPF_PROG_SELINUX_CONTEXT, DEFAULT_BPF_PROG_PIN_SUBDIR, \ + false, false, false) + +// Programs (here used in the sense of functions/sections) marked optional are allowed to fail +// to load (for example due to missing kernel patches). +// The bpfloader will just ignore these failures and continue processing the next section. +// +// A non-optional program (function/section) failing to load causes a failure and aborts +// processing of the entire .o, if the .o is additionally marked critical, this will result +// in the entire bpfloader process terminating with a failure and not setting the bpf.progs_loaded +// system property. This in turn results in waitForProgsLoaded() never finishing. +// +// ie. a non-optional program in a critical .o is mandatory for kernels matching the min/max kver. + +// programs requiring a kernel version >= min_kv && < max_kv +#define DEFINE_BPF_PROG_KVER_RANGE(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, max_kv) \ + DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, max_kv, \ + false) +#define DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, \ + max_kv) \ + DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, max_kv, true) + +// programs requiring a kernel version >= min_kv +#define DEFINE_BPF_PROG_KVER(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv) \ + DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, KVER_INF, \ + false) +#define DEFINE_OPTIONAL_BPF_PROG_KVER(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv) \ + DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, KVER_INF, \ + true) + +// programs with no kernel version requirements +#define DEFINE_BPF_PROG(SECTION_NAME, prog_uid, prog_gid, the_prog) \ + DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, 0, KVER_INF, false) +#define DEFINE_OPTIONAL_BPF_PROG(SECTION_NAME, prog_uid, prog_gid, the_prog) \ + DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, 0, KVER_INF, true) diff --git a/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h b/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h new file mode 100644 index 0000000000000000000000000000000000000000..e7428b6d3f4d936bc591e238580b4852f2f82831 --- /dev/null +++ b/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h @@ -0,0 +1,245 @@ +/* + * 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. + */ + +#pragma once + +/* This file is separate because it's included both by eBPF programs (via include + * in bpf_helpers.h) and directly by the boot time bpfloader (Loader.cpp). + */ + +#include <linux/bpf.h> + +// Pull in AID_* constants from //system/core/libcutils/include/private/android_filesystem_config.h +#include <cutils/android_filesystem_config.h> + +/****************************************************************************** + * * + * ! ! ! W A R N I N G ! ! ! * + * * + * CHANGES TO THESE STRUCTURE DEFINITIONS OUTSIDE OF AOSP/MASTER *WILL* BREAK * + * MAINLINE MODULE COMPATIBILITY * + * * + * AND THUS MAY RESULT IN YOUR DEVICE BRICKING AT SOME ARBITRARY POINT IN * + * THE FUTURE * + * * + * (and even in aosp/master you may only append new fields at the very end, * + * you may *never* delete fields, change their types, ordering, insert in * + * the middle, etc. If a mainline module using the old definition has * + * already shipped (which happens roughly monthly), then it's set in stone) * + * * + ******************************************************************************/ + +// These are the values used if these fields are missing +#define DEFAULT_BPFLOADER_MIN_VER 0u // v0.0 (this is inclusive ie. >= v0.0) +#define DEFAULT_BPFLOADER_MAX_VER 0x10000u // v1.0 (this is exclusive ie. < v1.0) +#define DEFAULT_SIZEOF_BPF_MAP_DEF 32 // v0.0 struct: enum (uint sized) + 7 uint +#define DEFAULT_SIZEOF_BPF_PROG_DEF 20 // v0.0 struct: 4 uint + bool + 3 byte alignment pad + +// By default, unless otherwise specified, allow the use of features only supported by v0.37. +#define COMPILE_FOR_BPFLOADER_VERSION 37u + +/* + * The bpf_{map,prog}_def structures are compiled for different architectures. + * Once by the BPF compiler for the BPF architecture, and once by a C++ + * compiler for the native Android architecture for the bpfloader. + * + * For things to work, their layout must be the same between the two. + * The BPF architecture is platform independent ('64-bit LSB bpf'). + * So this effectively means these structures must be the same layout + * on 5 architectures, all of them little endian: + * 64-bit BPF, x86_64, arm and 32-bit x86 and arm + * + * As such for any types we use inside of these structs we must make sure that + * the size and alignment are the same, so the same amount of padding is used. + * + * Currently we only use: bool, enum bpf_map_type and unsigned int. + * Additionally we use char for padding. + * + * !!! WARNING: HERE BE DRAGONS !!! + * + * Be particularly careful with 64-bit integers. + * You will need to manually override their alignment to 8 bytes. + * + * To quote some parts of https://gcc.gnu.org/bugzilla/show_bug.cgi?id=69560 + * + * Some types have weaker alignment requirements when they are structure members. + * + * unsigned long long on x86 is such a type. + * + * C distinguishes C11 _Alignof (the minimum alignment the type is guaranteed + * to have in all contexts, so 4, see min_align_of_type) from GNU C __alignof + * (the normal alignment of the type, so 8). + * + * alignof / _Alignof == minimum alignment required by target ABI + * __alignof / __alignof__ == preferred alignment + * + * When in a struct, apparently the minimum alignment is used. + */ + +_Static_assert(sizeof(bool) == 1, "sizeof bool != 1"); +_Static_assert(__alignof__(bool) == 1, "__alignof__ bool != 1"); +_Static_assert(_Alignof(bool) == 1, "_Alignof bool != 1"); + +_Static_assert(sizeof(char) == 1, "sizeof char != 1"); +_Static_assert(__alignof__(char) == 1, "__alignof__ char != 1"); +_Static_assert(_Alignof(char) == 1, "_Alignof char != 1"); + +// This basically verifies that an enum is 'just' a 32-bit int +_Static_assert(sizeof(enum bpf_map_type) == 4, "sizeof enum bpf_map_type != 4"); +_Static_assert(__alignof__(enum bpf_map_type) == 4, "__alignof__ enum bpf_map_type != 4"); +_Static_assert(_Alignof(enum bpf_map_type) == 4, "_Alignof enum bpf_map_type != 4"); + +// Linux kernel requires sizeof(int) == 4, sizeof(void*) == sizeof(long), sizeof(long long) == 8 +_Static_assert(sizeof(unsigned int) == 4, "sizeof unsigned int != 4"); +_Static_assert(__alignof__(unsigned int) == 4, "__alignof__ unsigned int != 4"); +_Static_assert(_Alignof(unsigned int) == 4, "_Alignof unsigned int != 4"); + +// We don't currently use any 64-bit types in these structs, so this is purely to document issue. +// Here sizeof & __alignof__ are consistent, but _Alignof is not: compile for 'aosp_cf_x86_phone' +_Static_assert(sizeof(unsigned long long) == 8, "sizeof unsigned long long != 8"); +_Static_assert(__alignof__(unsigned long long) == 8, "__alignof__ unsigned long long != 8"); +// BPF wants 8, but 32-bit x86 wants 4 +//_Static_assert(_Alignof(unsigned long long) == 8, "_Alignof unsigned long long != 8"); + +// Length of strings (incl. selinux_context and pin_subdir) +// in the bpf_map_def and bpf_prog_def structs. +// +// WARNING: YOU CANNOT *EVER* CHANGE THESE +// as this would affect the structure size in backwards incompatible ways +// and break mainline module loading on older Android T devices +#define BPF_SELINUX_CONTEXT_CHAR_ARRAY_SIZE 32 +#define BPF_PIN_SUBDIR_CHAR_ARRAY_SIZE 32 + +/* + * Map structure to be used by Android eBPF C programs. The Android eBPF loader + * uses this structure from eBPF object to create maps at boot time. + * + * The eBPF C program should define structure in the maps section using + * SECTION("maps") otherwise it will be ignored by the eBPF loader. + * + * For example: + * const struct bpf_map_def SECTION("maps") mymap { .type=... , .key_size=... } + * + * See 'bpf_helpers.h' for helpful macros for eBPF program use. + */ +struct bpf_map_def { + enum bpf_map_type type; + unsigned int key_size; + unsigned int value_size; + unsigned int max_entries; + unsigned int map_flags; + + // The following are not supported by the Android bpfloader: + // unsigned int inner_map_idx; + // unsigned int numa_node; + + unsigned int zero; // uid_t, for compat with old (buggy) bpfloader must be AID_ROOT == 0 + unsigned int gid; // gid_t + unsigned int mode; // mode_t + + // The following fields were added in version 0.1 + unsigned int bpfloader_min_ver; // if missing, defaults to 0, ie. v0.0 + unsigned int bpfloader_max_ver; // if missing, defaults to 0x10000, ie. v1.0 + + // The following fields were added in version 0.2 (S) + // kernelVersion() must be >= min_kver and < max_kver + unsigned int min_kver; + unsigned int max_kver; + + // The following fields were added in version 0.18 (T) + // + // These are fixed length strings, padded with null bytes + // + // Warning: supported values depend on .o location + // (additionally a newer Android OS and/or bpfloader may support more values) + // + // overrides default selinux context (which is based on pin subdir) + char selinux_context[BPF_SELINUX_CONTEXT_CHAR_ARRAY_SIZE]; + // + // overrides default prefix (which is based on .o location) + char pin_subdir[BPF_PIN_SUBDIR_CHAR_ARRAY_SIZE]; + + bool shared; // use empty string as 'file' component of pin path - allows cross .o map sharing + + // The following 3 ignore_on_* fields were added in version 0.32 (U). These are ignored in + // older bpfloader versions, and zero in programs compiled before 0.32. + bool ignore_on_eng:1; + bool ignore_on_user:1; + bool ignore_on_userdebug:1; + // The following 5 ignore_on_* fields were added in version 0.38 (U). These are ignored in + // older bpfloader versions, and zero in programs compiled before 0.38. + // These are tests on the kernel architecture, ie. they ignore userspace bit-ness. + bool ignore_on_arm32:1; + bool ignore_on_aarch64:1; + bool ignore_on_x86_32:1; + bool ignore_on_x86_64:1; + bool ignore_on_riscv64:1; + + char pad0[2]; // manually pad up to 4 byte alignment, may be used for extensions in the future + + unsigned int uid; // uid_t +}; + +_Static_assert(sizeof(((struct bpf_map_def *)0)->selinux_context) == 32, "must be 32 bytes"); +_Static_assert(sizeof(((struct bpf_map_def *)0)->pin_subdir) == 32, "must be 32 bytes"); + +// This needs to be updated whenever the above structure definition is expanded. +_Static_assert(sizeof(struct bpf_map_def) == 120, "sizeof struct bpf_map_def != 120"); +_Static_assert(__alignof__(struct bpf_map_def) == 4, "__alignof__ struct bpf_map_def != 4"); +_Static_assert(_Alignof(struct bpf_map_def) == 4, "_Alignof struct bpf_map_def != 4"); + +struct bpf_prog_def { + unsigned int uid; + unsigned int gid; + + // kernelVersion() must be >= min_kver and < max_kver + unsigned int min_kver; + unsigned int max_kver; + + bool optional; // program section (ie. function) may fail to load, continue onto next func. + + // The following 3 ignore_on_* fields were added in version 0.33 (U). These are ignored in + // older bpfloader versions, and zero in programs compiled before 0.33. + bool ignore_on_eng:1; + bool ignore_on_user:1; + bool ignore_on_userdebug:1; + // The following 5 ignore_on_* fields were added in version 0.38 (U). These are ignored in + // older bpfloader versions, and zero in programs compiled before 0.38. + // These are tests on the kernel architecture, ie. they ignore userspace bit-ness. + bool ignore_on_arm32:1; + bool ignore_on_aarch64:1; + bool ignore_on_x86_32:1; + bool ignore_on_x86_64:1; + bool ignore_on_riscv64:1; + + char pad0[2]; // manually pad up to 4 byte alignment, may be used for extensions in the future + + // The following fields were added in version 0.1 + unsigned int bpfloader_min_ver; // if missing, defaults to 0, ie. v0.0 + unsigned int bpfloader_max_ver; // if missing, defaults to 0x10000, ie. v1.0 + + // The following fields were added in version 0.18, see description up above in bpf_map_def + char selinux_context[BPF_SELINUX_CONTEXT_CHAR_ARRAY_SIZE]; + char pin_subdir[BPF_PIN_SUBDIR_CHAR_ARRAY_SIZE]; +}; + +_Static_assert(sizeof(((struct bpf_prog_def *)0)->selinux_context) == 32, "must be 32 bytes"); +_Static_assert(sizeof(((struct bpf_prog_def *)0)->pin_subdir) == 32, "must be 32 bytes"); + +// This needs to be updated whenever the above structure definition is expanded. +_Static_assert(sizeof(struct bpf_prog_def) == 92, "sizeof struct bpf_prog_def != 92"); +_Static_assert(__alignof__(struct bpf_prog_def) == 4, "__alignof__ struct bpf_prog_def != 4"); +_Static_assert(_Alignof(struct bpf_prog_def) == 4, "_Alignof struct bpf_prog_def != 4"); diff --git a/staticlibs/native/bpf_syscall_wrappers/Android.bp b/staticlibs/native/bpf_syscall_wrappers/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..b3efc216687be90c0cc87071368c63be78452eb9 --- /dev/null +++ b/staticlibs/native/bpf_syscall_wrappers/Android.bp @@ -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. + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_library_headers { + name: "bpf_syscall_wrappers", + vendor_available: true, + recovery_available: true, + host_supported: true, + native_bridge_supported: true, + export_include_dirs: ["include"], + cflags: [ + "-Wall", + "-Werror", + ], + sdk_version: "30", + min_sdk_version: "30", + apex_available: [ + "//apex_available:platform", + "com.android.art.debug", + "com.android.mediaprovider", + "com.android.os.statsd", + "com.android.resolv", + "com.android.tethering", + ], +} diff --git a/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h b/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h new file mode 100644 index 0000000000000000000000000000000000000000..13f7cb396cbf7a052fb951710b0791b2ce3cdc90 --- /dev/null +++ b/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h @@ -0,0 +1,224 @@ +/* + * 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. + */ + +#pragma once + +#include <linux/bpf.h> +#include <linux/unistd.h> + +#ifdef BPF_FD_JUST_USE_INT + #define BPF_FD_TYPE int + #define BPF_FD_TO_U32(x) static_cast<__u32>(x) +#else + #include <android-base/unique_fd.h> + #define BPF_FD_TYPE base::unique_fd& + #define BPF_FD_TO_U32(x) static_cast<__u32>((x).get()) +#endif + +namespace android { +namespace bpf { + +inline uint64_t ptr_to_u64(const void * const x) { + return (uint64_t)(uintptr_t)x; +} + +/* Note: bpf_attr is a union which might have a much larger size then the anonymous struct portion + * of it that we are using. The kernel's bpf() system call will perform a strict check to ensure + * all unused portions are zero. It will fail with E2BIG if we don't fully zero bpf_attr. + */ + +inline int bpf(enum bpf_cmd cmd, const bpf_attr& attr) { + return syscall(__NR_bpf, cmd, &attr, sizeof(attr)); +} + +inline int createMap(bpf_map_type map_type, uint32_t key_size, uint32_t value_size, + uint32_t max_entries, uint32_t map_flags) { + return bpf(BPF_MAP_CREATE, { + .map_type = map_type, + .key_size = key_size, + .value_size = value_size, + .max_entries = max_entries, + .map_flags = map_flags, + }); +} + +// Note: +// 'map_type' must be one of BPF_MAP_TYPE_{ARRAY,HASH}_OF_MAPS +// 'value_size' must be sizeof(u32), ie. 4 +// 'inner_map_fd' is basically a template specifying {map_type, key_size, value_size, max_entries, map_flags} +// of the inner map type (and possibly only key_size/value_size actually matter?). +inline int createOuterMap(bpf_map_type map_type, uint32_t key_size, uint32_t value_size, + uint32_t max_entries, uint32_t map_flags, const BPF_FD_TYPE inner_map_fd) { + return bpf(BPF_MAP_CREATE, { + .map_type = map_type, + .key_size = key_size, + .value_size = value_size, + .max_entries = max_entries, + .map_flags = map_flags, + .inner_map_fd = BPF_FD_TO_U32(inner_map_fd), + }); +} + +inline int writeToMapEntry(const BPF_FD_TYPE map_fd, const void* key, const void* value, + uint64_t flags) { + return bpf(BPF_MAP_UPDATE_ELEM, { + .map_fd = BPF_FD_TO_U32(map_fd), + .key = ptr_to_u64(key), + .value = ptr_to_u64(value), + .flags = flags, + }); +} + +inline int findMapEntry(const BPF_FD_TYPE map_fd, const void* key, void* value) { + return bpf(BPF_MAP_LOOKUP_ELEM, { + .map_fd = BPF_FD_TO_U32(map_fd), + .key = ptr_to_u64(key), + .value = ptr_to_u64(value), + }); +} + +inline int deleteMapEntry(const BPF_FD_TYPE map_fd, const void* key) { + return bpf(BPF_MAP_DELETE_ELEM, { + .map_fd = BPF_FD_TO_U32(map_fd), + .key = ptr_to_u64(key), + }); +} + +inline int getNextMapKey(const BPF_FD_TYPE map_fd, const void* key, void* next_key) { + return bpf(BPF_MAP_GET_NEXT_KEY, { + .map_fd = BPF_FD_TO_U32(map_fd), + .key = ptr_to_u64(key), + .next_key = ptr_to_u64(next_key), + }); +} + +inline int getFirstMapKey(const BPF_FD_TYPE map_fd, void* firstKey) { + return getNextMapKey(map_fd, NULL, firstKey); +} + +inline int bpfFdPin(const BPF_FD_TYPE map_fd, const char* pathname) { + return bpf(BPF_OBJ_PIN, { + .pathname = ptr_to_u64(pathname), + .bpf_fd = BPF_FD_TO_U32(map_fd), + }); +} + +inline int bpfFdGet(const char* pathname, uint32_t flag) { + return bpf(BPF_OBJ_GET, { + .pathname = ptr_to_u64(pathname), + .file_flags = flag, + }); +} + +inline int mapRetrieve(const char* pathname, uint32_t flag) { + return bpfFdGet(pathname, flag); +} + +inline int mapRetrieveRW(const char* pathname) { + return mapRetrieve(pathname, 0); +} + +inline int mapRetrieveRO(const char* pathname) { + return mapRetrieve(pathname, BPF_F_RDONLY); +} + +inline int mapRetrieveWO(const char* pathname) { + return mapRetrieve(pathname, BPF_F_WRONLY); +} + +inline int retrieveProgram(const char* pathname) { + return bpfFdGet(pathname, BPF_F_RDONLY); +} + +inline int attachProgram(bpf_attach_type type, const BPF_FD_TYPE prog_fd, + const BPF_FD_TYPE cg_fd, uint32_t flags = 0) { + return bpf(BPF_PROG_ATTACH, { + .target_fd = BPF_FD_TO_U32(cg_fd), + .attach_bpf_fd = BPF_FD_TO_U32(prog_fd), + .attach_type = type, + .attach_flags = flags, + }); +} + +inline int detachProgram(bpf_attach_type type, const BPF_FD_TYPE cg_fd) { + return bpf(BPF_PROG_DETACH, { + .target_fd = BPF_FD_TO_U32(cg_fd), + .attach_type = type, + }); +} + +inline int detachSingleProgram(bpf_attach_type type, const BPF_FD_TYPE prog_fd, + const BPF_FD_TYPE cg_fd) { + return bpf(BPF_PROG_DETACH, { + .target_fd = BPF_FD_TO_U32(cg_fd), + .attach_bpf_fd = BPF_FD_TO_U32(prog_fd), + .attach_type = type, + }); +} + +// Available in 4.12 and later kernels. +inline int runProgram(const BPF_FD_TYPE prog_fd, const void* data, + const uint32_t data_size) { + return bpf(BPF_PROG_RUN, { + .test = { + .prog_fd = BPF_FD_TO_U32(prog_fd), + .data_size_in = data_size, + .data_in = ptr_to_u64(data), + }, + }); +} + +// BPF_OBJ_GET_INFO_BY_FD requires 4.14+ kernel +// +// Note: some fields are only defined in newer kernels (ie. the map_info struct grows +// over time), so we need to check that the field we're interested in is actually +// supported/returned by the running kernel. We do this by checking it is fully +// within the bounds of the struct size as reported by the kernel. +#define DEFINE_BPF_GET_FD(TYPE, NAME, FIELD) \ +inline int bpfGetFd ## NAME(const BPF_FD_TYPE fd) { \ + struct bpf_ ## TYPE ## _info info = {}; \ + union bpf_attr attr = { .info = { \ + .bpf_fd = BPF_FD_TO_U32(fd), \ + .info_len = sizeof(info), \ + .info = ptr_to_u64(&info), \ + }}; \ + int rv = bpf(BPF_OBJ_GET_INFO_BY_FD, attr); \ + if (rv) return rv; \ + if (attr.info.info_len < offsetof(bpf_ ## TYPE ## _info, FIELD) + sizeof(info.FIELD)) { \ + errno = EOPNOTSUPP; \ + return -1; \ + }; \ + return info.FIELD; \ +} + +// All 7 of these fields are already present in Linux v4.14 (even ACK 4.14-P) +// while BPF_OBJ_GET_INFO_BY_FD is not implemented at all in v4.9 (even ACK 4.9-Q) +DEFINE_BPF_GET_FD(map, MapType, type) // int bpfGetFdMapType(const BPF_FD_TYPE map_fd) +DEFINE_BPF_GET_FD(map, MapId, id) // int bpfGetFdMapId(const BPF_FD_TYPE map_fd) +DEFINE_BPF_GET_FD(map, KeySize, key_size) // int bpfGetFdKeySize(const BPF_FD_TYPE map_fd) +DEFINE_BPF_GET_FD(map, ValueSize, value_size) // int bpfGetFdValueSize(const BPF_FD_TYPE map_fd) +DEFINE_BPF_GET_FD(map, MaxEntries, max_entries) // int bpfGetFdMaxEntries(const BPF_FD_TYPE map_fd) +DEFINE_BPF_GET_FD(map, MapFlags, map_flags) // int bpfGetFdMapFlags(const BPF_FD_TYPE map_fd) +DEFINE_BPF_GET_FD(prog, ProgId, id) // int bpfGetFdProgId(const BPF_FD_TYPE prog_fd) + +#undef DEFINE_BPF_GET_FD + +} // namespace bpf +} // namespace android + +#undef BPF_FD_TO_U32 +#undef BPF_FD_TYPE +#undef BPF_FD_JUST_USE_INT diff --git a/staticlibs/native/bpfmapjni/Android.bp b/staticlibs/native/bpfmapjni/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..8babcce3f72d2085db7c99ff9456c6e70a158174 --- /dev/null +++ b/staticlibs/native/bpfmapjni/Android.bp @@ -0,0 +1,52 @@ +// 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. + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_library_static { + name: "libnet_utils_device_common_bpfjni", + srcs: [ + "com_android_net_module_util_BpfMap.cpp", + "com_android_net_module_util_TcUtils.cpp", + ], + header_libs: [ + "bpf_headers", + "jni_headers", + ], + shared_libs: [ + "liblog", + "libnativehelper_compat_libc++", + ], + whole_static_libs: [ + "libtcutils", + ], + cflags: [ + "-Wall", + "-Werror", + "-Wno-unused-parameter", + ], + sdk_version: "30", + min_sdk_version: "30", + apex_available: [ + "com.android.tethering", + "//apex_available:platform", + ], + visibility: [ + "//packages/modules/Connectivity:__subpackages__", + // TODO: remove after NetworkStatsService moves to the module. + "//frameworks/base/packages/ConnectivityT/service", + ], +} diff --git a/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp b/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f93d6e175797d465724dc95a8411d3bdb24f53b3 --- /dev/null +++ b/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp @@ -0,0 +1,144 @@ +/* + * 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. + */ + +#include <errno.h> +#include <jni.h> +#include <nativehelper/JNIHelp.h> +#include <nativehelper/ScopedLocalRef.h> + +#include "nativehelper/scoped_primitive_array.h" +#include "nativehelper/scoped_utf_chars.h" + +#define BPF_FD_JUST_USE_INT +#include "BpfSyscallWrappers.h" + +#include "bpf/KernelUtils.h" + +namespace android { + +static jint com_android_net_module_util_BpfMap_nativeBpfFdGet(JNIEnv *env, jclass clazz, + jstring path, jint mode, jint keySize, jint valueSize) { + ScopedUtfChars pathname(env, path); + + jint fd = bpf::bpfFdGet(pathname.c_str(), static_cast<unsigned>(mode)); + + if (fd < 0) { + jniThrowErrnoException(env, "nativeBpfFdGet", errno); + return -1; + } + + if (bpf::isAtLeastKernelVersion(4, 14, 0)) { + // These likely fail with -1 and set errno to EINVAL on <4.14 + if (bpf::bpfGetFdKeySize(fd) != keySize) { + close(fd); + jniThrowErrnoException(env, "nativeBpfFdGet KeySize", EBADFD); + return -1; + } + if (bpf::bpfGetFdValueSize(fd) != valueSize) { + close(fd); + jniThrowErrnoException(env, "nativeBpfFdGet ValueSize", EBADFD); + return -1; + } + } + + return fd; +} + +static void com_android_net_module_util_BpfMap_nativeWriteToMapEntry(JNIEnv *env, jobject self, + jint fd, jbyteArray key, jbyteArray value, jint flags) { + ScopedByteArrayRO keyRO(env, key); + ScopedByteArrayRO valueRO(env, value); + + int ret = bpf::writeToMapEntry(static_cast<int>(fd), keyRO.get(), valueRO.get(), + static_cast<int>(flags)); + + if (ret) jniThrowErrnoException(env, "nativeWriteToMapEntry", errno); +} + +static jboolean throwIfNotEnoent(JNIEnv *env, const char* functionName, int ret, int err) { + if (ret == 0) return true; + + if (err != ENOENT) jniThrowErrnoException(env, functionName, err); + return false; +} + +static jboolean com_android_net_module_util_BpfMap_nativeDeleteMapEntry(JNIEnv *env, jobject self, + jint fd, jbyteArray key) { + ScopedByteArrayRO keyRO(env, key); + + // On success, zero is returned. If the element is not found, -1 is returned and errno is set + // to ENOENT. + int ret = bpf::deleteMapEntry(static_cast<int>(fd), keyRO.get()); + + return throwIfNotEnoent(env, "nativeDeleteMapEntry", ret, errno); +} + +static jboolean com_android_net_module_util_BpfMap_nativeGetNextMapKey(JNIEnv *env, jobject self, + jint fd, jbyteArray key, jbyteArray nextKey) { + // If key is found, the operation returns zero and sets the next key pointer to the key of the + // next element. If key is not found, the operation returns zero and sets the next key pointer + // to the key of the first element. If key is the last element, -1 is returned and errno is + // set to ENOENT. Other possible errno values are ENOMEM, EFAULT, EPERM, and EINVAL. + ScopedByteArrayRW nextKeyRW(env, nextKey); + int ret; + if (key == nullptr) { + // Called by getFirstKey. Find the first key in the map. + ret = bpf::getNextMapKey(static_cast<int>(fd), nullptr, nextKeyRW.get()); + } else { + ScopedByteArrayRO keyRO(env, key); + ret = bpf::getNextMapKey(static_cast<int>(fd), keyRO.get(), nextKeyRW.get()); + } + + return throwIfNotEnoent(env, "nativeGetNextMapKey", ret, errno); +} + +static jboolean com_android_net_module_util_BpfMap_nativeFindMapEntry(JNIEnv *env, jobject self, + jint fd, jbyteArray key, jbyteArray value) { + ScopedByteArrayRO keyRO(env, key); + ScopedByteArrayRW valueRW(env, value); + + // If an element is found, the operation returns zero and stores the element's value into + // "value". If no element is found, the operation returns -1 and sets errno to ENOENT. + int ret = bpf::findMapEntry(static_cast<int>(fd), keyRO.get(), valueRW.get()); + + return throwIfNotEnoent(env, "nativeFindMapEntry", ret, errno); +} + +/* + * JNI registration. + */ +static const JNINativeMethod gMethods[] = { + /* name, signature, funcPtr */ + { "nativeBpfFdGet", "(Ljava/lang/String;III)I", + (void*) com_android_net_module_util_BpfMap_nativeBpfFdGet }, + { "nativeWriteToMapEntry", "(I[B[BI)V", + (void*) com_android_net_module_util_BpfMap_nativeWriteToMapEntry }, + { "nativeDeleteMapEntry", "(I[B)Z", + (void*) com_android_net_module_util_BpfMap_nativeDeleteMapEntry }, + { "nativeGetNextMapKey", "(I[B[B)Z", + (void*) com_android_net_module_util_BpfMap_nativeGetNextMapKey }, + { "nativeFindMapEntry", "(I[B[B)Z", + (void*) com_android_net_module_util_BpfMap_nativeFindMapEntry }, + +}; + +int register_com_android_net_module_util_BpfMap(JNIEnv* env, char const* class_name) { + return jniRegisterNativeMethods(env, + class_name, + gMethods, NELEM(gMethods)); +} + +}; // namespace android diff --git a/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp b/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cb06afbda3a541724666d87be5a22074cc7a3564 --- /dev/null +++ b/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp @@ -0,0 +1,121 @@ +/* + * 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. + */ + +#include <jni.h> +#include <nativehelper/JNIHelp.h> +#include <nativehelper/scoped_utf_chars.h> +#include <tcutils/tcutils.h> + +namespace android { + +static void throwIOException(JNIEnv *env, const char *msg, int error) { + jniThrowExceptionFmt(env, "java/io/IOException", "%s: %s", msg, + strerror(error)); +} + +static jboolean com_android_net_module_util_TcUtils_isEthernet(JNIEnv *env, + jobject clazz, + jstring iface) { + ScopedUtfChars interface(env, iface); + bool result = false; + int error = isEthernet(interface.c_str(), result); + if (error) { + throwIOException( + env, "com_android_net_module_util_TcUtils_isEthernet error: ", -error); + } + // result is not touched when error is returned; leave false. + return result; +} + +// tc filter add dev .. in/egress prio 1 protocol ipv6/ip bpf object-pinned +// /sys/fs/bpf/... direct-action +static void com_android_net_module_util_TcUtils_tcFilterAddDevBpf( + JNIEnv *env, jobject clazz, jint ifIndex, jboolean ingress, jshort prio, + jshort proto, jstring bpfProgPath) { + ScopedUtfChars pathname(env, bpfProgPath); + int error = tcAddBpfFilter(ifIndex, ingress, prio, proto, pathname.c_str()); + if (error) { + throwIOException( + env, + "com_android_net_module_util_TcUtils_tcFilterAddDevBpf error: ", -error); + } +} + +// tc filter add dev .. ingress prio .. protocol .. matchall \ +// action police rate .. burst .. conform-exceed pipe/continue \ +// action bpf object-pinned .. \ +// drop +static void com_android_net_module_util_TcUtils_tcFilterAddDevIngressPolice( + JNIEnv *env, jobject clazz, jint ifIndex, jshort prio, jshort proto, + jint rateInBytesPerSec, jstring bpfProgPath) { + ScopedUtfChars pathname(env, bpfProgPath); + int error = tcAddIngressPoliceFilter(ifIndex, prio, proto, rateInBytesPerSec, + pathname.c_str()); + if (error) { + throwIOException(env, + "com_android_net_module_util_TcUtils_" + "tcFilterAddDevIngressPolice error: ", + -error); + } +} + +// tc filter del dev .. in/egress prio .. protocol .. +static void com_android_net_module_util_TcUtils_tcFilterDelDev( + JNIEnv *env, jobject clazz, jint ifIndex, jboolean ingress, jshort prio, + jshort proto) { + int error = tcDeleteFilter(ifIndex, ingress, prio, proto); + if (error) { + throwIOException( + env, + "com_android_net_module_util_TcUtils_tcFilterDelDev error: ", -error); + } +} + +// tc qdisc add dev .. clsact +static void com_android_net_module_util_TcUtils_tcQdiscAddDevClsact(JNIEnv *env, + jobject clazz, + jint ifIndex) { + int error = tcAddQdiscClsact(ifIndex); + if (error) { + throwIOException( + env, + "com_android_net_module_util_TcUtils_tcQdiscAddDevClsact error: ", -error); + } +} + +/* + * JNI registration. + */ +static const JNINativeMethod gMethods[] = { + /* name, signature, funcPtr */ + {"isEthernet", "(Ljava/lang/String;)Z", + (void *)com_android_net_module_util_TcUtils_isEthernet}, + {"tcFilterAddDevBpf", "(IZSSLjava/lang/String;)V", + (void *)com_android_net_module_util_TcUtils_tcFilterAddDevBpf}, + {"tcFilterAddDevIngressPolice", "(ISSILjava/lang/String;)V", + (void *)com_android_net_module_util_TcUtils_tcFilterAddDevIngressPolice}, + {"tcFilterDelDev", "(IZSS)V", + (void *)com_android_net_module_util_TcUtils_tcFilterDelDev}, + {"tcQdiscAddDevClsact", "(I)V", + (void *)com_android_net_module_util_TcUtils_tcQdiscAddDevClsact}, +}; + +int register_com_android_net_module_util_TcUtils(JNIEnv *env, + char const *class_name) { + return jniRegisterNativeMethods(env, class_name, gMethods, NELEM(gMethods)); +} + +}; // namespace android diff --git a/staticlibs/native/bpfutiljni/Android.bp b/staticlibs/native/bpfutiljni/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..39a2795086bc7084e99bfdd8534e297d40de2990 --- /dev/null +++ b/staticlibs/native/bpfutiljni/Android.bp @@ -0,0 +1,44 @@ +// Copyright (C) 2022 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_library_static { + name: "libnet_utils_device_common_bpfutils", + srcs: ["com_android_net_module_util_BpfUtils.cpp"], + header_libs: [ + "bpf_syscall_wrappers", + "jni_headers", + ], + shared_libs: [ + "libbase", + "liblog", + "libnativehelper_compat_libc++", + ], + cflags: [ + "-Wall", + "-Werror", + "-Wno-unused-parameter", + ], + min_sdk_version: "30", + apex_available: [ + "com.android.tethering", + "//apex_available:platform", + ], + visibility: [ + "//packages/modules/Connectivity/service", + ], +} diff --git a/staticlibs/native/bpfutiljni/com_android_net_module_util_BpfUtils.cpp b/staticlibs/native/bpfutiljni/com_android_net_module_util_BpfUtils.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0f2ebbd49a546675ec99951351a2dc1099b10450 --- /dev/null +++ b/staticlibs/native/bpfutiljni/com_android_net_module_util_BpfUtils.cpp @@ -0,0 +1,132 @@ +/* + * 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. + */ + +#include <android-base/unique_fd.h> +#include <errno.h> +#include <fcntl.h> +#include <jni.h> +#include <nativehelper/JNIHelp.h> +#include <nativehelper/ScopedLocalRef.h> +#include <nativehelper/scoped_utf_chars.h> +#include <string.h> +#include <sys/socket.h> + +#include "BpfSyscallWrappers.h" + +namespace android { + +using base::unique_fd; + +// If attach fails throw error and return false. +static jboolean com_android_net_module_util_BpfUtil_attachProgramToCgroup(JNIEnv *env, + jobject clazz, jint type, jstring bpfProgPath, jstring cgroupPath, jint flags) { + + ScopedUtfChars dirPath(env, cgroupPath); + unique_fd cg_fd(open(dirPath.c_str(), O_DIRECTORY | O_RDONLY | O_CLOEXEC)); + if (cg_fd == -1) { + jniThrowExceptionFmt(env, "java/io/IOException", + "Failed to open the cgroup directory %s: %s", + dirPath.c_str(), strerror(errno)); + return false; + } + + ScopedUtfChars bpfProg(env, bpfProgPath); + unique_fd bpf_fd(bpf::retrieveProgram(bpfProg.c_str())); + if (bpf_fd == -1) { + jniThrowExceptionFmt(env, "java/io/IOException", + "Failed to retrieve bpf program from %s: %s", + bpfProg.c_str(), strerror(errno)); + return false; + } + if (bpf::attachProgram((bpf_attach_type) type, bpf_fd, cg_fd, flags)) { + jniThrowExceptionFmt(env, "java/io/IOException", + "Failed to attach bpf program %s to %s: %s", + bpfProg.c_str(), dirPath.c_str(), strerror(errno)); + return false; + } + return true; +} + +// If detach fails throw error and return false. +static jboolean com_android_net_module_util_BpfUtil_detachProgramFromCgroup(JNIEnv *env, + jobject clazz, jint type, jstring cgroupPath) { + + ScopedUtfChars dirPath(env, cgroupPath); + unique_fd cg_fd(open(dirPath.c_str(), O_DIRECTORY | O_RDONLY | O_CLOEXEC)); + if (cg_fd == -1) { + jniThrowExceptionFmt(env, "java/io/IOException", + "Failed to open the cgroup directory %s: %s", + dirPath.c_str(), strerror(errno)); + return false; + } + + if (bpf::detachProgram((bpf_attach_type) type, cg_fd)) { + jniThrowExceptionFmt(env, "Failed to detach bpf program from %s: %s", + dirPath.c_str(), strerror(errno)); + return false; + } + return true; +} + +// If detach single program fails throw error and return false. +static jboolean com_android_net_module_util_BpfUtil_detachSingleProgramFromCgroup(JNIEnv *env, + jobject clazz, jint type, jstring bpfProgPath, jstring cgroupPath) { + + ScopedUtfChars dirPath(env, cgroupPath); + unique_fd cg_fd(open(dirPath.c_str(), O_DIRECTORY | O_RDONLY | O_CLOEXEC)); + if (cg_fd == -1) { + jniThrowExceptionFmt(env, "java/io/IOException", + "Failed to open the cgroup directory %s: %s", + dirPath.c_str(), strerror(errno)); + return false; + } + + ScopedUtfChars bpfProg(env, bpfProgPath); + unique_fd bpf_fd(bpf::retrieveProgram(bpfProg.c_str())); + if (bpf_fd == -1) { + jniThrowExceptionFmt(env, "java/io/IOException", + "Failed to retrieve bpf program from %s: %s", + bpfProg.c_str(), strerror(errno)); + return false; + } + if (bpf::detachSingleProgram((bpf_attach_type) type, bpf_fd, cg_fd)) { + jniThrowExceptionFmt(env, "Failed to detach bpf program %s from %s: %s", + bpfProg.c_str(), dirPath.c_str(), strerror(errno)); + return false; + } + return true; +} + +/* + * JNI registration. + */ +static const JNINativeMethod gMethods[] = { + /* name, signature, funcPtr */ + { "native_attachProgramToCgroup", "(ILjava/lang/String;Ljava/lang/String;I)Z", + (void*) com_android_net_module_util_BpfUtil_attachProgramToCgroup }, + { "native_detachProgramFromCgroup", "(ILjava/lang/String;)Z", + (void*) com_android_net_module_util_BpfUtil_detachProgramFromCgroup }, + { "native_detachSingleProgramFromCgroup", "(ILjava/lang/String;Ljava/lang/String;)Z", + (void*) com_android_net_module_util_BpfUtil_detachSingleProgramFromCgroup }, +}; + +int register_com_android_net_module_util_BpfUtils(JNIEnv* env, char const* class_name) { + return jniRegisterNativeMethods(env, + class_name, + gMethods, NELEM(gMethods)); +} + +}; // namespace android diff --git a/staticlibs/native/ip_checksum/Android.bp b/staticlibs/native/ip_checksum/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..9878d73771e07c9ad94ace27a772bd005d2ae372 --- /dev/null +++ b/staticlibs/native/ip_checksum/Android.bp @@ -0,0 +1,46 @@ +// 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. + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_library_static { + name: "libip_checksum", + + srcs: [ + "checksum.c", + ], + + cflags: [ + "-Wall", + "-Werror", + ], + + export_include_dirs: ["."], + + // Needed because libnetutils depends on libip_checksum, and libnetutils has + // vendor_available = true. Making this library vendor_available does not create any maintenance + // burden or version skew issues because this library is only static, not dynamic, and thus is + // not installed on the device. + // + // TODO: delete libnetutils from the VNDK in T, and remove this. + vendor_available: true, + + min_sdk_version: "30", + apex_available: [ + "com.android.tethering", + "//apex_available:platform", + ], +} diff --git a/staticlibs/native/ip_checksum/checksum.c b/staticlibs/native/ip_checksum/checksum.c new file mode 100644 index 0000000000000000000000000000000000000000..5641fad4e6dc2ee3d39b5b14a3fc54d5d11b44c0 --- /dev/null +++ b/staticlibs/native/ip_checksum/checksum.c @@ -0,0 +1,131 @@ +/* + * Copyright 2011 Daniel Drown + * + * 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. + * + * checksum.c - ipv4/ipv6 checksum calculation + */ +#include <netinet/icmp6.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <netinet/ip6.h> +#include <netinet/ip_icmp.h> +#include <netinet/tcp.h> +#include <netinet/udp.h> + +#include "checksum.h" + +/* function: ip_checksum_add + * adds data to a checksum. only known to work on little-endian hosts + * current - the current checksum (or 0 to start a new checksum) + * data - the data to add to the checksum + * len - length of data + */ +uint32_t ip_checksum_add(uint32_t current, const void* data, int len) { + const uint16_t* data_16 = data; + + while (len >= 2) { + current += *data_16; + data_16++; + len -= 2; + } + if (len) current += *(uint8_t*)data_16; // assumes little endian! + + return current; +} + +/* function: ip_checksum_fold + * folds a 32-bit partial checksum into 16 bits + * temp_sum - sum from ip_checksum_add + * returns: the folded checksum in network byte order + */ +uint16_t ip_checksum_fold(uint32_t temp_sum) { + temp_sum = (temp_sum >> 16) + (temp_sum & 0xFFFF); + temp_sum = (temp_sum >> 16) + (temp_sum & 0xFFFF); + return temp_sum; +} + +/* function: ip_checksum_finish + * folds and closes the checksum + * temp_sum - sum from ip_checksum_add + * returns: a header checksum value in network byte order + */ +uint16_t ip_checksum_finish(uint32_t temp_sum) { + return ~ip_checksum_fold(temp_sum); +} + +/* function: ip_checksum + * combined ip_checksum_add and ip_checksum_finish + * data - data to checksum + * len - length of data + */ +uint16_t ip_checksum(const void* data, int len) { + return ip_checksum_finish(ip_checksum_add(0xFFFF, data, len)); +} + +/* function: ipv6_pseudo_header_checksum + * calculate the pseudo header checksum for use in tcp/udp/icmp headers + * ip6 - the ipv6 header + * len - the transport length (transport header + payload) + * protocol - the transport layer protocol, can be different from ip6->ip6_nxt for fragments + */ +uint32_t ipv6_pseudo_header_checksum(const struct ip6_hdr* ip6, uint32_t len, uint8_t protocol) { + uint32_t checksum_len = htonl(len); + uint32_t checksum_next = htonl(protocol); + uint32_t current = 0; + + current = ip_checksum_add(current, &(ip6->ip6_src), sizeof(struct in6_addr)); + current = ip_checksum_add(current, &(ip6->ip6_dst), sizeof(struct in6_addr)); + current = ip_checksum_add(current, &checksum_len, sizeof(checksum_len)); + current = ip_checksum_add(current, &checksum_next, sizeof(checksum_next)); + + return current; +} + +/* function: ipv4_pseudo_header_checksum + * calculate the pseudo header checksum for use in tcp/udp headers + * ip - the ipv4 header + * len - the transport length (transport header + payload) + */ +uint32_t ipv4_pseudo_header_checksum(const struct iphdr* ip, uint16_t len) { + uint16_t temp_protocol = htons(ip->protocol); + uint16_t temp_length = htons(len); + uint32_t current = 0; + + current = ip_checksum_add(current, &(ip->saddr), sizeof(uint32_t)); + current = ip_checksum_add(current, &(ip->daddr), sizeof(uint32_t)); + current = ip_checksum_add(current, &temp_protocol, sizeof(uint16_t)); + current = ip_checksum_add(current, &temp_length, sizeof(uint16_t)); + + return current; +} + +/* function: ip_checksum_adjust + * calculates a new checksum given a previous checksum and the old and new pseudo-header checksums + * checksum - the header checksum in the original packet in network byte order + * old_hdr_sum - the pseudo-header checksum of the original packet + * new_hdr_sum - the pseudo-header checksum of the translated packet + * returns: the new header checksum in network byte order + */ +uint16_t ip_checksum_adjust(uint16_t checksum, uint32_t old_hdr_sum, uint32_t new_hdr_sum) { + // Algorithm suggested in RFC 1624. + // http://tools.ietf.org/html/rfc1624#section-3 + checksum = ~checksum; + uint16_t folded_sum = ip_checksum_fold(new_hdr_sum + checksum); + uint16_t folded_old = ip_checksum_fold(old_hdr_sum); + if (folded_sum > folded_old) { + return ~(folded_sum - folded_old); + } else { + return ~(folded_sum - folded_old - 1); // end-around borrow + } +} diff --git a/staticlibs/native/ip_checksum/checksum.h b/staticlibs/native/ip_checksum/checksum.h new file mode 100644 index 0000000000000000000000000000000000000000..87393c9613b0fd948383ea3bf70c9eba5180b8d7 --- /dev/null +++ b/staticlibs/native/ip_checksum/checksum.h @@ -0,0 +1,29 @@ +/* + * Copyright 2011 Daniel Drown + * + * 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. + */ +#pragma once + +#include <netinet/ip.h> +#include <netinet/ip6.h> +#include <stdint.h> + +uint32_t ip_checksum_add(uint32_t current, const void* data, int len); +uint16_t ip_checksum_finish(uint32_t temp_sum); +uint16_t ip_checksum(const void* data, int len); + +uint32_t ipv6_pseudo_header_checksum(const struct ip6_hdr* ip6, uint32_t len, uint8_t protocol); +uint32_t ipv4_pseudo_header_checksum(const struct iphdr* ip, uint16_t len); + +uint16_t ip_checksum_adjust(uint16_t checksum, uint32_t old_hdr_sum, uint32_t new_hdr_sum); diff --git a/staticlibs/native/netjniutils/Android.bp b/staticlibs/native/netjniutils/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..22fd1fa400656591b67aa61e86348a29cd5583a7 --- /dev/null +++ b/staticlibs/native/netjniutils/Android.bp @@ -0,0 +1,41 @@ +// 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. + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_library_static { + name: "libnetjniutils", + srcs: ["netjniutils.cpp"], + static_libs: [ + "libmodules-utils-build", + ], + header_libs: ["jni_headers"], + shared_libs: ["liblog"], + export_header_lib_headers: ["jni_headers"], + export_include_dirs: ["include"], + cflags: [ + "-Wall", + "-Werror", + "-Wno-unused-parameter", + ], + sdk_version: "29", + min_sdk_version: "29", + apex_available: [ + "//apex_available:anyapex", + "//apex_available:platform", + ], + visibility: ["//visibility:public"], +} diff --git a/staticlibs/native/netjniutils/include/netjniutils/netjniutils.h b/staticlibs/native/netjniutils/include/netjniutils/netjniutils.h new file mode 100644 index 0000000000000000000000000000000000000000..1b2b035daaef37a47db279ac5d16e761e7aedba5 --- /dev/null +++ b/staticlibs/native/netjniutils/include/netjniutils/netjniutils.h @@ -0,0 +1,25 @@ +// 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. + +#pragma once + +#include <jni.h> + +namespace android { +namespace netjniutils { + +int GetNativeFileDescriptor(JNIEnv* env, jobject javaFd); + +} // namespace netjniutils +} // namepsace android diff --git a/staticlibs/native/netjniutils/netjniutils.cpp b/staticlibs/native/netjniutils/netjniutils.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8b7f903f1aedac60944b3afff80d11f08e503605 --- /dev/null +++ b/staticlibs/native/netjniutils/netjniutils.cpp @@ -0,0 +1,85 @@ +// 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. + +#define LOG_TAG "netjniutils" + +#include "netjniutils/netjniutils.h" +#include <android-modules-utils/sdk_level.h> + +#include <dlfcn.h> +#include <stdbool.h> +#include <string.h> +#include <sys/system_properties.h> + +#include <android/api-level.h> +#include <android/log.h> + +namespace android { +namespace netjniutils { + +namespace { + + +int GetNativeFileDescriptorWithoutNdk(JNIEnv* env, jobject javaFd) { + // Prior to Android S, we need to find the descriptor field in the FileDescriptor class. The + // symbol name has been stable in libcore, but is a private implementation detail. + // Older libnativehelper_compat_c++ versions had a jniGetFdFromFileDescriptor method, but this + // was removed in S to replace it with the NDK API in libnativehelper. + // The code is copied here instead. This code can be removed once R is not supported anymore. + static const jfieldID descriptorFieldID = [env]() -> jfieldID { + jclass cls = env->FindClass("java/io/FileDescriptor"); + jfieldID fieldID = env->GetFieldID(cls, "descriptor", "I"); + env->DeleteLocalRef(cls); + if (fieldID == nullptr) { + __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, "Failed to get descriptor field."); + } + return fieldID; + }(); + + return javaFd != nullptr ? env->GetIntField(javaFd, descriptorFieldID) : -1; +} + +int GetNativeFileDescriptorWithNdk(JNIEnv* env, jobject javaFd) { + // Since Android S, there is an NDK API to get a file descriptor present in libnativehelper.so. + // libnativehelper is loaded into all processes by the zygote since the zygote uses it + // to load the Android Runtime and is also a public library (because of the NDK API). + typedef int (*ndkGetFd_t)(JNIEnv*, jobject); + static const ndkGetFd_t ndkGetFd = []() -> ndkGetFd_t { + void* handle = dlopen("libnativehelper.so", RTLD_NOLOAD | RTLD_NODELETE); + auto ndkGetFd = reinterpret_cast<ndkGetFd_t>(dlsym(handle, "AFileDescriptor_getFd")); + if (ndkGetFd == nullptr) { + __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, + "Failed to dlsym(AFileDescriptor_getFd): %s", dlerror()); + dlclose(handle); + } + return ndkGetFd; + }(); + + return javaFd != nullptr ? ndkGetFd(env, javaFd) : -1; +} + +} // namespace + +int GetNativeFileDescriptor(JNIEnv* env, jobject javaFd) { + static const bool preferNdkFileDescriptorApi = []() -> bool + { return android::modules::sdklevel::IsAtLeastS(); }(); + if (preferNdkFileDescriptorApi) { + return GetNativeFileDescriptorWithNdk(env, javaFd); + } else { + return GetNativeFileDescriptorWithoutNdk(env, javaFd); + } +} + +} // namespace netjniutils +} // namespace android diff --git a/staticlibs/native/nettestutils/Android.bp b/staticlibs/native/nettestutils/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..df3bb4288c3687e28d142b5c0332d0ad7c201508 --- /dev/null +++ b/staticlibs/native/nettestutils/Android.bp @@ -0,0 +1,38 @@ +// Copyright (C) 2022 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_library_static { + name: "libnettestutils", + export_include_dirs: ["include"], + srcs: ["DumpService.cpp"], + + // Don't depend on libbinder, because some users of this library may not want to link to it. + // CtsNativeNetPlatformTestCases is one such user. See r.android.com/2599405 . + header_libs: [ + "libbinder_headers", + ], + + shared_libs: [ + "libutils", + "libbinder_ndk", + ], + cflags: [ + "-Werror", + "-Wall", + ], +} diff --git a/staticlibs/native/nettestutils/DumpService.cpp b/staticlibs/native/nettestutils/DumpService.cpp new file mode 100644 index 0000000000000000000000000000000000000000..40c3b9a25eba3190d5553fea136b4bb1b1fe818f --- /dev/null +++ b/staticlibs/native/nettestutils/DumpService.cpp @@ -0,0 +1,92 @@ +/* + * 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. + */ + +#include "nettestutils/DumpService.h" + +#include <android-base/file.h> +#include <android/binder_status.h> + +#include <sstream> +#include <thread> + +// Version for code using libbinder (e.g., AIDL interfaces with the C++ backend). +android::status_t dumpService(const android::sp<android::IBinder>& binder, + const std::vector<std::string>& args, + std::vector<std::string>& outputLines) { + if (!outputLines.empty()) return -EUCLEAN; + + android::base::unique_fd localFd, remoteFd; + if (!Pipe(&localFd, &remoteFd)) return -errno; + + android::Vector<android::String16> str16Args; + for (const auto& arg : args) { + str16Args.push(android::String16(arg.c_str())); + } + android::status_t ret; + // dump() blocks until another thread has consumed all its output. + std::thread dumpThread = + std::thread([&ret, binder, remoteFd{std::move(remoteFd)}, str16Args]() { + ret = binder->dump(remoteFd, str16Args); + }); + + std::string dumpContent; + if (!android::base::ReadFdToString(localFd.get(), &dumpContent)) { + return -errno; + } + dumpThread.join(); + if (ret != android::OK) return ret; + + std::stringstream dumpStream(std::move(dumpContent)); + std::string line; + while (std::getline(dumpStream, line)) { + outputLines.push_back(line); + } + + return android::OK; +} + +// Version for code using libbinder_ndk (e.g., AIDL interfaces with the NDK backend).. +android::status_t dumpService(const ndk::SpAIBinder& binder, + const char** args, + uint32_t num_args, + std::vector<std::string>& outputLines) { + if (!outputLines.empty()) return -EUCLEAN; + + android::base::unique_fd localFd, remoteFd; + if (!Pipe(&localFd, &remoteFd)) return -errno; + + android::status_t ret; + // dump() blocks until another thread has consumed all its output. + std::thread dumpThread = + std::thread([&ret, binder, remoteFd{std::move(remoteFd)}, args, num_args]() { + ret = AIBinder_dump(binder.get(), remoteFd, args, num_args); + }); + + std::string dumpContent; + if (!android::base::ReadFdToString(localFd.get(), &dumpContent)) { + return -errno; + } + dumpThread.join(); + if (ret != android::OK) return ret; + + std::stringstream dumpStream(dumpContent); + std::string line; + while (std::getline(dumpStream, line)) { + outputLines.push_back(std::move(line)); + } + + return android::OK; +} diff --git a/staticlibs/native/nettestutils/include/nettestutils/DumpService.h b/staticlibs/native/nettestutils/include/nettestutils/DumpService.h new file mode 100644 index 0000000000000000000000000000000000000000..323d75243dd8f4335a0707febd7570d285a15904 --- /dev/null +++ b/staticlibs/native/nettestutils/include/nettestutils/DumpService.h @@ -0,0 +1,29 @@ +/* + * 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. + */ + +#include <binder/Binder.h> +#include <android/binder_auto_utils.h> + +#include <vector> + +android::status_t dumpService(const android::sp<android::IBinder>& binder, + const std::vector<std::string>& args, + std::vector<std::string>& outputLines); + +android::status_t dumpService(const ndk::SpAIBinder& binder, + const char** args, + uint32_t num_args, + std::vector<std::string>& outputLines); diff --git a/staticlibs/native/tcutils/Android.bp b/staticlibs/native/tcutils/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..9a38745c69612cedbf96c9f827bff1c6ffcd71c1 --- /dev/null +++ b/staticlibs/native/tcutils/Android.bp @@ -0,0 +1,67 @@ +// Copyright (C) 2022 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_library_static { + name: "libtcutils", + srcs: ["tcutils.cpp"], + export_include_dirs: ["include"], + header_libs: ["bpf_headers"], + shared_libs: [ + "liblog", + ], + stl: "libc++_static", + cflags: [ + "-Wall", + "-Werror", + "-Wno-unused-parameter", + ], + sdk_version: "30", + min_sdk_version: "30", + apex_available: [ + "com.android.tethering", + "//apex_available:platform", + ], + visibility: [ + "//packages/modules/Connectivity:__subpackages__", + "//system/netd:__subpackages__", + ], +} + +cc_test { + name: "libtcutils_test", + srcs: [ + "tests/tcutils_test.cpp", + ], + cflags: [ + "-Wall", + "-Werror", + "-Wno-error=unused-variable", + ], + header_libs: ["bpf_headers"], + static_libs: [ + "libgmock", + "libtcutils", + ], + shared_libs: [ + "libbase", + "liblog", + ], + min_sdk_version: "30", + require_root: true, + test_suites: ["general-tests"], +} diff --git a/staticlibs/native/tcutils/include/tcutils/tcutils.h b/staticlibs/native/tcutils/include/tcutils/tcutils.h new file mode 100644 index 0000000000000000000000000000000000000000..a8ec2e87c484e2f32dbd9204a060d20c8179de53 --- /dev/null +++ b/staticlibs/native/tcutils/include/tcutils/tcutils.h @@ -0,0 +1,47 @@ +/* + * 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. + */ + +#pragma once + +#include <cstdint> +#include <linux/rtnetlink.h> + +namespace android { + +int isEthernet(const char *iface, bool &isEthernet); + +int doTcQdiscClsact(int ifIndex, uint16_t nlMsgType, uint16_t nlMsgFlags); + +static inline int tcAddQdiscClsact(int ifIndex) { + return doTcQdiscClsact(ifIndex, RTM_NEWQDISC, NLM_F_EXCL | NLM_F_CREATE); +} + +static inline int tcReplaceQdiscClsact(int ifIndex) { + return doTcQdiscClsact(ifIndex, RTM_NEWQDISC, NLM_F_CREATE | NLM_F_REPLACE); +} + +static inline int tcDeleteQdiscClsact(int ifIndex) { + return doTcQdiscClsact(ifIndex, RTM_DELQDISC, 0); +} + +int tcAddBpfFilter(int ifIndex, bool ingress, uint16_t prio, uint16_t proto, + const char *bpfProgPath); +int tcAddIngressPoliceFilter(int ifIndex, uint16_t prio, uint16_t proto, + unsigned rateInBytesPerSec, + const char *bpfProgPath); +int tcDeleteFilter(int ifIndex, bool ingress, uint16_t prio, uint16_t proto); + +} // namespace android diff --git a/staticlibs/native/tcutils/logging.h b/staticlibs/native/tcutils/logging.h new file mode 100644 index 0000000000000000000000000000000000000000..7ed8f66a5c70e4b6a12fe9ea783f7e87da57f1d3 --- /dev/null +++ b/staticlibs/native/tcutils/logging.h @@ -0,0 +1,56 @@ +/* + * 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. + */ + +#pragma once + +#include <android/log.h> +#include <stdarg.h> + +#ifndef LOG_TAG +#define LOG_TAG "TcUtils_Undef" +#endif + +namespace android { + +static inline void ALOGE(const char *fmt...) { + va_list args; + va_start(args, fmt); + __android_log_vprint(ANDROID_LOG_ERROR, LOG_TAG, fmt, args); + va_end(args); +} + +static inline void ALOGW(const char *fmt...) { + va_list args; + va_start(args, fmt); + __android_log_vprint(ANDROID_LOG_WARN, LOG_TAG, fmt, args); + va_end(args); +} + +static inline void ALOGI(const char *fmt...) { + va_list args; + va_start(args, fmt); + __android_log_vprint(ANDROID_LOG_INFO, LOG_TAG, fmt, args); + va_end(args); +} + +static inline void ALOGD(const char *fmt...) { + va_list args; + va_start(args, fmt); + __android_log_vprint(ANDROID_LOG_DEBUG, LOG_TAG, fmt, args); + va_end(args); +} + +} diff --git a/staticlibs/native/tcutils/scopeguard.h b/staticlibs/native/tcutils/scopeguard.h new file mode 100644 index 0000000000000000000000000000000000000000..76bbb93a257ee15e628007f8509b49b16c909d5f --- /dev/null +++ b/staticlibs/native/tcutils/scopeguard.h @@ -0,0 +1,74 @@ +/* + * 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. + */ + +// ----------------------------------------------------------------------------- +// TODO: figure out a way to use libbase_ndk. This is currently not working +// because of missing apex availability. For now, we can use a copy of +// ScopeGuard which is very lean compared to unique_fd. This code has been +// copied verbatim from: +// https://cs.android.com/android/platform/superproject/+/master:system/libbase/include/android-base/scopeguard.h + +#pragma once + +#include <utility> // for std::move, std::forward + +namespace android { +namespace base { + +// ScopeGuard ensures that the specified functor is executed no matter how the +// current scope exits. +template <typename F> class ScopeGuard { +public: + ScopeGuard(F &&f) : f_(std::forward<F>(f)), active_(true) {} + + ScopeGuard(ScopeGuard &&that) noexcept + : f_(std::move(that.f_)), active_(that.active_) { + that.active_ = false; + } + + template <typename Functor> + ScopeGuard(ScopeGuard<Functor> &&that) + : f_(std::move(that.f_)), active_(that.active_) { + that.active_ = false; + } + + ~ScopeGuard() { + if (active_) + f_(); + } + + ScopeGuard() = delete; + ScopeGuard(const ScopeGuard &) = delete; + void operator=(const ScopeGuard &) = delete; + void operator=(ScopeGuard &&that) = delete; + + void Disable() { active_ = false; } + + bool active() const { return active_; } + +private: + template <typename Functor> friend class ScopeGuard; + + F f_; + bool active_; +}; + +template <typename F> ScopeGuard<F> make_scope_guard(F &&f) { + return ScopeGuard<F>(std::forward<F>(f)); +} + +} // namespace base +} // namespace android diff --git a/staticlibs/native/tcutils/tcutils.cpp b/staticlibs/native/tcutils/tcutils.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c82390fae5d1d6f904092198936deda4bf4c5412 --- /dev/null +++ b/staticlibs/native/tcutils/tcutils.cpp @@ -0,0 +1,742 @@ +/* + * 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. + */ + +#define LOG_TAG "TcUtils" + +#include "tcutils/tcutils.h" + +#include "logging.h" +#include "bpf/KernelUtils.h" +#include "scopeguard.h" + +#include <arpa/inet.h> +#include <cerrno> +#include <cstring> +#include <libgen.h> +#include <linux/if_arp.h> +#include <linux/if_ether.h> +#include <linux/netlink.h> +#include <linux/pkt_cls.h> +#include <linux/pkt_sched.h> +#include <linux/rtnetlink.h> +#include <linux/tc_act/tc_bpf.h> +#include <net/if.h> +#include <stdio.h> +#include <sys/socket.h> +#include <unistd.h> +#include <utility> + +#define BPF_FD_JUST_USE_INT +#include <BpfSyscallWrappers.h> +#undef BPF_FD_JUST_USE_INT + +// The maximum length of TCA_BPF_NAME. Sync from net/sched/cls_bpf.c. +#define CLS_BPF_NAME_LEN 256 + +// Classifier name. See cls_bpf_ops in net/sched/cls_bpf.c. +#define CLS_BPF_KIND_NAME "bpf" + +namespace android { +namespace { + +/** + * IngressPoliceFilterBuilder builds a nlmsg request equivalent to the following + * tc command: + * + * tc filter add dev .. ingress prio .. protocol .. matchall \ + * action police rate .. burst .. conform-exceed pipe/continue \ + * action bpf object-pinned .. \ + * drop + */ +class IngressPoliceFilterBuilder final { + // default mtu is 2047, so the cell logarithm factor (cell_log) is 3. + // 0x7FF >> 0x3FF x 2^1 >> 0x1FF x 2^2 >> 0xFF x 2^3 + static constexpr int RTAB_CELL_LOGARITHM = 3; + static constexpr size_t RTAB_SIZE = 256; + static constexpr unsigned TIME_UNITS_PER_SEC = 1000000; + + struct Request { + nlmsghdr n; + tcmsg t; + struct { + nlattr attr; + char str[NLMSG_ALIGN(sizeof("matchall"))]; + } kind; + struct { + nlattr attr; + struct { + nlattr attr; + struct { + nlattr attr; + struct { + nlattr attr; + char str[NLMSG_ALIGN(sizeof("police"))]; + } kind; + struct { + nlattr attr; + struct { + nlattr attr; + struct tc_police obj; + } police; + struct { + nlattr attr; + uint32_t u32[RTAB_SIZE]; + } rtab; + struct { + nlattr attr; + int32_t s32; + } notexceedact; + } opt; + } act1; + struct { + nlattr attr; + struct { + nlattr attr; + char str[NLMSG_ALIGN(sizeof("bpf"))]; + } kind; + struct { + nlattr attr; + struct { + nlattr attr; + uint32_t u32; + } fd; + struct { + nlattr attr; + char str[NLMSG_ALIGN(CLS_BPF_NAME_LEN)]; + } name; + struct { + nlattr attr; + struct tc_act_bpf obj; + } parms; + } opt; + } act2; + } acts; + } opt; + }; + + // class members + const unsigned mBurstInBytes; + const char *mBpfProgPath; + int mBpfFd; + Request mRequest; + + static double getTickInUsec() { + FILE *fp = fopen("/proc/net/psched", "re"); + if (!fp) { + ALOGE("fopen(\"/proc/net/psched\"): %s", strerror(errno)); + return 0.0; + } + auto scopeGuard = base::make_scope_guard([fp] { fclose(fp); }); + + uint32_t t2us; + uint32_t us2t; + uint32_t clockRes; + const bool isError = + fscanf(fp, "%08x%08x%08x", &t2us, &us2t, &clockRes) != 3; + + if (isError) { + ALOGE("fscanf(/proc/net/psched, \"%%08x%%08x%%08x\"): %s", + strerror(errno)); + return 0.0; + } + + const double clockFactor = + static_cast<double>(clockRes) / TIME_UNITS_PER_SEC; + return static_cast<double>(t2us) / static_cast<double>(us2t) * clockFactor; + } + + static inline const double kTickInUsec = getTickInUsec(); + +public: + // clang-format off + IngressPoliceFilterBuilder(int ifIndex, uint16_t prio, uint16_t proto, unsigned rateInBytesPerSec, + unsigned burstInBytes, const char* bpfProgPath) + : mBurstInBytes(burstInBytes), + mBpfProgPath(bpfProgPath), + mBpfFd(-1), + mRequest{ + .n = { + .nlmsg_len = sizeof(mRequest), + .nlmsg_type = RTM_NEWTFILTER, + .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE, + }, + .t = { + .tcm_family = AF_UNSPEC, + .tcm_ifindex = ifIndex, + .tcm_handle = TC_H_UNSPEC, + .tcm_parent = TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_INGRESS), + .tcm_info = (static_cast<uint32_t>(prio) << 16) + | static_cast<uint32_t>(htons(proto)), + }, + .kind = { + .attr = { + .nla_len = sizeof(mRequest.kind), + .nla_type = TCA_KIND, + }, + .str = "matchall", + }, + .opt = { + .attr = { + .nla_len = sizeof(mRequest.opt), + .nla_type = TCA_OPTIONS, + }, + .acts = { + .attr = { + .nla_len = sizeof(mRequest.opt.acts), + .nla_type = TCA_MATCHALL_ACT, + }, + .act1 = { + .attr = { + .nla_len = sizeof(mRequest.opt.acts.act1), + .nla_type = 1, // action priority + }, + .kind = { + .attr = { + .nla_len = sizeof(mRequest.opt.acts.act1.kind), + .nla_type = TCA_ACT_KIND, + }, + .str = "police", + }, + .opt = { + .attr = { + .nla_len = sizeof(mRequest.opt.acts.act1.opt), + .nla_type = TCA_ACT_OPTIONS | NLA_F_NESTED, + }, + .police = { + .attr = { + .nla_len = sizeof(mRequest.opt.acts.act1.opt.police), + .nla_type = TCA_POLICE_TBF, + }, + .obj = { + .action = TC_ACT_PIPE, + .burst = 0, + .rate = { + .cell_log = RTAB_CELL_LOGARITHM, + .linklayer = TC_LINKLAYER_ETHERNET, + .cell_align = -1, + .rate = rateInBytesPerSec, + }, + }, + }, + .rtab = { + .attr = { + .nla_len = sizeof(mRequest.opt.acts.act1.opt.rtab), + .nla_type = TCA_POLICE_RATE, + }, + .u32 = {}, + }, + .notexceedact = { + .attr = { + .nla_len = sizeof(mRequest.opt.acts.act1.opt.notexceedact), + .nla_type = TCA_POLICE_RESULT, + }, + .s32 = TC_ACT_UNSPEC, + }, + }, + }, + .act2 = { + .attr = { + .nla_len = sizeof(mRequest.opt.acts.act2), + .nla_type = 2, // action priority + }, + .kind = { + .attr = { + .nla_len = sizeof(mRequest.opt.acts.act2.kind), + .nla_type = TCA_ACT_KIND, + }, + .str = "bpf", + }, + .opt = { + .attr = { + .nla_len = sizeof(mRequest.opt.acts.act2.opt), + .nla_type = TCA_ACT_OPTIONS | NLA_F_NESTED, + }, + .fd = { + .attr = { + .nla_len = sizeof(mRequest.opt.acts.act2.opt.fd), + .nla_type = TCA_ACT_BPF_FD, + }, + .u32 = 0, // set during build() + }, + .name = { + .attr = { + .nla_len = sizeof(mRequest.opt.acts.act2.opt.name), + .nla_type = TCA_ACT_BPF_NAME, + }, + .str = "placeholder", + }, + .parms = { + .attr = { + .nla_len = sizeof(mRequest.opt.acts.act2.opt.parms), + .nla_type = TCA_ACT_BPF_PARMS, + }, + .obj = { + // default action to be executed when bpf prog + // returns TC_ACT_UNSPEC. + .action = TC_ACT_SHOT, + }, + }, + }, + }, + }, + }, + } { + // constructor body + } + // clang-format on + + ~IngressPoliceFilterBuilder() { + // TODO: use unique_fd + if (mBpfFd != -1) { + close(mBpfFd); + } + } + + constexpr unsigned getRequestSize() const { return sizeof(Request); } + +private: + unsigned calculateXmitTime(unsigned size) { + const uint32_t rate = mRequest.opt.acts.act1.opt.police.obj.rate.rate; + return (static_cast<double>(size) / static_cast<double>(rate)) * + TIME_UNITS_PER_SEC * kTickInUsec; + } + + void initBurstRate() { + mRequest.opt.acts.act1.opt.police.obj.burst = + calculateXmitTime(mBurstInBytes); + } + + // Calculates a table with 256 transmission times for different packet sizes + // (all the way up to MTU). RTAB_CELL_LOGARITHM is used as a scaling factor. + // In this case, MTU size is always 2048, so RTAB_CELL_LOGARITHM is always + // 3. Therefore, this function generates the transmission times for packets + // of size 1..256 x 2^3. + void initRateTable() { + for (unsigned i = 0; i < RTAB_SIZE; ++i) { + unsigned adjustedSize = (i + 1) << RTAB_CELL_LOGARITHM; + mRequest.opt.acts.act1.opt.rtab.u32[i] = calculateXmitTime(adjustedSize); + } + } + + int initBpfFd() { + mBpfFd = bpf::retrieveProgram(mBpfProgPath); + if (mBpfFd == -1) { + int error = errno; + ALOGE("retrieveProgram failed: %d", error); + return -error; + } + + mRequest.opt.acts.act2.opt.fd.u32 = static_cast<uint32_t>(mBpfFd); + snprintf(mRequest.opt.acts.act2.opt.name.str, + sizeof(mRequest.opt.acts.act2.opt.name.str), "%s:[*fsobj]", + basename(mBpfProgPath)); + + return 0; + } + +public: + int build() { + if (kTickInUsec == 0.0) { + return -EINVAL; + } + + initBurstRate(); + initRateTable(); + return initBpfFd(); + } + + const Request *getRequest() const { + // Make sure to call build() before calling this function. Otherwise, the + // request will be invalid. + return &mRequest; + } +}; + +const sockaddr_nl KERNEL_NLADDR = {AF_NETLINK, 0, 0, 0}; +const uint16_t NETLINK_REQUEST_FLAGS = NLM_F_REQUEST | NLM_F_ACK; + +int sendAndProcessNetlinkResponse(const void *req, int len) { + // TODO: use unique_fd instead of ScopeGuard + int fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE); + if (fd == -1) { + int error = errno; + ALOGE("socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE): %d", + error); + return -error; + } + auto scopeGuard = base::make_scope_guard([fd] { close(fd); }); + + static constexpr int on = 1; + if (setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, &on, sizeof(on))) { + int error = errno; + ALOGE("setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, 1): %d", error); + return -error; + } + + if (setsockopt(fd, SOL_NETLINK, NETLINK_EXT_ACK, &on, sizeof(on))) { + int error = errno; + ALOGW("setsockopt(fd, SOL_NETLINK, NETLINK_EXT_ACK, 1): %d", error); + // will fail on 4.9 kernels so don't: return -error; + } + + // this is needed to get valid strace netlink parsing, it allocates the pid + if (bind(fd, (const struct sockaddr *)&KERNEL_NLADDR, + sizeof(KERNEL_NLADDR))) { + int error = errno; + ALOGE("bind(fd, {AF_NETLINK, 0, 0}: %d)", error); + return -error; + } + + // we do not want to receive messages from anyone besides the kernel + if (connect(fd, (const struct sockaddr *)&KERNEL_NLADDR, + sizeof(KERNEL_NLADDR))) { + int error = errno; + ALOGE("connect(fd, {AF_NETLINK, 0, 0}): %d", error); + return -error; + } + + int rv = send(fd, req, len, 0); + + if (rv == -1) { + int error = errno; + ALOGE("send(fd, req, len, 0) failed: %d", error); + return -error; + } + + if (rv != len) { + ALOGE("send(fd, req, len = %d, 0) returned invalid message size %d", len, + rv); + return -EMSGSIZE; + } + + struct { + nlmsghdr h; + nlmsgerr e; + char buf[256]; + } resp = {}; + + rv = recv(fd, &resp, sizeof(resp), MSG_TRUNC); + + if (rv == -1) { + int error = errno; + ALOGE("recv() failed: %d", error); + return -error; + } + + if (rv < (int)NLMSG_SPACE(sizeof(struct nlmsgerr))) { + ALOGE("recv() returned short packet: %d", rv); + return -EBADMSG; + } + + if (resp.h.nlmsg_len != (unsigned)rv) { + ALOGE("recv() returned invalid header length: %d != %d", + resp.h.nlmsg_len, rv); + return -EBADMSG; + } + + if (resp.h.nlmsg_type != NLMSG_ERROR) { + ALOGE("recv() did not return NLMSG_ERROR message: %d", + resp.h.nlmsg_type); + return -ENOMSG; + } + + if (resp.e.error) { + ALOGE("NLMSG_ERROR message return error: %d", resp.e.error); + } + return resp.e.error; // returns 0 on success +} + +int hardwareAddressType(const char *interface) { + int fd = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (fd < 0) + return -errno; + auto scopeGuard = base::make_scope_guard([fd] { close(fd); }); + + struct ifreq ifr = {}; + // We use strncpy() instead of strlcpy() since kernel has to be able + // to handle non-zero terminated junk passed in by userspace anyway, + // and this way too long interface names (more than IFNAMSIZ-1 = 15 + // characters plus terminating NULL) will not get truncated to 15 + // characters and zero-terminated and thus potentially erroneously + // match a truncated interface if one were to exist. + strncpy(ifr.ifr_name, interface, sizeof(ifr.ifr_name)); + + if (ioctl(fd, SIOCGIFHWADDR, &ifr, sizeof(ifr))) { + return -errno; + } + return ifr.ifr_hwaddr.sa_family; +} + +} // namespace + +int isEthernet(const char *iface, bool &isEthernet) { + int rv = hardwareAddressType(iface); + if (rv < 0) { + ALOGE("Get hardware address type of interface %s failed: %s", iface, + strerror(-rv)); + return rv; + } + + // Backwards compatibility with pre-GKI kernels that use various custom + // ARPHRD_* for their cellular interface + switch (rv) { + // ARPHRD_PUREIP on at least some Mediatek Android kernels + // example: wembley with 4.19 kernel + case 520: + // in Linux 4.14+ rmnet support was upstreamed and ARHRD_RAWIP became 519, + // but it is 530 on at least some Qualcomm Android 4.9 kernels with rmnet + // example: Pixel 3 family + case 530: + // >5.4 kernels are GKI2.0 and thus upstream compatible, however 5.10 + // shipped with Android S, so (for safety) let's limit ourselves to + // >5.10, ie. 5.11+ as a guarantee we're on Android T+ and thus no + // longer need this non-upstream compatibility logic + static bool is_pre_5_11_kernel = !bpf::isAtLeastKernelVersion(5, 11, 0); + if (is_pre_5_11_kernel) + return false; + } + + switch (rv) { + case ARPHRD_ETHER: + isEthernet = true; + return 0; + case ARPHRD_NONE: + case ARPHRD_PPP: + case ARPHRD_RAWIP: + isEthernet = false; + return 0; + default: + ALOGE("Unknown hardware address type %d on interface %s", rv, iface); + return -EAFNOSUPPORT; + } +} + +// ADD: nlMsgType=RTM_NEWQDISC nlMsgFlags=NLM_F_EXCL|NLM_F_CREATE +// REPLACE: nlMsgType=RTM_NEWQDISC nlMsgFlags=NLM_F_CREATE|NLM_F_REPLACE +// DEL: nlMsgType=RTM_DELQDISC nlMsgFlags=0 +int doTcQdiscClsact(int ifIndex, uint16_t nlMsgType, uint16_t nlMsgFlags) { + // This is the name of the qdisc we are attaching. + // Some hoop jumping to make this compile time constant with known size, + // so that the structure declaration is well defined at compile time. +#define CLSACT "clsact" + // sizeof() includes the terminating NULL + static constexpr size_t ASCIIZ_LEN_CLSACT = sizeof(CLSACT); + + const struct { + nlmsghdr n; + tcmsg t; + struct { + nlattr attr; + char str[NLMSG_ALIGN(ASCIIZ_LEN_CLSACT)]; + } kind; + } req = { + .n = + { + .nlmsg_len = sizeof(req), + .nlmsg_type = nlMsgType, + .nlmsg_flags = + static_cast<__u16>(NETLINK_REQUEST_FLAGS | nlMsgFlags), + }, + .t = + { + .tcm_family = AF_UNSPEC, + .tcm_ifindex = ifIndex, + .tcm_handle = TC_H_MAKE(TC_H_CLSACT, 0), + .tcm_parent = TC_H_CLSACT, + }, + .kind = + { + .attr = + { + .nla_len = NLA_HDRLEN + ASCIIZ_LEN_CLSACT, + .nla_type = TCA_KIND, + }, + .str = CLSACT, + }, + }; +#undef CLSACT + + return sendAndProcessNetlinkResponse(&req, sizeof(req)); +} + +// tc filter add dev .. in/egress prio 1 protocol ipv6/ip bpf object-pinned +// /sys/fs/bpf/... direct-action +int tcAddBpfFilter(int ifIndex, bool ingress, uint16_t prio, uint16_t proto, + const char *bpfProgPath) { + const int bpfFd = bpf::retrieveProgram(bpfProgPath); + if (bpfFd == -1) { + ALOGE("retrieveProgram failed: %d", errno); + return -errno; + } + auto scopeGuard = base::make_scope_guard([bpfFd] { close(bpfFd); }); + + struct { + nlmsghdr n; + tcmsg t; + struct { + nlattr attr; + // The maximum classifier name length is defined in + // tcf_proto_ops in include/net/sch_generic.h. + char str[NLMSG_ALIGN(sizeof(CLS_BPF_KIND_NAME))]; + } kind; + struct { + nlattr attr; + struct { + nlattr attr; + __u32 u32; + } fd; + struct { + nlattr attr; + char str[NLMSG_ALIGN(CLS_BPF_NAME_LEN)]; + } name; + struct { + nlattr attr; + __u32 u32; + } flags; + } options; + } req = { + .n = + { + .nlmsg_len = sizeof(req), + .nlmsg_type = RTM_NEWTFILTER, + .nlmsg_flags = NETLINK_REQUEST_FLAGS | NLM_F_EXCL | NLM_F_CREATE, + }, + .t = + { + .tcm_family = AF_UNSPEC, + .tcm_ifindex = ifIndex, + .tcm_handle = TC_H_UNSPEC, + .tcm_parent = TC_H_MAKE(TC_H_CLSACT, ingress ? TC_H_MIN_INGRESS + : TC_H_MIN_EGRESS), + .tcm_info = + static_cast<__u32>((static_cast<uint16_t>(prio) << 16) | + htons(static_cast<uint16_t>(proto))), + }, + .kind = + { + .attr = + { + .nla_len = sizeof(req.kind), + .nla_type = TCA_KIND, + }, + .str = CLS_BPF_KIND_NAME, + }, + .options = + { + .attr = + { + .nla_len = sizeof(req.options), + .nla_type = NLA_F_NESTED | TCA_OPTIONS, + }, + .fd = + { + .attr = + { + .nla_len = sizeof(req.options.fd), + .nla_type = TCA_BPF_FD, + }, + .u32 = static_cast<__u32>(bpfFd), + }, + .name = + { + .attr = + { + .nla_len = sizeof(req.options.name), + .nla_type = TCA_BPF_NAME, + }, + // Visible via 'tc filter show', but + // is overwritten by strncpy below + .str = "placeholder", + }, + .flags = + { + .attr = + { + .nla_len = sizeof(req.options.flags), + .nla_type = TCA_BPF_FLAGS, + }, + .u32 = TCA_BPF_FLAG_ACT_DIRECT, + }, + }, + }; + + snprintf(req.options.name.str, sizeof(req.options.name.str), "%s:[*fsobj]", + basename(bpfProgPath)); + + int error = sendAndProcessNetlinkResponse(&req, sizeof(req)); + return error; +} + +// tc filter add dev .. ingress prio .. protocol .. matchall \ +// action police rate .. burst .. conform-exceed pipe/continue \ +// action bpf object-pinned .. \ +// drop +// +// TODO: tc-police does not do ECN marking, so in the future, we should consider +// adding a second tc-police filter at a lower priority that rate limits traffic +// at something like 0.8 times the global rate limit and ecn marks exceeding +// packets inside a bpf program (but does not drop them). +int tcAddIngressPoliceFilter(int ifIndex, uint16_t prio, uint16_t proto, + unsigned rateInBytesPerSec, + const char *bpfProgPath) { + // TODO: this value needs to be validated. + // TCP IW10 (initial congestion window) means servers will send 10 mtus worth + // of data on initial connect. + // If nic is LRO capable it could aggregate up to 64KiB, so again probably a + // bad idea to set burst below that, because ingress packets could get + // aggregated to 64KiB at the nic. + // I don't know, but I wonder whether we shouldn't just do 128KiB and not do + // any math. + static constexpr unsigned BURST_SIZE_IN_BYTES = 128 * 1024; // 128KiB + IngressPoliceFilterBuilder filter(ifIndex, prio, proto, rateInBytesPerSec, + BURST_SIZE_IN_BYTES, bpfProgPath); + const int error = filter.build(); + if (error) { + return error; + } + return sendAndProcessNetlinkResponse(filter.getRequest(), + filter.getRequestSize()); +} + +// tc filter del dev .. in/egress prio .. protocol .. +int tcDeleteFilter(int ifIndex, bool ingress, uint16_t prio, uint16_t proto) { + const struct { + nlmsghdr n; + tcmsg t; + } req = { + .n = + { + .nlmsg_len = sizeof(req), + .nlmsg_type = RTM_DELTFILTER, + .nlmsg_flags = NETLINK_REQUEST_FLAGS, + }, + .t = + { + .tcm_family = AF_UNSPEC, + .tcm_ifindex = ifIndex, + .tcm_handle = TC_H_UNSPEC, + .tcm_parent = TC_H_MAKE(TC_H_CLSACT, ingress ? TC_H_MIN_INGRESS + : TC_H_MIN_EGRESS), + .tcm_info = + static_cast<__u32>((static_cast<uint16_t>(prio) << 16) | + htons(static_cast<uint16_t>(proto))), + }, + }; + + return sendAndProcessNetlinkResponse(&req, sizeof(req)); +} + +} // namespace android diff --git a/staticlibs/native/tcutils/tests/tcutils_test.cpp b/staticlibs/native/tcutils/tests/tcutils_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..77322479b2fb48c67c63390422433b3bdfd8348b --- /dev/null +++ b/staticlibs/native/tcutils/tests/tcutils_test.cpp @@ -0,0 +1,154 @@ +/* + * Copyright 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. + * + * TcUtilsTest.cpp - unit tests for TcUtils.cpp + */ + +#include <gtest/gtest.h> + +#include "bpf/KernelUtils.h" +#include <tcutils/tcutils.h> + +#include <BpfSyscallWrappers.h> +#include <errno.h> +#include <linux/if_ether.h> + +namespace android { + +TEST(LibTcUtilsTest, IsEthernetOfNonExistingIf) { + bool result = false; + int error = isEthernet("not_existing_if", result); + ASSERT_FALSE(result); + ASSERT_EQ(-ENODEV, error); +} + +TEST(LibTcUtilsTest, IsEthernetOfLoopback) { + bool result = false; + int error = isEthernet("lo", result); + ASSERT_FALSE(result); + ASSERT_EQ(-EAFNOSUPPORT, error); +} + +// If wireless 'wlan0' interface exists it should be Ethernet. +// See also HardwareAddressTypeOfWireless. +TEST(LibTcUtilsTest, IsEthernetOfWireless) { + bool result = false; + int error = isEthernet("wlan0", result); + if (!result && error == -ENODEV) + return; + + ASSERT_EQ(0, error); + ASSERT_TRUE(result); +} + +// If cellular 'rmnet_data0' interface exists it should +// *probably* not be Ethernet and instead be RawIp. +// See also HardwareAddressTypeOfCellular. +TEST(LibTcUtilsTest, IsEthernetOfCellular) { + bool result = false; + int error = isEthernet("rmnet_data0", result); + if (!result && error == -ENODEV) + return; + + ASSERT_EQ(0, error); + ASSERT_FALSE(result); +} + +// See Linux kernel source in include/net/flow.h +static constexpr int LOOPBACK_IFINDEX = 1; + +TEST(LibTcUtilsTest, AttachReplaceDetachClsactLo) { + // This attaches and detaches a configuration-less and thus no-op clsact + // qdisc to loopback interface (and it takes fractions of a second) + EXPECT_EQ(0, tcAddQdiscClsact(LOOPBACK_IFINDEX)); + EXPECT_EQ(0, tcReplaceQdiscClsact(LOOPBACK_IFINDEX)); + EXPECT_EQ(0, tcDeleteQdiscClsact(LOOPBACK_IFINDEX)); + EXPECT_EQ(-EINVAL, tcDeleteQdiscClsact(LOOPBACK_IFINDEX)); +} + +TEST(LibTcUtilsTest, AddAndDeleteBpfFilter) { + // TODO: this should likely be in the tethering module, where using netd.h would be ok + static constexpr char bpfProgPath[] = + "/sys/fs/bpf/tethering/prog_offload_schedcls_tether_downstream6_ether"; + const int errNOENT = bpf::isAtLeastKernelVersion(4, 19, 0) ? ENOENT : EINVAL; + + // static test values + static constexpr bool ingress = true; + static constexpr uint16_t prio = 17; + static constexpr uint16_t proto = ETH_P_ALL; + + // try to delete missing filter from missing qdisc + EXPECT_EQ(-EINVAL, tcDeleteFilter(LOOPBACK_IFINDEX, ingress, prio, proto)); + // try to attach bpf filter to missing qdisc + EXPECT_EQ(-EINVAL, tcAddBpfFilter(LOOPBACK_IFINDEX, ingress, prio, proto, + bpfProgPath)); + // add the clsact qdisc + EXPECT_EQ(0, tcAddQdiscClsact(LOOPBACK_IFINDEX)); + // try to delete missing filter when there is a qdisc attached + EXPECT_EQ(-errNOENT, tcDeleteFilter(LOOPBACK_IFINDEX, ingress, prio, proto)); + // add and delete a bpf filter + EXPECT_EQ( + 0, tcAddBpfFilter(LOOPBACK_IFINDEX, ingress, prio, proto, bpfProgPath)); + EXPECT_EQ(0, tcDeleteFilter(LOOPBACK_IFINDEX, ingress, prio, proto)); + // try to remove the same filter a second time + EXPECT_EQ(-errNOENT, tcDeleteFilter(LOOPBACK_IFINDEX, ingress, prio, proto)); + // remove the clsact qdisc + EXPECT_EQ(0, tcDeleteQdiscClsact(LOOPBACK_IFINDEX)); + // once again, try to delete missing filter from missing qdisc + EXPECT_EQ(-EINVAL, tcDeleteFilter(LOOPBACK_IFINDEX, ingress, prio, proto)); +} + +TEST(LibTcUtilsTest, AddAndDeleteIngressPoliceFilter) { + // TODO: this should likely be in the tethering module, where using netd.h would be ok + static constexpr char bpfProgPath[] = + "/sys/fs/bpf/netd_shared/prog_netd_schedact_ingress_account"; + int fd = bpf::retrieveProgram(bpfProgPath); + ASSERT_LE(3, fd); + close(fd); + + const int errNOENT = bpf::isAtLeastKernelVersion(4, 19, 0) ? ENOENT : EINVAL; + + // static test values + static constexpr unsigned rateInBytesPerSec = + 1024 * 1024; // 8mbit/s => 1mbyte/s => 1024*1024 bytes/s. + static constexpr uint16_t prio = 17; + static constexpr uint16_t proto = ETH_P_ALL; + + // try to delete missing filter from missing qdisc + EXPECT_EQ(-EINVAL, + tcDeleteFilter(LOOPBACK_IFINDEX, true /*ingress*/, prio, proto)); + // try to attach bpf filter to missing qdisc + EXPECT_EQ(-EINVAL, tcAddIngressPoliceFilter(LOOPBACK_IFINDEX, prio, proto, + rateInBytesPerSec, bpfProgPath)); + // add the clsact qdisc + EXPECT_EQ(0, tcAddQdiscClsact(LOOPBACK_IFINDEX)); + // try to delete missing filter when there is a qdisc attached + EXPECT_EQ(-errNOENT, + tcDeleteFilter(LOOPBACK_IFINDEX, true /*ingress*/, prio, proto)); + // add and delete a bpf filter + EXPECT_EQ(0, tcAddIngressPoliceFilter(LOOPBACK_IFINDEX, prio, proto, + rateInBytesPerSec, bpfProgPath)); + EXPECT_EQ(0, tcDeleteFilter(LOOPBACK_IFINDEX, true /*ingress*/, prio, proto)); + // try to remove the same filter a second time + EXPECT_EQ(-errNOENT, + tcDeleteFilter(LOOPBACK_IFINDEX, true /*ingress*/, prio, proto)); + // remove the clsact qdisc + EXPECT_EQ(0, tcDeleteQdiscClsact(LOOPBACK_IFINDEX)); + // once again, try to delete missing filter from missing qdisc + EXPECT_EQ(-EINVAL, + tcDeleteFilter(LOOPBACK_IFINDEX, true /*ingress*/, prio, proto)); +} + +} // namespace android diff --git a/staticlibs/netd/Android.bp b/staticlibs/netd/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..d135a1c89a838bbf1f24e2a6a0f03fbeffeb7478 --- /dev/null +++ b/staticlibs/netd/Android.bp @@ -0,0 +1,253 @@ +// 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. + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +java_library { + name: "netd_aidl_interface-lateststable-java", + sdk_version: "system_current", + min_sdk_version: "29", + static_libs: [ + "netd_aidl_interface-V13-java", + ], + apex_available: [ + "//apex_available:platform", // used from services.net + "com.android.tethering", + "com.android.wifi", + ], +} + +cc_library_static { + name: "netd_event_listener_interface-lateststable-ndk", + whole_static_libs: [ + "netd_event_listener_interface-V1-ndk", + ], + apex_available: [ + "com.android.resolv", + ], + min_sdk_version: "29", +} + +cc_library_static { + name: "netd_aidl_interface-lateststable-ndk", + whole_static_libs: [ + "netd_aidl_interface-V13-ndk", + ], + apex_available: [ + "com.android.resolv", + "com.android.tethering", + ], + min_sdk_version: "29", +} + +cc_defaults { + name: "netd_aidl_interface_lateststable_cpp_static", + static_libs: ["netd_aidl_interface-V13-cpp"], +} + +cc_defaults { + name: "netd_aidl_interface_lateststable_cpp_shared", + shared_libs: ["netd_aidl_interface-V13-cpp"], +} + +aidl_interface { + name: "netd_aidl_interface", + local_include_dir: "binder", + srcs: [ + "binder/android/net/INetd.aidl", + // AIDL interface that callers can implement to receive networking events from netd. + "binder/android/net/INetdUnsolicitedEventListener.aidl", + "binder/android/net/InterfaceConfigurationParcel.aidl", + "binder/android/net/IpSecMigrateInfoParcel.aidl", + "binder/android/net/MarkMaskParcel.aidl", + "binder/android/net/NativeNetworkConfig.aidl", + "binder/android/net/NativeNetworkType.aidl", + "binder/android/net/NativeVpnType.aidl", + "binder/android/net/RouteInfoParcel.aidl", + "binder/android/net/TetherConfigParcel.aidl", + "binder/android/net/TetherOffloadRuleParcel.aidl", + "binder/android/net/TetherStatsParcel.aidl", + "binder/android/net/UidRangeParcel.aidl", + // Add new AIDL classes in android.net.netd.aidl to consist with other network modules. + "binder/android/net/netd/aidl/**/*.aidl", + ], + backend: { + cpp: { + gen_log: true, + }, + java: { + // TODO: Remove apex_available and restrict visibility to only mainline modules that are + // either outside the system server or use jarjar to rename the generated AIDL classes. + apex_available: [ + "//apex_available:platform", // used from services.net + "com.android.tethering", + "com.android.wifi", + ], + // this is part of updatable modules(NetworkStack) which targets 29(Q) + min_sdk_version: "29", + }, + ndk: { + apex_available: [ + "//apex_available:platform", + "com.android.tethering", + ], + // This is necessary for the DnsResovler tests to run in Android Q. + // Soong would recognize this value and produce the Q compatible aidl library. + min_sdk_version: "29", + }, + }, + versions_with_info: [ + { + version: "1", + imports: [], + }, + { + version: "2", + imports: [], + }, + { + version: "3", + imports: [], + }, + { + version: "4", + imports: [], + }, + { + version: "5", + imports: [], + }, + { + version: "6", + imports: [], + }, + { + version: "7", + imports: [], + }, + { + version: "8", + imports: [], + }, + { + version: "9", + imports: [], + }, + { + version: "10", + imports: [], + }, + { + version: "11", + imports: [], + }, + { + version: "12", + imports: [], + }, + { + version: "13", + imports: [], + }, + + ], + +} + +java_library { + name: "netd_event_listener_interface-lateststable-java", + sdk_version: "system_current", + min_sdk_version: "29", + static_libs: [ + "netd_event_listener_interface-V1-java", + ], + apex_available: [ + "//apex_available:platform", + "com.android.wifi", + "com.android.tethering", + ], +} + +aidl_interface { + name: "netd_event_listener_interface", + local_include_dir: "binder", + srcs: [ + "binder/android/net/metrics/INetdEventListener.aidl", + ], + + backend: { + ndk: { + apex_available: [ + "//apex_available:platform", + "com.android.resolv", + ], + min_sdk_version: "29", + }, + java: { + apex_available: [ + "//apex_available:platform", + "com.android.wifi", + "com.android.tethering", + ], + min_sdk_version: "29", + }, + }, + versions_with_info: [ + { + version: "1", + imports: [], + }, + { + version: "2", + imports: [], + }, + + ], + frozen: true, + +} + +java_library { + name: "mdns_aidl_interface-lateststable-java", + sdk_version: "module_current", + min_sdk_version: "30", + static_libs: [ + "mdns_aidl_interface-V1-java", + ], + apex_available: [ + "//apex_available:platform", + "com.android.tethering", + ], +} + +aidl_interface { + name: "mdns_aidl_interface", + local_include_dir: "binder", + srcs: [ + "binder/android/net/mdns/aidl/**/*.aidl", + ], + backend: { + java: { + sdk_version: "module_current", + apex_available: [ + "//apex_available:platform", + "com.android.tethering", + ], + min_sdk_version: "30", + }, + }, + versions: ["1"], +} diff --git a/staticlibs/netd/aidl_api/mdns_aidl_interface/1/.hash b/staticlibs/netd/aidl_api/mdns_aidl_interface/1/.hash new file mode 100644 index 0000000000000000000000000000000000000000..d952c5980236154b3b4e9965c04e513be47664e4 --- /dev/null +++ b/staticlibs/netd/aidl_api/mdns_aidl_interface/1/.hash @@ -0,0 +1 @@ +ae4cfe565d66acc7d816aabd0dfab991e64031ab diff --git a/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/DiscoveryInfo.aidl b/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/DiscoveryInfo.aidl new file mode 100644 index 0000000000000000000000000000000000000000..d31a3278939626505836cf4e2eb86b23aa4c66f5 --- /dev/null +++ b/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/DiscoveryInfo.aidl @@ -0,0 +1,45 @@ +/* + * 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.mdns.aidl; +/* @hide */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable DiscoveryInfo { + int id; + int result; + @utf8InCpp String serviceName; + @utf8InCpp String registrationType; + @utf8InCpp String domainName; + int interfaceIdx; + int netId; +} diff --git a/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/GetAddressInfo.aidl b/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/GetAddressInfo.aidl new file mode 100644 index 0000000000000000000000000000000000000000..2049274454288906ee837c0954a6e6f43ce6031c --- /dev/null +++ b/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/GetAddressInfo.aidl @@ -0,0 +1,44 @@ +/* + * 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.mdns.aidl; +/* @hide */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable GetAddressInfo { + int id; + int result; + @utf8InCpp String hostname; + @utf8InCpp String address; + int interfaceIdx; + int netId; +} diff --git a/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/IMDns.aidl b/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/IMDns.aidl new file mode 100644 index 0000000000000000000000000000000000000000..ecbe966dea6bc0cbbf8f3f78472bd76dcadf46f7 --- /dev/null +++ b/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/IMDns.aidl @@ -0,0 +1,46 @@ +/* + * 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.mdns.aidl; +/* @hide */ +interface IMDns { + void startDaemon(); + void stopDaemon(); + void registerService(in android.net.mdns.aidl.RegistrationInfo info); + void discover(in android.net.mdns.aidl.DiscoveryInfo info); + void resolve(in android.net.mdns.aidl.ResolutionInfo info); + void getServiceAddress(in android.net.mdns.aidl.GetAddressInfo info); + void stopOperation(int id); + void registerEventListener(in android.net.mdns.aidl.IMDnsEventListener listener); + void unregisterEventListener(in android.net.mdns.aidl.IMDnsEventListener listener); +} diff --git a/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/IMDnsEventListener.aidl b/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/IMDnsEventListener.aidl new file mode 100644 index 0000000000000000000000000000000000000000..4625cac7abcc1b24a62145227904407c649c5238 --- /dev/null +++ b/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/IMDnsEventListener.aidl @@ -0,0 +1,50 @@ +/** + * 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.mdns.aidl; +/* @hide */ +interface IMDnsEventListener { + oneway void onServiceRegistrationStatus(in android.net.mdns.aidl.RegistrationInfo status); + oneway void onServiceDiscoveryStatus(in android.net.mdns.aidl.DiscoveryInfo status); + oneway void onServiceResolutionStatus(in android.net.mdns.aidl.ResolutionInfo status); + oneway void onGettingServiceAddressStatus(in android.net.mdns.aidl.GetAddressInfo status); + const int SERVICE_DISCOVERY_FAILED = 602; + const int SERVICE_FOUND = 603; + const int SERVICE_LOST = 604; + const int SERVICE_REGISTRATION_FAILED = 605; + const int SERVICE_REGISTERED = 606; + const int SERVICE_RESOLUTION_FAILED = 607; + const int SERVICE_RESOLVED = 608; + const int SERVICE_GET_ADDR_FAILED = 611; + const int SERVICE_GET_ADDR_SUCCESS = 612; +} diff --git a/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/RegistrationInfo.aidl b/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/RegistrationInfo.aidl new file mode 100644 index 0000000000000000000000000000000000000000..185111b440a70bd6d7ae74063d6981ca05a1e6fa --- /dev/null +++ b/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/RegistrationInfo.aidl @@ -0,0 +1,45 @@ +/* + * 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.mdns.aidl; +/* @hide */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable RegistrationInfo { + int id; + int result; + @utf8InCpp String serviceName; + @utf8InCpp String registrationType; + int port; + byte[] txtRecord; + int interfaceIdx; +} diff --git a/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/ResolutionInfo.aidl b/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/ResolutionInfo.aidl new file mode 100644 index 0000000000000000000000000000000000000000..4aa7d79114a5e2a1020df7c11d33a36d199c629e --- /dev/null +++ b/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/ResolutionInfo.aidl @@ -0,0 +1,48 @@ +/* + * 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.mdns.aidl; +/* @hide */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable ResolutionInfo { + int id; + int result; + @utf8InCpp String serviceName; + @utf8InCpp String registrationType; + @utf8InCpp String domain; + @utf8InCpp String serviceFullName; + @utf8InCpp String hostname; + int port; + byte[] txtRecord; + int interfaceIdx; +} diff --git a/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/DiscoveryInfo.aidl b/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/DiscoveryInfo.aidl new file mode 100644 index 0000000000000000000000000000000000000000..d31a3278939626505836cf4e2eb86b23aa4c66f5 --- /dev/null +++ b/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/DiscoveryInfo.aidl @@ -0,0 +1,45 @@ +/* + * 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.mdns.aidl; +/* @hide */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable DiscoveryInfo { + int id; + int result; + @utf8InCpp String serviceName; + @utf8InCpp String registrationType; + @utf8InCpp String domainName; + int interfaceIdx; + int netId; +} diff --git a/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/GetAddressInfo.aidl b/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/GetAddressInfo.aidl new file mode 100644 index 0000000000000000000000000000000000000000..2049274454288906ee837c0954a6e6f43ce6031c --- /dev/null +++ b/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/GetAddressInfo.aidl @@ -0,0 +1,44 @@ +/* + * 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.mdns.aidl; +/* @hide */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable GetAddressInfo { + int id; + int result; + @utf8InCpp String hostname; + @utf8InCpp String address; + int interfaceIdx; + int netId; +} diff --git a/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/IMDns.aidl b/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/IMDns.aidl new file mode 100644 index 0000000000000000000000000000000000000000..ecbe966dea6bc0cbbf8f3f78472bd76dcadf46f7 --- /dev/null +++ b/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/IMDns.aidl @@ -0,0 +1,46 @@ +/* + * 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.mdns.aidl; +/* @hide */ +interface IMDns { + void startDaemon(); + void stopDaemon(); + void registerService(in android.net.mdns.aidl.RegistrationInfo info); + void discover(in android.net.mdns.aidl.DiscoveryInfo info); + void resolve(in android.net.mdns.aidl.ResolutionInfo info); + void getServiceAddress(in android.net.mdns.aidl.GetAddressInfo info); + void stopOperation(int id); + void registerEventListener(in android.net.mdns.aidl.IMDnsEventListener listener); + void unregisterEventListener(in android.net.mdns.aidl.IMDnsEventListener listener); +} diff --git a/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/IMDnsEventListener.aidl b/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/IMDnsEventListener.aidl new file mode 100644 index 0000000000000000000000000000000000000000..4625cac7abcc1b24a62145227904407c649c5238 --- /dev/null +++ b/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/IMDnsEventListener.aidl @@ -0,0 +1,50 @@ +/** + * 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.mdns.aidl; +/* @hide */ +interface IMDnsEventListener { + oneway void onServiceRegistrationStatus(in android.net.mdns.aidl.RegistrationInfo status); + oneway void onServiceDiscoveryStatus(in android.net.mdns.aidl.DiscoveryInfo status); + oneway void onServiceResolutionStatus(in android.net.mdns.aidl.ResolutionInfo status); + oneway void onGettingServiceAddressStatus(in android.net.mdns.aidl.GetAddressInfo status); + const int SERVICE_DISCOVERY_FAILED = 602; + const int SERVICE_FOUND = 603; + const int SERVICE_LOST = 604; + const int SERVICE_REGISTRATION_FAILED = 605; + const int SERVICE_REGISTERED = 606; + const int SERVICE_RESOLUTION_FAILED = 607; + const int SERVICE_RESOLVED = 608; + const int SERVICE_GET_ADDR_FAILED = 611; + const int SERVICE_GET_ADDR_SUCCESS = 612; +} diff --git a/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/RegistrationInfo.aidl b/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/RegistrationInfo.aidl new file mode 100644 index 0000000000000000000000000000000000000000..185111b440a70bd6d7ae74063d6981ca05a1e6fa --- /dev/null +++ b/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/RegistrationInfo.aidl @@ -0,0 +1,45 @@ +/* + * 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.mdns.aidl; +/* @hide */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable RegistrationInfo { + int id; + int result; + @utf8InCpp String serviceName; + @utf8InCpp String registrationType; + int port; + byte[] txtRecord; + int interfaceIdx; +} diff --git a/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/ResolutionInfo.aidl b/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/ResolutionInfo.aidl new file mode 100644 index 0000000000000000000000000000000000000000..4aa7d79114a5e2a1020df7c11d33a36d199c629e --- /dev/null +++ b/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/ResolutionInfo.aidl @@ -0,0 +1,48 @@ +/* + * 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.mdns.aidl; +/* @hide */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable ResolutionInfo { + int id; + int result; + @utf8InCpp String serviceName; + @utf8InCpp String registrationType; + @utf8InCpp String domain; + @utf8InCpp String serviceFullName; + @utf8InCpp String hostname; + int port; + byte[] txtRecord; + int interfaceIdx; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/1/.hash b/staticlibs/netd/aidl_api/netd_aidl_interface/1/.hash new file mode 100644 index 0000000000000000000000000000000000000000..d33e90387b2fc01b9c18d897abeae2e6c8a315b8 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/1/.hash @@ -0,0 +1 @@ +69c2ac134efbb31e9591d7e5c3640fb839e23bdb diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/1/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/1/android/net/INetd.aidl new file mode 100644 index 0000000000000000000000000000000000000000..664c643fb2aa83bcbe591be7bf19496e4193ad32 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/1/android/net/INetd.aidl @@ -0,0 +1,132 @@ +package android.net; +interface INetd { + boolean isAlive(); + boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isWhitelist, in int[] uids); + boolean bandwidthEnableDataSaver(boolean enable); + void networkCreatePhysical(int netId, int permission); + void networkCreateVpn(int netId, boolean secure); + void networkDestroy(int netId); + void networkAddInterface(int netId, in @utf8InCpp String iface); + void networkRemoveInterface(int netId, in @utf8InCpp String iface); + void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges); + void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges); + void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges); + void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids); + boolean tetherApplyDnsInterfaces(); + android.net.TetherStatsParcel[] tetherGetStats(); + void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength); + void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength); + @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter); + void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value); + void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid); + int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi); + void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId); + void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi); + void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket); + void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId); + void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId); + void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId); + void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName); + void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask); + void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask); + void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode); + void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel); + void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel); + void strictUidCleartextPenalty(int uid, int policyPenalty); + @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix); + void clatdStop(in @utf8InCpp String ifName); + boolean ipfwdEnabled(); + @utf8InCpp String[] ipfwdGetRequesterList(); + void ipfwdEnableForwarding(in @utf8InCpp String requester); + void ipfwdDisableForwarding(in @utf8InCpp String requester); + void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface); + void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface); + void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes); + void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName); + void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes); + void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName); + void bandwidthSetGlobalAlert(long bytes); + void bandwidthAddNaughtyApp(int uid); + void bandwidthRemoveNaughtyApp(int uid); + void bandwidthAddNiceApp(int uid); + void bandwidthRemoveNiceApp(int uid); + void tetherStart(in @utf8InCpp String[] dhcpRanges); + void tetherStop(); + boolean tetherIsEnabled(); + void tetherInterfaceAdd(in @utf8InCpp String ifName); + void tetherInterfaceRemove(in @utf8InCpp String ifName); + @utf8InCpp String[] tetherInterfaceList(); + void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs); + @utf8InCpp String[] tetherDnsList(); + void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop); + void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop); + void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid); + void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid); + int networkGetDefault(); + void networkSetDefault(int netId); + void networkClearDefault(); + void networkSetPermissionForNetwork(int netId, int permission); + void networkSetPermissionForUser(int permission, in int[] uids); + void networkClearPermissionForUser(in int[] uids); + void trafficSetNetPermForUids(int permission, in int[] uids); + void networkSetProtectAllow(int uid); + void networkSetProtectDeny(int uid); + boolean networkCanProtect(int uid); + void firewallSetFirewallType(int firewalltype); + void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule); + void firewallSetUidRule(int childChain, int uid, int firewallRule); + void firewallEnableChildChain(int childChain, boolean enable); + @utf8InCpp String[] interfaceGetList(); + android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName); + void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg); + void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable); + void interfaceClearAddrs(in @utf8InCpp String ifName); + void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable); + void interfaceSetMtu(in @utf8InCpp String ifName, int mtu); + void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface); + void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface); + void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues); + void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener); + const int IPV4 = 4; + const int IPV6 = 6; + const int CONF = 1; + const int NEIGH = 2; + const String IPSEC_INTERFACE_PREFIX = "ipsec"; + const int IPV6_ADDR_GEN_MODE_EUI64 = 0; + const int IPV6_ADDR_GEN_MODE_NONE = 1; + const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2; + const int IPV6_ADDR_GEN_MODE_RANDOM = 3; + const int IPV6_ADDR_GEN_MODE_DEFAULT = 0; + const int PENALTY_POLICY_ACCEPT = 1; + const int PENALTY_POLICY_LOG = 2; + const int PENALTY_POLICY_REJECT = 3; + const int LOCAL_NET_ID = 99; + const String NEXTHOP_NONE = ""; + const String NEXTHOP_UNREACHABLE = "unreachable"; + const String NEXTHOP_THROW = "throw"; + const int PERMISSION_NONE = 0; + const int PERMISSION_NETWORK = 1; + const int PERMISSION_SYSTEM = 2; + const int NO_PERMISSIONS = 0; + const int PERMISSION_INTERNET = 4; + const int PERMISSION_UPDATE_DEVICE_STATS = 8; + const int PERMISSION_UNINSTALLED = -1; + const int FIREWALL_WHITELIST = 0; + const int FIREWALL_BLACKLIST = 1; + const int FIREWALL_RULE_ALLOW = 1; + const int FIREWALL_RULE_DENY = 2; + const int FIREWALL_CHAIN_NONE = 0; + const int FIREWALL_CHAIN_DOZABLE = 1; + const int FIREWALL_CHAIN_STANDBY = 2; + const int FIREWALL_CHAIN_POWERSAVE = 3; + const String IF_STATE_UP = "up"; + const String IF_STATE_DOWN = "down"; + const String IF_FLAG_BROADCAST = "broadcast"; + const String IF_FLAG_LOOPBACK = "loopback"; + const String IF_FLAG_POINTOPOINT = "point-to-point"; + const String IF_FLAG_RUNNING = "running"; + const String IF_FLAG_MULTICAST = "multicast"; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/1/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/1/android/net/INetdUnsolicitedEventListener.aidl new file mode 100644 index 0000000000000000000000000000000000000000..18631ffa1afa81509e423993cf0110d8318bdd96 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/1/android/net/INetdUnsolicitedEventListener.aidl @@ -0,0 +1,14 @@ +package android.net; +interface INetdUnsolicitedEventListener { + oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid); + oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName); + oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers); + oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope); + oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope); + oneway void onInterfaceAdded(@utf8InCpp String ifName); + oneway void onInterfaceRemoved(@utf8InCpp String ifName); + oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up); + oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up); + oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName); + oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex); +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/1/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/1/android/net/InterfaceConfigurationParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..93407dcb902917f8b3fed86c24c968935cd25481 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/1/android/net/InterfaceConfigurationParcel.aidl @@ -0,0 +1,8 @@ +package android.net; +parcelable InterfaceConfigurationParcel { + @utf8InCpp String ifName; + @utf8InCpp String hwAddr; + @utf8InCpp String ipv4Addr; + int prefixLength; + @utf8InCpp String[] flags; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/1/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/1/android/net/TetherStatsParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..d1782bb0cf1595a683452214d7dc6c3f674523d5 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/1/android/net/TetherStatsParcel.aidl @@ -0,0 +1,8 @@ +package android.net; +parcelable TetherStatsParcel { + @utf8InCpp String iface; + long rxBytes; + long rxPackets; + long txBytes; + long txPackets; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/1/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/1/android/net/UidRangeParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..d3bc7edfd0f0dc46d1fa241164b0969e13ac5884 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/1/android/net/UidRangeParcel.aidl @@ -0,0 +1,5 @@ +package android.net; +parcelable UidRangeParcel { + int start; + int stop; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/10/.hash b/staticlibs/netd/aidl_api/netd_aidl_interface/10/.hash new file mode 100644 index 0000000000000000000000000000000000000000..6ec361355918fb9115a2a6ebdb8c12cfdc9ef747 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/10/.hash @@ -0,0 +1 @@ +3943383e838f39851675e3640fcdf27b42f8c9fc diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/INetd.aidl new file mode 100644 index 0000000000000000000000000000000000000000..e671fdb781b657d35a8bf3fed5a75c25529452a8 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/INetd.aidl @@ -0,0 +1,222 @@ +/** + * 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// 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 INetd { + boolean isAlive(); + boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isAllowlist, in int[] uids); + boolean bandwidthEnableDataSaver(boolean enable); + /** + * @deprecated use networkCreate() instead. + */ + void networkCreatePhysical(int netId, int permission); + /** + * @deprecated use networkCreate() instead. + */ + void networkCreateVpn(int netId, boolean secure); + void networkDestroy(int netId); + void networkAddInterface(int netId, in @utf8InCpp String iface); + void networkRemoveInterface(int netId, in @utf8InCpp String iface); + void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges); + void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges); + void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges); + void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids); + boolean tetherApplyDnsInterfaces(); + android.net.TetherStatsParcel[] tetherGetStats(); + void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength); + void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength); + @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter); + void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value); + void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid); + int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi); + void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId); + void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi); + void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket); + void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId); + void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId); + void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId); + void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName); + void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask); + void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask); + void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode); + void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel); + void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel); + void strictUidCleartextPenalty(int uid, int policyPenalty); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd control plane moved to the mainline module starting in T. See ClatCoordinator. + */ + @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd control plane moved to the mainline module starting in T. See ClatCoordinator. + */ + void clatdStop(in @utf8InCpp String ifName); + boolean ipfwdEnabled(); + @utf8InCpp String[] ipfwdGetRequesterList(); + void ipfwdEnableForwarding(in @utf8InCpp String requester); + void ipfwdDisableForwarding(in @utf8InCpp String requester); + void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface); + void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface); + void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes); + void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName); + void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes); + void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName); + void bandwidthSetGlobalAlert(long bytes); + void bandwidthAddNaughtyApp(int uid); + void bandwidthRemoveNaughtyApp(int uid); + void bandwidthAddNiceApp(int uid); + void bandwidthRemoveNiceApp(int uid); + void tetherStart(in @utf8InCpp String[] dhcpRanges); + void tetherStop(); + boolean tetherIsEnabled(); + void tetherInterfaceAdd(in @utf8InCpp String ifName); + void tetherInterfaceRemove(in @utf8InCpp String ifName); + @utf8InCpp String[] tetherInterfaceList(); + void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs); + @utf8InCpp String[] tetherDnsList(); + void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop); + void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop); + void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid); + void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid); + int networkGetDefault(); + void networkSetDefault(int netId); + void networkClearDefault(); + void networkSetPermissionForNetwork(int netId, int permission); + void networkSetPermissionForUser(int permission, in int[] uids); + void networkClearPermissionForUser(in int[] uids); + void trafficSetNetPermForUids(int permission, in int[] uids); + void networkSetProtectAllow(int uid); + void networkSetProtectDeny(int uid); + boolean networkCanProtect(int uid); + void firewallSetFirewallType(int firewalltype); + void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule); + void firewallSetUidRule(int childChain, int uid, int firewallRule); + void firewallEnableChildChain(int childChain, boolean enable); + @utf8InCpp String[] interfaceGetList(); + android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName); + void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg); + void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable); + void interfaceClearAddrs(in @utf8InCpp String ifName); + void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable); + void interfaceSetMtu(in @utf8InCpp String ifName, int mtu); + void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface); + void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface); + void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues); + void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener); + void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids); + void firewallRemoveUidInterfaceRules(in int[] uids); + void trafficSwapActiveStatsMap(); + IBinder getOemNetd(); + void tetherStartWithConfiguration(in android.net.TetherConfigParcel config); + android.net.MarkMaskParcel getFwmarkForNetwork(int netId); + void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator. + */ + void tetherOffloadRuleAdd(in android.net.TetherOffloadRuleParcel rule); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator. + */ + void tetherOffloadRuleRemove(in android.net.TetherOffloadRuleParcel rule); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator. + */ + android.net.TetherStatsParcel[] tetherOffloadGetStats(); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator. + */ + void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator. + */ + android.net.TetherStatsParcel tetherOffloadGetAndClearStats(int ifIndex); + void networkCreate(in android.net.NativeNetworkConfig config); + void networkAddUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig); + void networkRemoveUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig); + const int IPV4 = 4; + const int IPV6 = 6; + const int CONF = 1; + const int NEIGH = 2; + const String IPSEC_INTERFACE_PREFIX = "ipsec"; + const int IPV6_ADDR_GEN_MODE_EUI64 = 0; + const int IPV6_ADDR_GEN_MODE_NONE = 1; + const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2; + const int IPV6_ADDR_GEN_MODE_RANDOM = 3; + const int IPV6_ADDR_GEN_MODE_DEFAULT = 0; + const int PENALTY_POLICY_ACCEPT = 1; + const int PENALTY_POLICY_LOG = 2; + const int PENALTY_POLICY_REJECT = 3; + const int CLAT_MARK = -559038041; + const int LOCAL_NET_ID = 99; + const int DUMMY_NET_ID = 51; + const int UNREACHABLE_NET_ID = 52; + const String NEXTHOP_NONE = ""; + const String NEXTHOP_UNREACHABLE = "unreachable"; + const String NEXTHOP_THROW = "throw"; + const int PERMISSION_NONE = 0; + const int PERMISSION_NETWORK = 1; + const int PERMISSION_SYSTEM = 2; + const int NO_PERMISSIONS = 0; + const int PERMISSION_INTERNET = 4; + const int PERMISSION_UPDATE_DEVICE_STATS = 8; + const int PERMISSION_UNINSTALLED = -1; + /** + * @deprecated use FIREWALL_ALLOWLIST. + */ + const int FIREWALL_WHITELIST = 0; + const int FIREWALL_ALLOWLIST = 0; + /** + * @deprecated use FIREWALL_DENYLIST. + */ + const int FIREWALL_BLACKLIST = 1; + const int FIREWALL_DENYLIST = 1; + const int FIREWALL_RULE_ALLOW = 1; + const int FIREWALL_RULE_DENY = 2; + const int FIREWALL_CHAIN_NONE = 0; + const int FIREWALL_CHAIN_DOZABLE = 1; + const int FIREWALL_CHAIN_STANDBY = 2; + const int FIREWALL_CHAIN_POWERSAVE = 3; + const int FIREWALL_CHAIN_RESTRICTED = 4; + const String IF_STATE_UP = "up"; + const String IF_STATE_DOWN = "down"; + const String IF_FLAG_BROADCAST = "broadcast"; + const String IF_FLAG_LOOPBACK = "loopback"; + const String IF_FLAG_POINTOPOINT = "point-to-point"; + const String IF_FLAG_RUNNING = "running"; + const String IF_FLAG_MULTICAST = "multicast"; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/INetdUnsolicitedEventListener.aidl new file mode 100644 index 0000000000000000000000000000000000000000..31775dfd11fe25f2fede661b7e4da11ab4ce8f91 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/INetdUnsolicitedEventListener.aidl @@ -0,0 +1,48 @@ +/** + * 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 INetdUnsolicitedEventListener { + oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid); + oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName); + oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers); + oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope); + oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope); + oneway void onInterfaceAdded(@utf8InCpp String ifName); + oneway void onInterfaceRemoved(@utf8InCpp String ifName); + oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up); + oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up); + oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName); + oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex); +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/InterfaceConfigurationParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..1869d8d49e6a59a1294c7925c460a592f945e677 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/InterfaceConfigurationParcel.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 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 */ +parcelable InterfaceConfigurationParcel { + @utf8InCpp String ifName; + @utf8InCpp String hwAddr; + @utf8InCpp String ipv4Addr; + int prefixLength; + @utf8InCpp String[] flags; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/MarkMaskParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/MarkMaskParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..8ea20d118fc8c3350249e6268348f77578a2b0b6 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/MarkMaskParcel.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 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 */ +parcelable MarkMaskParcel { + int mark; + int mask; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/NativeNetworkConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/NativeNetworkConfig.aidl new file mode 100644 index 0000000000000000000000000000000000000000..77d814b825b348f04612a11b0459150c02453c68 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/NativeNetworkConfig.aidl @@ -0,0 +1,44 @@ +/* + * 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; +/* @hide */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable NativeNetworkConfig { + int netId; + android.net.NativeNetworkType networkType = android.net.NativeNetworkType.PHYSICAL; + int permission; + boolean secure; + android.net.NativeVpnType vpnType = android.net.NativeVpnType.PLATFORM; + boolean excludeLocalRoutes = false; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/NativeNetworkType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/NativeNetworkType.aidl new file mode 100644 index 0000000000000000000000000000000000000000..06c8979d51778df6bd58542a5f5324a849b5b5fe --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/NativeNetworkType.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; +@Backing(type="int") +enum NativeNetworkType { + PHYSICAL = 0, + VIRTUAL = 1, +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/NativeVpnType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/NativeVpnType.aidl new file mode 100644 index 0000000000000000000000000000000000000000..8a8be83958c80bdaa6e5401bb4411ff1e9b72938 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/NativeVpnType.aidl @@ -0,0 +1,41 @@ +/* + * 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; +@Backing(type="int") +enum NativeVpnType { + SERVICE = 1, + PLATFORM = 2, + LEGACY = 3, + OEM = 4, +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/RouteInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/RouteInfoParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..5ef95e67ca963ed343c8481d8061e0982cc8f866 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/RouteInfoParcel.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; +parcelable RouteInfoParcel { + @utf8InCpp String destination; + @utf8InCpp String ifName; + @utf8InCpp String nextHop; + int mtu; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/TetherConfigParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/TetherConfigParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..7b39c22e4ef0bef2a148578ec2a345b98f9ca706 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/TetherConfigParcel.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 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 */ +parcelable TetherConfigParcel { + boolean usingLegacyDnsProxy; + @utf8InCpp String[] dhcpRanges; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/TetherOffloadRuleParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/TetherOffloadRuleParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..983e9860ea8e94007af826844f4fbcbe3d5aee22 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/TetherOffloadRuleParcel.aidl @@ -0,0 +1,44 @@ +/* + * 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; +/* @hide */ +parcelable TetherOffloadRuleParcel { + int inputInterfaceIndex; + int outputInterfaceIndex; + byte[] destination; + int prefixLength; + byte[] srcL2Address; + byte[] dstL2Address; + int pmtu = 1500; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/TetherStatsParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..5f1b722690b0f2ee3e7ec3309edf3b1a48ab7979 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/TetherStatsParcel.aidl @@ -0,0 +1,43 @@ +/* + * 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 */ +parcelable TetherStatsParcel { + @utf8InCpp String iface; + long rxBytes; + long rxPackets; + long txBytes; + long txPackets; + int ifIndex = 0; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/UidRangeParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..72e987a2af49e9249320622f5ba9e729ef6d6309 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/UidRangeParcel.aidl @@ -0,0 +1,40 @@ +/* + * 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 */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable UidRangeParcel { + int start; + int stop; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/netd/aidl/NativeUidRangeConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/netd/aidl/NativeUidRangeConfig.aidl new file mode 100644 index 0000000000000000000000000000000000000000..9bb679f1c115a4d69683f9c1c6e3d8aead8bab75 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/netd/aidl/NativeUidRangeConfig.aidl @@ -0,0 +1,41 @@ +/* + * 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.netd.aidl; +/* @hide */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable NativeUidRangeConfig { + int netId; + android.net.UidRangeParcel[] uidRanges; + int subPriority; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/11/.hash b/staticlibs/netd/aidl_api/netd_aidl_interface/11/.hash new file mode 100644 index 0000000000000000000000000000000000000000..d22cb04b48e31b3f1bbd324f09de8b9b56984818 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/11/.hash @@ -0,0 +1 @@ +d384a1d9f2f6dc0301a43d2b9d6d3a49e3898ae7 diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/INetd.aidl new file mode 100644 index 0000000000000000000000000000000000000000..e671fdb781b657d35a8bf3fed5a75c25529452a8 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/INetd.aidl @@ -0,0 +1,222 @@ +/** + * 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// 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 INetd { + boolean isAlive(); + boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isAllowlist, in int[] uids); + boolean bandwidthEnableDataSaver(boolean enable); + /** + * @deprecated use networkCreate() instead. + */ + void networkCreatePhysical(int netId, int permission); + /** + * @deprecated use networkCreate() instead. + */ + void networkCreateVpn(int netId, boolean secure); + void networkDestroy(int netId); + void networkAddInterface(int netId, in @utf8InCpp String iface); + void networkRemoveInterface(int netId, in @utf8InCpp String iface); + void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges); + void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges); + void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges); + void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids); + boolean tetherApplyDnsInterfaces(); + android.net.TetherStatsParcel[] tetherGetStats(); + void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength); + void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength); + @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter); + void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value); + void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid); + int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi); + void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId); + void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi); + void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket); + void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId); + void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId); + void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId); + void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName); + void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask); + void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask); + void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode); + void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel); + void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel); + void strictUidCleartextPenalty(int uid, int policyPenalty); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd control plane moved to the mainline module starting in T. See ClatCoordinator. + */ + @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd control plane moved to the mainline module starting in T. See ClatCoordinator. + */ + void clatdStop(in @utf8InCpp String ifName); + boolean ipfwdEnabled(); + @utf8InCpp String[] ipfwdGetRequesterList(); + void ipfwdEnableForwarding(in @utf8InCpp String requester); + void ipfwdDisableForwarding(in @utf8InCpp String requester); + void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface); + void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface); + void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes); + void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName); + void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes); + void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName); + void bandwidthSetGlobalAlert(long bytes); + void bandwidthAddNaughtyApp(int uid); + void bandwidthRemoveNaughtyApp(int uid); + void bandwidthAddNiceApp(int uid); + void bandwidthRemoveNiceApp(int uid); + void tetherStart(in @utf8InCpp String[] dhcpRanges); + void tetherStop(); + boolean tetherIsEnabled(); + void tetherInterfaceAdd(in @utf8InCpp String ifName); + void tetherInterfaceRemove(in @utf8InCpp String ifName); + @utf8InCpp String[] tetherInterfaceList(); + void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs); + @utf8InCpp String[] tetherDnsList(); + void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop); + void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop); + void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid); + void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid); + int networkGetDefault(); + void networkSetDefault(int netId); + void networkClearDefault(); + void networkSetPermissionForNetwork(int netId, int permission); + void networkSetPermissionForUser(int permission, in int[] uids); + void networkClearPermissionForUser(in int[] uids); + void trafficSetNetPermForUids(int permission, in int[] uids); + void networkSetProtectAllow(int uid); + void networkSetProtectDeny(int uid); + boolean networkCanProtect(int uid); + void firewallSetFirewallType(int firewalltype); + void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule); + void firewallSetUidRule(int childChain, int uid, int firewallRule); + void firewallEnableChildChain(int childChain, boolean enable); + @utf8InCpp String[] interfaceGetList(); + android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName); + void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg); + void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable); + void interfaceClearAddrs(in @utf8InCpp String ifName); + void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable); + void interfaceSetMtu(in @utf8InCpp String ifName, int mtu); + void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface); + void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface); + void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues); + void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener); + void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids); + void firewallRemoveUidInterfaceRules(in int[] uids); + void trafficSwapActiveStatsMap(); + IBinder getOemNetd(); + void tetherStartWithConfiguration(in android.net.TetherConfigParcel config); + android.net.MarkMaskParcel getFwmarkForNetwork(int netId); + void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator. + */ + void tetherOffloadRuleAdd(in android.net.TetherOffloadRuleParcel rule); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator. + */ + void tetherOffloadRuleRemove(in android.net.TetherOffloadRuleParcel rule); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator. + */ + android.net.TetherStatsParcel[] tetherOffloadGetStats(); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator. + */ + void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator. + */ + android.net.TetherStatsParcel tetherOffloadGetAndClearStats(int ifIndex); + void networkCreate(in android.net.NativeNetworkConfig config); + void networkAddUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig); + void networkRemoveUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig); + const int IPV4 = 4; + const int IPV6 = 6; + const int CONF = 1; + const int NEIGH = 2; + const String IPSEC_INTERFACE_PREFIX = "ipsec"; + const int IPV6_ADDR_GEN_MODE_EUI64 = 0; + const int IPV6_ADDR_GEN_MODE_NONE = 1; + const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2; + const int IPV6_ADDR_GEN_MODE_RANDOM = 3; + const int IPV6_ADDR_GEN_MODE_DEFAULT = 0; + const int PENALTY_POLICY_ACCEPT = 1; + const int PENALTY_POLICY_LOG = 2; + const int PENALTY_POLICY_REJECT = 3; + const int CLAT_MARK = -559038041; + const int LOCAL_NET_ID = 99; + const int DUMMY_NET_ID = 51; + const int UNREACHABLE_NET_ID = 52; + const String NEXTHOP_NONE = ""; + const String NEXTHOP_UNREACHABLE = "unreachable"; + const String NEXTHOP_THROW = "throw"; + const int PERMISSION_NONE = 0; + const int PERMISSION_NETWORK = 1; + const int PERMISSION_SYSTEM = 2; + const int NO_PERMISSIONS = 0; + const int PERMISSION_INTERNET = 4; + const int PERMISSION_UPDATE_DEVICE_STATS = 8; + const int PERMISSION_UNINSTALLED = -1; + /** + * @deprecated use FIREWALL_ALLOWLIST. + */ + const int FIREWALL_WHITELIST = 0; + const int FIREWALL_ALLOWLIST = 0; + /** + * @deprecated use FIREWALL_DENYLIST. + */ + const int FIREWALL_BLACKLIST = 1; + const int FIREWALL_DENYLIST = 1; + const int FIREWALL_RULE_ALLOW = 1; + const int FIREWALL_RULE_DENY = 2; + const int FIREWALL_CHAIN_NONE = 0; + const int FIREWALL_CHAIN_DOZABLE = 1; + const int FIREWALL_CHAIN_STANDBY = 2; + const int FIREWALL_CHAIN_POWERSAVE = 3; + const int FIREWALL_CHAIN_RESTRICTED = 4; + const String IF_STATE_UP = "up"; + const String IF_STATE_DOWN = "down"; + const String IF_FLAG_BROADCAST = "broadcast"; + const String IF_FLAG_LOOPBACK = "loopback"; + const String IF_FLAG_POINTOPOINT = "point-to-point"; + const String IF_FLAG_RUNNING = "running"; + const String IF_FLAG_MULTICAST = "multicast"; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/INetdUnsolicitedEventListener.aidl new file mode 100644 index 0000000000000000000000000000000000000000..31775dfd11fe25f2fede661b7e4da11ab4ce8f91 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/INetdUnsolicitedEventListener.aidl @@ -0,0 +1,48 @@ +/** + * 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 INetdUnsolicitedEventListener { + oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid); + oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName); + oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers); + oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope); + oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope); + oneway void onInterfaceAdded(@utf8InCpp String ifName); + oneway void onInterfaceRemoved(@utf8InCpp String ifName); + oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up); + oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up); + oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName); + oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex); +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/InterfaceConfigurationParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..1869d8d49e6a59a1294c7925c460a592f945e677 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/InterfaceConfigurationParcel.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 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 */ +parcelable InterfaceConfigurationParcel { + @utf8InCpp String ifName; + @utf8InCpp String hwAddr; + @utf8InCpp String ipv4Addr; + int prefixLength; + @utf8InCpp String[] flags; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/MarkMaskParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/MarkMaskParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..8ea20d118fc8c3350249e6268348f77578a2b0b6 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/MarkMaskParcel.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 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 */ +parcelable MarkMaskParcel { + int mark; + int mask; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/NativeNetworkConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/NativeNetworkConfig.aidl new file mode 100644 index 0000000000000000000000000000000000000000..77d814b825b348f04612a11b0459150c02453c68 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/NativeNetworkConfig.aidl @@ -0,0 +1,44 @@ +/* + * 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; +/* @hide */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable NativeNetworkConfig { + int netId; + android.net.NativeNetworkType networkType = android.net.NativeNetworkType.PHYSICAL; + int permission; + boolean secure; + android.net.NativeVpnType vpnType = android.net.NativeVpnType.PLATFORM; + boolean excludeLocalRoutes = false; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/NativeNetworkType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/NativeNetworkType.aidl new file mode 100644 index 0000000000000000000000000000000000000000..e77a1432e84d7dfdb307254c9c02a2a197d772f2 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/NativeNetworkType.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; +@Backing(type="int") +enum NativeNetworkType { + PHYSICAL = 0, + VIRTUAL = 1, + PHYSICAL_LOCAL = 2, +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/NativeVpnType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/NativeVpnType.aidl new file mode 100644 index 0000000000000000000000000000000000000000..8a8be83958c80bdaa6e5401bb4411ff1e9b72938 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/NativeVpnType.aidl @@ -0,0 +1,41 @@ +/* + * 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; +@Backing(type="int") +enum NativeVpnType { + SERVICE = 1, + PLATFORM = 2, + LEGACY = 3, + OEM = 4, +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/RouteInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/RouteInfoParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..5ef95e67ca963ed343c8481d8061e0982cc8f866 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/RouteInfoParcel.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; +parcelable RouteInfoParcel { + @utf8InCpp String destination; + @utf8InCpp String ifName; + @utf8InCpp String nextHop; + int mtu; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/TetherConfigParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/TetherConfigParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..7b39c22e4ef0bef2a148578ec2a345b98f9ca706 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/TetherConfigParcel.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 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 */ +parcelable TetherConfigParcel { + boolean usingLegacyDnsProxy; + @utf8InCpp String[] dhcpRanges; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/TetherOffloadRuleParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/TetherOffloadRuleParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..983e9860ea8e94007af826844f4fbcbe3d5aee22 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/TetherOffloadRuleParcel.aidl @@ -0,0 +1,44 @@ +/* + * 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; +/* @hide */ +parcelable TetherOffloadRuleParcel { + int inputInterfaceIndex; + int outputInterfaceIndex; + byte[] destination; + int prefixLength; + byte[] srcL2Address; + byte[] dstL2Address; + int pmtu = 1500; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/TetherStatsParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..5f1b722690b0f2ee3e7ec3309edf3b1a48ab7979 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/TetherStatsParcel.aidl @@ -0,0 +1,43 @@ +/* + * 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 */ +parcelable TetherStatsParcel { + @utf8InCpp String iface; + long rxBytes; + long rxPackets; + long txBytes; + long txPackets; + int ifIndex = 0; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/UidRangeParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..72e987a2af49e9249320622f5ba9e729ef6d6309 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/UidRangeParcel.aidl @@ -0,0 +1,40 @@ +/* + * 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 */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable UidRangeParcel { + int start; + int stop; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/netd/aidl/NativeUidRangeConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/netd/aidl/NativeUidRangeConfig.aidl new file mode 100644 index 0000000000000000000000000000000000000000..9bb679f1c115a4d69683f9c1c6e3d8aead8bab75 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/netd/aidl/NativeUidRangeConfig.aidl @@ -0,0 +1,41 @@ +/* + * 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.netd.aidl; +/* @hide */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable NativeUidRangeConfig { + int netId; + android.net.UidRangeParcel[] uidRanges; + int subPriority; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/12/.hash b/staticlibs/netd/aidl_api/netd_aidl_interface/12/.hash new file mode 100644 index 0000000000000000000000000000000000000000..7a4d53c20af2371a7b9e36e2472a3e68231713ca --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/12/.hash @@ -0,0 +1 @@ +5a3a5f46fe31c118889d7a92be4faafb3df651ff diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/INetd.aidl new file mode 100644 index 0000000000000000000000000000000000000000..763b92ac60bd3732cdd988d4ca7ac9c64d2104dc --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/INetd.aidl @@ -0,0 +1,225 @@ +/** + * 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// 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 INetd { + boolean isAlive(); + boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isAllowlist, in int[] uids); + boolean bandwidthEnableDataSaver(boolean enable); + /** + * @deprecated use networkCreate() instead. + */ + void networkCreatePhysical(int netId, int permission); + /** + * @deprecated use networkCreate() instead. + */ + void networkCreateVpn(int netId, boolean secure); + void networkDestroy(int netId); + void networkAddInterface(int netId, in @utf8InCpp String iface); + void networkRemoveInterface(int netId, in @utf8InCpp String iface); + void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges); + void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges); + void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges); + void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids); + boolean tetherApplyDnsInterfaces(); + android.net.TetherStatsParcel[] tetherGetStats(); + void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength); + void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength); + @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter); + void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value); + void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid); + int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi); + void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId); + void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi); + void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket); + void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId); + void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId); + void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId); + void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName); + void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask); + void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask); + void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode); + void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel); + void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel); + void strictUidCleartextPenalty(int uid, int policyPenalty); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd control plane moved to the mainline module starting in T. See ClatCoordinator. + */ + @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd control plane moved to the mainline module starting in T. See ClatCoordinator. + */ + void clatdStop(in @utf8InCpp String ifName); + boolean ipfwdEnabled(); + @utf8InCpp String[] ipfwdGetRequesterList(); + void ipfwdEnableForwarding(in @utf8InCpp String requester); + void ipfwdDisableForwarding(in @utf8InCpp String requester); + void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface); + void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface); + void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes); + void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName); + void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes); + void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName); + void bandwidthSetGlobalAlert(long bytes); + void bandwidthAddNaughtyApp(int uid); + void bandwidthRemoveNaughtyApp(int uid); + void bandwidthAddNiceApp(int uid); + void bandwidthRemoveNiceApp(int uid); + void tetherStart(in @utf8InCpp String[] dhcpRanges); + void tetherStop(); + boolean tetherIsEnabled(); + void tetherInterfaceAdd(in @utf8InCpp String ifName); + void tetherInterfaceRemove(in @utf8InCpp String ifName); + @utf8InCpp String[] tetherInterfaceList(); + void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs); + @utf8InCpp String[] tetherDnsList(); + void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop); + void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop); + void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid); + void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid); + int networkGetDefault(); + void networkSetDefault(int netId); + void networkClearDefault(); + void networkSetPermissionForNetwork(int netId, int permission); + void networkSetPermissionForUser(int permission, in int[] uids); + void networkClearPermissionForUser(in int[] uids); + void trafficSetNetPermForUids(int permission, in int[] uids); + void networkSetProtectAllow(int uid); + void networkSetProtectDeny(int uid); + boolean networkCanProtect(int uid); + void firewallSetFirewallType(int firewalltype); + void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule); + void firewallSetUidRule(int childChain, int uid, int firewallRule); + void firewallEnableChildChain(int childChain, boolean enable); + @utf8InCpp String[] interfaceGetList(); + android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName); + void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg); + void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable); + void interfaceClearAddrs(in @utf8InCpp String ifName); + void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable); + void interfaceSetMtu(in @utf8InCpp String ifName, int mtu); + void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface); + void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface); + void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues); + void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener); + void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids); + void firewallRemoveUidInterfaceRules(in int[] uids); + void trafficSwapActiveStatsMap(); + IBinder getOemNetd(); + void tetherStartWithConfiguration(in android.net.TetherConfigParcel config); + android.net.MarkMaskParcel getFwmarkForNetwork(int netId); + void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator. + */ + void tetherOffloadRuleAdd(in android.net.TetherOffloadRuleParcel rule); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator. + */ + void tetherOffloadRuleRemove(in android.net.TetherOffloadRuleParcel rule); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator. + */ + android.net.TetherStatsParcel[] tetherOffloadGetStats(); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator. + */ + void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator. + */ + android.net.TetherStatsParcel tetherOffloadGetAndClearStats(int ifIndex); + void networkCreate(in android.net.NativeNetworkConfig config); + void networkAddUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig); + void networkRemoveUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig); + void ipSecMigrate(in android.net.IpSecMigrateInfoParcel migrateInfo); + const int IPV4 = 4; + const int IPV6 = 6; + const int CONF = 1; + const int NEIGH = 2; + const String IPSEC_INTERFACE_PREFIX = "ipsec"; + const int IPV6_ADDR_GEN_MODE_EUI64 = 0; + const int IPV6_ADDR_GEN_MODE_NONE = 1; + const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2; + const int IPV6_ADDR_GEN_MODE_RANDOM = 3; + const int IPV6_ADDR_GEN_MODE_DEFAULT = 0; + const int PENALTY_POLICY_ACCEPT = 1; + const int PENALTY_POLICY_LOG = 2; + const int PENALTY_POLICY_REJECT = 3; + const int CLAT_MARK = -559038041; + const int LOCAL_NET_ID = 99; + const int DUMMY_NET_ID = 51; + const int UNREACHABLE_NET_ID = 52; + const String NEXTHOP_NONE = ""; + const String NEXTHOP_UNREACHABLE = "unreachable"; + const String NEXTHOP_THROW = "throw"; + const int PERMISSION_NONE = 0; + const int PERMISSION_NETWORK = 1; + const int PERMISSION_SYSTEM = 2; + const int NO_PERMISSIONS = 0; + const int PERMISSION_INTERNET = 4; + const int PERMISSION_UPDATE_DEVICE_STATS = 8; + const int PERMISSION_UNINSTALLED = -1; + /** + * @deprecated use FIREWALL_ALLOWLIST. + */ + const int FIREWALL_WHITELIST = 0; + const int FIREWALL_ALLOWLIST = 0; + /** + * @deprecated use FIREWALL_DENYLIST. + */ + const int FIREWALL_BLACKLIST = 1; + const int FIREWALL_DENYLIST = 1; + const int FIREWALL_RULE_ALLOW = 1; + const int FIREWALL_RULE_DENY = 2; + const int FIREWALL_CHAIN_NONE = 0; + const int FIREWALL_CHAIN_DOZABLE = 1; + const int FIREWALL_CHAIN_STANDBY = 2; + const int FIREWALL_CHAIN_POWERSAVE = 3; + const int FIREWALL_CHAIN_RESTRICTED = 4; + const String IF_STATE_UP = "up"; + const String IF_STATE_DOWN = "down"; + const String IF_FLAG_BROADCAST = "broadcast"; + const String IF_FLAG_LOOPBACK = "loopback"; + const String IF_FLAG_POINTOPOINT = "point-to-point"; + const String IF_FLAG_RUNNING = "running"; + const String IF_FLAG_MULTICAST = "multicast"; + const int IPSEC_DIRECTION_IN = 0; + const int IPSEC_DIRECTION_OUT = 1; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/INetdUnsolicitedEventListener.aidl new file mode 100644 index 0000000000000000000000000000000000000000..31775dfd11fe25f2fede661b7e4da11ab4ce8f91 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/INetdUnsolicitedEventListener.aidl @@ -0,0 +1,48 @@ +/** + * 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 INetdUnsolicitedEventListener { + oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid); + oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName); + oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers); + oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope); + oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope); + oneway void onInterfaceAdded(@utf8InCpp String ifName); + oneway void onInterfaceRemoved(@utf8InCpp String ifName); + oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up); + oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up); + oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName); + oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex); +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/InterfaceConfigurationParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..1869d8d49e6a59a1294c7925c460a592f945e677 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/InterfaceConfigurationParcel.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 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 */ +parcelable InterfaceConfigurationParcel { + @utf8InCpp String ifName; + @utf8InCpp String hwAddr; + @utf8InCpp String ipv4Addr; + int prefixLength; + @utf8InCpp String[] flags; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/IpSecMigrateInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/IpSecMigrateInfoParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..975a26199d52bb532523ed86d050118dac35a102 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/IpSecMigrateInfoParcel.aidl @@ -0,0 +1,45 @@ +/** + * 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; +@JavaOnlyImmutable +parcelable IpSecMigrateInfoParcel { + int requestId; + int selAddrFamily; + int direction; + @utf8InCpp String oldSourceAddress; + @utf8InCpp String oldDestinationAddress; + @utf8InCpp String newSourceAddress; + @utf8InCpp String newDestinationAddress; + int interfaceId; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/MarkMaskParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/MarkMaskParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..8ea20d118fc8c3350249e6268348f77578a2b0b6 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/MarkMaskParcel.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 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 */ +parcelable MarkMaskParcel { + int mark; + int mask; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/NativeNetworkConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/NativeNetworkConfig.aidl new file mode 100644 index 0000000000000000000000000000000000000000..77d814b825b348f04612a11b0459150c02453c68 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/NativeNetworkConfig.aidl @@ -0,0 +1,44 @@ +/* + * 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; +/* @hide */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable NativeNetworkConfig { + int netId; + android.net.NativeNetworkType networkType = android.net.NativeNetworkType.PHYSICAL; + int permission; + boolean secure; + android.net.NativeVpnType vpnType = android.net.NativeVpnType.PLATFORM; + boolean excludeLocalRoutes = false; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/NativeNetworkType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/NativeNetworkType.aidl new file mode 100644 index 0000000000000000000000000000000000000000..e77a1432e84d7dfdb307254c9c02a2a197d772f2 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/NativeNetworkType.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; +@Backing(type="int") +enum NativeNetworkType { + PHYSICAL = 0, + VIRTUAL = 1, + PHYSICAL_LOCAL = 2, +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/NativeVpnType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/NativeVpnType.aidl new file mode 100644 index 0000000000000000000000000000000000000000..8a8be83958c80bdaa6e5401bb4411ff1e9b72938 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/NativeVpnType.aidl @@ -0,0 +1,41 @@ +/* + * 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; +@Backing(type="int") +enum NativeVpnType { + SERVICE = 1, + PLATFORM = 2, + LEGACY = 3, + OEM = 4, +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/RouteInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/RouteInfoParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..5ef95e67ca963ed343c8481d8061e0982cc8f866 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/RouteInfoParcel.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; +parcelable RouteInfoParcel { + @utf8InCpp String destination; + @utf8InCpp String ifName; + @utf8InCpp String nextHop; + int mtu; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/TetherConfigParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/TetherConfigParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..7b39c22e4ef0bef2a148578ec2a345b98f9ca706 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/TetherConfigParcel.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 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 */ +parcelable TetherConfigParcel { + boolean usingLegacyDnsProxy; + @utf8InCpp String[] dhcpRanges; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/TetherOffloadRuleParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/TetherOffloadRuleParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..983e9860ea8e94007af826844f4fbcbe3d5aee22 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/TetherOffloadRuleParcel.aidl @@ -0,0 +1,44 @@ +/* + * 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; +/* @hide */ +parcelable TetherOffloadRuleParcel { + int inputInterfaceIndex; + int outputInterfaceIndex; + byte[] destination; + int prefixLength; + byte[] srcL2Address; + byte[] dstL2Address; + int pmtu = 1500; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/TetherStatsParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..5f1b722690b0f2ee3e7ec3309edf3b1a48ab7979 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/TetherStatsParcel.aidl @@ -0,0 +1,43 @@ +/* + * 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 */ +parcelable TetherStatsParcel { + @utf8InCpp String iface; + long rxBytes; + long rxPackets; + long txBytes; + long txPackets; + int ifIndex = 0; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/UidRangeParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..72e987a2af49e9249320622f5ba9e729ef6d6309 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/UidRangeParcel.aidl @@ -0,0 +1,40 @@ +/* + * 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 */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable UidRangeParcel { + int start; + int stop; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/netd/aidl/NativeUidRangeConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/netd/aidl/NativeUidRangeConfig.aidl new file mode 100644 index 0000000000000000000000000000000000000000..9bb679f1c115a4d69683f9c1c6e3d8aead8bab75 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/netd/aidl/NativeUidRangeConfig.aidl @@ -0,0 +1,41 @@ +/* + * 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.netd.aidl; +/* @hide */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable NativeUidRangeConfig { + int netId; + android.net.UidRangeParcel[] uidRanges; + int subPriority; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/13/.hash b/staticlibs/netd/aidl_api/netd_aidl_interface/13/.hash new file mode 100644 index 0000000000000000000000000000000000000000..d3c72cfb1e0c53354f4fbb4616a93c4bdef488dc --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/13/.hash @@ -0,0 +1 @@ +38614f80a23b92603d4851177e57c460aec1b606 diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/INetd.aidl new file mode 100644 index 0000000000000000000000000000000000000000..3507784723c3f638133143083462dbe045fa1329 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/INetd.aidl @@ -0,0 +1,226 @@ +/** + * 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// 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 INetd { + boolean isAlive(); + boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isAllowlist, in int[] uids); + boolean bandwidthEnableDataSaver(boolean enable); + /** + * @deprecated use networkCreate() instead. + */ + void networkCreatePhysical(int netId, int permission); + /** + * @deprecated use networkCreate() instead. + */ + void networkCreateVpn(int netId, boolean secure); + void networkDestroy(int netId); + void networkAddInterface(int netId, in @utf8InCpp String iface); + void networkRemoveInterface(int netId, in @utf8InCpp String iface); + void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges); + void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges); + void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges); + void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids); + boolean tetherApplyDnsInterfaces(); + android.net.TetherStatsParcel[] tetherGetStats(); + void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength); + void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength); + @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter); + void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value); + void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid); + int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi); + void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId); + void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi); + void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket); + void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId); + void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId); + void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId); + void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName); + void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask); + void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask); + void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode); + void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel); + void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel); + void strictUidCleartextPenalty(int uid, int policyPenalty); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd control plane moved to the mainline module starting in T. See ClatCoordinator. + */ + @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd control plane moved to the mainline module starting in T. See ClatCoordinator. + */ + void clatdStop(in @utf8InCpp String ifName); + boolean ipfwdEnabled(); + @utf8InCpp String[] ipfwdGetRequesterList(); + void ipfwdEnableForwarding(in @utf8InCpp String requester); + void ipfwdDisableForwarding(in @utf8InCpp String requester); + void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface); + void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface); + void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes); + void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName); + void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes); + void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName); + void bandwidthSetGlobalAlert(long bytes); + void bandwidthAddNaughtyApp(int uid); + void bandwidthRemoveNaughtyApp(int uid); + void bandwidthAddNiceApp(int uid); + void bandwidthRemoveNiceApp(int uid); + void tetherStart(in @utf8InCpp String[] dhcpRanges); + void tetherStop(); + boolean tetherIsEnabled(); + void tetherInterfaceAdd(in @utf8InCpp String ifName); + void tetherInterfaceRemove(in @utf8InCpp String ifName); + @utf8InCpp String[] tetherInterfaceList(); + void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs); + @utf8InCpp String[] tetherDnsList(); + void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop); + void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop); + void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid); + void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid); + int networkGetDefault(); + void networkSetDefault(int netId); + void networkClearDefault(); + void networkSetPermissionForNetwork(int netId, int permission); + void networkSetPermissionForUser(int permission, in int[] uids); + void networkClearPermissionForUser(in int[] uids); + void trafficSetNetPermForUids(int permission, in int[] uids); + void networkSetProtectAllow(int uid); + void networkSetProtectDeny(int uid); + boolean networkCanProtect(int uid); + void firewallSetFirewallType(int firewalltype); + void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule); + void firewallSetUidRule(int childChain, int uid, int firewallRule); + void firewallEnableChildChain(int childChain, boolean enable); + @utf8InCpp String[] interfaceGetList(); + android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName); + void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg); + void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable); + void interfaceClearAddrs(in @utf8InCpp String ifName); + void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable); + void interfaceSetMtu(in @utf8InCpp String ifName, int mtu); + void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface); + void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface); + void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues); + void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener); + void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids); + void firewallRemoveUidInterfaceRules(in int[] uids); + void trafficSwapActiveStatsMap(); + IBinder getOemNetd(); + void tetherStartWithConfiguration(in android.net.TetherConfigParcel config); + android.net.MarkMaskParcel getFwmarkForNetwork(int netId); + void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator. + */ + void tetherOffloadRuleAdd(in android.net.TetherOffloadRuleParcel rule); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator. + */ + void tetherOffloadRuleRemove(in android.net.TetherOffloadRuleParcel rule); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator. + */ + android.net.TetherStatsParcel[] tetherOffloadGetStats(); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator. + */ + void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator. + */ + android.net.TetherStatsParcel tetherOffloadGetAndClearStats(int ifIndex); + void networkCreate(in android.net.NativeNetworkConfig config); + void networkAddUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig); + void networkRemoveUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig); + void ipSecMigrate(in android.net.IpSecMigrateInfoParcel migrateInfo); + void setNetworkAllowlist(in android.net.netd.aidl.NativeUidRangeConfig[] allowedNetworks); + const int IPV4 = 4; + const int IPV6 = 6; + const int CONF = 1; + const int NEIGH = 2; + const String IPSEC_INTERFACE_PREFIX = "ipsec"; + const int IPV6_ADDR_GEN_MODE_EUI64 = 0; + const int IPV6_ADDR_GEN_MODE_NONE = 1; + const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2; + const int IPV6_ADDR_GEN_MODE_RANDOM = 3; + const int IPV6_ADDR_GEN_MODE_DEFAULT = 0; + const int PENALTY_POLICY_ACCEPT = 1; + const int PENALTY_POLICY_LOG = 2; + const int PENALTY_POLICY_REJECT = 3; + const int CLAT_MARK = 0xdeadc1a7; + const int LOCAL_NET_ID = 99; + const int DUMMY_NET_ID = 51; + const int UNREACHABLE_NET_ID = 52; + const String NEXTHOP_NONE = ""; + const String NEXTHOP_UNREACHABLE = "unreachable"; + const String NEXTHOP_THROW = "throw"; + const int PERMISSION_NONE = 0; + const int PERMISSION_NETWORK = 1; + const int PERMISSION_SYSTEM = 2; + const int NO_PERMISSIONS = 0; + const int PERMISSION_INTERNET = 4; + const int PERMISSION_UPDATE_DEVICE_STATS = 8; + const int PERMISSION_UNINSTALLED = (-1); + /** + * @deprecated use FIREWALL_ALLOWLIST. + */ + const int FIREWALL_WHITELIST = 0; + const int FIREWALL_ALLOWLIST = 0; + /** + * @deprecated use FIREWALL_DENYLIST. + */ + const int FIREWALL_BLACKLIST = 1; + const int FIREWALL_DENYLIST = 1; + const int FIREWALL_RULE_ALLOW = 1; + const int FIREWALL_RULE_DENY = 2; + const int FIREWALL_CHAIN_NONE = 0; + const int FIREWALL_CHAIN_DOZABLE = 1; + const int FIREWALL_CHAIN_STANDBY = 2; + const int FIREWALL_CHAIN_POWERSAVE = 3; + const int FIREWALL_CHAIN_RESTRICTED = 4; + const String IF_STATE_UP = "up"; + const String IF_STATE_DOWN = "down"; + const String IF_FLAG_BROADCAST = "broadcast"; + const String IF_FLAG_LOOPBACK = "loopback"; + const String IF_FLAG_POINTOPOINT = "point-to-point"; + const String IF_FLAG_RUNNING = "running"; + const String IF_FLAG_MULTICAST = "multicast"; + const int IPSEC_DIRECTION_IN = 0; + const int IPSEC_DIRECTION_OUT = 1; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/INetdUnsolicitedEventListener.aidl new file mode 100644 index 0000000000000000000000000000000000000000..31775dfd11fe25f2fede661b7e4da11ab4ce8f91 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/INetdUnsolicitedEventListener.aidl @@ -0,0 +1,48 @@ +/** + * 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 INetdUnsolicitedEventListener { + oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid); + oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName); + oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers); + oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope); + oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope); + oneway void onInterfaceAdded(@utf8InCpp String ifName); + oneway void onInterfaceRemoved(@utf8InCpp String ifName); + oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up); + oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up); + oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName); + oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex); +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/InterfaceConfigurationParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..1869d8d49e6a59a1294c7925c460a592f945e677 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/InterfaceConfigurationParcel.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 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 */ +parcelable InterfaceConfigurationParcel { + @utf8InCpp String ifName; + @utf8InCpp String hwAddr; + @utf8InCpp String ipv4Addr; + int prefixLength; + @utf8InCpp String[] flags; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/IpSecMigrateInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/IpSecMigrateInfoParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..975a26199d52bb532523ed86d050118dac35a102 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/IpSecMigrateInfoParcel.aidl @@ -0,0 +1,45 @@ +/** + * 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; +@JavaOnlyImmutable +parcelable IpSecMigrateInfoParcel { + int requestId; + int selAddrFamily; + int direction; + @utf8InCpp String oldSourceAddress; + @utf8InCpp String oldDestinationAddress; + @utf8InCpp String newSourceAddress; + @utf8InCpp String newDestinationAddress; + int interfaceId; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/MarkMaskParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/MarkMaskParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..8ea20d118fc8c3350249e6268348f77578a2b0b6 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/MarkMaskParcel.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 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 */ +parcelable MarkMaskParcel { + int mark; + int mask; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/NativeNetworkConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/NativeNetworkConfig.aidl new file mode 100644 index 0000000000000000000000000000000000000000..77d814b825b348f04612a11b0459150c02453c68 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/NativeNetworkConfig.aidl @@ -0,0 +1,44 @@ +/* + * 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; +/* @hide */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable NativeNetworkConfig { + int netId; + android.net.NativeNetworkType networkType = android.net.NativeNetworkType.PHYSICAL; + int permission; + boolean secure; + android.net.NativeVpnType vpnType = android.net.NativeVpnType.PLATFORM; + boolean excludeLocalRoutes = false; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/NativeNetworkType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/NativeNetworkType.aidl new file mode 100644 index 0000000000000000000000000000000000000000..e77a1432e84d7dfdb307254c9c02a2a197d772f2 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/NativeNetworkType.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; +@Backing(type="int") +enum NativeNetworkType { + PHYSICAL = 0, + VIRTUAL = 1, + PHYSICAL_LOCAL = 2, +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/NativeVpnType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/NativeVpnType.aidl new file mode 100644 index 0000000000000000000000000000000000000000..8a8be83958c80bdaa6e5401bb4411ff1e9b72938 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/NativeVpnType.aidl @@ -0,0 +1,41 @@ +/* + * 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; +@Backing(type="int") +enum NativeVpnType { + SERVICE = 1, + PLATFORM = 2, + LEGACY = 3, + OEM = 4, +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/RouteInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/RouteInfoParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..5ef95e67ca963ed343c8481d8061e0982cc8f866 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/RouteInfoParcel.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; +parcelable RouteInfoParcel { + @utf8InCpp String destination; + @utf8InCpp String ifName; + @utf8InCpp String nextHop; + int mtu; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/TetherConfigParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/TetherConfigParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..7b39c22e4ef0bef2a148578ec2a345b98f9ca706 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/TetherConfigParcel.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 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 */ +parcelable TetherConfigParcel { + boolean usingLegacyDnsProxy; + @utf8InCpp String[] dhcpRanges; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/TetherOffloadRuleParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/TetherOffloadRuleParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..983e9860ea8e94007af826844f4fbcbe3d5aee22 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/TetherOffloadRuleParcel.aidl @@ -0,0 +1,44 @@ +/* + * 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; +/* @hide */ +parcelable TetherOffloadRuleParcel { + int inputInterfaceIndex; + int outputInterfaceIndex; + byte[] destination; + int prefixLength; + byte[] srcL2Address; + byte[] dstL2Address; + int pmtu = 1500; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/TetherStatsParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..5f1b722690b0f2ee3e7ec3309edf3b1a48ab7979 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/TetherStatsParcel.aidl @@ -0,0 +1,43 @@ +/* + * 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 */ +parcelable TetherStatsParcel { + @utf8InCpp String iface; + long rxBytes; + long rxPackets; + long txBytes; + long txPackets; + int ifIndex = 0; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/UidRangeParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..72e987a2af49e9249320622f5ba9e729ef6d6309 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/UidRangeParcel.aidl @@ -0,0 +1,40 @@ +/* + * 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 */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable UidRangeParcel { + int start; + int stop; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/netd/aidl/NativeUidRangeConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/netd/aidl/NativeUidRangeConfig.aidl new file mode 100644 index 0000000000000000000000000000000000000000..9bb679f1c115a4d69683f9c1c6e3d8aead8bab75 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/netd/aidl/NativeUidRangeConfig.aidl @@ -0,0 +1,41 @@ +/* + * 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.netd.aidl; +/* @hide */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable NativeUidRangeConfig { + int netId; + android.net.UidRangeParcel[] uidRanges; + int subPriority; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/2/.hash b/staticlibs/netd/aidl_api/netd_aidl_interface/2/.hash new file mode 100644 index 0000000000000000000000000000000000000000..5fc5b2d7ed193bfabd2b9f939cf75c420f0fdf72 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/2/.hash @@ -0,0 +1 @@ +e395d63302c47e7d2dac0d503045779029ff598b diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/2/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/2/android/net/INetd.aidl new file mode 100644 index 0000000000000000000000000000000000000000..0e2d5f436545354d0209d7489539838900327ae5 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/2/android/net/INetd.aidl @@ -0,0 +1,153 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not +// try to edit this file. It looks like you are doing that because you have +// modified an AIDL interface in a backward-incompatible way, e.g., deleting a +// function from an interface or a field from a parcelable and it broke the +// build. That breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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; +interface INetd { + boolean isAlive(); + boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isWhitelist, in int[] uids); + boolean bandwidthEnableDataSaver(boolean enable); + void networkCreatePhysical(int netId, int permission); + void networkCreateVpn(int netId, boolean secure); + void networkDestroy(int netId); + void networkAddInterface(int netId, in @utf8InCpp String iface); + void networkRemoveInterface(int netId, in @utf8InCpp String iface); + void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges); + void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges); + void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges); + void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids); + boolean tetherApplyDnsInterfaces(); + android.net.TetherStatsParcel[] tetherGetStats(); + void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength); + void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength); + @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter); + void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value); + void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid); + int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi); + void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId); + void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi); + void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket); + void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId); + void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId); + void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId); + void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName); + void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask); + void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask); + void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode); + void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel); + void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel); + void strictUidCleartextPenalty(int uid, int policyPenalty); + @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix); + void clatdStop(in @utf8InCpp String ifName); + boolean ipfwdEnabled(); + @utf8InCpp String[] ipfwdGetRequesterList(); + void ipfwdEnableForwarding(in @utf8InCpp String requester); + void ipfwdDisableForwarding(in @utf8InCpp String requester); + void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface); + void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface); + void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes); + void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName); + void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes); + void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName); + void bandwidthSetGlobalAlert(long bytes); + void bandwidthAddNaughtyApp(int uid); + void bandwidthRemoveNaughtyApp(int uid); + void bandwidthAddNiceApp(int uid); + void bandwidthRemoveNiceApp(int uid); + void tetherStart(in @utf8InCpp String[] dhcpRanges); + void tetherStop(); + boolean tetherIsEnabled(); + void tetherInterfaceAdd(in @utf8InCpp String ifName); + void tetherInterfaceRemove(in @utf8InCpp String ifName); + @utf8InCpp String[] tetherInterfaceList(); + void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs); + @utf8InCpp String[] tetherDnsList(); + void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop); + void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop); + void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid); + void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid); + int networkGetDefault(); + void networkSetDefault(int netId); + void networkClearDefault(); + void networkSetPermissionForNetwork(int netId, int permission); + void networkSetPermissionForUser(int permission, in int[] uids); + void networkClearPermissionForUser(in int[] uids); + void trafficSetNetPermForUids(int permission, in int[] uids); + void networkSetProtectAllow(int uid); + void networkSetProtectDeny(int uid); + boolean networkCanProtect(int uid); + void firewallSetFirewallType(int firewalltype); + void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule); + void firewallSetUidRule(int childChain, int uid, int firewallRule); + void firewallEnableChildChain(int childChain, boolean enable); + @utf8InCpp String[] interfaceGetList(); + android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName); + void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg); + void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable); + void interfaceClearAddrs(in @utf8InCpp String ifName); + void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable); + void interfaceSetMtu(in @utf8InCpp String ifName, int mtu); + void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface); + void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface); + void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues); + void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener); + void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids); + void firewallRemoveUidInterfaceRules(in int[] uids); + void trafficSwapActiveStatsMap(); + IBinder getOemNetd(); + const int IPV4 = 4; + const int IPV6 = 6; + const int CONF = 1; + const int NEIGH = 2; + const String IPSEC_INTERFACE_PREFIX = "ipsec"; + const int IPV6_ADDR_GEN_MODE_EUI64 = 0; + const int IPV6_ADDR_GEN_MODE_NONE = 1; + const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2; + const int IPV6_ADDR_GEN_MODE_RANDOM = 3; + const int IPV6_ADDR_GEN_MODE_DEFAULT = 0; + const int PENALTY_POLICY_ACCEPT = 1; + const int PENALTY_POLICY_LOG = 2; + const int PENALTY_POLICY_REJECT = 3; + const int LOCAL_NET_ID = 99; + const String NEXTHOP_NONE = ""; + const String NEXTHOP_UNREACHABLE = "unreachable"; + const String NEXTHOP_THROW = "throw"; + const int PERMISSION_NONE = 0; + const int PERMISSION_NETWORK = 1; + const int PERMISSION_SYSTEM = 2; + const int NO_PERMISSIONS = 0; + const int PERMISSION_INTERNET = 4; + const int PERMISSION_UPDATE_DEVICE_STATS = 8; + const int PERMISSION_UNINSTALLED = -1; + const int FIREWALL_WHITELIST = 0; + const int FIREWALL_BLACKLIST = 1; + const int FIREWALL_RULE_ALLOW = 1; + const int FIREWALL_RULE_DENY = 2; + const int FIREWALL_CHAIN_NONE = 0; + const int FIREWALL_CHAIN_DOZABLE = 1; + const int FIREWALL_CHAIN_STANDBY = 2; + const int FIREWALL_CHAIN_POWERSAVE = 3; + const String IF_STATE_UP = "up"; + const String IF_STATE_DOWN = "down"; + const String IF_FLAG_BROADCAST = "broadcast"; + const String IF_FLAG_LOOPBACK = "loopback"; + const String IF_FLAG_POINTOPOINT = "point-to-point"; + const String IF_FLAG_RUNNING = "running"; + const String IF_FLAG_MULTICAST = "multicast"; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/2/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/2/android/net/INetdUnsolicitedEventListener.aidl new file mode 100644 index 0000000000000000000000000000000000000000..621f1cf86571b7c7d8774849d1f17d331dc0c3c3 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/2/android/net/INetdUnsolicitedEventListener.aidl @@ -0,0 +1,31 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not +// try to edit this file. It looks like you are doing that because you have +// modified an AIDL interface in a backward-incompatible way, e.g., deleting a +// function from an interface or a field from a parcelable and it broke the +// build. That breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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; +interface INetdUnsolicitedEventListener { + oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid); + oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName); + oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers); + oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope); + oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope); + oneway void onInterfaceAdded(@utf8InCpp String ifName); + oneway void onInterfaceRemoved(@utf8InCpp String ifName); + oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up); + oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up); + oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName); + oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex); +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/2/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/2/android/net/InterfaceConfigurationParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..18de61f32f1083edc043c4fa4ef0d8bcb6390e06 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/2/android/net/InterfaceConfigurationParcel.aidl @@ -0,0 +1,25 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not +// try to edit this file. It looks like you are doing that because you have +// modified an AIDL interface in a backward-incompatible way, e.g., deleting a +// function from an interface or a field from a parcelable and it broke the +// build. That breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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; +parcelable InterfaceConfigurationParcel { + @utf8InCpp String ifName; + @utf8InCpp String hwAddr; + @utf8InCpp String ipv4Addr; + int prefixLength; + @utf8InCpp String[] flags; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/2/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/2/android/net/TetherStatsParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..c0ba67666a9ac79ded77e908d9c9fa07cc17d95f --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/2/android/net/TetherStatsParcel.aidl @@ -0,0 +1,25 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not +// try to edit this file. It looks like you are doing that because you have +// modified an AIDL interface in a backward-incompatible way, e.g., deleting a +// function from an interface or a field from a parcelable and it broke the +// build. That breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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; +parcelable TetherStatsParcel { + @utf8InCpp String iface; + long rxBytes; + long rxPackets; + long txBytes; + long txPackets; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/2/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/2/android/net/UidRangeParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..c2c35db2d387a0886bb95a4fe5b618bfe7a59411 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/2/android/net/UidRangeParcel.aidl @@ -0,0 +1,22 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not +// try to edit this file. It looks like you are doing that because you have +// modified an AIDL interface in a backward-incompatible way, e.g., deleting a +// function from an interface or a field from a parcelable and it broke the +// build. That breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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; +parcelable UidRangeParcel { + int start; + int stop; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/3/.hash b/staticlibs/netd/aidl_api/netd_aidl_interface/3/.hash new file mode 100644 index 0000000000000000000000000000000000000000..59cf70898dac204cd58ffc3f27b42163836994de --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/3/.hash @@ -0,0 +1 @@ +e17c1f9b2068b539b22e3a4a447edea3c80aee4b diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/INetd.aidl new file mode 100644 index 0000000000000000000000000000000000000000..135b7385088d799d7da17addfa8ec0a80001927b --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/INetd.aidl @@ -0,0 +1,161 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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 INetd { + boolean isAlive(); + boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isWhitelist, in int[] uids); + boolean bandwidthEnableDataSaver(boolean enable); + void networkCreatePhysical(int netId, int permission); + void networkCreateVpn(int netId, boolean secure); + void networkDestroy(int netId); + void networkAddInterface(int netId, in @utf8InCpp String iface); + void networkRemoveInterface(int netId, in @utf8InCpp String iface); + void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges); + void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges); + void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges); + void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids); + boolean tetherApplyDnsInterfaces(); + android.net.TetherStatsParcel[] tetherGetStats(); + void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength); + void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength); + @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter); + void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value); + void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid); + int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi); + void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId); + void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi); + void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket); + void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId); + void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId); + void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId); + void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName); + void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask); + void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask); + void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode); + void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel); + void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel); + void strictUidCleartextPenalty(int uid, int policyPenalty); + @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix); + void clatdStop(in @utf8InCpp String ifName); + boolean ipfwdEnabled(); + @utf8InCpp String[] ipfwdGetRequesterList(); + void ipfwdEnableForwarding(in @utf8InCpp String requester); + void ipfwdDisableForwarding(in @utf8InCpp String requester); + void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface); + void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface); + void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes); + void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName); + void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes); + void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName); + void bandwidthSetGlobalAlert(long bytes); + void bandwidthAddNaughtyApp(int uid); + void bandwidthRemoveNaughtyApp(int uid); + void bandwidthAddNiceApp(int uid); + void bandwidthRemoveNiceApp(int uid); + void tetherStart(in @utf8InCpp String[] dhcpRanges); + void tetherStop(); + boolean tetherIsEnabled(); + void tetherInterfaceAdd(in @utf8InCpp String ifName); + void tetherInterfaceRemove(in @utf8InCpp String ifName); + @utf8InCpp String[] tetherInterfaceList(); + void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs); + @utf8InCpp String[] tetherDnsList(); + void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop); + void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop); + void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid); + void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid); + int networkGetDefault(); + void networkSetDefault(int netId); + void networkClearDefault(); + void networkSetPermissionForNetwork(int netId, int permission); + void networkSetPermissionForUser(int permission, in int[] uids); + void networkClearPermissionForUser(in int[] uids); + void trafficSetNetPermForUids(int permission, in int[] uids); + void networkSetProtectAllow(int uid); + void networkSetProtectDeny(int uid); + boolean networkCanProtect(int uid); + void firewallSetFirewallType(int firewalltype); + void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule); + void firewallSetUidRule(int childChain, int uid, int firewallRule); + void firewallEnableChildChain(int childChain, boolean enable); + @utf8InCpp String[] interfaceGetList(); + android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName); + void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg); + void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable); + void interfaceClearAddrs(in @utf8InCpp String ifName); + void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable); + void interfaceSetMtu(in @utf8InCpp String ifName, int mtu); + void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface); + void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface); + void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues); + void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener); + void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids); + void firewallRemoveUidInterfaceRules(in int[] uids); + void trafficSwapActiveStatsMap(); + IBinder getOemNetd(); + void tetherStartWithConfiguration(in android.net.TetherConfigParcel config); + android.net.MarkMaskParcel getFwmarkForNetwork(int netId); + void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + void tetherOffloadRuleAdd(in android.net.TetherOffloadRuleParcel rule); + void tetherOffloadRuleRemove(in android.net.TetherOffloadRuleParcel rule); + const int IPV4 = 4; + const int IPV6 = 6; + const int CONF = 1; + const int NEIGH = 2; + const String IPSEC_INTERFACE_PREFIX = "ipsec"; + const int IPV6_ADDR_GEN_MODE_EUI64 = 0; + const int IPV6_ADDR_GEN_MODE_NONE = 1; + const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2; + const int IPV6_ADDR_GEN_MODE_RANDOM = 3; + const int IPV6_ADDR_GEN_MODE_DEFAULT = 0; + const int PENALTY_POLICY_ACCEPT = 1; + const int PENALTY_POLICY_LOG = 2; + const int PENALTY_POLICY_REJECT = 3; + const int LOCAL_NET_ID = 99; + const String NEXTHOP_NONE = ""; + const String NEXTHOP_UNREACHABLE = "unreachable"; + const String NEXTHOP_THROW = "throw"; + const int PERMISSION_NONE = 0; + const int PERMISSION_NETWORK = 1; + const int PERMISSION_SYSTEM = 2; + const int NO_PERMISSIONS = 0; + const int PERMISSION_INTERNET = 4; + const int PERMISSION_UPDATE_DEVICE_STATS = 8; + const int PERMISSION_UNINSTALLED = -1; + const int FIREWALL_WHITELIST = 0; + const int FIREWALL_BLACKLIST = 1; + const int FIREWALL_RULE_ALLOW = 1; + const int FIREWALL_RULE_DENY = 2; + const int FIREWALL_CHAIN_NONE = 0; + const int FIREWALL_CHAIN_DOZABLE = 1; + const int FIREWALL_CHAIN_STANDBY = 2; + const int FIREWALL_CHAIN_POWERSAVE = 3; + const String IF_STATE_UP = "up"; + const String IF_STATE_DOWN = "down"; + const String IF_FLAG_BROADCAST = "broadcast"; + const String IF_FLAG_LOOPBACK = "loopback"; + const String IF_FLAG_POINTOPOINT = "point-to-point"; + const String IF_FLAG_RUNNING = "running"; + const String IF_FLAG_MULTICAST = "multicast"; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/INetdUnsolicitedEventListener.aidl new file mode 100644 index 0000000000000000000000000000000000000000..44593632171c57ab29ec437c7792bb9271ac030b --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/INetdUnsolicitedEventListener.aidl @@ -0,0 +1,32 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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 INetdUnsolicitedEventListener { + oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid); + oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName); + oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers); + oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope); + oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope); + oneway void onInterfaceAdded(@utf8InCpp String ifName); + oneway void onInterfaceRemoved(@utf8InCpp String ifName); + oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up); + oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up); + oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName); + oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex); +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/InterfaceConfigurationParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..01e0f955414128cc47311f0a5fdd9977a229bb82 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/InterfaceConfigurationParcel.aidl @@ -0,0 +1,26 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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 */ +parcelable InterfaceConfigurationParcel { + @utf8InCpp String ifName; + @utf8InCpp String hwAddr; + @utf8InCpp String ipv4Addr; + int prefixLength; + @utf8InCpp String[] flags; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/MarkMaskParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/MarkMaskParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..62be8384209429d97b2bf40100a973296cdcf162 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/MarkMaskParcel.aidl @@ -0,0 +1,23 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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 */ +parcelable MarkMaskParcel { + int mark; + int mask; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/RouteInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/RouteInfoParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..5e0ee62a0fb70234aef03c0d3d40431c8302dccc --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/RouteInfoParcel.aidl @@ -0,0 +1,24 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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; +parcelable RouteInfoParcel { + @utf8InCpp String destination; + @utf8InCpp String ifName; + @utf8InCpp String nextHop; + int mtu; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/TetherConfigParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/TetherConfigParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..b13645450ac0b8e0bde093a18eda0e342e814c63 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/TetherConfigParcel.aidl @@ -0,0 +1,23 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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 */ +parcelable TetherConfigParcel { + boolean usingLegacyDnsProxy; + @utf8InCpp String[] dhcpRanges; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/TetherOffloadRuleParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/TetherOffloadRuleParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..3abf0f89a3585cf6ea1f8fcc0d93d34d773ab617 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/TetherOffloadRuleParcel.aidl @@ -0,0 +1,27 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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 */ +parcelable TetherOffloadRuleParcel { + int inputInterfaceIndex; + int outputInterfaceIndex; + byte[] destination; + int prefixLength; + byte[] srcL2Address; + byte[] dstL2Address; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/TetherStatsParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..71ffb9b964cbabbd3f921e73154488f5ccdc4b2d --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/TetherStatsParcel.aidl @@ -0,0 +1,26 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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 */ +parcelable TetherStatsParcel { + @utf8InCpp String iface; + long rxBytes; + long rxPackets; + long txBytes; + long txPackets; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/UidRangeParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..84ff4572dd9cba34b30d9319f9c5c9facf5ec45e --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/UidRangeParcel.aidl @@ -0,0 +1,23 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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 */ +parcelable UidRangeParcel { + int start; + int stop; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/4/.hash b/staticlibs/netd/aidl_api/netd_aidl_interface/4/.hash new file mode 100644 index 0000000000000000000000000000000000000000..0c3f810e67a7ba792343a4d5561957a78a1d5c3b --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/4/.hash @@ -0,0 +1 @@ +63adaa5098e4d8621e90c5a84f7cb93505c79311 diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/INetd.aidl new file mode 100644 index 0000000000000000000000000000000000000000..47e2931d7caa65787235da0fba6631403386b665 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/INetd.aidl @@ -0,0 +1,164 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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 INetd { + boolean isAlive(); + boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isWhitelist, in int[] uids); + boolean bandwidthEnableDataSaver(boolean enable); + void networkCreatePhysical(int netId, int permission); + void networkCreateVpn(int netId, boolean secure); + void networkDestroy(int netId); + void networkAddInterface(int netId, in @utf8InCpp String iface); + void networkRemoveInterface(int netId, in @utf8InCpp String iface); + void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges); + void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges); + void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges); + void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids); + boolean tetherApplyDnsInterfaces(); + android.net.TetherStatsParcel[] tetherGetStats(); + void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength); + void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength); + @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter); + void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value); + void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid); + int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi); + void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId); + void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi); + void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket); + void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId); + void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId); + void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId); + void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName); + void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask); + void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask); + void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode); + void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel); + void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel); + void strictUidCleartextPenalty(int uid, int policyPenalty); + @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix); + void clatdStop(in @utf8InCpp String ifName); + boolean ipfwdEnabled(); + @utf8InCpp String[] ipfwdGetRequesterList(); + void ipfwdEnableForwarding(in @utf8InCpp String requester); + void ipfwdDisableForwarding(in @utf8InCpp String requester); + void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface); + void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface); + void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes); + void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName); + void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes); + void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName); + void bandwidthSetGlobalAlert(long bytes); + void bandwidthAddNaughtyApp(int uid); + void bandwidthRemoveNaughtyApp(int uid); + void bandwidthAddNiceApp(int uid); + void bandwidthRemoveNiceApp(int uid); + void tetherStart(in @utf8InCpp String[] dhcpRanges); + void tetherStop(); + boolean tetherIsEnabled(); + void tetherInterfaceAdd(in @utf8InCpp String ifName); + void tetherInterfaceRemove(in @utf8InCpp String ifName); + @utf8InCpp String[] tetherInterfaceList(); + void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs); + @utf8InCpp String[] tetherDnsList(); + void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop); + void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop); + void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid); + void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid); + int networkGetDefault(); + void networkSetDefault(int netId); + void networkClearDefault(); + void networkSetPermissionForNetwork(int netId, int permission); + void networkSetPermissionForUser(int permission, in int[] uids); + void networkClearPermissionForUser(in int[] uids); + void trafficSetNetPermForUids(int permission, in int[] uids); + void networkSetProtectAllow(int uid); + void networkSetProtectDeny(int uid); + boolean networkCanProtect(int uid); + void firewallSetFirewallType(int firewalltype); + void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule); + void firewallSetUidRule(int childChain, int uid, int firewallRule); + void firewallEnableChildChain(int childChain, boolean enable); + @utf8InCpp String[] interfaceGetList(); + android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName); + void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg); + void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable); + void interfaceClearAddrs(in @utf8InCpp String ifName); + void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable); + void interfaceSetMtu(in @utf8InCpp String ifName, int mtu); + void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface); + void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface); + void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues); + void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener); + void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids); + void firewallRemoveUidInterfaceRules(in int[] uids); + void trafficSwapActiveStatsMap(); + IBinder getOemNetd(); + void tetherStartWithConfiguration(in android.net.TetherConfigParcel config); + android.net.MarkMaskParcel getFwmarkForNetwork(int netId); + void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + void tetherOffloadRuleAdd(in android.net.TetherOffloadRuleParcel rule); + void tetherOffloadRuleRemove(in android.net.TetherOffloadRuleParcel rule); + android.net.TetherStatsParcel[] tetherOffloadGetStats(); + void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes); + android.net.TetherStatsParcel tetherOffloadGetAndClearStats(int ifIndex); + const int IPV4 = 4; + const int IPV6 = 6; + const int CONF = 1; + const int NEIGH = 2; + const String IPSEC_INTERFACE_PREFIX = "ipsec"; + const int IPV6_ADDR_GEN_MODE_EUI64 = 0; + const int IPV6_ADDR_GEN_MODE_NONE = 1; + const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2; + const int IPV6_ADDR_GEN_MODE_RANDOM = 3; + const int IPV6_ADDR_GEN_MODE_DEFAULT = 0; + const int PENALTY_POLICY_ACCEPT = 1; + const int PENALTY_POLICY_LOG = 2; + const int PENALTY_POLICY_REJECT = 3; + const int LOCAL_NET_ID = 99; + const String NEXTHOP_NONE = ""; + const String NEXTHOP_UNREACHABLE = "unreachable"; + const String NEXTHOP_THROW = "throw"; + const int PERMISSION_NONE = 0; + const int PERMISSION_NETWORK = 1; + const int PERMISSION_SYSTEM = 2; + const int NO_PERMISSIONS = 0; + const int PERMISSION_INTERNET = 4; + const int PERMISSION_UPDATE_DEVICE_STATS = 8; + const int PERMISSION_UNINSTALLED = -1; + const int FIREWALL_WHITELIST = 0; + const int FIREWALL_BLACKLIST = 1; + const int FIREWALL_RULE_ALLOW = 1; + const int FIREWALL_RULE_DENY = 2; + const int FIREWALL_CHAIN_NONE = 0; + const int FIREWALL_CHAIN_DOZABLE = 1; + const int FIREWALL_CHAIN_STANDBY = 2; + const int FIREWALL_CHAIN_POWERSAVE = 3; + const String IF_STATE_UP = "up"; + const String IF_STATE_DOWN = "down"; + const String IF_FLAG_BROADCAST = "broadcast"; + const String IF_FLAG_LOOPBACK = "loopback"; + const String IF_FLAG_POINTOPOINT = "point-to-point"; + const String IF_FLAG_RUNNING = "running"; + const String IF_FLAG_MULTICAST = "multicast"; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/INetdUnsolicitedEventListener.aidl new file mode 100644 index 0000000000000000000000000000000000000000..44593632171c57ab29ec437c7792bb9271ac030b --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/INetdUnsolicitedEventListener.aidl @@ -0,0 +1,32 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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 INetdUnsolicitedEventListener { + oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid); + oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName); + oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers); + oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope); + oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope); + oneway void onInterfaceAdded(@utf8InCpp String ifName); + oneway void onInterfaceRemoved(@utf8InCpp String ifName); + oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up); + oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up); + oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName); + oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex); +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/InterfaceConfigurationParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..01e0f955414128cc47311f0a5fdd9977a229bb82 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/InterfaceConfigurationParcel.aidl @@ -0,0 +1,26 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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 */ +parcelable InterfaceConfigurationParcel { + @utf8InCpp String ifName; + @utf8InCpp String hwAddr; + @utf8InCpp String ipv4Addr; + int prefixLength; + @utf8InCpp String[] flags; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/MarkMaskParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/MarkMaskParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..62be8384209429d97b2bf40100a973296cdcf162 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/MarkMaskParcel.aidl @@ -0,0 +1,23 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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 */ +parcelable MarkMaskParcel { + int mark; + int mask; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/RouteInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/RouteInfoParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..5e0ee62a0fb70234aef03c0d3d40431c8302dccc --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/RouteInfoParcel.aidl @@ -0,0 +1,24 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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; +parcelable RouteInfoParcel { + @utf8InCpp String destination; + @utf8InCpp String ifName; + @utf8InCpp String nextHop; + int mtu; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/TetherConfigParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/TetherConfigParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..b13645450ac0b8e0bde093a18eda0e342e814c63 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/TetherConfigParcel.aidl @@ -0,0 +1,23 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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 */ +parcelable TetherConfigParcel { + boolean usingLegacyDnsProxy; + @utf8InCpp String[] dhcpRanges; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/TetherOffloadRuleParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/TetherOffloadRuleParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..c9d845808e356d69ab2431375ee1cd331aecd106 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/TetherOffloadRuleParcel.aidl @@ -0,0 +1,28 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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 */ +parcelable TetherOffloadRuleParcel { + int inputInterfaceIndex; + int outputInterfaceIndex; + byte[] destination; + int prefixLength; + byte[] srcL2Address; + byte[] dstL2Address; + int pmtu = 1500; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/TetherStatsParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..0b0960efc54bc029854c4090dce7034007282f4d --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/TetherStatsParcel.aidl @@ -0,0 +1,27 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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 */ +parcelable TetherStatsParcel { + @utf8InCpp String iface; + long rxBytes; + long rxPackets; + long txBytes; + long txPackets; + int ifIndex = 0; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/UidRangeParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..84ff4572dd9cba34b30d9319f9c5c9facf5ec45e --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/UidRangeParcel.aidl @@ -0,0 +1,23 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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 */ +parcelable UidRangeParcel { + int start; + int stop; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/5/.hash b/staticlibs/netd/aidl_api/netd_aidl_interface/5/.hash new file mode 100644 index 0000000000000000000000000000000000000000..a6ced4538a607ae9daca0b3f92739bac30d7fc1c --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/5/.hash @@ -0,0 +1 @@ +d97c56dd789cee9eeb5cdcec43a99df0a01873a5 diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/INetd.aidl new file mode 100644 index 0000000000000000000000000000000000000000..b30748a3c8a76f24ccbf849a469345ad7ac51fc3 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/INetd.aidl @@ -0,0 +1,167 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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 INetd { + boolean isAlive(); + boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isAllowlist, in int[] uids); + boolean bandwidthEnableDataSaver(boolean enable); + void networkCreatePhysical(int netId, int permission); + void networkCreateVpn(int netId, boolean secure); + void networkDestroy(int netId); + void networkAddInterface(int netId, in @utf8InCpp String iface); + void networkRemoveInterface(int netId, in @utf8InCpp String iface); + void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges); + void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges); + void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges); + void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids); + boolean tetherApplyDnsInterfaces(); + android.net.TetherStatsParcel[] tetherGetStats(); + void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength); + void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength); + @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter); + void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value); + void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid); + int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi); + void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId); + void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi); + void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket); + void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId); + void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId); + void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId); + void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName); + void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask); + void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask); + void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode); + void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel); + void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel); + void strictUidCleartextPenalty(int uid, int policyPenalty); + @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix); + void clatdStop(in @utf8InCpp String ifName); + boolean ipfwdEnabled(); + @utf8InCpp String[] ipfwdGetRequesterList(); + void ipfwdEnableForwarding(in @utf8InCpp String requester); + void ipfwdDisableForwarding(in @utf8InCpp String requester); + void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface); + void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface); + void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes); + void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName); + void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes); + void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName); + void bandwidthSetGlobalAlert(long bytes); + void bandwidthAddNaughtyApp(int uid); + void bandwidthRemoveNaughtyApp(int uid); + void bandwidthAddNiceApp(int uid); + void bandwidthRemoveNiceApp(int uid); + void tetherStart(in @utf8InCpp String[] dhcpRanges); + void tetherStop(); + boolean tetherIsEnabled(); + void tetherInterfaceAdd(in @utf8InCpp String ifName); + void tetherInterfaceRemove(in @utf8InCpp String ifName); + @utf8InCpp String[] tetherInterfaceList(); + void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs); + @utf8InCpp String[] tetherDnsList(); + void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop); + void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop); + void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid); + void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid); + int networkGetDefault(); + void networkSetDefault(int netId); + void networkClearDefault(); + void networkSetPermissionForNetwork(int netId, int permission); + void networkSetPermissionForUser(int permission, in int[] uids); + void networkClearPermissionForUser(in int[] uids); + void trafficSetNetPermForUids(int permission, in int[] uids); + void networkSetProtectAllow(int uid); + void networkSetProtectDeny(int uid); + boolean networkCanProtect(int uid); + void firewallSetFirewallType(int firewalltype); + void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule); + void firewallSetUidRule(int childChain, int uid, int firewallRule); + void firewallEnableChildChain(int childChain, boolean enable); + @utf8InCpp String[] interfaceGetList(); + android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName); + void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg); + void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable); + void interfaceClearAddrs(in @utf8InCpp String ifName); + void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable); + void interfaceSetMtu(in @utf8InCpp String ifName, int mtu); + void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface); + void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface); + void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues); + void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener); + void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids); + void firewallRemoveUidInterfaceRules(in int[] uids); + void trafficSwapActiveStatsMap(); + IBinder getOemNetd(); + void tetherStartWithConfiguration(in android.net.TetherConfigParcel config); + android.net.MarkMaskParcel getFwmarkForNetwork(int netId); + void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + void tetherOffloadRuleAdd(in android.net.TetherOffloadRuleParcel rule); + void tetherOffloadRuleRemove(in android.net.TetherOffloadRuleParcel rule); + android.net.TetherStatsParcel[] tetherOffloadGetStats(); + void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes); + android.net.TetherStatsParcel tetherOffloadGetAndClearStats(int ifIndex); + const int IPV4 = 4; + const int IPV6 = 6; + const int CONF = 1; + const int NEIGH = 2; + const String IPSEC_INTERFACE_PREFIX = "ipsec"; + const int IPV6_ADDR_GEN_MODE_EUI64 = 0; + const int IPV6_ADDR_GEN_MODE_NONE = 1; + const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2; + const int IPV6_ADDR_GEN_MODE_RANDOM = 3; + const int IPV6_ADDR_GEN_MODE_DEFAULT = 0; + const int PENALTY_POLICY_ACCEPT = 1; + const int PENALTY_POLICY_LOG = 2; + const int PENALTY_POLICY_REJECT = 3; + const int LOCAL_NET_ID = 99; + const String NEXTHOP_NONE = ""; + const String NEXTHOP_UNREACHABLE = "unreachable"; + const String NEXTHOP_THROW = "throw"; + const int PERMISSION_NONE = 0; + const int PERMISSION_NETWORK = 1; + const int PERMISSION_SYSTEM = 2; + const int NO_PERMISSIONS = 0; + const int PERMISSION_INTERNET = 4; + const int PERMISSION_UPDATE_DEVICE_STATS = 8; + const int PERMISSION_UNINSTALLED = -1; + const @JavaPassthrough(annotation="@Deprecated") int FIREWALL_WHITELIST = 0; + const int FIREWALL_ALLOWLIST = 0; + const @JavaPassthrough(annotation="@Deprecated") int FIREWALL_BLACKLIST = 1; + const int FIREWALL_DENYLIST = 1; + const int FIREWALL_RULE_ALLOW = 1; + const int FIREWALL_RULE_DENY = 2; + const int FIREWALL_CHAIN_NONE = 0; + const int FIREWALL_CHAIN_DOZABLE = 1; + const int FIREWALL_CHAIN_STANDBY = 2; + const int FIREWALL_CHAIN_POWERSAVE = 3; + const int FIREWALL_CHAIN_RESTRICTED = 4; + const String IF_STATE_UP = "up"; + const String IF_STATE_DOWN = "down"; + const String IF_FLAG_BROADCAST = "broadcast"; + const String IF_FLAG_LOOPBACK = "loopback"; + const String IF_FLAG_POINTOPOINT = "point-to-point"; + const String IF_FLAG_RUNNING = "running"; + const String IF_FLAG_MULTICAST = "multicast"; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/INetdUnsolicitedEventListener.aidl new file mode 100644 index 0000000000000000000000000000000000000000..44593632171c57ab29ec437c7792bb9271ac030b --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/INetdUnsolicitedEventListener.aidl @@ -0,0 +1,32 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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 INetdUnsolicitedEventListener { + oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid); + oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName); + oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers); + oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope); + oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope); + oneway void onInterfaceAdded(@utf8InCpp String ifName); + oneway void onInterfaceRemoved(@utf8InCpp String ifName); + oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up); + oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up); + oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName); + oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex); +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/InterfaceConfigurationParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..01e0f955414128cc47311f0a5fdd9977a229bb82 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/InterfaceConfigurationParcel.aidl @@ -0,0 +1,26 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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 */ +parcelable InterfaceConfigurationParcel { + @utf8InCpp String ifName; + @utf8InCpp String hwAddr; + @utf8InCpp String ipv4Addr; + int prefixLength; + @utf8InCpp String[] flags; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/MarkMaskParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/MarkMaskParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..62be8384209429d97b2bf40100a973296cdcf162 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/MarkMaskParcel.aidl @@ -0,0 +1,23 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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 */ +parcelable MarkMaskParcel { + int mark; + int mask; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/RouteInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/RouteInfoParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..5e0ee62a0fb70234aef03c0d3d40431c8302dccc --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/RouteInfoParcel.aidl @@ -0,0 +1,24 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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; +parcelable RouteInfoParcel { + @utf8InCpp String destination; + @utf8InCpp String ifName; + @utf8InCpp String nextHop; + int mtu; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/TetherConfigParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/TetherConfigParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..b13645450ac0b8e0bde093a18eda0e342e814c63 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/TetherConfigParcel.aidl @@ -0,0 +1,23 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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 */ +parcelable TetherConfigParcel { + boolean usingLegacyDnsProxy; + @utf8InCpp String[] dhcpRanges; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/TetherOffloadRuleParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/TetherOffloadRuleParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..c9d845808e356d69ab2431375ee1cd331aecd106 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/TetherOffloadRuleParcel.aidl @@ -0,0 +1,28 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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 */ +parcelable TetherOffloadRuleParcel { + int inputInterfaceIndex; + int outputInterfaceIndex; + byte[] destination; + int prefixLength; + byte[] srcL2Address; + byte[] dstL2Address; + int pmtu = 1500; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/TetherStatsParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..0b0960efc54bc029854c4090dce7034007282f4d --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/TetherStatsParcel.aidl @@ -0,0 +1,27 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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 */ +parcelable TetherStatsParcel { + @utf8InCpp String iface; + long rxBytes; + long rxPackets; + long txBytes; + long txPackets; + int ifIndex = 0; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/UidRangeParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..debc6be2d37aa88fa59663c26ecf9f152f34668a --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/UidRangeParcel.aidl @@ -0,0 +1,24 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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 */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable UidRangeParcel { + int start; + int stop; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/6/.hash b/staticlibs/netd/aidl_api/netd_aidl_interface/6/.hash new file mode 100644 index 0000000000000000000000000000000000000000..f5acf5d1ee6eb36dfa97820f1427efbf6f7e3ec1 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/6/.hash @@ -0,0 +1 @@ +b08451d9673b09cba84f1fd8740e1fdac64ff7be diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/INetd.aidl new file mode 100644 index 0000000000000000000000000000000000000000..a7952f28df05c24fe249e082278a77f7a40fda8f --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/INetd.aidl @@ -0,0 +1,198 @@ +/** + * 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// 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 INetd { + boolean isAlive(); + boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isAllowlist, in int[] uids); + boolean bandwidthEnableDataSaver(boolean enable); + /** + * @deprecated use networkCreate() instead. + */ + void networkCreatePhysical(int netId, int permission); + /** + * @deprecated use networkCreate() instead. + */ + void networkCreateVpn(int netId, boolean secure); + void networkDestroy(int netId); + void networkAddInterface(int netId, in @utf8InCpp String iface); + void networkRemoveInterface(int netId, in @utf8InCpp String iface); + void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges); + void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges); + void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges); + void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids); + boolean tetherApplyDnsInterfaces(); + android.net.TetherStatsParcel[] tetherGetStats(); + void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength); + void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength); + @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter); + void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value); + void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid); + int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi); + void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId); + void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi); + void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket); + void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId); + void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId); + void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId); + void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName); + void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask); + void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask); + void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode); + void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel); + void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel); + void strictUidCleartextPenalty(int uid, int policyPenalty); + @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix); + void clatdStop(in @utf8InCpp String ifName); + boolean ipfwdEnabled(); + @utf8InCpp String[] ipfwdGetRequesterList(); + void ipfwdEnableForwarding(in @utf8InCpp String requester); + void ipfwdDisableForwarding(in @utf8InCpp String requester); + void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface); + void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface); + void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes); + void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName); + void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes); + void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName); + void bandwidthSetGlobalAlert(long bytes); + void bandwidthAddNaughtyApp(int uid); + void bandwidthRemoveNaughtyApp(int uid); + void bandwidthAddNiceApp(int uid); + void bandwidthRemoveNiceApp(int uid); + void tetherStart(in @utf8InCpp String[] dhcpRanges); + void tetherStop(); + boolean tetherIsEnabled(); + void tetherInterfaceAdd(in @utf8InCpp String ifName); + void tetherInterfaceRemove(in @utf8InCpp String ifName); + @utf8InCpp String[] tetherInterfaceList(); + void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs); + @utf8InCpp String[] tetherDnsList(); + void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop); + void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop); + void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid); + void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid); + int networkGetDefault(); + void networkSetDefault(int netId); + void networkClearDefault(); + void networkSetPermissionForNetwork(int netId, int permission); + void networkSetPermissionForUser(int permission, in int[] uids); + void networkClearPermissionForUser(in int[] uids); + void trafficSetNetPermForUids(int permission, in int[] uids); + void networkSetProtectAllow(int uid); + void networkSetProtectDeny(int uid); + boolean networkCanProtect(int uid); + void firewallSetFirewallType(int firewalltype); + void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule); + void firewallSetUidRule(int childChain, int uid, int firewallRule); + void firewallEnableChildChain(int childChain, boolean enable); + @utf8InCpp String[] interfaceGetList(); + android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName); + void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg); + void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable); + void interfaceClearAddrs(in @utf8InCpp String ifName); + void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable); + void interfaceSetMtu(in @utf8InCpp String ifName, int mtu); + void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface); + void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface); + void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues); + void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener); + void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids); + void firewallRemoveUidInterfaceRules(in int[] uids); + void trafficSwapActiveStatsMap(); + IBinder getOemNetd(); + void tetherStartWithConfiguration(in android.net.TetherConfigParcel config); + android.net.MarkMaskParcel getFwmarkForNetwork(int netId); + void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + void tetherOffloadRuleAdd(in android.net.TetherOffloadRuleParcel rule); + void tetherOffloadRuleRemove(in android.net.TetherOffloadRuleParcel rule); + android.net.TetherStatsParcel[] tetherOffloadGetStats(); + void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes); + android.net.TetherStatsParcel tetherOffloadGetAndClearStats(int ifIndex); + void networkCreate(in android.net.NativeNetworkConfig config); + const int IPV4 = 4; + const int IPV6 = 6; + const int CONF = 1; + const int NEIGH = 2; + const String IPSEC_INTERFACE_PREFIX = "ipsec"; + const int IPV6_ADDR_GEN_MODE_EUI64 = 0; + const int IPV6_ADDR_GEN_MODE_NONE = 1; + const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2; + const int IPV6_ADDR_GEN_MODE_RANDOM = 3; + const int IPV6_ADDR_GEN_MODE_DEFAULT = 0; + const int PENALTY_POLICY_ACCEPT = 1; + const int PENALTY_POLICY_LOG = 2; + const int PENALTY_POLICY_REJECT = 3; + const int LOCAL_NET_ID = 99; + const int DUMMY_NET_ID = 51; + const int UNREACHABLE_NET_ID = 52; + const String NEXTHOP_NONE = ""; + const String NEXTHOP_UNREACHABLE = "unreachable"; + const String NEXTHOP_THROW = "throw"; + const int PERMISSION_NONE = 0; + const int PERMISSION_NETWORK = 1; + const int PERMISSION_SYSTEM = 2; + const int NO_PERMISSIONS = 0; + const int PERMISSION_INTERNET = 4; + const int PERMISSION_UPDATE_DEVICE_STATS = 8; + const int PERMISSION_UNINSTALLED = -1; + /** + * @deprecated use FIREWALL_ALLOWLIST. + */ + const int FIREWALL_WHITELIST = 0; + const int FIREWALL_ALLOWLIST = 0; + /** + * @deprecated use FIREWALL_DENYLIST. + */ + const int FIREWALL_BLACKLIST = 1; + const int FIREWALL_DENYLIST = 1; + const int FIREWALL_RULE_ALLOW = 1; + const int FIREWALL_RULE_DENY = 2; + const int FIREWALL_CHAIN_NONE = 0; + const int FIREWALL_CHAIN_DOZABLE = 1; + const int FIREWALL_CHAIN_STANDBY = 2; + const int FIREWALL_CHAIN_POWERSAVE = 3; + const int FIREWALL_CHAIN_RESTRICTED = 4; + const String IF_STATE_UP = "up"; + const String IF_STATE_DOWN = "down"; + const String IF_FLAG_BROADCAST = "broadcast"; + const String IF_FLAG_LOOPBACK = "loopback"; + const String IF_FLAG_POINTOPOINT = "point-to-point"; + const String IF_FLAG_RUNNING = "running"; + const String IF_FLAG_MULTICAST = "multicast"; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/INetdUnsolicitedEventListener.aidl new file mode 100644 index 0000000000000000000000000000000000000000..31775dfd11fe25f2fede661b7e4da11ab4ce8f91 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/INetdUnsolicitedEventListener.aidl @@ -0,0 +1,48 @@ +/** + * 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 INetdUnsolicitedEventListener { + oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid); + oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName); + oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers); + oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope); + oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope); + oneway void onInterfaceAdded(@utf8InCpp String ifName); + oneway void onInterfaceRemoved(@utf8InCpp String ifName); + oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up); + oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up); + oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName); + oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex); +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/InterfaceConfigurationParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..1869d8d49e6a59a1294c7925c460a592f945e677 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/InterfaceConfigurationParcel.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 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 */ +parcelable InterfaceConfigurationParcel { + @utf8InCpp String ifName; + @utf8InCpp String hwAddr; + @utf8InCpp String ipv4Addr; + int prefixLength; + @utf8InCpp String[] flags; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/MarkMaskParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/MarkMaskParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..8ea20d118fc8c3350249e6268348f77578a2b0b6 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/MarkMaskParcel.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 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 */ +parcelable MarkMaskParcel { + int mark; + int mask; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/NativeNetworkConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/NativeNetworkConfig.aidl new file mode 100644 index 0000000000000000000000000000000000000000..76562b294a65d2058e4704624b9aa4c826ebe0de --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/NativeNetworkConfig.aidl @@ -0,0 +1,43 @@ +/* + * 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; +/* @hide */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable NativeNetworkConfig { + int netId; + android.net.NativeNetworkType networkType = android.net.NativeNetworkType.PHYSICAL; + int permission; + boolean secure; + android.net.NativeVpnType vpnType = android.net.NativeVpnType.PLATFORM; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/NativeNetworkType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/NativeNetworkType.aidl new file mode 100644 index 0000000000000000000000000000000000000000..06c8979d51778df6bd58542a5f5324a849b5b5fe --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/NativeNetworkType.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; +@Backing(type="int") +enum NativeNetworkType { + PHYSICAL = 0, + VIRTUAL = 1, +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/NativeVpnType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/NativeVpnType.aidl new file mode 100644 index 0000000000000000000000000000000000000000..8a8be83958c80bdaa6e5401bb4411ff1e9b72938 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/NativeVpnType.aidl @@ -0,0 +1,41 @@ +/* + * 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; +@Backing(type="int") +enum NativeVpnType { + SERVICE = 1, + PLATFORM = 2, + LEGACY = 3, + OEM = 4, +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/RouteInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/RouteInfoParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..5ef95e67ca963ed343c8481d8061e0982cc8f866 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/RouteInfoParcel.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; +parcelable RouteInfoParcel { + @utf8InCpp String destination; + @utf8InCpp String ifName; + @utf8InCpp String nextHop; + int mtu; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/TetherConfigParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/TetherConfigParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..7b39c22e4ef0bef2a148578ec2a345b98f9ca706 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/TetherConfigParcel.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 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 */ +parcelable TetherConfigParcel { + boolean usingLegacyDnsProxy; + @utf8InCpp String[] dhcpRanges; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/TetherOffloadRuleParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/TetherOffloadRuleParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..983e9860ea8e94007af826844f4fbcbe3d5aee22 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/TetherOffloadRuleParcel.aidl @@ -0,0 +1,44 @@ +/* + * 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; +/* @hide */ +parcelable TetherOffloadRuleParcel { + int inputInterfaceIndex; + int outputInterfaceIndex; + byte[] destination; + int prefixLength; + byte[] srcL2Address; + byte[] dstL2Address; + int pmtu = 1500; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/TetherStatsParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..5f1b722690b0f2ee3e7ec3309edf3b1a48ab7979 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/TetherStatsParcel.aidl @@ -0,0 +1,43 @@ +/* + * 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 */ +parcelable TetherStatsParcel { + @utf8InCpp String iface; + long rxBytes; + long rxPackets; + long txBytes; + long txPackets; + int ifIndex = 0; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/UidRangeParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..72e987a2af49e9249320622f5ba9e729ef6d6309 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/UidRangeParcel.aidl @@ -0,0 +1,40 @@ +/* + * 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 */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable UidRangeParcel { + int start; + int stop; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/7/.hash b/staticlibs/netd/aidl_api/netd_aidl_interface/7/.hash new file mode 100644 index 0000000000000000000000000000000000000000..cad59dfd74368bb693b97ff8c00306788b13f954 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/7/.hash @@ -0,0 +1 @@ +850353de5d19a0dd718f8fd20791f0532e6a34c7 diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/INetd.aidl new file mode 100644 index 0000000000000000000000000000000000000000..ec03d86b6c73d9e1967a12955980f86054701222 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/INetd.aidl @@ -0,0 +1,200 @@ +/** + * 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// 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 INetd { + boolean isAlive(); + boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isAllowlist, in int[] uids); + boolean bandwidthEnableDataSaver(boolean enable); + /** + * @deprecated use networkCreate() instead. + */ + void networkCreatePhysical(int netId, int permission); + /** + * @deprecated use networkCreate() instead. + */ + void networkCreateVpn(int netId, boolean secure); + void networkDestroy(int netId); + void networkAddInterface(int netId, in @utf8InCpp String iface); + void networkRemoveInterface(int netId, in @utf8InCpp String iface); + void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges); + void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges); + void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges); + void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids); + boolean tetherApplyDnsInterfaces(); + android.net.TetherStatsParcel[] tetherGetStats(); + void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength); + void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength); + @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter); + void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value); + void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid); + int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi); + void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId); + void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi); + void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket); + void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId); + void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId); + void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId); + void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName); + void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask); + void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask); + void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode); + void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel); + void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel); + void strictUidCleartextPenalty(int uid, int policyPenalty); + @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix); + void clatdStop(in @utf8InCpp String ifName); + boolean ipfwdEnabled(); + @utf8InCpp String[] ipfwdGetRequesterList(); + void ipfwdEnableForwarding(in @utf8InCpp String requester); + void ipfwdDisableForwarding(in @utf8InCpp String requester); + void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface); + void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface); + void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes); + void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName); + void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes); + void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName); + void bandwidthSetGlobalAlert(long bytes); + void bandwidthAddNaughtyApp(int uid); + void bandwidthRemoveNaughtyApp(int uid); + void bandwidthAddNiceApp(int uid); + void bandwidthRemoveNiceApp(int uid); + void tetherStart(in @utf8InCpp String[] dhcpRanges); + void tetherStop(); + boolean tetherIsEnabled(); + void tetherInterfaceAdd(in @utf8InCpp String ifName); + void tetherInterfaceRemove(in @utf8InCpp String ifName); + @utf8InCpp String[] tetherInterfaceList(); + void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs); + @utf8InCpp String[] tetherDnsList(); + void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop); + void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop); + void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid); + void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid); + int networkGetDefault(); + void networkSetDefault(int netId); + void networkClearDefault(); + void networkSetPermissionForNetwork(int netId, int permission); + void networkSetPermissionForUser(int permission, in int[] uids); + void networkClearPermissionForUser(in int[] uids); + void trafficSetNetPermForUids(int permission, in int[] uids); + void networkSetProtectAllow(int uid); + void networkSetProtectDeny(int uid); + boolean networkCanProtect(int uid); + void firewallSetFirewallType(int firewalltype); + void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule); + void firewallSetUidRule(int childChain, int uid, int firewallRule); + void firewallEnableChildChain(int childChain, boolean enable); + @utf8InCpp String[] interfaceGetList(); + android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName); + void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg); + void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable); + void interfaceClearAddrs(in @utf8InCpp String ifName); + void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable); + void interfaceSetMtu(in @utf8InCpp String ifName, int mtu); + void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface); + void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface); + void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues); + void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener); + void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids); + void firewallRemoveUidInterfaceRules(in int[] uids); + void trafficSwapActiveStatsMap(); + IBinder getOemNetd(); + void tetherStartWithConfiguration(in android.net.TetherConfigParcel config); + android.net.MarkMaskParcel getFwmarkForNetwork(int netId); + void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + void tetherOffloadRuleAdd(in android.net.TetherOffloadRuleParcel rule); + void tetherOffloadRuleRemove(in android.net.TetherOffloadRuleParcel rule); + android.net.TetherStatsParcel[] tetherOffloadGetStats(); + void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes); + android.net.TetherStatsParcel tetherOffloadGetAndClearStats(int ifIndex); + void networkCreate(in android.net.NativeNetworkConfig config); + void networkAddUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig); + void networkRemoveUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig); + const int IPV4 = 4; + const int IPV6 = 6; + const int CONF = 1; + const int NEIGH = 2; + const String IPSEC_INTERFACE_PREFIX = "ipsec"; + const int IPV6_ADDR_GEN_MODE_EUI64 = 0; + const int IPV6_ADDR_GEN_MODE_NONE = 1; + const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2; + const int IPV6_ADDR_GEN_MODE_RANDOM = 3; + const int IPV6_ADDR_GEN_MODE_DEFAULT = 0; + const int PENALTY_POLICY_ACCEPT = 1; + const int PENALTY_POLICY_LOG = 2; + const int PENALTY_POLICY_REJECT = 3; + const int LOCAL_NET_ID = 99; + const int DUMMY_NET_ID = 51; + const int UNREACHABLE_NET_ID = 52; + const String NEXTHOP_NONE = ""; + const String NEXTHOP_UNREACHABLE = "unreachable"; + const String NEXTHOP_THROW = "throw"; + const int PERMISSION_NONE = 0; + const int PERMISSION_NETWORK = 1; + const int PERMISSION_SYSTEM = 2; + const int NO_PERMISSIONS = 0; + const int PERMISSION_INTERNET = 4; + const int PERMISSION_UPDATE_DEVICE_STATS = 8; + const int PERMISSION_UNINSTALLED = -1; + /** + * @deprecated use FIREWALL_ALLOWLIST. + */ + const int FIREWALL_WHITELIST = 0; + const int FIREWALL_ALLOWLIST = 0; + /** + * @deprecated use FIREWALL_DENYLIST. + */ + const int FIREWALL_BLACKLIST = 1; + const int FIREWALL_DENYLIST = 1; + const int FIREWALL_RULE_ALLOW = 1; + const int FIREWALL_RULE_DENY = 2; + const int FIREWALL_CHAIN_NONE = 0; + const int FIREWALL_CHAIN_DOZABLE = 1; + const int FIREWALL_CHAIN_STANDBY = 2; + const int FIREWALL_CHAIN_POWERSAVE = 3; + const int FIREWALL_CHAIN_RESTRICTED = 4; + const String IF_STATE_UP = "up"; + const String IF_STATE_DOWN = "down"; + const String IF_FLAG_BROADCAST = "broadcast"; + const String IF_FLAG_LOOPBACK = "loopback"; + const String IF_FLAG_POINTOPOINT = "point-to-point"; + const String IF_FLAG_RUNNING = "running"; + const String IF_FLAG_MULTICAST = "multicast"; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/INetdUnsolicitedEventListener.aidl new file mode 100644 index 0000000000000000000000000000000000000000..31775dfd11fe25f2fede661b7e4da11ab4ce8f91 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/INetdUnsolicitedEventListener.aidl @@ -0,0 +1,48 @@ +/** + * 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 INetdUnsolicitedEventListener { + oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid); + oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName); + oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers); + oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope); + oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope); + oneway void onInterfaceAdded(@utf8InCpp String ifName); + oneway void onInterfaceRemoved(@utf8InCpp String ifName); + oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up); + oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up); + oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName); + oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex); +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/InterfaceConfigurationParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..1869d8d49e6a59a1294c7925c460a592f945e677 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/InterfaceConfigurationParcel.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 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 */ +parcelable InterfaceConfigurationParcel { + @utf8InCpp String ifName; + @utf8InCpp String hwAddr; + @utf8InCpp String ipv4Addr; + int prefixLength; + @utf8InCpp String[] flags; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/MarkMaskParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/MarkMaskParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..8ea20d118fc8c3350249e6268348f77578a2b0b6 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/MarkMaskParcel.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 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 */ +parcelable MarkMaskParcel { + int mark; + int mask; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/NativeNetworkConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/NativeNetworkConfig.aidl new file mode 100644 index 0000000000000000000000000000000000000000..76562b294a65d2058e4704624b9aa4c826ebe0de --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/NativeNetworkConfig.aidl @@ -0,0 +1,43 @@ +/* + * 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; +/* @hide */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable NativeNetworkConfig { + int netId; + android.net.NativeNetworkType networkType = android.net.NativeNetworkType.PHYSICAL; + int permission; + boolean secure; + android.net.NativeVpnType vpnType = android.net.NativeVpnType.PLATFORM; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/NativeNetworkType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/NativeNetworkType.aidl new file mode 100644 index 0000000000000000000000000000000000000000..06c8979d51778df6bd58542a5f5324a849b5b5fe --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/NativeNetworkType.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; +@Backing(type="int") +enum NativeNetworkType { + PHYSICAL = 0, + VIRTUAL = 1, +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/NativeVpnType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/NativeVpnType.aidl new file mode 100644 index 0000000000000000000000000000000000000000..8a8be83958c80bdaa6e5401bb4411ff1e9b72938 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/NativeVpnType.aidl @@ -0,0 +1,41 @@ +/* + * 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; +@Backing(type="int") +enum NativeVpnType { + SERVICE = 1, + PLATFORM = 2, + LEGACY = 3, + OEM = 4, +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/RouteInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/RouteInfoParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..5ef95e67ca963ed343c8481d8061e0982cc8f866 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/RouteInfoParcel.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; +parcelable RouteInfoParcel { + @utf8InCpp String destination; + @utf8InCpp String ifName; + @utf8InCpp String nextHop; + int mtu; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/TetherConfigParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/TetherConfigParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..7b39c22e4ef0bef2a148578ec2a345b98f9ca706 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/TetherConfigParcel.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 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 */ +parcelable TetherConfigParcel { + boolean usingLegacyDnsProxy; + @utf8InCpp String[] dhcpRanges; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/TetherOffloadRuleParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/TetherOffloadRuleParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..983e9860ea8e94007af826844f4fbcbe3d5aee22 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/TetherOffloadRuleParcel.aidl @@ -0,0 +1,44 @@ +/* + * 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; +/* @hide */ +parcelable TetherOffloadRuleParcel { + int inputInterfaceIndex; + int outputInterfaceIndex; + byte[] destination; + int prefixLength; + byte[] srcL2Address; + byte[] dstL2Address; + int pmtu = 1500; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/TetherStatsParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..5f1b722690b0f2ee3e7ec3309edf3b1a48ab7979 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/TetherStatsParcel.aidl @@ -0,0 +1,43 @@ +/* + * 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 */ +parcelable TetherStatsParcel { + @utf8InCpp String iface; + long rxBytes; + long rxPackets; + long txBytes; + long txPackets; + int ifIndex = 0; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/UidRangeParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..72e987a2af49e9249320622f5ba9e729ef6d6309 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/UidRangeParcel.aidl @@ -0,0 +1,40 @@ +/* + * 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 */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable UidRangeParcel { + int start; + int stop; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/netd/aidl/NativeUidRangeConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/netd/aidl/NativeUidRangeConfig.aidl new file mode 100644 index 0000000000000000000000000000000000000000..9bb679f1c115a4d69683f9c1c6e3d8aead8bab75 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/netd/aidl/NativeUidRangeConfig.aidl @@ -0,0 +1,41 @@ +/* + * 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.netd.aidl; +/* @hide */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable NativeUidRangeConfig { + int netId; + android.net.UidRangeParcel[] uidRanges; + int subPriority; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/.hash b/staticlibs/netd/aidl_api/netd_aidl_interface/8/.hash new file mode 100644 index 0000000000000000000000000000000000000000..093381616e72347eb1a28a983b8170e3aa903c47 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/.hash @@ -0,0 +1 @@ +e8cf8586fc5da9063818d8775e9a21c4b0addb5b diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/INetd.aidl new file mode 100644 index 0000000000000000000000000000000000000000..ec03d86b6c73d9e1967a12955980f86054701222 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/INetd.aidl @@ -0,0 +1,200 @@ +/** + * 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// 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 INetd { + boolean isAlive(); + boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isAllowlist, in int[] uids); + boolean bandwidthEnableDataSaver(boolean enable); + /** + * @deprecated use networkCreate() instead. + */ + void networkCreatePhysical(int netId, int permission); + /** + * @deprecated use networkCreate() instead. + */ + void networkCreateVpn(int netId, boolean secure); + void networkDestroy(int netId); + void networkAddInterface(int netId, in @utf8InCpp String iface); + void networkRemoveInterface(int netId, in @utf8InCpp String iface); + void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges); + void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges); + void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges); + void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids); + boolean tetherApplyDnsInterfaces(); + android.net.TetherStatsParcel[] tetherGetStats(); + void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength); + void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength); + @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter); + void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value); + void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid); + int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi); + void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId); + void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi); + void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket); + void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId); + void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId); + void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId); + void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName); + void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask); + void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask); + void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode); + void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel); + void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel); + void strictUidCleartextPenalty(int uid, int policyPenalty); + @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix); + void clatdStop(in @utf8InCpp String ifName); + boolean ipfwdEnabled(); + @utf8InCpp String[] ipfwdGetRequesterList(); + void ipfwdEnableForwarding(in @utf8InCpp String requester); + void ipfwdDisableForwarding(in @utf8InCpp String requester); + void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface); + void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface); + void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes); + void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName); + void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes); + void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName); + void bandwidthSetGlobalAlert(long bytes); + void bandwidthAddNaughtyApp(int uid); + void bandwidthRemoveNaughtyApp(int uid); + void bandwidthAddNiceApp(int uid); + void bandwidthRemoveNiceApp(int uid); + void tetherStart(in @utf8InCpp String[] dhcpRanges); + void tetherStop(); + boolean tetherIsEnabled(); + void tetherInterfaceAdd(in @utf8InCpp String ifName); + void tetherInterfaceRemove(in @utf8InCpp String ifName); + @utf8InCpp String[] tetherInterfaceList(); + void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs); + @utf8InCpp String[] tetherDnsList(); + void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop); + void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop); + void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid); + void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid); + int networkGetDefault(); + void networkSetDefault(int netId); + void networkClearDefault(); + void networkSetPermissionForNetwork(int netId, int permission); + void networkSetPermissionForUser(int permission, in int[] uids); + void networkClearPermissionForUser(in int[] uids); + void trafficSetNetPermForUids(int permission, in int[] uids); + void networkSetProtectAllow(int uid); + void networkSetProtectDeny(int uid); + boolean networkCanProtect(int uid); + void firewallSetFirewallType(int firewalltype); + void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule); + void firewallSetUidRule(int childChain, int uid, int firewallRule); + void firewallEnableChildChain(int childChain, boolean enable); + @utf8InCpp String[] interfaceGetList(); + android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName); + void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg); + void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable); + void interfaceClearAddrs(in @utf8InCpp String ifName); + void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable); + void interfaceSetMtu(in @utf8InCpp String ifName, int mtu); + void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface); + void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface); + void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues); + void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener); + void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids); + void firewallRemoveUidInterfaceRules(in int[] uids); + void trafficSwapActiveStatsMap(); + IBinder getOemNetd(); + void tetherStartWithConfiguration(in android.net.TetherConfigParcel config); + android.net.MarkMaskParcel getFwmarkForNetwork(int netId); + void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + void tetherOffloadRuleAdd(in android.net.TetherOffloadRuleParcel rule); + void tetherOffloadRuleRemove(in android.net.TetherOffloadRuleParcel rule); + android.net.TetherStatsParcel[] tetherOffloadGetStats(); + void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes); + android.net.TetherStatsParcel tetherOffloadGetAndClearStats(int ifIndex); + void networkCreate(in android.net.NativeNetworkConfig config); + void networkAddUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig); + void networkRemoveUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig); + const int IPV4 = 4; + const int IPV6 = 6; + const int CONF = 1; + const int NEIGH = 2; + const String IPSEC_INTERFACE_PREFIX = "ipsec"; + const int IPV6_ADDR_GEN_MODE_EUI64 = 0; + const int IPV6_ADDR_GEN_MODE_NONE = 1; + const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2; + const int IPV6_ADDR_GEN_MODE_RANDOM = 3; + const int IPV6_ADDR_GEN_MODE_DEFAULT = 0; + const int PENALTY_POLICY_ACCEPT = 1; + const int PENALTY_POLICY_LOG = 2; + const int PENALTY_POLICY_REJECT = 3; + const int LOCAL_NET_ID = 99; + const int DUMMY_NET_ID = 51; + const int UNREACHABLE_NET_ID = 52; + const String NEXTHOP_NONE = ""; + const String NEXTHOP_UNREACHABLE = "unreachable"; + const String NEXTHOP_THROW = "throw"; + const int PERMISSION_NONE = 0; + const int PERMISSION_NETWORK = 1; + const int PERMISSION_SYSTEM = 2; + const int NO_PERMISSIONS = 0; + const int PERMISSION_INTERNET = 4; + const int PERMISSION_UPDATE_DEVICE_STATS = 8; + const int PERMISSION_UNINSTALLED = -1; + /** + * @deprecated use FIREWALL_ALLOWLIST. + */ + const int FIREWALL_WHITELIST = 0; + const int FIREWALL_ALLOWLIST = 0; + /** + * @deprecated use FIREWALL_DENYLIST. + */ + const int FIREWALL_BLACKLIST = 1; + const int FIREWALL_DENYLIST = 1; + const int FIREWALL_RULE_ALLOW = 1; + const int FIREWALL_RULE_DENY = 2; + const int FIREWALL_CHAIN_NONE = 0; + const int FIREWALL_CHAIN_DOZABLE = 1; + const int FIREWALL_CHAIN_STANDBY = 2; + const int FIREWALL_CHAIN_POWERSAVE = 3; + const int FIREWALL_CHAIN_RESTRICTED = 4; + const String IF_STATE_UP = "up"; + const String IF_STATE_DOWN = "down"; + const String IF_FLAG_BROADCAST = "broadcast"; + const String IF_FLAG_LOOPBACK = "loopback"; + const String IF_FLAG_POINTOPOINT = "point-to-point"; + const String IF_FLAG_RUNNING = "running"; + const String IF_FLAG_MULTICAST = "multicast"; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/INetdUnsolicitedEventListener.aidl new file mode 100644 index 0000000000000000000000000000000000000000..31775dfd11fe25f2fede661b7e4da11ab4ce8f91 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/INetdUnsolicitedEventListener.aidl @@ -0,0 +1,48 @@ +/** + * 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 INetdUnsolicitedEventListener { + oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid); + oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName); + oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers); + oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope); + oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope); + oneway void onInterfaceAdded(@utf8InCpp String ifName); + oneway void onInterfaceRemoved(@utf8InCpp String ifName); + oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up); + oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up); + oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName); + oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex); +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/InterfaceConfigurationParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..1869d8d49e6a59a1294c7925c460a592f945e677 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/InterfaceConfigurationParcel.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 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 */ +parcelable InterfaceConfigurationParcel { + @utf8InCpp String ifName; + @utf8InCpp String hwAddr; + @utf8InCpp String ipv4Addr; + int prefixLength; + @utf8InCpp String[] flags; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/MarkMaskParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/MarkMaskParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..8ea20d118fc8c3350249e6268348f77578a2b0b6 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/MarkMaskParcel.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 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 */ +parcelable MarkMaskParcel { + int mark; + int mask; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/NativeNetworkConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/NativeNetworkConfig.aidl new file mode 100644 index 0000000000000000000000000000000000000000..77d814b825b348f04612a11b0459150c02453c68 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/NativeNetworkConfig.aidl @@ -0,0 +1,44 @@ +/* + * 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; +/* @hide */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable NativeNetworkConfig { + int netId; + android.net.NativeNetworkType networkType = android.net.NativeNetworkType.PHYSICAL; + int permission; + boolean secure; + android.net.NativeVpnType vpnType = android.net.NativeVpnType.PLATFORM; + boolean excludeLocalRoutes = false; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/NativeNetworkType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/NativeNetworkType.aidl new file mode 100644 index 0000000000000000000000000000000000000000..06c8979d51778df6bd58542a5f5324a849b5b5fe --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/NativeNetworkType.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; +@Backing(type="int") +enum NativeNetworkType { + PHYSICAL = 0, + VIRTUAL = 1, +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/NativeVpnType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/NativeVpnType.aidl new file mode 100644 index 0000000000000000000000000000000000000000..8a8be83958c80bdaa6e5401bb4411ff1e9b72938 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/NativeVpnType.aidl @@ -0,0 +1,41 @@ +/* + * 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; +@Backing(type="int") +enum NativeVpnType { + SERVICE = 1, + PLATFORM = 2, + LEGACY = 3, + OEM = 4, +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/RouteInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/RouteInfoParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..5ef95e67ca963ed343c8481d8061e0982cc8f866 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/RouteInfoParcel.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; +parcelable RouteInfoParcel { + @utf8InCpp String destination; + @utf8InCpp String ifName; + @utf8InCpp String nextHop; + int mtu; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/TetherConfigParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/TetherConfigParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..7b39c22e4ef0bef2a148578ec2a345b98f9ca706 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/TetherConfigParcel.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 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 */ +parcelable TetherConfigParcel { + boolean usingLegacyDnsProxy; + @utf8InCpp String[] dhcpRanges; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/TetherOffloadRuleParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/TetherOffloadRuleParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..983e9860ea8e94007af826844f4fbcbe3d5aee22 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/TetherOffloadRuleParcel.aidl @@ -0,0 +1,44 @@ +/* + * 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; +/* @hide */ +parcelable TetherOffloadRuleParcel { + int inputInterfaceIndex; + int outputInterfaceIndex; + byte[] destination; + int prefixLength; + byte[] srcL2Address; + byte[] dstL2Address; + int pmtu = 1500; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/TetherStatsParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..5f1b722690b0f2ee3e7ec3309edf3b1a48ab7979 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/TetherStatsParcel.aidl @@ -0,0 +1,43 @@ +/* + * 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 */ +parcelable TetherStatsParcel { + @utf8InCpp String iface; + long rxBytes; + long rxPackets; + long txBytes; + long txPackets; + int ifIndex = 0; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/UidRangeParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..72e987a2af49e9249320622f5ba9e729ef6d6309 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/UidRangeParcel.aidl @@ -0,0 +1,40 @@ +/* + * 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 */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable UidRangeParcel { + int start; + int stop; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/netd/aidl/NativeUidRangeConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/netd/aidl/NativeUidRangeConfig.aidl new file mode 100644 index 0000000000000000000000000000000000000000..9bb679f1c115a4d69683f9c1c6e3d8aead8bab75 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/netd/aidl/NativeUidRangeConfig.aidl @@ -0,0 +1,41 @@ +/* + * 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.netd.aidl; +/* @hide */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable NativeUidRangeConfig { + int netId; + android.net.UidRangeParcel[] uidRanges; + int subPriority; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/9/.hash b/staticlibs/netd/aidl_api/netd_aidl_interface/9/.hash new file mode 100644 index 0000000000000000000000000000000000000000..94dd240d92d8a7cd36c3c855ad027e5d7e7b8427 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/9/.hash @@ -0,0 +1 @@ +2bffe06ea8c13f35f90b86d6dfd1a2b4c4d4daf5 diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/INetd.aidl new file mode 100644 index 0000000000000000000000000000000000000000..c780a59ee795a269a862cd2c7b6d737a410dbc10 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/INetd.aidl @@ -0,0 +1,221 @@ +/** + * 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// 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 INetd { + boolean isAlive(); + boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isAllowlist, in int[] uids); + boolean bandwidthEnableDataSaver(boolean enable); + /** + * @deprecated use networkCreate() instead. + */ + void networkCreatePhysical(int netId, int permission); + /** + * @deprecated use networkCreate() instead. + */ + void networkCreateVpn(int netId, boolean secure); + void networkDestroy(int netId); + void networkAddInterface(int netId, in @utf8InCpp String iface); + void networkRemoveInterface(int netId, in @utf8InCpp String iface); + void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges); + void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges); + void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges); + void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids); + boolean tetherApplyDnsInterfaces(); + android.net.TetherStatsParcel[] tetherGetStats(); + void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength); + void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength); + @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter); + void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value); + void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid); + int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi); + void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId); + void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi); + void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket); + void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId); + void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId); + void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId); + void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName); + void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask); + void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask); + void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode); + void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel); + void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel); + void strictUidCleartextPenalty(int uid, int policyPenalty); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd control plane moved to the mainline module starting in T. See ClatCoordinator. + */ + @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd control plane moved to the mainline module starting in T. See ClatCoordinator. + */ + void clatdStop(in @utf8InCpp String ifName); + boolean ipfwdEnabled(); + @utf8InCpp String[] ipfwdGetRequesterList(); + void ipfwdEnableForwarding(in @utf8InCpp String requester); + void ipfwdDisableForwarding(in @utf8InCpp String requester); + void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface); + void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface); + void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes); + void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName); + void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes); + void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName); + void bandwidthSetGlobalAlert(long bytes); + void bandwidthAddNaughtyApp(int uid); + void bandwidthRemoveNaughtyApp(int uid); + void bandwidthAddNiceApp(int uid); + void bandwidthRemoveNiceApp(int uid); + void tetherStart(in @utf8InCpp String[] dhcpRanges); + void tetherStop(); + boolean tetherIsEnabled(); + void tetherInterfaceAdd(in @utf8InCpp String ifName); + void tetherInterfaceRemove(in @utf8InCpp String ifName); + @utf8InCpp String[] tetherInterfaceList(); + void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs); + @utf8InCpp String[] tetherDnsList(); + void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop); + void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop); + void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid); + void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid); + int networkGetDefault(); + void networkSetDefault(int netId); + void networkClearDefault(); + void networkSetPermissionForNetwork(int netId, int permission); + void networkSetPermissionForUser(int permission, in int[] uids); + void networkClearPermissionForUser(in int[] uids); + void trafficSetNetPermForUids(int permission, in int[] uids); + void networkSetProtectAllow(int uid); + void networkSetProtectDeny(int uid); + boolean networkCanProtect(int uid); + void firewallSetFirewallType(int firewalltype); + void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule); + void firewallSetUidRule(int childChain, int uid, int firewallRule); + void firewallEnableChildChain(int childChain, boolean enable); + @utf8InCpp String[] interfaceGetList(); + android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName); + void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg); + void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable); + void interfaceClearAddrs(in @utf8InCpp String ifName); + void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable); + void interfaceSetMtu(in @utf8InCpp String ifName, int mtu); + void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface); + void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface); + void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues); + void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener); + void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids); + void firewallRemoveUidInterfaceRules(in int[] uids); + void trafficSwapActiveStatsMap(); + IBinder getOemNetd(); + void tetherStartWithConfiguration(in android.net.TetherConfigParcel config); + android.net.MarkMaskParcel getFwmarkForNetwork(int netId); + void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator. + */ + void tetherOffloadRuleAdd(in android.net.TetherOffloadRuleParcel rule); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator. + */ + void tetherOffloadRuleRemove(in android.net.TetherOffloadRuleParcel rule); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator. + */ + android.net.TetherStatsParcel[] tetherOffloadGetStats(); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator. + */ + void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator. + */ + android.net.TetherStatsParcel tetherOffloadGetAndClearStats(int ifIndex); + void networkCreate(in android.net.NativeNetworkConfig config); + void networkAddUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig); + void networkRemoveUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig); + const int IPV4 = 4; + const int IPV6 = 6; + const int CONF = 1; + const int NEIGH = 2; + const String IPSEC_INTERFACE_PREFIX = "ipsec"; + const int IPV6_ADDR_GEN_MODE_EUI64 = 0; + const int IPV6_ADDR_GEN_MODE_NONE = 1; + const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2; + const int IPV6_ADDR_GEN_MODE_RANDOM = 3; + const int IPV6_ADDR_GEN_MODE_DEFAULT = 0; + const int PENALTY_POLICY_ACCEPT = 1; + const int PENALTY_POLICY_LOG = 2; + const int PENALTY_POLICY_REJECT = 3; + const int LOCAL_NET_ID = 99; + const int DUMMY_NET_ID = 51; + const int UNREACHABLE_NET_ID = 52; + const String NEXTHOP_NONE = ""; + const String NEXTHOP_UNREACHABLE = "unreachable"; + const String NEXTHOP_THROW = "throw"; + const int PERMISSION_NONE = 0; + const int PERMISSION_NETWORK = 1; + const int PERMISSION_SYSTEM = 2; + const int NO_PERMISSIONS = 0; + const int PERMISSION_INTERNET = 4; + const int PERMISSION_UPDATE_DEVICE_STATS = 8; + const int PERMISSION_UNINSTALLED = -1; + /** + * @deprecated use FIREWALL_ALLOWLIST. + */ + const int FIREWALL_WHITELIST = 0; + const int FIREWALL_ALLOWLIST = 0; + /** + * @deprecated use FIREWALL_DENYLIST. + */ + const int FIREWALL_BLACKLIST = 1; + const int FIREWALL_DENYLIST = 1; + const int FIREWALL_RULE_ALLOW = 1; + const int FIREWALL_RULE_DENY = 2; + const int FIREWALL_CHAIN_NONE = 0; + const int FIREWALL_CHAIN_DOZABLE = 1; + const int FIREWALL_CHAIN_STANDBY = 2; + const int FIREWALL_CHAIN_POWERSAVE = 3; + const int FIREWALL_CHAIN_RESTRICTED = 4; + const String IF_STATE_UP = "up"; + const String IF_STATE_DOWN = "down"; + const String IF_FLAG_BROADCAST = "broadcast"; + const String IF_FLAG_LOOPBACK = "loopback"; + const String IF_FLAG_POINTOPOINT = "point-to-point"; + const String IF_FLAG_RUNNING = "running"; + const String IF_FLAG_MULTICAST = "multicast"; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/INetdUnsolicitedEventListener.aidl new file mode 100644 index 0000000000000000000000000000000000000000..31775dfd11fe25f2fede661b7e4da11ab4ce8f91 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/INetdUnsolicitedEventListener.aidl @@ -0,0 +1,48 @@ +/** + * 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 INetdUnsolicitedEventListener { + oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid); + oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName); + oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers); + oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope); + oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope); + oneway void onInterfaceAdded(@utf8InCpp String ifName); + oneway void onInterfaceRemoved(@utf8InCpp String ifName); + oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up); + oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up); + oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName); + oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex); +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/InterfaceConfigurationParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..1869d8d49e6a59a1294c7925c460a592f945e677 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/InterfaceConfigurationParcel.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 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 */ +parcelable InterfaceConfigurationParcel { + @utf8InCpp String ifName; + @utf8InCpp String hwAddr; + @utf8InCpp String ipv4Addr; + int prefixLength; + @utf8InCpp String[] flags; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/MarkMaskParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/MarkMaskParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..8ea20d118fc8c3350249e6268348f77578a2b0b6 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/MarkMaskParcel.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 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 */ +parcelable MarkMaskParcel { + int mark; + int mask; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/NativeNetworkConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/NativeNetworkConfig.aidl new file mode 100644 index 0000000000000000000000000000000000000000..77d814b825b348f04612a11b0459150c02453c68 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/NativeNetworkConfig.aidl @@ -0,0 +1,44 @@ +/* + * 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; +/* @hide */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable NativeNetworkConfig { + int netId; + android.net.NativeNetworkType networkType = android.net.NativeNetworkType.PHYSICAL; + int permission; + boolean secure; + android.net.NativeVpnType vpnType = android.net.NativeVpnType.PLATFORM; + boolean excludeLocalRoutes = false; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/NativeNetworkType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/NativeNetworkType.aidl new file mode 100644 index 0000000000000000000000000000000000000000..06c8979d51778df6bd58542a5f5324a849b5b5fe --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/NativeNetworkType.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; +@Backing(type="int") +enum NativeNetworkType { + PHYSICAL = 0, + VIRTUAL = 1, +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/NativeVpnType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/NativeVpnType.aidl new file mode 100644 index 0000000000000000000000000000000000000000..8a8be83958c80bdaa6e5401bb4411ff1e9b72938 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/NativeVpnType.aidl @@ -0,0 +1,41 @@ +/* + * 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; +@Backing(type="int") +enum NativeVpnType { + SERVICE = 1, + PLATFORM = 2, + LEGACY = 3, + OEM = 4, +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/RouteInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/RouteInfoParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..5ef95e67ca963ed343c8481d8061e0982cc8f866 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/RouteInfoParcel.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; +parcelable RouteInfoParcel { + @utf8InCpp String destination; + @utf8InCpp String ifName; + @utf8InCpp String nextHop; + int mtu; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/TetherConfigParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/TetherConfigParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..7b39c22e4ef0bef2a148578ec2a345b98f9ca706 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/TetherConfigParcel.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 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 */ +parcelable TetherConfigParcel { + boolean usingLegacyDnsProxy; + @utf8InCpp String[] dhcpRanges; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/TetherOffloadRuleParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/TetherOffloadRuleParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..983e9860ea8e94007af826844f4fbcbe3d5aee22 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/TetherOffloadRuleParcel.aidl @@ -0,0 +1,44 @@ +/* + * 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; +/* @hide */ +parcelable TetherOffloadRuleParcel { + int inputInterfaceIndex; + int outputInterfaceIndex; + byte[] destination; + int prefixLength; + byte[] srcL2Address; + byte[] dstL2Address; + int pmtu = 1500; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/TetherStatsParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..5f1b722690b0f2ee3e7ec3309edf3b1a48ab7979 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/TetherStatsParcel.aidl @@ -0,0 +1,43 @@ +/* + * 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 */ +parcelable TetherStatsParcel { + @utf8InCpp String iface; + long rxBytes; + long rxPackets; + long txBytes; + long txPackets; + int ifIndex = 0; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/UidRangeParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..72e987a2af49e9249320622f5ba9e729ef6d6309 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/UidRangeParcel.aidl @@ -0,0 +1,40 @@ +/* + * 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 */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable UidRangeParcel { + int start; + int stop; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/netd/aidl/NativeUidRangeConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/netd/aidl/NativeUidRangeConfig.aidl new file mode 100644 index 0000000000000000000000000000000000000000..9bb679f1c115a4d69683f9c1c6e3d8aead8bab75 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/netd/aidl/NativeUidRangeConfig.aidl @@ -0,0 +1,41 @@ +/* + * 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.netd.aidl; +/* @hide */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable NativeUidRangeConfig { + int netId; + android.net.UidRangeParcel[] uidRanges; + int subPriority; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/INetd.aidl new file mode 100644 index 0000000000000000000000000000000000000000..3507784723c3f638133143083462dbe045fa1329 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/INetd.aidl @@ -0,0 +1,226 @@ +/** + * 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// 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 INetd { + boolean isAlive(); + boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isAllowlist, in int[] uids); + boolean bandwidthEnableDataSaver(boolean enable); + /** + * @deprecated use networkCreate() instead. + */ + void networkCreatePhysical(int netId, int permission); + /** + * @deprecated use networkCreate() instead. + */ + void networkCreateVpn(int netId, boolean secure); + void networkDestroy(int netId); + void networkAddInterface(int netId, in @utf8InCpp String iface); + void networkRemoveInterface(int netId, in @utf8InCpp String iface); + void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges); + void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges); + void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges); + void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids); + boolean tetherApplyDnsInterfaces(); + android.net.TetherStatsParcel[] tetherGetStats(); + void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength); + void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength); + @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter); + void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value); + void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid); + int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi); + void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId); + void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi); + void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket); + void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId); + void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId); + void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId); + void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId); + void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName); + void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask); + void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask); + void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode); + void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel); + void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel); + void strictUidCleartextPenalty(int uid, int policyPenalty); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd control plane moved to the mainline module starting in T. See ClatCoordinator. + */ + @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd control plane moved to the mainline module starting in T. See ClatCoordinator. + */ + void clatdStop(in @utf8InCpp String ifName); + boolean ipfwdEnabled(); + @utf8InCpp String[] ipfwdGetRequesterList(); + void ipfwdEnableForwarding(in @utf8InCpp String requester); + void ipfwdDisableForwarding(in @utf8InCpp String requester); + void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface); + void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface); + void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes); + void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName); + void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes); + void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName); + void bandwidthSetGlobalAlert(long bytes); + void bandwidthAddNaughtyApp(int uid); + void bandwidthRemoveNaughtyApp(int uid); + void bandwidthAddNiceApp(int uid); + void bandwidthRemoveNiceApp(int uid); + void tetherStart(in @utf8InCpp String[] dhcpRanges); + void tetherStop(); + boolean tetherIsEnabled(); + void tetherInterfaceAdd(in @utf8InCpp String ifName); + void tetherInterfaceRemove(in @utf8InCpp String ifName); + @utf8InCpp String[] tetherInterfaceList(); + void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs); + @utf8InCpp String[] tetherDnsList(); + void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop); + void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop); + void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid); + void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid); + int networkGetDefault(); + void networkSetDefault(int netId); + void networkClearDefault(); + void networkSetPermissionForNetwork(int netId, int permission); + void networkSetPermissionForUser(int permission, in int[] uids); + void networkClearPermissionForUser(in int[] uids); + void trafficSetNetPermForUids(int permission, in int[] uids); + void networkSetProtectAllow(int uid); + void networkSetProtectDeny(int uid); + boolean networkCanProtect(int uid); + void firewallSetFirewallType(int firewalltype); + void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule); + void firewallSetUidRule(int childChain, int uid, int firewallRule); + void firewallEnableChildChain(int childChain, boolean enable); + @utf8InCpp String[] interfaceGetList(); + android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName); + void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg); + void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable); + void interfaceClearAddrs(in @utf8InCpp String ifName); + void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable); + void interfaceSetMtu(in @utf8InCpp String ifName, int mtu); + void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface); + void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface); + void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues); + void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener); + void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids); + void firewallRemoveUidInterfaceRules(in int[] uids); + void trafficSwapActiveStatsMap(); + IBinder getOemNetd(); + void tetherStartWithConfiguration(in android.net.TetherConfigParcel config); + android.net.MarkMaskParcel getFwmarkForNetwork(int netId); + void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator. + */ + void tetherOffloadRuleAdd(in android.net.TetherOffloadRuleParcel rule); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator. + */ + void tetherOffloadRuleRemove(in android.net.TetherOffloadRuleParcel rule); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator. + */ + android.net.TetherStatsParcel[] tetherOffloadGetStats(); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator. + */ + void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes); + /** + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator. + */ + android.net.TetherStatsParcel tetherOffloadGetAndClearStats(int ifIndex); + void networkCreate(in android.net.NativeNetworkConfig config); + void networkAddUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig); + void networkRemoveUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig); + void ipSecMigrate(in android.net.IpSecMigrateInfoParcel migrateInfo); + void setNetworkAllowlist(in android.net.netd.aidl.NativeUidRangeConfig[] allowedNetworks); + const int IPV4 = 4; + const int IPV6 = 6; + const int CONF = 1; + const int NEIGH = 2; + const String IPSEC_INTERFACE_PREFIX = "ipsec"; + const int IPV6_ADDR_GEN_MODE_EUI64 = 0; + const int IPV6_ADDR_GEN_MODE_NONE = 1; + const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2; + const int IPV6_ADDR_GEN_MODE_RANDOM = 3; + const int IPV6_ADDR_GEN_MODE_DEFAULT = 0; + const int PENALTY_POLICY_ACCEPT = 1; + const int PENALTY_POLICY_LOG = 2; + const int PENALTY_POLICY_REJECT = 3; + const int CLAT_MARK = 0xdeadc1a7; + const int LOCAL_NET_ID = 99; + const int DUMMY_NET_ID = 51; + const int UNREACHABLE_NET_ID = 52; + const String NEXTHOP_NONE = ""; + const String NEXTHOP_UNREACHABLE = "unreachable"; + const String NEXTHOP_THROW = "throw"; + const int PERMISSION_NONE = 0; + const int PERMISSION_NETWORK = 1; + const int PERMISSION_SYSTEM = 2; + const int NO_PERMISSIONS = 0; + const int PERMISSION_INTERNET = 4; + const int PERMISSION_UPDATE_DEVICE_STATS = 8; + const int PERMISSION_UNINSTALLED = (-1); + /** + * @deprecated use FIREWALL_ALLOWLIST. + */ + const int FIREWALL_WHITELIST = 0; + const int FIREWALL_ALLOWLIST = 0; + /** + * @deprecated use FIREWALL_DENYLIST. + */ + const int FIREWALL_BLACKLIST = 1; + const int FIREWALL_DENYLIST = 1; + const int FIREWALL_RULE_ALLOW = 1; + const int FIREWALL_RULE_DENY = 2; + const int FIREWALL_CHAIN_NONE = 0; + const int FIREWALL_CHAIN_DOZABLE = 1; + const int FIREWALL_CHAIN_STANDBY = 2; + const int FIREWALL_CHAIN_POWERSAVE = 3; + const int FIREWALL_CHAIN_RESTRICTED = 4; + const String IF_STATE_UP = "up"; + const String IF_STATE_DOWN = "down"; + const String IF_FLAG_BROADCAST = "broadcast"; + const String IF_FLAG_LOOPBACK = "loopback"; + const String IF_FLAG_POINTOPOINT = "point-to-point"; + const String IF_FLAG_RUNNING = "running"; + const String IF_FLAG_MULTICAST = "multicast"; + const int IPSEC_DIRECTION_IN = 0; + const int IPSEC_DIRECTION_OUT = 1; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/INetdUnsolicitedEventListener.aidl new file mode 100644 index 0000000000000000000000000000000000000000..31775dfd11fe25f2fede661b7e4da11ab4ce8f91 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/INetdUnsolicitedEventListener.aidl @@ -0,0 +1,48 @@ +/** + * 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 INetdUnsolicitedEventListener { + oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid); + oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName); + oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers); + oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope); + oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope); + oneway void onInterfaceAdded(@utf8InCpp String ifName); + oneway void onInterfaceRemoved(@utf8InCpp String ifName); + oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up); + oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up); + oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName); + oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex); +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/InterfaceConfigurationParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..1869d8d49e6a59a1294c7925c460a592f945e677 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/InterfaceConfigurationParcel.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 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 */ +parcelable InterfaceConfigurationParcel { + @utf8InCpp String ifName; + @utf8InCpp String hwAddr; + @utf8InCpp String ipv4Addr; + int prefixLength; + @utf8InCpp String[] flags; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/IpSecMigrateInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/IpSecMigrateInfoParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..975a26199d52bb532523ed86d050118dac35a102 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/IpSecMigrateInfoParcel.aidl @@ -0,0 +1,45 @@ +/** + * 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; +@JavaOnlyImmutable +parcelable IpSecMigrateInfoParcel { + int requestId; + int selAddrFamily; + int direction; + @utf8InCpp String oldSourceAddress; + @utf8InCpp String oldDestinationAddress; + @utf8InCpp String newSourceAddress; + @utf8InCpp String newDestinationAddress; + int interfaceId; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/MarkMaskParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/MarkMaskParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..8ea20d118fc8c3350249e6268348f77578a2b0b6 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/MarkMaskParcel.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 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 */ +parcelable MarkMaskParcel { + int mark; + int mask; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/NativeNetworkConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/NativeNetworkConfig.aidl new file mode 100644 index 0000000000000000000000000000000000000000..77d814b825b348f04612a11b0459150c02453c68 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/NativeNetworkConfig.aidl @@ -0,0 +1,44 @@ +/* + * 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; +/* @hide */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable NativeNetworkConfig { + int netId; + android.net.NativeNetworkType networkType = android.net.NativeNetworkType.PHYSICAL; + int permission; + boolean secure; + android.net.NativeVpnType vpnType = android.net.NativeVpnType.PLATFORM; + boolean excludeLocalRoutes = false; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/NativeNetworkType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/NativeNetworkType.aidl new file mode 100644 index 0000000000000000000000000000000000000000..e77a1432e84d7dfdb307254c9c02a2a197d772f2 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/NativeNetworkType.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; +@Backing(type="int") +enum NativeNetworkType { + PHYSICAL = 0, + VIRTUAL = 1, + PHYSICAL_LOCAL = 2, +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/NativeVpnType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/NativeVpnType.aidl new file mode 100644 index 0000000000000000000000000000000000000000..8a8be83958c80bdaa6e5401bb4411ff1e9b72938 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/NativeVpnType.aidl @@ -0,0 +1,41 @@ +/* + * 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; +@Backing(type="int") +enum NativeVpnType { + SERVICE = 1, + PLATFORM = 2, + LEGACY = 3, + OEM = 4, +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/RouteInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/RouteInfoParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..5ef95e67ca963ed343c8481d8061e0982cc8f866 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/RouteInfoParcel.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; +parcelable RouteInfoParcel { + @utf8InCpp String destination; + @utf8InCpp String ifName; + @utf8InCpp String nextHop; + int mtu; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/TetherConfigParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/TetherConfigParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..7b39c22e4ef0bef2a148578ec2a345b98f9ca706 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/TetherConfigParcel.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 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 */ +parcelable TetherConfigParcel { + boolean usingLegacyDnsProxy; + @utf8InCpp String[] dhcpRanges; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/TetherOffloadRuleParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/TetherOffloadRuleParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..983e9860ea8e94007af826844f4fbcbe3d5aee22 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/TetherOffloadRuleParcel.aidl @@ -0,0 +1,44 @@ +/* + * 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; +/* @hide */ +parcelable TetherOffloadRuleParcel { + int inputInterfaceIndex; + int outputInterfaceIndex; + byte[] destination; + int prefixLength; + byte[] srcL2Address; + byte[] dstL2Address; + int pmtu = 1500; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/TetherStatsParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..5f1b722690b0f2ee3e7ec3309edf3b1a48ab7979 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/TetherStatsParcel.aidl @@ -0,0 +1,43 @@ +/* + * 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 */ +parcelable TetherStatsParcel { + @utf8InCpp String iface; + long rxBytes; + long rxPackets; + long txBytes; + long txPackets; + int ifIndex = 0; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/UidRangeParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..72e987a2af49e9249320622f5ba9e729ef6d6309 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/UidRangeParcel.aidl @@ -0,0 +1,40 @@ +/* + * 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 */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable UidRangeParcel { + int start; + int stop; +} diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/netd/aidl/NativeUidRangeConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/netd/aidl/NativeUidRangeConfig.aidl new file mode 100644 index 0000000000000000000000000000000000000000..9bb679f1c115a4d69683f9c1c6e3d8aead8bab75 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/netd/aidl/NativeUidRangeConfig.aidl @@ -0,0 +1,41 @@ +/* + * 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.netd.aidl; +/* @hide */ +@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable +parcelable NativeUidRangeConfig { + int netId; + android.net.UidRangeParcel[] uidRanges; + int subPriority; +} diff --git a/staticlibs/netd/aidl_api/netd_event_listener_interface/1/.hash b/staticlibs/netd/aidl_api/netd_event_listener_interface/1/.hash new file mode 100644 index 0000000000000000000000000000000000000000..f39f730fe58e1d861835e6e98acfa6999e5124e5 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_event_listener_interface/1/.hash @@ -0,0 +1 @@ +8e27594d285ca7c567d87e8cf74766c27647e02b diff --git a/staticlibs/netd/aidl_api/netd_event_listener_interface/1/android/net/metrics/INetdEventListener.aidl b/staticlibs/netd/aidl_api/netd_event_listener_interface/1/android/net/metrics/INetdEventListener.aidl new file mode 100644 index 0000000000000000000000000000000000000000..9898a6740363f2114dd63525d7b59b1628ab651a --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_event_listener_interface/1/android/net/metrics/INetdEventListener.aidl @@ -0,0 +1,34 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not +// try to edit this file. It looks like you are doing that because you have +// modified an AIDL interface in a backward-incompatible way, e.g., deleting a +// function from an interface or a field from a parcelable and it broke the +// build. That breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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.metrics; +interface INetdEventListener { + oneway void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs, @utf8InCpp String hostname, in @utf8InCpp String[] ipAddresses, int ipAddressesCount, int uid); + oneway void onPrivateDnsValidationEvent(int netId, String ipAddress, String hostname, boolean validated); + oneway void onConnectEvent(int netId, int error, int latencyMs, String ipAddr, int port, int uid); + oneway void onWakeupEvent(String prefix, int uid, int ethertype, int ipNextHeader, in byte[] dstHw, String srcIp, String dstIp, int srcPort, int dstPort, long timestampNs); + oneway void onTcpSocketStatsEvent(in int[] networkIds, in int[] sentPackets, in int[] lostPackets, in int[] rttUs, in int[] sentAckDiffMs); + oneway void onNat64PrefixEvent(int netId, boolean added, @utf8InCpp String prefixString, int prefixLength); + const int EVENT_GETADDRINFO = 1; + const int EVENT_GETHOSTBYNAME = 2; + const int EVENT_GETHOSTBYADDR = 3; + const int EVENT_RES_NSEND = 4; + const int REPORTING_LEVEL_NONE = 0; + const int REPORTING_LEVEL_METRICS = 1; + const int REPORTING_LEVEL_FULL = 2; + const int DNS_REPORTED_IP_ADDRESSES_LIMIT = 10; +} diff --git a/staticlibs/netd/aidl_api/netd_event_listener_interface/2/.hash b/staticlibs/netd/aidl_api/netd_event_listener_interface/2/.hash new file mode 100644 index 0000000000000000000000000000000000000000..67c55b77b68b2b49d53d31f87399b5eb6c700859 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_event_listener_interface/2/.hash @@ -0,0 +1 @@ +1b765b02815e970a124de92e793e42e0ceff5384 diff --git a/staticlibs/netd/aidl_api/netd_event_listener_interface/2/android/net/metrics/INetdEventListener.aidl b/staticlibs/netd/aidl_api/netd_event_listener_interface/2/android/net/metrics/INetdEventListener.aidl new file mode 100644 index 0000000000000000000000000000000000000000..1b0fe132ee3ea11c247f88084f508309702b12c7 --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_event_listener_interface/2/android/net/metrics/INetdEventListener.aidl @@ -0,0 +1,51 @@ +/** + * 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// 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.metrics; +/* @hide */ +interface INetdEventListener { + oneway void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs, @utf8InCpp String hostname, in @utf8InCpp String[] ipAddresses, int ipAddressesCount, int uid); + oneway void onPrivateDnsValidationEvent(int netId, String ipAddress, String hostname, boolean validated); + oneway void onConnectEvent(int netId, int error, int latencyMs, String ipAddr, int port, int uid); + oneway void onWakeupEvent(String prefix, int uid, int ethertype, int ipNextHeader, in byte[] dstHw, String srcIp, String dstIp, int srcPort, int dstPort, long timestampNs); + oneway void onTcpSocketStatsEvent(in int[] networkIds, in int[] sentPackets, in int[] lostPackets, in int[] rttUs, in int[] sentAckDiffMs); + oneway void onNat64PrefixEvent(int netId, boolean added, @utf8InCpp String prefixString, int prefixLength); + const int EVENT_GETADDRINFO = 1; + const int EVENT_GETHOSTBYNAME = 2; + const int EVENT_GETHOSTBYADDR = 3; + const int EVENT_RES_NSEND = 4; + const int REPORTING_LEVEL_NONE = 0; + const int REPORTING_LEVEL_METRICS = 1; + const int REPORTING_LEVEL_FULL = 2; + const int DNS_REPORTED_IP_ADDRESSES_LIMIT = 10; +} diff --git a/staticlibs/netd/aidl_api/netd_event_listener_interface/current/android/net/metrics/INetdEventListener.aidl b/staticlibs/netd/aidl_api/netd_event_listener_interface/current/android/net/metrics/INetdEventListener.aidl new file mode 100644 index 0000000000000000000000000000000000000000..d71c3f29b56d86050e21f2f7e00f8214e2adaf3a --- /dev/null +++ b/staticlibs/netd/aidl_api/netd_event_listener_interface/current/android/net/metrics/INetdEventListener.aidl @@ -0,0 +1,35 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files 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.metrics; +/* @hide */ +interface INetdEventListener { + oneway void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs, @utf8InCpp String hostname, in @utf8InCpp String[] ipAddresses, int ipAddressesCount, int uid); + oneway void onPrivateDnsValidationEvent(int netId, String ipAddress, String hostname, boolean validated); + oneway void onConnectEvent(int netId, int error, int latencyMs, String ipAddr, int port, int uid); + oneway void onWakeupEvent(String prefix, int uid, int ethertype, int ipNextHeader, in byte[] dstHw, String srcIp, String dstIp, int srcPort, int dstPort, long timestampNs); + oneway void onTcpSocketStatsEvent(in int[] networkIds, in int[] sentPackets, in int[] lostPackets, in int[] rttUs, in int[] sentAckDiffMs); + oneway void onNat64PrefixEvent(int netId, boolean added, @utf8InCpp String prefixString, int prefixLength); + const int EVENT_GETADDRINFO = 1; + const int EVENT_GETHOSTBYNAME = 2; + const int EVENT_GETHOSTBYADDR = 3; + const int EVENT_RES_NSEND = 4; + const int REPORTING_LEVEL_NONE = 0; + const int REPORTING_LEVEL_METRICS = 1; + const int REPORTING_LEVEL_FULL = 2; + const int DNS_REPORTED_IP_ADDRESSES_LIMIT = 10; +} diff --git a/staticlibs/netd/binder/android/net/INetd.aidl b/staticlibs/netd/binder/android/net/INetd.aidl new file mode 100644 index 0000000000000000000000000000000000000000..27d9a035ff00e5e70bddfa0ed5588ebf44d71105 --- /dev/null +++ b/staticlibs/netd/binder/android/net/INetd.aidl @@ -0,0 +1,1438 @@ +/** + * 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; + +import android.net.INetdUnsolicitedEventListener; +import android.net.InterfaceConfigurationParcel; +import android.net.IpSecMigrateInfoParcel; +import android.net.MarkMaskParcel; +import android.net.NativeNetworkConfig; +import android.net.RouteInfoParcel; +import android.net.TetherConfigParcel; +import android.net.TetherOffloadRuleParcel; +import android.net.TetherStatsParcel; +import android.net.UidRangeParcel; +import android.net.netd.aidl.NativeUidRangeConfig; + +/** {@hide} */ +interface INetd { + /** + * Returns true if the service is responding. + */ + boolean isAlive(); + + /** + * Replaces the contents of the specified UID-based firewall chain. + * + * The chain may be an allowlist chain or a denylist chain. A denylist chain contains DROP + * rules for the specified UIDs and a RETURN rule at the end. An allowlist chain contains RETURN + * rules for the system UID range (0 to {@code UID_APP} - 1), RETURN rules for for the specified + * UIDs, and a DROP rule at the end. The chain will be created if it does not exist. + * + * @param chainName The name of the chain to replace. + * @param isAllowlist Whether this is an allowlist or denylist chain. + * @param uids The list of UIDs to allow/deny. + * @return true if the chain was successfully replaced, false otherwise. + */ + boolean firewallReplaceUidChain(in @utf8InCpp String chainName, + boolean isAllowlist, + in int[] uids); + + /** + * Enables or disables data saver mode on costly network interfaces. + * + * - When disabled, all packets to/from apps in the penalty box chain are rejected on costly + * interfaces. Traffic to/from other apps or on other network interfaces is allowed. + * - When enabled, only apps that are in the happy box chain and not in the penalty box chain + * are allowed network connectivity on costly interfaces. All other packets on these + * interfaces are rejected. The happy box chain always contains all system UIDs; to disallow + * traffic from system UIDs, place them in the penalty box chain. + * + * By default, data saver mode is disabled. This command has no effect but might still return an + * error) if {@code enable} is the same as the current value. + * + * @param enable whether to enable or disable data saver mode. + * @return true if the if the operation was successful, false otherwise. + */ + boolean bandwidthEnableDataSaver(boolean enable); + + /** + * Creates a physical network (i.e., one containing physical interfaces. + * @deprecated use networkCreate() instead. + * + * @param netId the networkId to create. + * @param permission the permission necessary to use the network. Must be one of + * PERMISSION_NONE/PERMISSION_NETWORK/PERMISSION_SYSTEM. + * + * @throws ServiceSpecificException in case of failure, with an error code corresponding to the + * unix errno. + */ + void networkCreatePhysical(int netId, int permission); + + /** + * Creates a VPN network. + * @deprecated use networkCreate() instead. + * + * @param netId the network to create. + * @param secure whether unprivileged apps are allowed to bypass the VPN. + * + * @throws ServiceSpecificException in case of failure, with an error code corresponding to the + * unix errno. + */ + void networkCreateVpn(int netId, boolean secure); + + /** + * Destroys a network. Any interfaces added to the network are removed, and the network ceases + * to be the default network. + * + * @param netId the network to destroy. + * + * @throws ServiceSpecificException in case of failure, with an error code corresponding to the + * unix errno. + */ + void networkDestroy(int netId); + + /** + * Adds an interface to a network. The interface must not be assigned to any network, including + * the specified network. + * + * @param netId the network to add the interface to. + * @param interface the name of the interface to add. + * + * @throws ServiceSpecificException in case of failure, with an error code corresponding to the + * unix errno. + */ + void networkAddInterface(int netId, in @utf8InCpp String iface); + + /** + * Adds an interface to a network. The interface must be assigned to the specified network. + * + * @param netId the network to remove the interface from. + * @param interface the name of the interface to remove. + * + * @throws ServiceSpecificException in case of failure, with an error code corresponding to the + * unix errno. + */ + void networkRemoveInterface(int netId, in @utf8InCpp String iface); + + /** + * Adds the specified UID ranges to the specified network. The network can be physical or + * virtual. Traffic from the UID ranges will be routed to the network by default. + * + * @param netId the network ID of the network to add the ranges to. + * @param uidRanges a set of non-overlapping ranges of UIDs to add. These exact ranges + * must not overlap with existing ranges assigned to this network. + * + * @throws ServiceSpecificException in case of failure, with an error code corresponding to the + * unix errno. + */ + void networkAddUidRanges(int netId, in UidRangeParcel[] uidRanges); + + /** + * Remove the specified UID ranges from the specified network. The network can be physical or + * virtual. Traffic from the UID ranges will no longer be routed to the network by default. + * + * @param netId the network ID of the network to remove the ranges from. + * @param uidRanges a set of non-overlapping ranges of UIDs to remove. These exact ranges + * must already be assigned to this network. + * + * @throws ServiceSpecificException in case of failure, with an error code corresponding to the + * unix errno. + */ + void networkRemoveUidRanges(int netId, in UidRangeParcel[] uidRanges); + + /** + * Adds or removes one rule for each supplied UID range to prohibit all network activity outside + * of secure VPN. + * + * When a UID is covered by one of these rules, traffic sent through any socket that is not + * protected or explicitly overriden by the system will be rejected. The kernel will respond + * with an ICMP prohibit message. + * + * Initially, there are no such rules. Any rules that are added will only last until the next + * restart of netd or the device. + * + * @param add {@code true} if the specified UID ranges should be denied access to any network + * which is not secure VPN by adding rules, {@code false} to remove existing rules. + * @param uidRanges a set of non-overlapping, contiguous ranges of UIDs to which to apply or + * remove this restriction. + * <p> Added rules should not overlap with existing rules. Likewise, removed rules should + * each correspond to an existing rule. + * + * @throws ServiceSpecificException in case of failure, with an error code corresponding to the + * unix errno. + */ + void networkRejectNonSecureVpn(boolean add, in UidRangeParcel[] uidRanges); + + /** + * Administratively closes sockets belonging to the specified UIDs. + */ + void socketDestroy(in UidRangeParcel[] uidRanges, in int[] exemptUids); + + /** + * Instruct the tethering DNS server to reevaluated serving interfaces. + * This is needed to for the DNS server to observe changes in the set + * of potential listening IP addresses. (Listening on wildcard addresses + * can turn the device into an open resolver; b/7530468) + * + * TODO: Return something richer than just a boolean. + */ + boolean tetherApplyDnsInterfaces(); + + /** + * Return tethering statistics. + * + * @return an array of TetherStatsParcel, where each entry contains the upstream interface + * name and its tethering statistics since netd startup. + * There will only ever be one entry for a given interface. + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + TetherStatsParcel[] tetherGetStats(); + + /** + * Add/Remove and IP address from an interface. + * + * @param ifName the interface name + * @param addrString the IP address to add/remove as a string literal + * @param prefixLength the prefix length associated with this IP address + * + * @throws ServiceSpecificException in case of failure, with an error code corresponding to the + * unix errno. + */ + void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, + int prefixLength); + void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, + int prefixLength); + + /** + * Set and get /proc/sys/net interface configuration parameters. + * + * @param ipversion One of IPV4/IPV6 integers, indicating the desired IP version directory. + * @param which One of CONF/NEIGH integers, indicating the desired parameter category directory. + * @param ifname The interface name portion of the path; may also be "all" or "default". + * @param parameter The parameter name portion of the path. + * @param value The value string to be written into the assembled path. + * + * @throws ServiceSpecificException in case of failure, with an error code corresponding to the + * unix errno. + */ + + const int IPV4 = 4; + const int IPV6 = 6; + const int CONF = 1; + const int NEIGH = 2; + @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, + in @utf8InCpp String parameter); + void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, + in @utf8InCpp String parameter, in @utf8InCpp String value); + + /** + * Sets owner of socket ParcelFileDescriptor to the new UID, checking to ensure that the caller's + * uid is that of the old owner's, and that this is a UDP-encap socket + * + * @param ParcelFileDescriptor socket Socket file descriptor + * @param int newUid UID of the new socket fd owner + */ + void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid); + + /** + * Reserve an SPI from the kernel + * + * @param transformId a unique identifier for allocated resources + * @param sourceAddress InetAddress as string for the sending endpoint + * @param destinationAddress InetAddress as string for the receiving endpoint + * @param spi a requested 32-bit unique ID or 0 to request random allocation + * @return the SPI that was allocated or 0 if failed + */ + int ipSecAllocateSpi( + int transformId, + in @utf8InCpp String sourceAddress, + in @utf8InCpp String destinationAddress, + int spi); + + /** + * Update an IPsec SA (xfrm_state) describing how ip(v6) traffic will be encrypted + * or decrypted. + * + * @param transformId a unique identifier for allocated resources + * @param mode either Transport or Tunnel mode + * @param sourceAddress InetAddress as string for the sending endpoint + * @param destinationAddress InetAddress as string for the receiving endpoint + * @param underlyingNetId the netId of the network to which the SA is applied. Only accepted for + * tunnel mode SAs. + * @param spi a 32-bit unique ID allocated to the user + * @param markValue a 32-bit unique ID chosen by the user + * @param markMask a 32-bit mask chosen by the user + * @param authAlgo a string identifying the authentication algorithm to be used + * @param authKey a byte array containing the authentication key + * @param authTruncBits the truncation length of the MAC produced by the authentication algorithm + * @param cryptAlgo a string identifying the encryption algorithm to be used + * @param cryptKey a byte arrray containing the encryption key + * @param cryptTruncBits unused parameter + * @param aeadAlgo a string identifying the authenticated encryption algorithm to be used + * @param aeadKey a byte arrray containing the key to be used in authenticated encryption + * @param aeadIcvBits the truncation length of the ICV produced by the authentication algorithm + * (similar to authTruncBits in function) + * @param encapType encapsulation type used (if any) for the udp encap socket + * @param encapLocalPort the port number on the host to be used in encap packets + * @param encapRemotePort the port number of the remote to be used for encap packets + * @param interfaceId the identifier for the IPsec tunnel interface. + * Only accepted for tunnel mode SAs. + */ + void ipSecAddSecurityAssociation( + int transformId, + int mode, + in @utf8InCpp String sourceAddress, + in @utf8InCpp String destinationAddress, + int underlyingNetId, + int spi, + int markValue, + int markMask, + in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, + in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, + in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, + int encapType, + int encapLocalPort, + int encapRemotePort, + int interfaceId); + + /** + * Delete a previously created security association identified by the provided parameters + * + * @param transformId a unique identifier for allocated resources + * @param sourceAddress InetAddress as string for the sending endpoint + * @param destinationAddress InetAddress as string for the receiving endpoint + * @param spi a requested 32-bit unique ID allocated to the user + * @param markValue a 32-bit unique ID chosen by the user + * @param markMask a 32-bit mask chosen by the user + * @param interfaceId the identifier for the IPsec tunnel interface. + */ + void ipSecDeleteSecurityAssociation( + int transformId, + in @utf8InCpp String sourceAddress, + in @utf8InCpp String destinationAddress, + int spi, + int markValue, + int markMask, + int interfaceId); + + /** + * Apply a previously created SA to a specified socket, starting IPsec on that socket + * + * @param socket a user-provided socket that will have IPsec applied + * @param transformId a unique identifier for allocated resources + * @param direction DIRECTION_IN or DIRECTION_OUT + * @param sourceAddress InetAddress as string for the sending endpoint + * @param destinationAddress InetAddress as string for the receiving endpoint + * @param spi a 32-bit unique ID allocated to the user (socket owner) + */ + void ipSecApplyTransportModeTransform( + in ParcelFileDescriptor socket, + int transformId, + int direction, + in @utf8InCpp String sourceAddress, + in @utf8InCpp String destinationAddress, + int spi); + + /** + * Remove an IPsec SA from a given socket. This will allow unencrypted traffic to flow + * on that socket if a transform had been previously applied. + * + * @param socket a user-provided socket from which to remove any IPsec configuration + */ + void ipSecRemoveTransportModeTransform( + in ParcelFileDescriptor socket); + + /** + * Adds an IPsec global policy. + * + * @param transformId a unique identifier for allocated resources + * @param selAddrFamily the address family identifier for the selector + * @param direction DIRECTION_IN or DIRECTION_OUT + * @param tmplSrcAddress InetAddress as string for the sending endpoint + * @param tmplDstAddress InetAddress as string for the receiving endpoint + * @param spi a 32-bit unique ID allocated to the user + * @param markValue a 32-bit unique ID chosen by the user + * @param markMask a 32-bit mask chosen by the user + * @param interfaceId the identifier for the IPsec tunnel interface. + */ + void ipSecAddSecurityPolicy( + int transformId, + int selAddrFamily, + int direction, + in @utf8InCpp String tmplSrcAddress, + in @utf8InCpp String tmplDstAddress, + int spi, + int markValue, + int markMask, + int interfaceId); + + /** + * Updates an IPsec global policy. + * + * @param transformId a unique identifier for allocated resources + * @param selAddrFamily the address family identifier for the selector + * @param direction DIRECTION_IN or DIRECTION_OUT + * @param tmplSrcAddress InetAddress as string for the sending endpoint + * @param tmplDstAddress InetAddress as string for the receiving endpoint + * @param spi a 32-bit unique ID allocated to the user + * @param markValue a 32-bit unique ID chosen by the user + * @param markMask a 32-bit mask chosen by the user + * @param interfaceId the identifier for the IPsec tunnel interface. + */ + void ipSecUpdateSecurityPolicy( + int transformId, + int selAddrFamily, + int direction, + in @utf8InCpp String tmplSrcAddress, + in @utf8InCpp String tmplDstAddress, + int spi, + int markValue, + int markMask, + int interfaceId); + + /** + * Deletes an IPsec global policy. + * + * Deletion of global policies does not do any matching based on the templates, thus + * template source/destination addresses are not needed (as opposed to add/update). + * + * @param transformId a unique identifier for allocated resources + * @param selAddrFamily the address family identifier for the selector + * @param direction DIRECTION_IN or DIRECTION_OUT + * @param markValue a 32-bit unique ID chosen by the user + * @param markMask a 32-bit mask chosen by the user + * @param interfaceId the identifier for the IPsec tunnel interface. + */ + void ipSecDeleteSecurityPolicy( + int transformId, + int selAddrFamily, + int direction, + int markValue, + int markMask, + int interfaceId); + + // This could not be declared as @uft8InCpp; thus, when used in native code it must be + // converted from a UTF-16 string to an ASCII string. + const String IPSEC_INTERFACE_PREFIX = "ipsec"; + + /** + * Add a IPsec Tunnel Interface. + * + * @param devName a unique identifier that represents the name of the device + * @param localAddress InetAddress as string for the local endpoint + * @param remoteAddress InetAddress as string for the remote endpoint + * @param iKey, to match Policies and SAs for input packets. + * @param oKey, to match Policies and SAs for output packets. + * @param interfaceId the identifier for the IPsec tunnel interface. + */ + void ipSecAddTunnelInterface( + in @utf8InCpp String deviceName, + in @utf8InCpp String localAddress, + in @utf8InCpp String remoteAddress, + int iKey, + int oKey, + int interfaceId); + + /** + * Update a IPsec Tunnel Interface. + * + * @param devName a unique identifier that represents the name of the device + * @param localAddress InetAddress as string for the local endpoint + * @param remoteAddress InetAddress as string for the remote endpoint + * @param iKey, to match Policies and SAs for input packets. + * @param oKey, to match Policies and SAs for output packets. + * @param interfaceId the identifier for the IPsec tunnel interface. + */ + void ipSecUpdateTunnelInterface( + in @utf8InCpp String deviceName, + in @utf8InCpp String localAddress, + in @utf8InCpp String remoteAddress, + int iKey, + int oKey, + int interfaceId); + + /** + * Removes a IPsec Tunnel Interface. + * + * @param devName a unique identifier that represents the name of the device + */ + void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName); + + /** + * Request notification of wakeup packets arriving on an interface. Notifications will be + * delivered to INetdEventListener.onWakeupEvent(). + * + * @param ifName the interface + * @param prefix arbitrary string used to identify wakeup sources in onWakeupEvent + */ + void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask); + + /** + * Stop notification of wakeup packets arriving on an interface. + * + * @param ifName the interface + * @param prefix arbitrary string used to identify wakeup sources in onWakeupEvent + */ + void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask); + + const int IPV6_ADDR_GEN_MODE_EUI64 = 0; + const int IPV6_ADDR_GEN_MODE_NONE = 1; + const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2; + const int IPV6_ADDR_GEN_MODE_RANDOM = 3; + + const int IPV6_ADDR_GEN_MODE_DEFAULT = 0; + /** + * Set IPv6 address generation mode. IPv6 should be disabled before changing mode. + * + * @param mode SLAAC address generation mechanism to use + */ + void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode); + + /** + * Add idletimer for specific interface + * + * @param ifName Name of target interface + * @param timeout The time in seconds that will trigger idletimer + * @param classLabel The unique identifier for this idletimer + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void idletimerAddInterface( + in @utf8InCpp String ifName, + int timeout, + in @utf8InCpp String classLabel); + + /** + * Remove idletimer for specific interface + * + * @param ifName Name of target interface + * @param timeout The time in seconds that will trigger idletimer + * @param classLabel The unique identifier for this idletimer + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void idletimerRemoveInterface( + in @utf8InCpp String ifName, + int timeout, + in @utf8InCpp String classLabel); + + const int PENALTY_POLICY_ACCEPT = 1; + const int PENALTY_POLICY_LOG = 2; + const int PENALTY_POLICY_REJECT = 3; + + /** + * Offers to detect sockets sending data not wrapped inside a layer of SSL/TLS encryption. + * + * @param uid Uid of the app + * @param policyPenalty The penalty policy of the app + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void strictUidCleartextPenalty(int uid, int policyPenalty); + + /** + * Start clatd + * + * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd + * control plane moved to the mainline module starting in T. See ClatCoordinator. + * @param ifName interface name to start clatd + * @param nat64Prefix the NAT64 prefix, e.g., "2001:db8:64::/96". + * @return a string, the IPv6 address that will be used for 464xlat. + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix); + + /** + * Stop clatd + * + * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd + * control plane moved to the mainline module starting in T. See ClatCoordinator. + * @param ifName interface name to stop clatd + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void clatdStop(in @utf8InCpp String ifName); + + /** + * Packet mark that identifies non-offloaded ingress clat packets. + */ + const int CLAT_MARK = 0xdeadc1a7; + + /** + * Get status of IP forwarding + * + * @return true if IP forwarding is enabled, false otherwise. + */ + boolean ipfwdEnabled(); + + /** + * Get requester list of IP forwarding + * + * @return An array of strings containing requester list of IP forwarding + */ + @utf8InCpp String[] ipfwdGetRequesterList(); + + /** + * Enable IP forwarding for specific requester + * + * @param requester requester name to enable IP forwarding. It is a unique name which will be + * stored in Netd to make sure if any requester needs IP forwarding. + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void ipfwdEnableForwarding(in @utf8InCpp String requester); + + /** + * Disable IP forwarding for specific requester + * + * @param requester requester name to disable IP forwarding. This name should match the + * names which are set by ipfwdEnableForwarding. + * IP forwarding would be disabled if it is the last requester. + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void ipfwdDisableForwarding(in @utf8InCpp String requester); + + /** + * Add forwarding ip rule + * + * @param fromIface interface name to add forwarding ip rule + * @param toIface interface name to add forwarding ip rule + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface); + + /** + * Remove forwarding ip rule + * + * @param fromIface interface name to remove forwarding ip rule + * @param toIface interface name to remove forwarding ip rule + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface); + + /** + * Set quota for interface + * + * @param ifName Name of target interface + * @param bytes Quota value in bytes + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes); + + /** + * Remove quota for interface + * + * @param ifName Name of target interface + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName); + + /** + * Set alert for interface + * + * @param ifName Name of target interface + * @param bytes Alert value in bytes + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes); + + /** + * Remove alert for interface + * + * @param ifName Name of target interface + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName); + + /** + * Set global alert + * + * @param bytes Alert value in bytes + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void bandwidthSetGlobalAlert(long bytes); + + /** + * Add naughty app bandwidth rule for specific app + * + * @param uid uid of target app + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void bandwidthAddNaughtyApp(int uid); + + /** + * Remove naughty app bandwidth rule for specific app + * + * @param uid uid of target app + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void bandwidthRemoveNaughtyApp(int uid); + + /** + * Add nice app bandwidth rule for specific app + * + * @param uid uid of target app + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void bandwidthAddNiceApp(int uid); + + /** + * Remove nice app bandwidth rule for specific app + * + * @param uid uid of target app + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void bandwidthRemoveNiceApp(int uid); + + /** + * Start tethering + * + * @param dhcpRanges dhcp ranges to set. + * dhcpRanges might contain many addresss {addr1, addr2, aadr3, addr4...} + * Netd splits them into ranges: addr1-addr2, addr3-addr4, etc. + * An odd number of addrs will fail. + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void tetherStart(in @utf8InCpp String[] dhcpRanges); + + /** + * Stop tethering + * + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void tetherStop(); + + /** + * Get status of tethering + * + * @return true if tethering is enabled, false otherwise. + */ + boolean tetherIsEnabled(); + + /** + * Setup interface for tethering + * + * @param ifName interface name to add + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void tetherInterfaceAdd(in @utf8InCpp String ifName); + + /** + * Reset interface for tethering + * + * @param ifName interface name to remove + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void tetherInterfaceRemove(in @utf8InCpp String ifName); + + /** + * Get the interface list which is stored in netd + * The list contains the interfaces managed by tetherInterfaceAdd/tetherInterfaceRemove + * + * @return An array of strings containing interface list result + */ + @utf8InCpp String[] tetherInterfaceList(); + + /** + * Set DNS forwarder server + * + * @param netId the upstream network to forward DNS queries to + * @param dnsAddrs DNS server address to set + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs); + + /** + * Return the DNS list set by tetherDnsSet + * + * @return An array of strings containing the list of DNS servers + */ + @utf8InCpp String[] tetherDnsList(); + + const int LOCAL_NET_ID = 99; + + /** + * Constant net ID for the "dummy" network. + * + * The dummy network is used to blackhole or reject traffic. Any attempt to use it will + * either drop the packets or fail with ENETUNREACH. + */ + const int DUMMY_NET_ID = 51; + + /** + * Constant net ID for the "unreachable" network. + * + * The unreachable network is used to reject traffic. Any attempt to use it will fail + * with ENETUNREACH. + */ + const int UNREACHABLE_NET_ID = 52; + + // Route does not specify a next hop + const String NEXTHOP_NONE = ""; + // Route next hop is unreachable + const String NEXTHOP_UNREACHABLE = "unreachable"; + // Route next hop is throw + const String NEXTHOP_THROW = "throw"; + + /** + * Add a route for specific network + * + * @param netId the network to add the route to + * @param ifName the name of interface of the route. + * This interface should be assigned to the netID. + * @param destination the destination of the route + * @param nextHop The route's next hop address, + * or it could be either NEXTHOP_NONE, NEXTHOP_UNREACHABLE, NEXTHOP_THROW. + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void networkAddRoute( + int netId, + in @utf8InCpp String ifName, + in @utf8InCpp String destination, + in @utf8InCpp String nextHop); + + /** + * Remove a route for specific network + * + * @param netId the network to remove the route from + * @param ifName the name of interface of the route. + * This interface should be assigned to the netID. + * @param destination the destination of the route + * @param nextHop The route's next hop address, + * or it could be either NEXTHOP_NONE, NEXTHOP_UNREACHABLE, NEXTHOP_THROW. + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void networkRemoveRoute( + int netId, + in @utf8InCpp String ifName, + in @utf8InCpp String destination, + in @utf8InCpp String nextHop); + + /** + * Add a route to legacy routing table for specific network + * + * @param netId the network to add the route to + * @param ifName the name of interface of the route. + * This interface should be assigned to the netID. + * @param destination the destination of the route + * @param nextHop The route's next hop address, + * or it could be either NEXTHOP_NONE, NEXTHOP_UNREACHABLE, NEXTHOP_THROW. + * @param uid uid of the user + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void networkAddLegacyRoute( + int netId, + in @utf8InCpp String ifName, + in @utf8InCpp String destination, + in @utf8InCpp String nextHop, + int uid); + + /** + * Remove a route from legacy routing table for specific network + * + * @param netId the network to remove the route from + * @param ifName the name of interface of the route. + * This interface should be assigned to the netID. + * @param destination the destination of the route + * @param nextHop The route's next hop address, + * or it could be either NEXTHOP_NONE, NEXTHOP_UNREACHABLE, NEXTHOP_THROW. + * @param uid uid of the user + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void networkRemoveLegacyRoute( + int netId, + in @utf8InCpp String ifName, + in @utf8InCpp String destination, + in @utf8InCpp String nextHop, + int uid); + + /** + * Get default network + * + * @return netId of default network + */ + int networkGetDefault(); + + /** + * Set network as default network + * + * @param netId the network to set as the default + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void networkSetDefault(int netId); + + /** + * Clear default network + * + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void networkClearDefault(); + + /** + * PERMISSION_NONE is used for regular networks and apps. TODO: use PERMISSION_INTERNET + * for this instead, and use PERMISSION_NONE to indicate no network permissions at all. + */ + const int PERMISSION_NONE = 0; + + /** + * PERMISSION_NETWORK represents the CHANGE_NETWORK_STATE permission. + */ + const int PERMISSION_NETWORK = 1; + + /** + * PERMISSION_SYSTEM represents the ability to use restricted networks. This is mostly + * equivalent to the CONNECTIVITY_USE_RESTRICTED_NETWORKS permission. + */ + const int PERMISSION_SYSTEM = 2; + + /** + * NO_PERMISSIONS indicates that this app is installed and doesn't have either + * PERMISSION_INTERNET or PERMISSION_UPDATE_DEVICE_STATS. + * TODO: use PERMISSION_NONE to represent this case + */ + const int NO_PERMISSIONS = 0; + + /** + * PERMISSION_INTERNET indicates that the app can create AF_INET and AF_INET6 sockets + */ + const int PERMISSION_INTERNET = 4; + + /** + * PERMISSION_UPDATE_DEVICE_STATS is used for system UIDs and privileged apps + * that have the UPDATE_DEVICE_STATS permission + */ + const int PERMISSION_UPDATE_DEVICE_STATS = 8; + + /** + * PERMISSION_UNINSTALLED is used when an app is uninstalled from the device. All internet + * related permissions need to be cleaned + */ + const int PERMISSION_UNINSTALLED = -1; + + + /** + * Sets the permission required to access a specific network. + * + * @param netId the network to set + * @param permission network permission to use + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void networkSetPermissionForNetwork(int netId, int permission); + + /** + * Assigns network access permissions to the specified users. + * + * @param permission network permission to use + * @param uids uid of users to set permission + */ + void networkSetPermissionForUser(int permission, in int[] uids); + + /** + * Clears network access permissions for the specified users. + * + * @param uids uid of users to clear permission + */ + void networkClearPermissionForUser(in int[] uids); + + /** + * Assigns android.permission.INTERNET and/or android.permission.UPDATE_DEVICE_STATS to the uids + * specified. Or remove all permissions from the uids. + * + * @param permission The permission to grant, it could be either PERMISSION_INTERNET and/or + * PERMISSION_UPDATE_DEVICE_STATS. If the permission is NO_PERMISSIONS, then + * revoke all permissions for the uids. + * @param uids uid of users to grant permission + */ + void trafficSetNetPermForUids(int permission, in int[] uids); + + /** + * Gives the specified user permission to protect sockets from VPNs. + * Typically used by VPN apps themselves, to ensure that the sockets + * they use to communicate with the VPN server aren't routed through + * the VPN network. + * + * @param uid uid of user to set + */ + void networkSetProtectAllow(int uid); + + /** + * Removes the permission to protect sockets from VPN. + * + * @param uid uid of user to set + */ + void networkSetProtectDeny(int uid); + + /** + * Get the status of network protect for user + * + * @param uids uid of user + * @return true if the user can protect sockets from VPN, false otherwise. + */ + boolean networkCanProtect(int uid); + + /** Only allows packets from specific UID/Interface. + @deprecated use FIREWALL_ALLOWLIST. */ + const int FIREWALL_WHITELIST = 0; + + /** Only allows packets from specific UID/Interface. */ + const int FIREWALL_ALLOWLIST = 0; + + /** Blocks packets from specific UID/Interface. + @deprecated use FIREWALL_DENYLIST. */ + const int FIREWALL_BLACKLIST = 1; + + /** Blocks packets from specific UID/Interface. */ + const int FIREWALL_DENYLIST = 1; + + /** + * Set type of firewall + * Type allowlist only allows packets from specific UID/Interface + * Type denylist blocks packets from specific UID/Interface + * + * @param firewalltype type of firewall, either FIREWALL_ALLOWLIST or FIREWALL_DENYLIST + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void firewallSetFirewallType(int firewalltype); + + // Specify allow Rule which allows packets + const int FIREWALL_RULE_ALLOW = 1; + // Specify deny Rule which drops packets + const int FIREWALL_RULE_DENY = 2; + + // No specific chain is chosen, use general firewall chain(fw_input, fw_output) + const int FIREWALL_CHAIN_NONE = 0; + // Specify DOZABLE chain(fw_dozable) which is used in dozable mode + const int FIREWALL_CHAIN_DOZABLE = 1; + // Specify STANDBY chain(fw_standby) which is used in standby mode + const int FIREWALL_CHAIN_STANDBY = 2; + // Specify POWERSAVE chain(fw_powersave) which is used in power save mode + const int FIREWALL_CHAIN_POWERSAVE = 3; + // Specify RESTRICTED chain(fw_restricted) which is used in restricted + // networking mode + const int FIREWALL_CHAIN_RESTRICTED = 4; + + /** + * Set firewall rule for interface + * + * @param ifName the interface to allow/deny + * @param firewallRule either FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule); + + /** + * Set firewall rule for uid + * + * @param childChain target chain + * @param uid uid to allow/deny + * @param firewallRule either FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void firewallSetUidRule(int childChain, int uid, int firewallRule); + + /** + * Enable/Disable target firewall child chain + * + * @param childChain target chain to enable + * @param enable whether to enable or disable child chain. + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void firewallEnableChildChain(int childChain, boolean enable); + + /** + * Get interface list + * + * @return An array of strings containing all the interfaces on the system. + * @throws ServiceSpecificException in case of failure, with an error code corresponding to the + * unix errno. + */ + @utf8InCpp String[] interfaceGetList(); + + // Must be kept in sync with constant in InterfaceConfiguration.java + const String IF_STATE_UP = "up"; + const String IF_STATE_DOWN = "down"; + + const String IF_FLAG_BROADCAST = "broadcast"; + const String IF_FLAG_LOOPBACK = "loopback"; + const String IF_FLAG_POINTOPOINT = "point-to-point"; + const String IF_FLAG_RUNNING = "running"; + const String IF_FLAG_MULTICAST = "multicast"; + + /** + * Get interface configuration + * + * @param ifName interface name + * @return An InterfaceConfigurationParcel for the specified interface. + * @throws ServiceSpecificException in case of failure, with an error code corresponding to the + * unix errno. + */ + InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName); + + /** + * Set interface configuration + * + * @param cfg Interface configuration to set + * @throws ServiceSpecificException in case of failure, with an error code corresponding to the + * unix errno. + */ + void interfaceSetCfg(in InterfaceConfigurationParcel cfg); + + /** + * Set interface IPv6 privacy extensions + * + * @param ifName interface name + * @param enable whether to enable or disable this setting. + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable); + + /** + * Clear all IP addresses on the given interface + * + * @param ifName interface name + * @throws ServiceSpecificException in case of failure, with an error code corresponding to the + * POSIX errno. + */ + void interfaceClearAddrs(in @utf8InCpp String ifName); + + /** + * Enable or disable IPv6 on the given interface + * + * @param ifName interface name + * @param enable whether to enable or disable this setting. + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable); + + /** + * Set interface MTU + * + * @param ifName interface name + * @param mtu MTU value + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void interfaceSetMtu(in @utf8InCpp String ifName, int mtu); + + /** + * Add forwarding rule/stats on given interface. + * + * @param intIface downstream interface + * @param extIface upstream interface + */ + void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface); + + /** + * Remove forwarding rule/stats on given interface. + * + * @param intIface downstream interface + * @param extIface upstream interface + */ + void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface); + + /** + * Set the values of tcp_{rmem,wmem}. + * + * @param rmemValues the target values of tcp_rmem, each value is separated by spaces + * @param wmemValues the target values of tcp_wmem, each value is separated by spaces + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues); + + /** + * Register unsolicited event listener + * Netd supports multiple unsolicited event listeners. + * + * @param listener unsolicited event listener to register + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void registerUnsolicitedEventListener(INetdUnsolicitedEventListener listener); + + /** + * Add ingress interface filtering rules to a list of UIDs + * + * For a given uid, once a filtering rule is added, the kernel will only allow packets from the + * allowed interface and loopback to be sent to the list of UIDs. + * + * Calling this method on one or more UIDs with an existing filtering rule but a different + * interface name will result in the filtering rule being updated to allow the new interface + * instead. Otherwise calling this method will not affect existing rules set on other UIDs. + * + * @param ifName the name of the interface on which the filtering rules will allow packets to + be received. + * @param uids an array of UIDs which the filtering rules will be set + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids); + + /** + * Remove ingress interface filtering rules from a list of UIDs + * + * Clear the ingress interface filtering rules from the list of UIDs which were previously set + * by firewallAddUidInterfaceRules(). Ignore any uid which does not have filtering rule. + * + * @param uids an array of UIDs from which the filtering rules will be removed + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void firewallRemoveUidInterfaceRules(in int[] uids); + + /** + * Request netd to change the current active network stats map. + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void trafficSwapActiveStatsMap(); + + /** + * Retrieves OEM netd listener interface + * + * @return a IBinder object, it could be casted to oem specific interface. + */ + IBinder getOemNetd(); + + /** + * Start tethering with given configuration + * + * @param config config to start tethering. + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void tetherStartWithConfiguration(in TetherConfigParcel config); + + + /** + * Get the fwmark and its net id mask for the given network id. + * + * @param netId the network to get the fwmark and mask for. + * @return A MarkMaskParcel of the given network id. + */ + MarkMaskParcel getFwmarkForNetwork(int netId); + + /** + * Add a route for specific network + * + * @param netId the network to add the route to + * @param routeInfo parcelable with route information + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + + /** + * Update a route for specific network + * + * @param routeInfo parcelable with route information + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + + /** + * Remove a route for specific network + * + * @param routeInfo parcelable with route information + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo); + + /** + * Adds a tethering offload rule, or updates it if it already exists. + * + * Currently, only downstream /128 IPv6 entries are supported. An existing rule will be updated + * if the input interface and destination prefix match. Otherwise, a new rule will be created. + * + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline + * module accesses the BPF map directly starting in S. See BpfCoordinator. + * @param rule The rule to add or update. + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void tetherOffloadRuleAdd(in TetherOffloadRuleParcel rule); + + /** + * Deletes a tethering offload rule. + * + * Currently, only downstream /128 IPv6 entries are supported. An existing rule will be deleted + * if the destination IP address and the source interface match. It is not an error if there is + * no matching rule to delete. + * + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline + * module accesses the BPF map directly starting in S. See BpfCoordinator. + * @param rule The rule to delete. + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void tetherOffloadRuleRemove(in TetherOffloadRuleParcel rule); + + /** + * Return BPF tethering offload statistics. + * + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline + * module accesses the BPF map directly starting in S. See BpfCoordinator. + * @return an array of TetherStatsParcel's, where each entry contains the upstream interface + * index and its tethering statistics since tethering was first started. + * There will only ever be one entry for a given interface index. + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + TetherStatsParcel[] tetherOffloadGetStats(); + + /** + * Set a per-interface quota for tethering offload. + * + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline + * module accesses the BPF map directly starting in S. See BpfCoordinator. + * @param ifIndex Index of upstream interface + * @param quotaBytes The quota defined as the number of bytes, starting from zero and counting + * from *now*. A value of QUOTA_UNLIMITED (-1) indicates there is no limit. + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes); + + /** + * Return BPF tethering offload statistics and clear the stats for a given upstream. + * + * Must only be called once all offload rules have already been deleted for the given upstream + * interface. The existing stats will be fetched and returned. The stats and the limit for the + * given upstream interface will be deleted as well. + * + * The stats and limit for a given upstream interface must be initialized (using + * tetherOffloadSetInterfaceQuota) before any offload will occur on that interface. + * + * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline + * module accesses the BPF map directly starting in S. See BpfCoordinator. + * @param ifIndex Index of upstream interface. + * @return TetherStatsParcel, which contains the given upstream interface index and its + * tethering statistics since tethering was first started on that upstream interface. + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + TetherStatsParcel tetherOffloadGetAndClearStats(int ifIndex); + + /** + * Creates a network. + * + * @param config the configuration of network. + * @throws ServiceSpecificException in case of failure, with an error code corresponding to the + * unix errno. + */ + void networkCreate(in NativeNetworkConfig config); + + /** + * Adds the specified UID ranges to the specified network. The network can be physical or + * virtual. Traffic from the UID ranges will be routed to the network by default. The possible + * value of subsidiary priority for physical and unreachable networks is 0-999. 0 is the highest + * priority. 0 is also the default value. Virtual network supports only the default value. + * + * @param NativeUidRangeConfig a parcel contains netId, UID ranges, subsidiary priority, etc. + * + * @throws ServiceSpecificException in case of failure, with an error code corresponding to the + * unix errno. + */ + void networkAddUidRangesParcel(in NativeUidRangeConfig uidRangesConfig); + + /** + * Removes the specified UID ranges from the specified network. The network can be physical or + * virtual. Traffic from the UID ranges will no longer be routed to the network by default. The + * possible value of subsidiary priority for physical and unreachable networks is 0-999. 0 is + * the highest priority. 0 is also the default value. Virtual network supports only the default + * value. + * + * @param NativeUidRangeConfig a parcel contains netId, UID ranges, subsidiary priority, etc. + * + * @throws ServiceSpecificException in case of failure, with an error code corresponding to the + * unix errno. + */ + void networkRemoveUidRangesParcel(in NativeUidRangeConfig uidRangesConfig); + + /** + * Migrate an existing IPsec tunnel mode SA to different addresses. + * + * If the underlying network also changes, caller must update it by + * calling ipSecAddSecurityAssociation. + * + * @param migrateInfo parcelable with migration info. + * + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void ipSecMigrate(in android.net.IpSecMigrateInfoParcel migrateInfo); + + /** + * IPSEC_DIRECTION_IN is used for IPsec SAs or policies that direct traffic towards the host. + */ + const int IPSEC_DIRECTION_IN = 0; + + /** + * IPSEC_DIRECTION_OUT is used for IPsec SAs or policies that direct traffic away from the host. + */ + const int IPSEC_DIRECTION_OUT = 1; + + /** + * Set the list of allowed UIDs for all networks with restrictions. + * + * This list is the entire list of restrictions for all networks known by + * netd. Calling this function always defines the entire list of restrictions, + * and networks not in the passed list are always reset to having no + * restrictions. + * + * @param NativeUidRangeConfig[] An array of allowlists, one per network. For each allowlist: + * - netId: the netId on which to set the allowlist + * - uidRanges: the UIDs allowed to use this network + * - subPriority: unused + */ + void setNetworkAllowlist(in NativeUidRangeConfig[] allowedNetworks); +} diff --git a/staticlibs/netd/binder/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/binder/android/net/INetdUnsolicitedEventListener.aidl new file mode 100644 index 0000000000000000000000000000000000000000..652a79cb6817b7f37db7aa6ee8bdb8ceaabba017 --- /dev/null +++ b/staticlibs/netd/binder/android/net/INetdUnsolicitedEventListener.aidl @@ -0,0 +1,145 @@ +/** + * 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. + */ + +package android.net; + +/** + * Unsolicited netd events which are reported by the kernel via netlink. + * This one-way interface groups asynchronous notifications sent + * by netd to any process that registered itself via INetd.registerUnsolEventListener. + * + * {@hide} + */ +oneway interface INetdUnsolicitedEventListener { + + /** + * Notifies that an interface has been idle/active for a certain period of time. + * It is the event for idletimer. + * + * @param isActive true for active status, false for idle + * @param timerLabel unique identifier of the idletimer. + * Since NMS only set the identifier as int, only report event with int label. + * @param timestampNs kernel timestamp of this event, 0 for no timestamp + * @param uid uid of this event, -1 for no uid. + * It represents the uid that was responsible for waking the radio. + */ + void onInterfaceClassActivityChanged( + boolean isActive, + int timerLabel, + long timestampNs, + int uid); + + /** + * Notifies that a specific interface reached its quota limit. + * + * @param alertName alert name of the quota limit + * @param ifName interface which reached the limit + */ + void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName); + + /** + * Provides information on IPv6 DNS servers on a specific interface. + * + * @param ifName interface name + * @param lifetimeS lifetime for the DNS servers in seconds + * @param servers the address of servers. + * e.g. IpV6: "2001:4860:4860::6464" + * + */ + void onInterfaceDnsServerInfo( + @utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers); + + /** + * Notifies that an address has updated on a specific interface. + * + * @param addr address that is being updated + * @param ifName the name of the interface on which the address is configured + * @param flags address flags, see ifa_flags in if_addr.h + * @param scope current scope of the address + */ + void onInterfaceAddressUpdated( + @utf8InCpp String addr, + @utf8InCpp String ifName, + int flags, + int scope); + + /** + * Notifies that an address has been removed on a specific interface. + * + * @param addr address of this change + * @param ifName the name of the interface that changed addresses + * @param flags address flags, see ifa_flags in if_addr.h + * @param scope address address scope + */ + void onInterfaceAddressRemoved( + @utf8InCpp String addr, + @utf8InCpp String ifName, + int flags, + int scope); + + /** + * Notifies that an interface has been added. + * + * @param ifName the name of the added interface + */ + void onInterfaceAdded(@utf8InCpp String ifName); + + /** + * Notifies that an interface has been removed. + * + * @param ifName the name of the removed interface + */ + void onInterfaceRemoved(@utf8InCpp String ifName); + + /** + * Notifies that the status of the specific interface has changed. + * + * @param ifName the name of the interface that changed status + * @param up true for interface up, false for down + */ + void onInterfaceChanged(@utf8InCpp String ifName, boolean up); + + /** + * Notifies that the link state of the specific interface has changed. + * + * @param ifName the name of the interface whose link state has changed + * @param up true for interface link state up, false for link state down + */ + void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up); + + /** + * Notifies that an IP route has changed. + * + * @param updated true for update, false for remove + * @param route destination prefix of this route, e.g., "2001:db8::/64" + * @param gateway address of gateway, empty string for no gateway + * @param ifName interface name of this route, empty string for no interface + */ + void onRouteChanged( + boolean updated, + @utf8InCpp String route, + @utf8InCpp String gateway, + @utf8InCpp String ifName); + + /** + * Notifies that kernel has detected a socket sending data not wrapped + * inside a layer of SSL/TLS encryption. + * + * @param uid uid of this event + * @param hex packet content in hex format + */ + void onStrictCleartextDetected(int uid, @utf8InCpp String hex); +} diff --git a/staticlibs/netd/binder/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/binder/android/net/InterfaceConfigurationParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..c20792c0df00d8da71fe46c6503ec266a571e509 --- /dev/null +++ b/staticlibs/netd/binder/android/net/InterfaceConfigurationParcel.aidl @@ -0,0 +1,33 @@ +/* + * 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. + */ + +package android.net; + +/** + * Configuration details for a network interface. + * + * {@hide} + */ +parcelable InterfaceConfigurationParcel { + @utf8InCpp String ifName; + @utf8InCpp String hwAddr; + @utf8InCpp String ipv4Addr; + int prefixLength; + /** + * Interface flags, String versions of IFF_* defined in netd/if.h + */ + @utf8InCpp String[] flags; +} diff --git a/staticlibs/netd/binder/android/net/IpSecMigrateInfoParcel.aidl b/staticlibs/netd/binder/android/net/IpSecMigrateInfoParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..e192d6630842c44552599019f5ddc9be296e957d --- /dev/null +++ b/staticlibs/netd/binder/android/net/IpSecMigrateInfoParcel.aidl @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2022, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +@JavaOnlyImmutable +parcelable IpSecMigrateInfoParcel { + /** The unique identifier for allocated resources. */ + int requestId; + /** + * The address family identifier for the new selector. Can be AF_INET + * or AF_INET6. + */ + int selAddrFamily; + /** IPSEC_DIRECTION_IN or IPSEC_DIRECTION_OUT. */ + int direction; + /** + * The IP address for the current sending endpoint. + * + * The local address for an outbound SA and the remote address for an + * inbound SA. + */ + @utf8InCpp String oldSourceAddress; + /** + * The IP address for the current receiving endpoint. + * + * The remote address for an outbound SA and the local address for an + * inbound SA. + */ + @utf8InCpp String oldDestinationAddress; + /** The IP address for the new sending endpoint. */ + @utf8InCpp String newSourceAddress; + /** The IP address for the new receiving endpoint. */ + @utf8InCpp String newDestinationAddress; + /** The identifier for the XFRM interface. */ + int interfaceId; +} diff --git a/staticlibs/netd/binder/android/net/MarkMaskParcel.aidl b/staticlibs/netd/binder/android/net/MarkMaskParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..932b7bffbd89f99454a1724f55ac272d9f73eb78 --- /dev/null +++ b/staticlibs/netd/binder/android/net/MarkMaskParcel.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +/** + * Structure that stores a firewall mark and its mask. + * + * {@hide} + */ +parcelable MarkMaskParcel { + // The fwmark. + int mark; + // Net id mask of fwmark. + int mask; +} diff --git a/staticlibs/netd/binder/android/net/NativeNetworkConfig.aidl b/staticlibs/netd/binder/android/net/NativeNetworkConfig.aidl new file mode 100644 index 0000000000000000000000000000000000000000..96eccc7acfe58c9baf1e0f9803cb4718d77b12f5 --- /dev/null +++ b/staticlibs/netd/binder/android/net/NativeNetworkConfig.aidl @@ -0,0 +1,57 @@ +/* + * 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. + */ + +package android.net; + +import android.net.NativeNetworkType; +import android.net.NativeVpnType; + +/** + * The configuration to create a network. + * + * {@hide} + */ +@JavaDerive(toString=true, equals=true) +@JavaOnlyImmutable +parcelable NativeNetworkConfig { + /** The networkId to create. */ + int netId; + + /** + * The type of network : virtual, physical or physical local network. + */ + NativeNetworkType networkType = NativeNetworkType.PHYSICAL; + + /** + * The permission necessary to use the network. Must be PERMISSION_NONE, PERMISSION_NETWORK + * or PERMISSION_SYSTEM. Ignored for virtual network types. + */ + int permission; + + /** + * For virtual networks. Whether unprivileged apps are allowed to bypass the VPN. Ignored for + * all other network types. + */ + boolean secure; + + /** For virtual networks. The type of VPN to create. Ignored for all other network types. */ + NativeVpnType vpnType = NativeVpnType.PLATFORM; + + /** + * For virtual networks. Whether local traffic is excluded from the VPN. + */ + boolean excludeLocalRoutes = false; +} diff --git a/staticlibs/netd/binder/android/net/NativeNetworkType.aidl b/staticlibs/netd/binder/android/net/NativeNetworkType.aidl new file mode 100644 index 0000000000000000000000000000000000000000..70055357917bd740c8aed0892c1554d70c087b95 --- /dev/null +++ b/staticlibs/netd/binder/android/net/NativeNetworkType.aidl @@ -0,0 +1,35 @@ +/* + * 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. + */ + +package android.net; + +@Backing(type="int") +enum NativeNetworkType { + /** + * Physical network type. + */ + PHYSICAL = 0, + + /** + * Virtual private network type. + */ + VIRTUAL = 1, + + /** + * Physical local network, such as a tethering downstream. + */ + PHYSICAL_LOCAL = 2, +} diff --git a/staticlibs/netd/binder/android/net/NativeVpnType.aidl b/staticlibs/netd/binder/android/net/NativeVpnType.aidl new file mode 100644 index 0000000000000000000000000000000000000000..cd1b4474d6e592c89dbf0bf19bc79920b0e495dc --- /dev/null +++ b/staticlibs/netd/binder/android/net/NativeVpnType.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. + */ + +package android.net; + +@Backing(type="int") +enum NativeVpnType { + /** + * A VPN created by an app using the VpnService API. + */ + SERVICE = 1, + + /** + * A VPN created using a VpnManager API such as startProvisionedVpnProfile. + */ + PLATFORM = 2, + + /** + * An IPsec VPN created by the built-in LegacyVpnRunner. + */ + LEGACY = 3, + + /** + * An VPN created by OEM code through other means than VpnService or VpnManager. + */ + OEM = 4, +} \ No newline at end of file diff --git a/staticlibs/netd/binder/android/net/RouteInfoParcel.aidl b/staticlibs/netd/binder/android/net/RouteInfoParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..fcc86e32acef6d25bb4491667b0e79f5aff91295 --- /dev/null +++ b/staticlibs/netd/binder/android/net/RouteInfoParcel.aidl @@ -0,0 +1,28 @@ +/** + * 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. + */ + +package android.net; + +parcelable RouteInfoParcel { + // The destination of the route. + @utf8InCpp String destination; + // The name of interface of the route. This interface should be assigned to the netID. + @utf8InCpp String ifName; + // The route's next hop address, or one of the NEXTHOP_* constants defined in INetd.aidl. + @utf8InCpp String nextHop; + // The MTU of the route. + int mtu; +} diff --git a/staticlibs/netd/binder/android/net/TetherConfigParcel.aidl b/staticlibs/netd/binder/android/net/TetherConfigParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..9f371ce106f0b3ced4186edb28b4a8bedcb3667a --- /dev/null +++ b/staticlibs/netd/binder/android/net/TetherConfigParcel.aidl @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +/** + * The configuration to start tethering. + * + * {@hide} + */ +parcelable TetherConfigParcel { + // Whether to enable or disable legacy DNS proxy server. + boolean usingLegacyDnsProxy; + // DHCP ranges to set. + // dhcpRanges might contain many addresss {addr1, addr2, addr3, addr4...} + // Netd splits them into ranges: addr1-addr2, addr3-addr4, etc. + // An odd number of addrs will fail. + @utf8InCpp String[] dhcpRanges; +} diff --git a/staticlibs/netd/binder/android/net/TetherOffloadRuleParcel.aidl b/staticlibs/netd/binder/android/net/TetherOffloadRuleParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..c549e610c0f2f908ae52bf6ac7ff2a6a4a5455f5 --- /dev/null +++ b/staticlibs/netd/binder/android/net/TetherOffloadRuleParcel.aidl @@ -0,0 +1,45 @@ +/* + * 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. + */ + +package android.net; + +/** + * Represents a forwarding rule for tethering offload. + * + * {@hide} + */ +parcelable TetherOffloadRuleParcel { + /** The interface index of the input interface. */ + int inputInterfaceIndex; + + /** The interface index of the output interface. */ + int outputInterfaceIndex; + + /** The base IP address of the destination prefix as a byte array. */ + byte[] destination; + + /** The destination prefix length. */ + int prefixLength; + + /** The source link-layer address. Currently, must be a 6-byte MAC address.*/ + byte[] srcL2Address; + + /** The destination link-layer address. Currently, must be a 6-byte MAC address. */ + byte[] dstL2Address; + + /** The outbound path mtu. */ + int pmtu = 1500; +} diff --git a/staticlibs/netd/binder/android/net/TetherStatsParcel.aidl b/staticlibs/netd/binder/android/net/TetherStatsParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..6bf60a86e33233279e8229304a94e032347974f4 --- /dev/null +++ b/staticlibs/netd/binder/android/net/TetherStatsParcel.aidl @@ -0,0 +1,55 @@ +/* + * 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. + */ + +package android.net; + +/** + * The statistics of tethering interface + * + * {@hide} + */ +parcelable TetherStatsParcel { + /** + * Parcel representing tethering interface statistics. + * + * This parcel is used by tetherGetStats, tetherOffloadGetStats and + * tetherOffloadGetAndClearStats in INetd.aidl. tetherGetStats uses this parcel to return the + * tethering statistics since netd startup and presents the interface via its interface name. + * Both tetherOffloadGetStats and tetherOffloadGetAndClearStats use this parcel to return + * the tethering statistics since tethering was first started. They present the interface via + * its interface index. Note that the interface must be presented by either interface name + * |iface| or interface index |ifIndex| in this parcel. The unused interface name is set to + * an empty string "" by default and the unused interface index is set to 0 by default. + */ + + /** The interface name. */ + @utf8InCpp String iface; + + /** Total number of received bytes. */ + long rxBytes; + + /** Total number of received packets. */ + long rxPackets; + + /** Total number of transmitted bytes. */ + long txBytes; + + /** Total number of transmitted packets. */ + long txPackets; + + /** The interface index. */ + int ifIndex = 0; +} diff --git a/staticlibs/netd/binder/android/net/UidRangeParcel.aidl b/staticlibs/netd/binder/android/net/UidRangeParcel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..8f1fef6f115a61396d512a030bc3b944ae4f437f --- /dev/null +++ b/staticlibs/netd/binder/android/net/UidRangeParcel.aidl @@ -0,0 +1,28 @@ +/* + * 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. + */ + +package android.net; + +/** + * An inclusive range of UIDs. + * + * {@hide} + */ +@JavaOnlyImmutable @JavaDerive(toString=true, equals=true) +parcelable UidRangeParcel { + int start; + int stop; +} diff --git a/staticlibs/netd/binder/android/net/mdns/aidl/DiscoveryInfo.aidl b/staticlibs/netd/binder/android/net/mdns/aidl/DiscoveryInfo.aidl new file mode 100644 index 0000000000000000000000000000000000000000..f8273828b1039db2392ff734e8d3f6151b98c95b --- /dev/null +++ b/staticlibs/netd/binder/android/net/mdns/aidl/DiscoveryInfo.aidl @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.mdns.aidl; + +/** + * Discovery service information. + * This information combine all arguments that used by both request and callback. + * Arguments are used by request: + * - id + * - registrationType + * - interfaceIdx + * + * Arguments are used by callback: + * - id + * - serviceName + * - registrationType + * - domainName + * - interfaceIdx + * - netId + * - result + * + * {@hide} + */ +@JavaOnlyImmutable +@JavaDerive(equals=true, toString=true) +parcelable DiscoveryInfo { + /** + * The operation ID. + * Must be unique among all operations (registration/discovery/resolution/getting address) and + * can't be reused. + * To stop a operation, it needs to use corresponding operation id. + */ + int id; + + /** + * The discovery result. + */ + int result; + + /** + * The discovered service name. + */ + @utf8InCpp String serviceName; + + /** + * The service type being discovered for followed by the protocol, separated by a dot + * (e.g. "_ftp._tcp"). The transport protocol must be "_tcp" or "_udp". + */ + @utf8InCpp String registrationType; + + /** + * The domain of the discovered service instance. + */ + @utf8InCpp String domainName; + + /** + * The interface index on which to discover services. 0 indicates "all interfaces". + */ + int interfaceIdx; + + /** + * The net id on which the service is advertised. + */ + int netId; +} diff --git a/staticlibs/netd/binder/android/net/mdns/aidl/GetAddressInfo.aidl b/staticlibs/netd/binder/android/net/mdns/aidl/GetAddressInfo.aidl new file mode 100644 index 0000000000000000000000000000000000000000..d53174a4d794cc74d80d2d1e8463994b70e73026 --- /dev/null +++ b/staticlibs/netd/binder/android/net/mdns/aidl/GetAddressInfo.aidl @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.mdns.aidl; + +/** + * Get service address information. + * This information combine all arguments that used by both request and callback. + * Arguments are used by request: + * - id + * - hostname + * - interfaceIdx + * + * Arguments are used by callback: + * - id + * - hostname + * - interfaceIdx + * - netId + * - address + * - result + * + * {@hide} + */ +@JavaOnlyImmutable +@JavaDerive(equals=true, toString=true) +parcelable GetAddressInfo { + /** + * The operation ID. + */ + int id; + + /** + * The getting address result. + */ + int result; + + /** + * The fully qualified domain name of the host to be queried for. + */ + @utf8InCpp String hostname; + + /** + * The service address info, it's IPv4 or IPv6 addres. + */ + @utf8InCpp String address; + + /** + * The interface index on which to issue the query. 0 indicates "all interfaces". + */ + int interfaceIdx; + + /** + * The net id to which the answers pertain. + */ + int netId; +} diff --git a/staticlibs/netd/binder/android/net/mdns/aidl/IMDns.aidl b/staticlibs/netd/binder/android/net/mdns/aidl/IMDns.aidl new file mode 100644 index 0000000000000000000000000000000000000000..255d70ffeab5ef53029eae2eefd271c5f2f700a9 --- /dev/null +++ b/staticlibs/netd/binder/android/net/mdns/aidl/IMDns.aidl @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.mdns.aidl; + +import android.net.mdns.aidl.DiscoveryInfo; +import android.net.mdns.aidl.GetAddressInfo; +import android.net.mdns.aidl.IMDnsEventListener; +import android.net.mdns.aidl.RegistrationInfo; +import android.net.mdns.aidl.ResolutionInfo; + +/** {@hide} */ +interface IMDns { + /** + * Start the MDNSResponder daemon. + * + * @throws ServiceSpecificException with unix errno EALREADY if daemon is already running. + */ + void startDaemon(); + + /** + * Stop the MDNSResponder daemon. + * + * @throws ServiceSpecificException with unix errno EBUSY if daemon is still in use. + */ + void stopDaemon(); + + /** + * Start registering a service. + * This operation will send a service registration request to MDNSResponder. Register a listener + * via IMDns#registerEventListener to get the registration result SERVICE_REGISTERED/ + * SERVICE_REGISTRATION_FAILED from callback IMDnsEventListener#onServiceRegistrationStatus. + * + * @param info The service information to register. + * + * @throws ServiceSpecificException with one of the following error values: + * - Unix errno EBUSY if request id is already in use. + * - kDNSServiceErr_* list in dns_sd.h if registration fail. + */ + void registerService(in RegistrationInfo info); + + /** + * Start discovering services. + * This operation will send a request to MDNSResponder to discover services. Register a listener + * via IMDns#registerEventListener to get the discovery result SERVICE_FOUND/SERVICE_LOST/ + * SERVICE_DISCOVERY_FAILED from callback IMDnsEventListener#onServiceDiscoveryStatus. + * + * @param info The service to discover. + * + * @throws ServiceSpecificException with one of the following error values: + * - Unix errno EBUSY if request id is already in use. + * - kDNSServiceErr_* list in dns_sd.h if discovery fail. + */ + void discover(in DiscoveryInfo info); + + /** + * Start resolving the target service. + * This operation will send a request to MDNSResponder to resolve the target service. Register a + * listener via IMDns#registerEventListener to get the resolution result SERVICE_RESOLVED/ + * SERVICE_RESOLUTION_FAILED from callback IMDnsEventListener#onServiceResolutionStatus. + * + * @param info The service to resolve. + * + * @throws ServiceSpecificException with one of the following error values: + * - Unix errno EBUSY if request id is already in use. + * - kDNSServiceErr_* list in dns_sd.h if resolution fail. + */ + void resolve(in ResolutionInfo info); + + /** + * Start getting the target service address. + * This operation will send a request to MDNSResponder to get the target service address. + * Register a listener via IMDns#registerEventListener to get the query result + * SERVICE_GET_ADDR_SUCCESS/SERVICE_GET_ADDR_FAILED from callback + * IMDnsEventListener#onGettingServiceAddressStatus. + * + * @param info the getting service address information. + * + * @throws ServiceSpecificException with one of the following error values: + * - Unix errno EBUSY if request id is already in use. + * - kDNSServiceErr_* list in dns_sd.h if getting address fail. + */ + void getServiceAddress(in GetAddressInfo info); + + /** + * Stop a operation which's requested before. + * + * @param id the operation id to be stopped. + * + * @throws ServiceSpecificException with unix errno ESRCH if request id is not in use. + */ + void stopOperation(int id); + + /** + * Register an event listener. + * + * @param listener The listener to be registered. + * + * @throws ServiceSpecificException with one of the following error values: + * - Unix errno EINVAL if listener is null. + * - Unix errno EEXIST if register duplicated listener. + */ + void registerEventListener(in IMDnsEventListener listener); + + /** + * Unregister an event listener. + * + * @param listener The listener to be unregistered. + * + * @throws ServiceSpecificException with unix errno EINVAL if listener is null. + */ + void unregisterEventListener(in IMDnsEventListener listener); +} + diff --git a/staticlibs/netd/binder/android/net/mdns/aidl/IMDnsEventListener.aidl b/staticlibs/netd/binder/android/net/mdns/aidl/IMDnsEventListener.aidl new file mode 100644 index 0000000000000000000000000000000000000000..a202a261a36610b3285cd24c43d9230e2cc7f1b3 --- /dev/null +++ b/staticlibs/netd/binder/android/net/mdns/aidl/IMDnsEventListener.aidl @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2022, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.mdns.aidl; + +import android.net.mdns.aidl.DiscoveryInfo; +import android.net.mdns.aidl.GetAddressInfo; +import android.net.mdns.aidl.RegistrationInfo; +import android.net.mdns.aidl.ResolutionInfo; + +/** + * MDNS events which are reported by the MDNSResponder. + * This one-way interface defines the asynchronous notifications sent by mdns service to any process + * that registered itself via IMDns.registerEventListener. + * + * {@hide} + */ +oneway interface IMDnsEventListener { + /** + * Types for MDNS operation result. + * These are in sync with frameworks/libs/net/common/netd/libnetdutils/include/netdutils/\ + * ResponseCode.h + */ + const int SERVICE_DISCOVERY_FAILED = 602; + const int SERVICE_FOUND = 603; + const int SERVICE_LOST = 604; + const int SERVICE_REGISTRATION_FAILED = 605; + const int SERVICE_REGISTERED = 606; + const int SERVICE_RESOLUTION_FAILED = 607; + const int SERVICE_RESOLVED = 608; + const int SERVICE_GET_ADDR_FAILED = 611; + const int SERVICE_GET_ADDR_SUCCESS = 612; + + /** + * Notify service registration status. + */ + void onServiceRegistrationStatus(in RegistrationInfo status); + + /** + * Notify service discovery status. + */ + void onServiceDiscoveryStatus(in DiscoveryInfo status); + + /** + * Notify service resolution status. + */ + void onServiceResolutionStatus(in ResolutionInfo status); + + /** + * Notify getting service address status. + */ + void onGettingServiceAddressStatus(in GetAddressInfo status); +} diff --git a/staticlibs/netd/binder/android/net/mdns/aidl/RegistrationInfo.aidl b/staticlibs/netd/binder/android/net/mdns/aidl/RegistrationInfo.aidl new file mode 100644 index 0000000000000000000000000000000000000000..5483559453473fbbab959f91feb6cb9fdb8f06b5 --- /dev/null +++ b/staticlibs/netd/binder/android/net/mdns/aidl/RegistrationInfo.aidl @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.mdns.aidl; + +/** + * Registration service information. + * This information combine all arguments that used by both request and callback. + * Arguments are used by request: + * - id + * - serviceName + * - registrationType + * - port + * - txtRecord + * - interfaceIdx + * + * Arguments are used by callback: + * - id + * - serviceName + * - registrationType + * - result + * + * {@hide} + */ +@JavaOnlyImmutable +@JavaDerive(equals=true, toString=true) +parcelable RegistrationInfo { + /** + * The operation ID. + */ + int id; + + /** + * The registration result. + */ + int result; + + /** + * The service name to be registered. + */ + @utf8InCpp String serviceName; + + /** + * The service type followed by the protocol, separated by a dot (e.g. "_ftp._tcp"). The service + * type must be an underscore, followed by 1-15 characters, which may be letters, digits, or + * hyphens. The transport protocol must be "_tcp" or "_udp". New service types should be + * registered at <http://www.dns-sd.org/ServiceTypes.html>. + */ + @utf8InCpp String registrationType; + + /** + * The port on which the service accepts connections. + */ + int port; + + /** + * The txt record. + */ + byte[] txtRecord; + + /** + * The interface index on which to register the service. 0 indicates "all interfaces". + */ + int interfaceIdx; +} diff --git a/staticlibs/netd/binder/android/net/mdns/aidl/ResolutionInfo.aidl b/staticlibs/netd/binder/android/net/mdns/aidl/ResolutionInfo.aidl new file mode 100644 index 0000000000000000000000000000000000000000..26e0cee75e7fc671056e76680563245fc923d3ef --- /dev/null +++ b/staticlibs/netd/binder/android/net/mdns/aidl/ResolutionInfo.aidl @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.mdns.aidl; + +/** + * Resolution service information. + * This information combine all arguments that used by both request and callback. + * Arguments are used by request: + * - id + * - serviceName + * - registrationType + * - domain + * - interfaceIdx + * + * Arguments are used by callback: + * - id + * - port + * - serviceFullName + * - hostname + * - txtRecord + * - interfaceIdx + * - result + * + * {@hide} + */ +@JavaOnlyImmutable +@JavaDerive(equals=true, toString=true) +parcelable ResolutionInfo { + /** + * The operation ID. + */ + int id; + + /** + * The resolution result. + */ + int result; + + /** + * The service name to be resolved. + */ + @utf8InCpp String serviceName; + + /** + * The service type to be resolved. + */ + @utf8InCpp String registrationType; + + /** + * The service domain to be resolved. + */ + @utf8InCpp String domain; + + /** + * The resolved full service domain name, in the form <servicename>.<protocol>.<domain>. + */ + @utf8InCpp String serviceFullName; + + /** + * The target hostname of the machine providing the service. + */ + @utf8InCpp String hostname; + + /** + * The port on which connections are accepted for this service. + */ + int port; + + /** + * The service's txt record. + */ + byte[] txtRecord; + + /** + * The interface index on which to resolve the service. 0 indicates "all interfaces". + */ + int interfaceIdx; +} diff --git a/staticlibs/netd/binder/android/net/metrics/INetdEventListener.aidl b/staticlibs/netd/binder/android/net/metrics/INetdEventListener.aidl new file mode 100644 index 0000000000000000000000000000000000000000..ef1b2cbcd247574de34a66ae5517c80b560df330 --- /dev/null +++ b/staticlibs/netd/binder/android/net/metrics/INetdEventListener.aidl @@ -0,0 +1,128 @@ +/** + * 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.metrics; + +/** + * Logs netd events. + * + * {@hide} + */ +oneway interface INetdEventListener { + const int EVENT_GETADDRINFO = 1; + const int EVENT_GETHOSTBYNAME = 2; + const int EVENT_GETHOSTBYADDR = 3; + const int EVENT_RES_NSEND = 4; + + const int REPORTING_LEVEL_NONE = 0; + const int REPORTING_LEVEL_METRICS = 1; + const int REPORTING_LEVEL_FULL = 2; + + // Maximum number of IP addresses logged for DNS lookups before we truncate the full list. + const int DNS_REPORTED_IP_ADDRESSES_LIMIT = 10; + + /** + * Logs a DNS lookup function call (getaddrinfo and gethostbyname). + * + * @param netId the ID of the network the lookup was performed on. + * @param eventType one of the EVENT_* constants in this interface. + * @param returnCode the return value of the function call. + * @param latencyMs the latency of the function call. + * @param hostname the name that was looked up. + * @param ipAddresses (possibly a subset of) the IP addresses returned. + * At most {@link #DNS_REPORTED_IP_ADDRESSES_LIMIT} addresses are logged. + * @param ipAddressesCount the number of IP addresses returned. May be different from the length + * of ipAddresses if there were too many addresses to log. + * @param uid the UID of the application that performed the query. + */ + void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs, + @utf8InCpp String hostname, in @utf8InCpp String[] ipAddresses, + int ipAddressesCount, int uid); + + /** + * Represents a private DNS validation success or failure. + * + * @param netId the ID of the network the validation was performed on. + * @param ipAddress the IP address for which validation was performed. + * @param hostname the hostname for which validation was performed. + * @param validated whether or not validation was successful. + */ + void onPrivateDnsValidationEvent(int netId, String ipAddress, String hostname, + boolean validated); + + /** + * Logs a single connect library call. + * + * @param netId the ID of the network the connect was performed on. + * @param error 0 if the connect call succeeded, otherwise errno if it failed. + * @param latencyMs the latency of the connect call. + * @param ipAddr destination IP address. + * @param port destination port number. + * @param uid the UID of the application that performed the connection. + */ + void onConnectEvent(int netId, int error, int latencyMs, String ipAddr, int port, int uid); + + /** + * Logs a single RX packet which caused the main CPU to exit sleep state. + * @param prefix arbitrary string provided via wakeupAddInterface() + * @param uid UID of the destination process or -1 if no UID is available. + * @param ethertype of the RX packet encoded in an int in native order, or -1 if not available. + * @param ipNextHeader ip protocol of the RX packet as IPPROTO_* number, + or -1 if the packet was not IPv4 or IPv6. + * @param dstHw destination hardware address, or 0 if not available. + * @param srcIp source IP address, or null if not available. + * @param dstIp destination IP address, or null if not available. + * @param srcPort src port of RX packet in native order, or -1 if the packet was not UDP or TCP. + * @param dstPort dst port of RX packet in native order, or -1 if the packet was not UDP or TCP. + * @param timestampNs receive timestamp for the offending packet. In units of nanoseconds and + * synchronized to CLOCK_MONOTONIC. + */ + void onWakeupEvent(String prefix, int uid, int ethertype, int ipNextHeader, in byte[] dstHw, + String srcIp, String dstIp, int srcPort, int dstPort, long timestampNs); + + /** + * An event sent after every Netlink sock_diag poll performed by Netd. This reported batch + * groups TCP socket stats aggregated by network id. Per-network data are stored in a + * structure-of-arrays style where networkIds, sentPackets, lostPackets, rttUs, and + * sentAckDiffMs have the same length. Stats for the i-th network is spread across all these + * arrays at index i. + * @param networkIds an array of network ids for which there was tcp socket stats to collect in + * the last sock_diag poll. + * @param sentPackets an array of packet sent across all TCP sockets still alive and new + TCP sockets since the last sock_diag poll, summed per network id. + * @param lostPackets, an array of packet lost across all TCP sockets still alive and new + TCP sockets since the last sock_diag poll, summed per network id. + * @param rttUs an array of smoothed round trip times in microseconds, averaged across all TCP + sockets since the last sock_diag poll for a given network id. + * @param sentAckDiffMs an array of milliseconds duration between the last packet sent and the + last ack received for a socket, averaged across all TCP sockets for a network id. + */ + void onTcpSocketStatsEvent(in int[] networkIds, in int[] sentPackets, + in int[] lostPackets, in int[] rttUs, in int[] sentAckDiffMs); + + /** + * Represents adding or removing a NAT64 prefix. + * + * @param netId the ID of the network the prefix was discovered on. + * @param added true if the NAT64 prefix was added, or false if the NAT64 prefix was removed. + * There is only one prefix at a time for each netId. If a prefix is added, it replaces + * the previous-added prefix. + * @param prefixString the detected NAT64 prefix as a string literal. + * @param prefixLength the prefix length associated with this NAT64 prefix. + */ + void onNat64PrefixEvent(int netId, boolean added, @utf8InCpp String prefixString, + int prefixLength); +} diff --git a/staticlibs/netd/binder/android/net/netd/aidl/NativeUidRangeConfig.aidl b/staticlibs/netd/binder/android/net/netd/aidl/NativeUidRangeConfig.aidl new file mode 100644 index 0000000000000000000000000000000000000000..99497a863c23c10a629b1546fc8aa4cc53edbfe6 --- /dev/null +++ b/staticlibs/netd/binder/android/net/netd/aidl/NativeUidRangeConfig.aidl @@ -0,0 +1,41 @@ +/* + * 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. + */ + +package android.net.netd.aidl; + +import android.net.UidRangeParcel; + +/** + * The configuration to add or remove UID ranges. + * + * {@hide} + */ +@JavaDerive(toString=true, equals=true) +@JavaOnlyImmutable +parcelable NativeUidRangeConfig { + /** The network ID of the network to add/remove the ranges to/from. */ + int netId; + + /** A set of non-overlapping ranges of UIDs. */ + UidRangeParcel[] uidRanges; + + /** + * The priority of this UID range config. 0 is the highest priority; 999 is the lowest priority. + * The function of this parameter is to adjust the priority when the same UID is set to + * different networks for different features. + */ + int subPriority; +} \ No newline at end of file diff --git a/staticlibs/netd/libnetdutils/Android.bp b/staticlibs/netd/libnetdutils/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..31690331cc79fc6d0256fb0cbd9a1c197ed81605 --- /dev/null +++ b/staticlibs/netd/libnetdutils/Android.bp @@ -0,0 +1,73 @@ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_library { + name: "libnetdutils", + srcs: [ + "DumpWriter.cpp", + "Fd.cpp", + "InternetAddresses.cpp", + "Log.cpp", + "Netfilter.cpp", + "Netlink.cpp", + "NetlinkListener.cpp", + "Slice.cpp", + "Socket.cpp", + "SocketOption.cpp", + "Status.cpp", + "Syscalls.cpp", + "UniqueFd.cpp", + "UniqueFile.cpp", + "Utils.cpp", + ], + defaults: ["netd_defaults"], + cflags: ["-Wall", "-Werror"], + shared_libs: [ + "libbase", + "liblog", + ], + export_shared_lib_headers: [ + "libbase", + ], + export_include_dirs: ["include"], + sanitize: { + cfi: true, + }, + + apex_available: [ + "//apex_available:platform", + "com.android.resolv", + "com.android.tethering", + ], + min_sdk_version: "29", +} + +cc_test { + name: "netdutils_test", + srcs: [ + "BackoffSequenceTest.cpp", + "FdTest.cpp", + "InternetAddressesTest.cpp", + "LogTest.cpp", + "MemBlockTest.cpp", + "SliceTest.cpp", + "StatusTest.cpp", + "SyscallsTest.cpp", + "ThreadUtilTest.cpp", + ], + defaults: ["netd_defaults"], + test_suites: ["device-tests"], + static_libs: [ + "libgmock", + "libnetdutils", + ], + shared_libs: [ + "libbase", + ], +} + +cc_library_headers { + name: "libnetd_utils_headers", + export_include_dirs: ["include"], +} diff --git a/staticlibs/netd/libnetdutils/BackoffSequenceTest.cpp b/staticlibs/netd/libnetdutils/BackoffSequenceTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b6653fe80ac249bffc432b505a238ec14e11d369 --- /dev/null +++ b/staticlibs/netd/libnetdutils/BackoffSequenceTest.cpp @@ -0,0 +1,186 @@ +/* + * 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. + */ + +#include <gtest/gtest.h> + +#include "netdutils/BackoffSequence.h" + +namespace android { +namespace netdutils { + +TEST(BackoffSequence, defaults) { + BackoffSequence<uint32_t> backoff; + + EXPECT_TRUE(backoff.hasNextTimeout()); + EXPECT_EQ(0x00000001U, backoff.getNextTimeout()); + EXPECT_EQ(0x00000002U, backoff.getNextTimeout()); + EXPECT_EQ(0x00000004U, backoff.getNextTimeout()); + EXPECT_EQ(0x00000008U, backoff.getNextTimeout()); + EXPECT_EQ(0x00000010U, backoff.getNextTimeout()); + EXPECT_EQ(0x00000020U, backoff.getNextTimeout()); + EXPECT_EQ(0x00000040U, backoff.getNextTimeout()); + EXPECT_EQ(0x00000080U, backoff.getNextTimeout()); + EXPECT_EQ(0x00000100U, backoff.getNextTimeout()); + EXPECT_EQ(0x00000200U, backoff.getNextTimeout()); + EXPECT_EQ(0x00000400U, backoff.getNextTimeout()); + EXPECT_EQ(0x00000800U, backoff.getNextTimeout()); + EXPECT_EQ(0x00001000U, backoff.getNextTimeout()); + EXPECT_EQ(0x00002000U, backoff.getNextTimeout()); + EXPECT_EQ(0x00004000U, backoff.getNextTimeout()); + EXPECT_EQ(0x00008000U, backoff.getNextTimeout()); + EXPECT_EQ(0x00010000U, backoff.getNextTimeout()); + EXPECT_EQ(0x00020000U, backoff.getNextTimeout()); + EXPECT_EQ(0x00040000U, backoff.getNextTimeout()); + EXPECT_EQ(0x00080000U, backoff.getNextTimeout()); + EXPECT_EQ(0x00100000U, backoff.getNextTimeout()); + EXPECT_EQ(0x00200000U, backoff.getNextTimeout()); + EXPECT_EQ(0x00400000U, backoff.getNextTimeout()); + EXPECT_EQ(0x00800000U, backoff.getNextTimeout()); + EXPECT_EQ(0x01000000U, backoff.getNextTimeout()); + EXPECT_EQ(0x02000000U, backoff.getNextTimeout()); + EXPECT_EQ(0x04000000U, backoff.getNextTimeout()); + EXPECT_EQ(0x08000000U, backoff.getNextTimeout()); + EXPECT_EQ(0x10000000U, backoff.getNextTimeout()); + EXPECT_EQ(0x20000000U, backoff.getNextTimeout()); + EXPECT_EQ(0x40000000U, backoff.getNextTimeout()); + EXPECT_EQ(0x80000000U, backoff.getNextTimeout()); + // Maxes out, and stays there, ad infinitum. + for (int i = 0; i < 10; i++) { + EXPECT_TRUE(backoff.hasNextTimeout()); + EXPECT_EQ(0xffffffffU, backoff.getNextTimeout()); + } +} + +TEST(BackoffSequence, backoffToOncePerHour) { + auto backoff = BackoffSequence<uint32_t>::Builder() + .withInitialRetransmissionTime(1) + .withMaximumRetransmissionTime(3600) + .build(); + + EXPECT_TRUE(backoff.hasNextTimeout()); + EXPECT_EQ(0x00000001U, backoff.getNextTimeout()); + EXPECT_EQ(0x00000002U, backoff.getNextTimeout()); + EXPECT_EQ(0x00000004U, backoff.getNextTimeout()); + EXPECT_EQ(0x00000008U, backoff.getNextTimeout()); + EXPECT_EQ(0x00000010U, backoff.getNextTimeout()); + EXPECT_EQ(0x00000020U, backoff.getNextTimeout()); + EXPECT_EQ(0x00000040U, backoff.getNextTimeout()); + EXPECT_EQ(0x00000080U, backoff.getNextTimeout()); + EXPECT_EQ(0x00000100U, backoff.getNextTimeout()); + EXPECT_EQ(0x00000200U, backoff.getNextTimeout()); + EXPECT_EQ(0x00000400U, backoff.getNextTimeout()); + EXPECT_EQ(0x00000800U, backoff.getNextTimeout()); + // Maxes out, and stays there, ad infinitum. + for (int i = 0; i < 10; i++) { + EXPECT_TRUE(backoff.hasNextTimeout()); + EXPECT_EQ(3600U, backoff.getNextTimeout()); + } +} + +TEST(BackoffSequence, simpleMaxRetransCount) { + auto backoff = BackoffSequence<uint32_t>::Builder() + .withInitialRetransmissionTime(3) + .withMaximumRetransmissionCount(7) + .build(); + + EXPECT_TRUE(backoff.hasNextTimeout()); + EXPECT_EQ(0x00000003U, backoff.getNextTimeout()); + EXPECT_EQ(0x00000006U, backoff.getNextTimeout()); + EXPECT_EQ(0x0000000cU, backoff.getNextTimeout()); + EXPECT_EQ(0x00000018U, backoff.getNextTimeout()); + EXPECT_EQ(0x00000030U, backoff.getNextTimeout()); + EXPECT_EQ(0x00000060U, backoff.getNextTimeout()); + EXPECT_EQ(0x000000c0U, backoff.getNextTimeout()); + + for (int i = 0; i < 10; i++) { + EXPECT_FALSE(backoff.hasNextTimeout()); + EXPECT_EQ(backoff.getEndOfSequenceIndicator(), backoff.getNextTimeout()); + } +} + +TEST(BackoffSequence, simpleMaxDuration) { + auto backoff = BackoffSequence<int>::Builder() + .withInitialRetransmissionTime(3) + .withMaximumRetransmissionDuration(7) + .withEndOfSequenceIndicator(-1) + .build(); + + EXPECT_TRUE(backoff.hasNextTimeout()); + EXPECT_EQ(0x00000003, backoff.getNextTimeout()); + EXPECT_EQ(0x00000004, backoff.getNextTimeout()); + + for (int i = 0; i < 10; i++) { + EXPECT_FALSE(backoff.hasNextTimeout()); + EXPECT_EQ(backoff.getEndOfSequenceIndicator(), backoff.getNextTimeout()); + EXPECT_EQ(-1, backoff.getNextTimeout()); + } +} + +TEST(PathologicalBackoffSequence, ZeroInitialRetransTime) { + auto backoff = BackoffSequence<std::chrono::seconds>::Builder() + .withInitialRetransmissionTime(std::chrono::seconds(0)) + .build(); + + for (int i = 0; i < 10; i++) { + // TODO: Decide whether this needs fixing, and how. + EXPECT_TRUE(backoff.hasNextTimeout()); + EXPECT_EQ(backoff.getEndOfSequenceIndicator(), backoff.getNextTimeout()); + } +} + +TEST(PathologicalBackoffSequence, MaxRetransDurationGreaterThanInitialRetransTime) { + auto backoff = BackoffSequence<std::chrono::milliseconds>::Builder() + .withInitialRetransmissionTime(std::chrono::milliseconds(5)) + .withMaximumRetransmissionDuration(std::chrono::milliseconds(3)) + .build(); + + EXPECT_EQ(std::chrono::milliseconds(3), backoff.getNextTimeout()); + for (int i = 0; i < 10; i++) { + EXPECT_FALSE(backoff.hasNextTimeout()); + EXPECT_EQ(backoff.getEndOfSequenceIndicator(), backoff.getNextTimeout()); + } +} + +TEST(PathologicalBackoffSequence, MaxRetransDurationEqualsInitialRetransTime) { + auto backoff = BackoffSequence<std::chrono::hours>::Builder() + .withInitialRetransmissionTime(std::chrono::hours(5)) + .withMaximumRetransmissionDuration(std::chrono::hours(5)) + .build(); + + EXPECT_EQ(std::chrono::hours(5), backoff.getNextTimeout()); + for (int i = 0; i < 10; i++) { + EXPECT_FALSE(backoff.hasNextTimeout()); + EXPECT_EQ(backoff.getEndOfSequenceIndicator(), backoff.getNextTimeout()); + } +} + +TEST(PathologicalBackoffSequence, MaxRetransTimeAndDurationGreaterThanInitialRetransTime) { + auto backoff = BackoffSequence<std::chrono::nanoseconds>::Builder() + .withInitialRetransmissionTime(std::chrono::nanoseconds(7)) + .withMaximumRetransmissionTime(std::chrono::nanoseconds(3)) + .withMaximumRetransmissionDuration(std::chrono::nanoseconds(5)) + .build(); + + EXPECT_EQ(std::chrono::nanoseconds(3), backoff.getNextTimeout()); + EXPECT_EQ(std::chrono::nanoseconds(2), backoff.getNextTimeout()); + for (int i = 0; i < 10; i++) { + EXPECT_FALSE(backoff.hasNextTimeout()); + EXPECT_EQ(backoff.getEndOfSequenceIndicator(), backoff.getNextTimeout()); + } +} + +} // namespace netdutils +} // namespace android diff --git a/staticlibs/netd/libnetdutils/DumpWriter.cpp b/staticlibs/netd/libnetdutils/DumpWriter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..092ddbae2dc6fa262424d3a62d9d7577643f8ae8 --- /dev/null +++ b/staticlibs/netd/libnetdutils/DumpWriter.cpp @@ -0,0 +1,72 @@ +/* + * 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. + */ + +#include "netdutils/DumpWriter.h" + +#include <unistd.h> +#include <limits> + +#include <android-base/stringprintf.h> +#include <utils/String8.h> + +using android::base::StringAppendV; + +namespace android { +namespace netdutils { + +namespace { + +const char kIndentString[] = " "; +const size_t kIndentStringLen = strlen(kIndentString); + +} // namespace + +DumpWriter::DumpWriter(int fd) : mIndentLevel(0), mFd(fd) {} + +void DumpWriter::incIndent() { + if (mIndentLevel < std::numeric_limits<decltype(mIndentLevel)>::max()) { + mIndentLevel++; + } +} + +void DumpWriter::decIndent() { + if (mIndentLevel > std::numeric_limits<decltype(mIndentLevel)>::min()) { + mIndentLevel--; + } +} + +void DumpWriter::println(const std::string& line) { + if (!line.empty()) { + for (int i = 0; i < mIndentLevel; i++) { + ::write(mFd, kIndentString, kIndentStringLen); + } + ::write(mFd, line.c_str(), line.size()); + } + ::write(mFd, "\n", 1); +} + +// NOLINTNEXTLINE(cert-dcl50-cpp): Grandfathered C-style variadic function. +void DumpWriter::println(const char* fmt, ...) { + std::string line; + va_list ap; + va_start(ap, fmt); + StringAppendV(&line, fmt, ap); + va_end(ap); + println(line); +} + +} // namespace netdutils +} // namespace android diff --git a/staticlibs/netd/libnetdutils/Fd.cpp b/staticlibs/netd/libnetdutils/Fd.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2651f906e7ade6e384b65edfdb8447b81815757d --- /dev/null +++ b/staticlibs/netd/libnetdutils/Fd.cpp @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017 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. + */ + +#include "netdutils/Fd.h" + +namespace android { +namespace netdutils { + +std::ostream& operator<<(std::ostream& os, const Fd& fd) { + return os << "Fd[" << fd.get() << "]"; +} + +} // namespace netdutils +} // namespace android diff --git a/staticlibs/netd/libnetdutils/FdTest.cpp b/staticlibs/netd/libnetdutils/FdTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7080f831a19584c30315847a7d154c1b6eebb51c --- /dev/null +++ b/staticlibs/netd/libnetdutils/FdTest.cpp @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2017 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. + */ + +#include <cstdint> + +#include <gtest/gtest.h> + +#include "netdutils/MockSyscalls.h" +#include "netdutils/Status.h" +#include "netdutils/Syscalls.h" + +using testing::Mock; +using testing::Return; +using testing::StrictMock; + +namespace android { +namespace netdutils { +namespace { + +// Force implicit conversion from UniqueFd -> Fd +inline Fd toFd(const UniqueFd& fd) { + return fd; +} + +} // namespace + +TEST(Fd, smoke) { + // Expect the following lines to compile + Fd fd1(1); + Fd fd2(fd1); + Fd fd3 = fd2; + const Fd fd4(8); + const Fd fd5(fd4); + const Fd fd6 = fd5; + EXPECT_TRUE(isWellFormed(fd3)); + EXPECT_TRUE(isWellFormed(fd6)); + + // Corner case + Fd zero(0); + EXPECT_TRUE(isWellFormed(zero)); + + // Invalid file descriptors + Fd bad(-1); + Fd weird(-9); + EXPECT_FALSE(isWellFormed(bad)); + EXPECT_FALSE(isWellFormed(weird)); + + // Default constructor + EXPECT_EQ(Fd(-1), Fd()); + std::stringstream ss; + ss << fd3 << " " << fd6 << " " << bad << " " << weird; + EXPECT_EQ("Fd[1] Fd[8] Fd[-1] Fd[-9]", ss.str()); +} + +class UniqueFdTest : public testing::Test { + protected: + StrictMock<ScopedMockSyscalls> mSyscalls; +}; + +TEST_F(UniqueFdTest, operatorOstream) { + UniqueFd u(97); + EXPECT_CALL(mSyscalls, close(toFd(u))).WillOnce(Return(status::ok)); + std::stringstream ss; + ss << u; + EXPECT_EQ("UniqueFd[Fd[97]]", ss.str()); + u.reset(); +} + +TEST_F(UniqueFdTest, destructor) { + { + UniqueFd u(98); + EXPECT_CALL(mSyscalls, close(toFd(u))).WillOnce(Return(status::ok)); + } + // Expectation above should be upon leaving nested scope + Mock::VerifyAndClearExpectations(&mSyscalls); +} + +TEST_F(UniqueFdTest, reset) { + UniqueFd u(99); + EXPECT_CALL(mSyscalls, close(toFd(u))).WillOnce(Return(status::ok)); + u.reset(); + + // Expectation above should be upon reset + Mock::VerifyAndClearExpectations(&mSyscalls); +} + +TEST_F(UniqueFdTest, moveConstructor) { + constexpr Fd kFd(101); + UniqueFd u1(kFd); + { + UniqueFd u2(std::move(u1)); + // NOLINTNEXTLINE bugprone-use-after-move + EXPECT_FALSE(isWellFormed(u1)); + EXPECT_TRUE(isWellFormed(u2)); + EXPECT_CALL(mSyscalls, close(kFd)).WillOnce(Return(status::ok)); + } + // Expectation above should be upon leaving nested scope + Mock::VerifyAndClearExpectations(&mSyscalls); +} + +TEST_F(UniqueFdTest, moveAssignment) { + constexpr Fd kFd(102); + UniqueFd u1(kFd); + { + UniqueFd u2 = std::move(u1); + // NOLINTNEXTLINE bugprone-use-after-move + EXPECT_FALSE(isWellFormed(u1)); + EXPECT_TRUE(isWellFormed(u2)); + UniqueFd u3; + u3 = std::move(u2); + // NOLINTNEXTLINE bugprone-use-after-move + EXPECT_FALSE(isWellFormed(u2)); + EXPECT_TRUE(isWellFormed(u3)); + EXPECT_CALL(mSyscalls, close(kFd)).WillOnce(Return(status::ok)); + } + // Expectation above should be upon leaving nested scope + Mock::VerifyAndClearExpectations(&mSyscalls); +} + +TEST_F(UniqueFdTest, constConstructor) { + constexpr Fd kFd(103); + const UniqueFd u(kFd); + EXPECT_CALL(mSyscalls, close(toFd(u))).WillOnce(Return(status::ok)); +} + +TEST_F(UniqueFdTest, closeFailure) { + constexpr Fd kFd(103); + UniqueFd u(kFd); + EXPECT_CALL(mSyscalls, close(toFd(u))).WillOnce(Return(statusFromErrno(EINTR, "test"))); + EXPECT_DEBUG_DEATH(u.reset(), ""); +} + +} // namespace netdutils +} // namespace android diff --git a/staticlibs/netd/libnetdutils/InternetAddresses.cpp b/staticlibs/netd/libnetdutils/InternetAddresses.cpp new file mode 100644 index 0000000000000000000000000000000000000000..322f1b1d6c57d23e130d6775b7c9d31812673b0a --- /dev/null +++ b/staticlibs/netd/libnetdutils/InternetAddresses.cpp @@ -0,0 +1,164 @@ +/* + * 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. + */ + +#include "netdutils/InternetAddresses.h" + +#include <string> + +#include <android-base/stringprintf.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <sys/socket.h> +#include <sys/types.h> + +namespace android { + +using base::StringPrintf; + +namespace netdutils { + +std::string IPAddress::toString() const noexcept { + char repr[INET6_ADDRSTRLEN] = "\0"; + + switch (mData.family) { + case AF_UNSPEC: + return "<unspecified>"; + case AF_INET: { + const in_addr v4 = mData.ip.v4; + inet_ntop(AF_INET, &v4, repr, sizeof(repr)); + break; + } + case AF_INET6: { + const in6_addr v6 = mData.ip.v6; + inet_ntop(AF_INET6, &v6, repr, sizeof(repr)); + break; + } + default: + return "<unknown_family>"; + } + + if (mData.family == AF_INET6 && mData.scope_id > 0) { + return StringPrintf("%s%%%u", repr, mData.scope_id); + } + + return repr; +} + +bool IPAddress::forString(const std::string& repr, IPAddress* ip) { + const addrinfo hints = { + .ai_flags = AI_NUMERICHOST | AI_NUMERICSERV, + }; + addrinfo* res; + const int ret = getaddrinfo(repr.c_str(), nullptr, &hints, &res); + ScopedAddrinfo res_cleanup(res); + if (ret != 0) { + return false; + } + + bool rval = true; + switch (res[0].ai_family) { + case AF_INET: { + sockaddr_in* sin = (sockaddr_in*) res[0].ai_addr; + if (ip) *ip = IPAddress(sin->sin_addr); + break; + } + case AF_INET6: { + sockaddr_in6* sin6 = (sockaddr_in6*) res[0].ai_addr; + if (ip) *ip = IPAddress(sin6->sin6_addr, sin6->sin6_scope_id); + break; + } + default: + rval = false; + break; + } + + return rval; +} + +IPPrefix::IPPrefix(const IPAddress& ip, int length) : IPPrefix(ip) { + // Silently treat CIDR lengths like "-1" as meaning the full bit length + // appropriate to the address family. + if (length < 0) return; + if (length >= mData.cidrlen) return; + + switch (mData.family) { + case AF_UNSPEC: + break; + case AF_INET: { + const in_addr_t mask = (length > 0) ? (~0U) << (IPV4_ADDR_BITS - length) : 0U; + mData.ip.v4.s_addr &= htonl(mask); + mData.cidrlen = static_cast<uint8_t>(length); + break; + } + case AF_INET6: { + // The byte in which this CIDR length falls. + const int which = length / 8; + const int mask = (length % 8 == 0) ? 0 : 0xff << (8 - length % 8); + mData.ip.v6.s6_addr[which] &= mask; + for (int i = which + 1; i < IPV6_ADDR_LEN; i++) { + mData.ip.v6.s6_addr[i] = 0U; + } + mData.cidrlen = static_cast<uint8_t>(length); + break; + } + default: + // TODO: Complain bitterly about possible data corruption? + return; + } +} + +bool IPPrefix::isUninitialized() const noexcept { + static const internal_::compact_ipdata empty{}; + return mData == empty; +} + +bool IPPrefix::forString(const std::string& repr, IPPrefix* prefix) { + size_t index = repr.find('/'); + if (index == std::string::npos) return false; + + // Parse the IP address. + IPAddress ip; + if (!IPAddress::forString(repr.substr(0, index), &ip)) return false; + + // Parse the prefix length. Can't use base::ParseUint because it accepts non-base 10 input. + const char* prefixString = repr.c_str() + index + 1; + if (!isdigit(*prefixString)) return false; + char* endptr; + unsigned long prefixlen = strtoul(prefixString, &endptr, 10); + if (*endptr != '\0') return false; + + uint8_t maxlen = (ip.family() == AF_INET) ? 32 : 128; + if (prefixlen > maxlen) return false; + + *prefix = IPPrefix(ip, prefixlen); + return true; +} + +std::string IPPrefix::toString() const noexcept { + return StringPrintf("%s/%d", ip().toString().c_str(), mData.cidrlen); +} + +std::string IPSockAddr::toString() const noexcept { + switch (mData.family) { + case AF_INET6: + return StringPrintf("[%s]:%u", ip().toString().c_str(), mData.port); + default: + return StringPrintf("%s:%u", ip().toString().c_str(), mData.port); + } +} + +} // namespace netdutils +} // namespace android diff --git a/staticlibs/netd/libnetdutils/InternetAddressesTest.cpp b/staticlibs/netd/libnetdutils/InternetAddressesTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9e37d11f7ec50b884236c99840d57d9136791119 --- /dev/null +++ b/staticlibs/netd/libnetdutils/InternetAddressesTest.cpp @@ -0,0 +1,728 @@ +/* + * 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. + */ + +#include <cstdint> +#include <limits> +#include <sstream> +#include <string> +#include <vector> + +#include <android-base/macros.h> +#include <fmt/format.h> +#include <gtest/gtest.h> + +#include "netdutils/InternetAddresses.h" + +namespace android { +namespace netdutils { +namespace { + +enum Relation { EQ, LT }; + +std::ostream& operator<<(std::ostream& os, Relation relation) { + switch (relation) { + case EQ: os << "eq"; break; + case LT: os << "lt"; break; + default: os << "?!"; break; + } + return os; +} + +template <typename T> +struct OperatorExpectation { + const Relation relation; + const T obj1; + const T obj2; + + std::string toString() const { + std::stringstream output; + output << obj1 << " " << relation << " " << obj2; + return output.str(); + } +}; + +template <typename T> +void testGamutOfOperators(const OperatorExpectation<T>& expectation) { + switch (expectation.relation) { + case EQ: + EXPECT_TRUE(expectation.obj1 == expectation.obj2); + EXPECT_TRUE(expectation.obj1 <= expectation.obj2); + EXPECT_TRUE(expectation.obj1 >= expectation.obj2); + EXPECT_FALSE(expectation.obj1 != expectation.obj2); + EXPECT_FALSE(expectation.obj1 < expectation.obj2); + EXPECT_FALSE(expectation.obj1 > expectation.obj2); + break; + + case LT: + EXPECT_TRUE(expectation.obj1 < expectation.obj2); + EXPECT_TRUE(expectation.obj1 <= expectation.obj2); + EXPECT_TRUE(expectation.obj1 != expectation.obj2); + EXPECT_FALSE(expectation.obj1 > expectation.obj2); + EXPECT_FALSE(expectation.obj1 >= expectation.obj2); + EXPECT_FALSE(expectation.obj1 == expectation.obj2); + break; + + default: + FAIL() << "Unknown relation given in test expectation"; + } +} + +const in_addr IPV4_ANY{htonl(INADDR_ANY)}; +const in_addr IPV4_LOOPBACK{htonl(INADDR_LOOPBACK)}; +const in_addr IPV4_ONES{~0U}; +const in6_addr IPV6_ANY = IN6ADDR_ANY_INIT; +const in6_addr IPV6_LOOPBACK = IN6ADDR_LOOPBACK_INIT; +const in6_addr FE80{{{0xfe,0x80,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}}; +const in6_addr FE80_1{{{0xfe,0x80,0,0,0,0,0,0,0,0,0,0,0,0,0,1}}}; +const in6_addr FE80_2{{{0xfe,0x80,0,0,0,0,0,0,0,0,0,0,0,0,0,2}}}; +const uint8_t ff = std::numeric_limits<uint8_t>::max(); +const in6_addr IPV6_ONES{{{ff,ff,ff,ff,ff,ff,ff,ff,ff,ff,ff,ff,ff,ff,ff,ff}}}; + +TEST(IPAddressTest, GamutOfOperators) { + const std::vector<OperatorExpectation<IPAddress>> kExpectations{ + {EQ, IPAddress(), IPAddress()}, + {EQ, IPAddress(IPV4_ONES), IPAddress(IPV4_ONES)}, + {EQ, IPAddress(IPV6_ONES), IPAddress(IPV6_ONES)}, + {EQ, IPAddress(FE80_1), IPAddress(FE80_1)}, + {EQ, IPAddress(FE80_2), IPAddress(FE80_2)}, + {LT, IPAddress(), IPAddress(IPV4_ANY)}, + {LT, IPAddress(), IPAddress(IPV4_ONES)}, + {LT, IPAddress(), IPAddress(IPV6_ANY)}, + {LT, IPAddress(), IPAddress(IPV6_ONES)}, + {LT, IPAddress(IPV4_ANY), IPAddress(IPV4_ONES)}, + {LT, IPAddress(IPV4_ANY), IPAddress(IPV6_ANY)}, + {LT, IPAddress(IPV4_ONES), IPAddress(IPV6_ANY)}, + {LT, IPAddress(IPV4_ONES), IPAddress(IPV6_ONES)}, + {LT, IPAddress(IPV6_ANY), IPAddress(IPV6_LOOPBACK)}, + {LT, IPAddress(IPV6_ANY), IPAddress(IPV6_ONES)}, + {LT, IPAddress(IPV6_LOOPBACK), IPAddress(IPV6_ONES)}, + {LT, IPAddress(FE80_1), IPAddress(FE80_2)}, + {LT, IPAddress(FE80_1), IPAddress(IPV6_ONES)}, + {LT, IPAddress(FE80_2), IPAddress(IPV6_ONES)}, + // Sort by scoped_id within the same address. + {LT, IPAddress(FE80_1), IPAddress(FE80_1, 1)}, + {LT, IPAddress(FE80_1, 1), IPAddress(FE80_1, 2)}, + // Sort by address first, scope_id second. + {LT, IPAddress(FE80_1, 2), IPAddress(FE80_2, 1)}, + }; + + size_t tests_run = 0; + for (const auto& expectation : kExpectations) { + SCOPED_TRACE(expectation.toString()); + EXPECT_NO_FATAL_FAILURE(testGamutOfOperators(expectation)); + tests_run++; + } + EXPECT_EQ(kExpectations.size(), tests_run); +} + +TEST(IPAddressTest, ScopeIds) { + // Scope IDs ignored for IPv4 addresses. + const IPAddress ones(IPV4_ONES); + EXPECT_EQ(0U, ones.scope_id()); + const IPAddress ones22(ones, 22); + EXPECT_EQ(0U, ones22.scope_id()); + EXPECT_EQ(ones, ones22); + const IPAddress ones23(ones, 23); + EXPECT_EQ(0U, ones23.scope_id()); + EXPECT_EQ(ones22, ones23); + + EXPECT_EQ("fe80::1%22", IPAddress(FE80_1, 22).toString()); + EXPECT_EQ("fe80::2%23", IPAddress(FE80_2, 23).toString()); + + // Verify that given an IPAddress with a scope_id an address without a + // scope_id can be constructed (just in case it's useful). + const IPAddress fe80_intf22(FE80_1, 22); + EXPECT_EQ(22U, fe80_intf22.scope_id()); + EXPECT_EQ(fe80_intf22, IPAddress(fe80_intf22)); + EXPECT_EQ(IPAddress(FE80_1), IPAddress(fe80_intf22, 0)); +} + +TEST(IPAddressTest, forString) { + IPAddress ip; + + EXPECT_FALSE(IPAddress::forString("not_an_ip", &ip)); + EXPECT_FALSE(IPAddress::forString("not_an_ip", nullptr)); + EXPECT_EQ(IPAddress(), IPAddress::forString("not_an_ip")); + + EXPECT_EQ(IPAddress(IPV4_ANY), IPAddress::forString("0.0.0.0")); + EXPECT_EQ(IPAddress(IPV4_ONES), IPAddress::forString("255.255.255.255")); + EXPECT_EQ(IPAddress(IPV4_LOOPBACK), IPAddress::forString("127.0.0.1")); + + EXPECT_EQ(IPAddress(IPV6_ANY), IPAddress::forString("::")); + EXPECT_EQ(IPAddress(IPV6_ANY), IPAddress::forString("::0")); + EXPECT_EQ(IPAddress(IPV6_ANY), IPAddress::forString("0::")); + EXPECT_EQ(IPAddress(IPV6_LOOPBACK), IPAddress::forString("::1")); + EXPECT_EQ(IPAddress(IPV6_LOOPBACK), IPAddress::forString("0::1")); + EXPECT_EQ(IPAddress(FE80_1), IPAddress::forString("fe80::1")); + EXPECT_EQ(IPAddress(FE80_1, 22), IPAddress::forString("fe80::1%22")); + // This relies upon having a loopback interface named "lo" with ifindex 1. + EXPECT_EQ(IPAddress(FE80_1, 1), IPAddress::forString("fe80::1%lo")); +} + +TEST(IPPrefixTest, forString) { + IPPrefix prefix; + + EXPECT_FALSE(IPPrefix::forString("", &prefix)); + EXPECT_FALSE(IPPrefix::forString("invalid", &prefix)); + EXPECT_FALSE(IPPrefix::forString("192.0.2.0", &prefix)); + EXPECT_FALSE(IPPrefix::forString("2001::db8::", &prefix)); + + EXPECT_FALSE(IPPrefix::forString("2001:db8::/", &prefix)); + EXPECT_FALSE(IPPrefix::forString("2001:db8:://32", &prefix)); + EXPECT_FALSE(IPPrefix::forString("2001:db8::/32z", &prefix)); + EXPECT_FALSE(IPPrefix::forString("2001:db8::/32/", &prefix)); + EXPECT_FALSE(IPPrefix::forString("2001:db8::/0x20", &prefix)); + EXPECT_FALSE(IPPrefix::forString("2001:db8:: /32", &prefix)); + EXPECT_FALSE(IPPrefix::forString("2001:db8::/ 32", &prefix)); + EXPECT_FALSE(IPPrefix::forString(" 2001:db8::/32", &prefix)); + EXPECT_FALSE(IPPrefix::forString("2001:db8::/32 ", &prefix)); + EXPECT_FALSE(IPPrefix::forString("2001:db8::/+32", &prefix)); + + EXPECT_FALSE(IPPrefix::forString("192.0.2.0/33", &prefix)); + EXPECT_FALSE(IPPrefix::forString("2001:db8::/129", &prefix)); + EXPECT_FALSE(IPPrefix::forString("192.0.2.0/-1", &prefix)); + EXPECT_FALSE(IPPrefix::forString("2001:db8::/-1", &prefix)); + + EXPECT_TRUE(IPPrefix::forString("2001:db8::/32", &prefix)); + EXPECT_EQ("2001:db8::/32", prefix.toString()); + EXPECT_EQ(IPPrefix(IPAddress::forString("2001:db8::"), 32), prefix); + + EXPECT_EQ(IPPrefix(), IPPrefix::forString("invalid")); + + EXPECT_EQ("0.0.0.0/0", IPPrefix::forString("0.0.0.0/0").toString()); + EXPECT_EQ("::/0", IPPrefix::forString("::/0").toString()); + EXPECT_EQ("192.0.2.128/25", IPPrefix::forString("192.0.2.131/25").toString()); + EXPECT_EQ("2001:db8:1:2:3:4:5:4/126", + IPPrefix::forString("2001:db8:1:2:3:4:5:6/126").toString()); +} + +TEST(IPPrefixTest, IPv4Truncation) { + const auto prefixStr = [](int length) -> std::string { + return IPPrefix(IPAddress(IPV4_ONES), length).toString(); + }; + + EXPECT_EQ("0.0.0.0/0", prefixStr(0)); + + EXPECT_EQ("128.0.0.0/1", prefixStr(1)); + EXPECT_EQ("192.0.0.0/2", prefixStr(2)); + EXPECT_EQ("224.0.0.0/3", prefixStr(3)); + EXPECT_EQ("240.0.0.0/4", prefixStr(4)); + EXPECT_EQ("248.0.0.0/5", prefixStr(5)); + EXPECT_EQ("252.0.0.0/6", prefixStr(6)); + EXPECT_EQ("254.0.0.0/7", prefixStr(7)); + EXPECT_EQ("255.0.0.0/8", prefixStr(8)); + + EXPECT_EQ("255.128.0.0/9", prefixStr(9)); + EXPECT_EQ("255.192.0.0/10", prefixStr(10)); + EXPECT_EQ("255.224.0.0/11", prefixStr(11)); + EXPECT_EQ("255.240.0.0/12", prefixStr(12)); + EXPECT_EQ("255.248.0.0/13", prefixStr(13)); + EXPECT_EQ("255.252.0.0/14", prefixStr(14)); + EXPECT_EQ("255.254.0.0/15", prefixStr(15)); + EXPECT_EQ("255.255.0.0/16", prefixStr(16)); + + EXPECT_EQ("255.255.128.0/17", prefixStr(17)); + EXPECT_EQ("255.255.192.0/18", prefixStr(18)); + EXPECT_EQ("255.255.224.0/19", prefixStr(19)); + EXPECT_EQ("255.255.240.0/20", prefixStr(20)); + EXPECT_EQ("255.255.248.0/21", prefixStr(21)); + EXPECT_EQ("255.255.252.0/22", prefixStr(22)); + EXPECT_EQ("255.255.254.0/23", prefixStr(23)); + EXPECT_EQ("255.255.255.0/24", prefixStr(24)); + + EXPECT_EQ("255.255.255.128/25", prefixStr(25)); + EXPECT_EQ("255.255.255.192/26", prefixStr(26)); + EXPECT_EQ("255.255.255.224/27", prefixStr(27)); + EXPECT_EQ("255.255.255.240/28", prefixStr(28)); + EXPECT_EQ("255.255.255.248/29", prefixStr(29)); + EXPECT_EQ("255.255.255.252/30", prefixStr(30)); + EXPECT_EQ("255.255.255.254/31", prefixStr(31)); + EXPECT_EQ("255.255.255.255/32", prefixStr(32)); +} + +TEST(IPPrefixTest, IPv6Truncation) { + const auto prefixStr = [](int length) -> std::string { + return IPPrefix(IPAddress(IPV6_ONES), length).toString(); + }; + + EXPECT_EQ("::/0", prefixStr(0)); + + EXPECT_EQ("8000::/1", prefixStr(1)); + EXPECT_EQ("c000::/2", prefixStr(2)); + EXPECT_EQ("e000::/3", prefixStr(3)); + EXPECT_EQ("f000::/4", prefixStr(4)); + EXPECT_EQ("f800::/5", prefixStr(5)); + EXPECT_EQ("fc00::/6", prefixStr(6)); + EXPECT_EQ("fe00::/7", prefixStr(7)); + EXPECT_EQ("ff00::/8", prefixStr(8)); + + EXPECT_EQ("ff80::/9", prefixStr(9)); + EXPECT_EQ("ffc0::/10", prefixStr(10)); + EXPECT_EQ("ffe0::/11", prefixStr(11)); + EXPECT_EQ("fff0::/12", prefixStr(12)); + EXPECT_EQ("fff8::/13", prefixStr(13)); + EXPECT_EQ("fffc::/14", prefixStr(14)); + EXPECT_EQ("fffe::/15", prefixStr(15)); + EXPECT_EQ("ffff::/16", prefixStr(16)); + + EXPECT_EQ("ffff:8000::/17", prefixStr(17)); + EXPECT_EQ("ffff:c000::/18", prefixStr(18)); + EXPECT_EQ("ffff:e000::/19", prefixStr(19)); + EXPECT_EQ("ffff:f000::/20", prefixStr(20)); + EXPECT_EQ("ffff:f800::/21", prefixStr(21)); + EXPECT_EQ("ffff:fc00::/22", prefixStr(22)); + EXPECT_EQ("ffff:fe00::/23", prefixStr(23)); + EXPECT_EQ("ffff:ff00::/24", prefixStr(24)); + + EXPECT_EQ("ffff:ff80::/25", prefixStr(25)); + EXPECT_EQ("ffff:ffc0::/26", prefixStr(26)); + EXPECT_EQ("ffff:ffe0::/27", prefixStr(27)); + EXPECT_EQ("ffff:fff0::/28", prefixStr(28)); + EXPECT_EQ("ffff:fff8::/29", prefixStr(29)); + EXPECT_EQ("ffff:fffc::/30", prefixStr(30)); + EXPECT_EQ("ffff:fffe::/31", prefixStr(31)); + EXPECT_EQ("ffff:ffff::/32", prefixStr(32)); + + EXPECT_EQ("ffff:ffff:8000::/33", prefixStr(33)); + EXPECT_EQ("ffff:ffff:c000::/34", prefixStr(34)); + EXPECT_EQ("ffff:ffff:e000::/35", prefixStr(35)); + EXPECT_EQ("ffff:ffff:f000::/36", prefixStr(36)); + EXPECT_EQ("ffff:ffff:f800::/37", prefixStr(37)); + EXPECT_EQ("ffff:ffff:fc00::/38", prefixStr(38)); + EXPECT_EQ("ffff:ffff:fe00::/39", prefixStr(39)); + EXPECT_EQ("ffff:ffff:ff00::/40", prefixStr(40)); + + EXPECT_EQ("ffff:ffff:ff80::/41", prefixStr(41)); + EXPECT_EQ("ffff:ffff:ffc0::/42", prefixStr(42)); + EXPECT_EQ("ffff:ffff:ffe0::/43", prefixStr(43)); + EXPECT_EQ("ffff:ffff:fff0::/44", prefixStr(44)); + EXPECT_EQ("ffff:ffff:fff8::/45", prefixStr(45)); + EXPECT_EQ("ffff:ffff:fffc::/46", prefixStr(46)); + EXPECT_EQ("ffff:ffff:fffe::/47", prefixStr(47)); + EXPECT_EQ("ffff:ffff:ffff::/48", prefixStr(48)); + + EXPECT_EQ("ffff:ffff:ffff:8000::/49", prefixStr(49)); + EXPECT_EQ("ffff:ffff:ffff:c000::/50", prefixStr(50)); + EXPECT_EQ("ffff:ffff:ffff:e000::/51", prefixStr(51)); + EXPECT_EQ("ffff:ffff:ffff:f000::/52", prefixStr(52)); + EXPECT_EQ("ffff:ffff:ffff:f800::/53", prefixStr(53)); + EXPECT_EQ("ffff:ffff:ffff:fc00::/54", prefixStr(54)); + EXPECT_EQ("ffff:ffff:ffff:fe00::/55", prefixStr(55)); + EXPECT_EQ("ffff:ffff:ffff:ff00::/56", prefixStr(56)); + + EXPECT_EQ("ffff:ffff:ffff:ff80::/57", prefixStr(57)); + EXPECT_EQ("ffff:ffff:ffff:ffc0::/58", prefixStr(58)); + EXPECT_EQ("ffff:ffff:ffff:ffe0::/59", prefixStr(59)); + EXPECT_EQ("ffff:ffff:ffff:fff0::/60", prefixStr(60)); + EXPECT_EQ("ffff:ffff:ffff:fff8::/61", prefixStr(61)); + EXPECT_EQ("ffff:ffff:ffff:fffc::/62", prefixStr(62)); + EXPECT_EQ("ffff:ffff:ffff:fffe::/63", prefixStr(63)); + EXPECT_EQ("ffff:ffff:ffff:ffff::/64", prefixStr(64)); + + EXPECT_EQ("ffff:ffff:ffff:ffff:8000::/65", prefixStr(65)); + EXPECT_EQ("ffff:ffff:ffff:ffff:c000::/66", prefixStr(66)); + EXPECT_EQ("ffff:ffff:ffff:ffff:e000::/67", prefixStr(67)); + EXPECT_EQ("ffff:ffff:ffff:ffff:f000::/68", prefixStr(68)); + EXPECT_EQ("ffff:ffff:ffff:ffff:f800::/69", prefixStr(69)); + EXPECT_EQ("ffff:ffff:ffff:ffff:fc00::/70", prefixStr(70)); + EXPECT_EQ("ffff:ffff:ffff:ffff:fe00::/71", prefixStr(71)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ff00::/72", prefixStr(72)); + + EXPECT_EQ("ffff:ffff:ffff:ffff:ff80::/73", prefixStr(73)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffc0::/74", prefixStr(74)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffe0::/75", prefixStr(75)); + EXPECT_EQ("ffff:ffff:ffff:ffff:fff0::/76", prefixStr(76)); + EXPECT_EQ("ffff:ffff:ffff:ffff:fff8::/77", prefixStr(77)); + EXPECT_EQ("ffff:ffff:ffff:ffff:fffc::/78", prefixStr(78)); + EXPECT_EQ("ffff:ffff:ffff:ffff:fffe::/79", prefixStr(79)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff::/80", prefixStr(80)); + + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:8000::/81", prefixStr(81)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:c000::/82", prefixStr(82)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:e000::/83", prefixStr(83)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:f000::/84", prefixStr(84)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:f800::/85", prefixStr(85)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:fc00::/86", prefixStr(86)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:fe00::/87", prefixStr(87)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ff00::/88", prefixStr(88)); + + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ff80::/89", prefixStr(89)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffc0::/90", prefixStr(90)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffe0::/91", prefixStr(91)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:fff0::/92", prefixStr(92)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:fff8::/93", prefixStr(93)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:fffc::/94", prefixStr(94)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:fffe::/95", prefixStr(95)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff::/96", prefixStr(96)); + + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:8000:0/97", prefixStr(97)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:c000:0/98", prefixStr(98)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:e000:0/99", prefixStr(99)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:f000:0/100", prefixStr(100)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:f800:0/101", prefixStr(101)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:fc00:0/102", prefixStr(102)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:fe00:0/103", prefixStr(103)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ff00:0/104", prefixStr(104)); + + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ff80:0/105", prefixStr(105)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffc0:0/106", prefixStr(106)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffe0:0/107", prefixStr(107)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:fff0:0/108", prefixStr(108)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:fff8:0/109", prefixStr(109)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:fffc:0/110", prefixStr(110)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:fffe:0/111", prefixStr(111)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:0/112", prefixStr(112)); + + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:8000/113", prefixStr(113)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:c000/114", prefixStr(114)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:e000/115", prefixStr(115)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:f000/116", prefixStr(116)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:f800/117", prefixStr(117)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fc00/118", prefixStr(118)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fe00/119", prefixStr(119)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00/120", prefixStr(120)); + + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff80/121", prefixStr(121)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffc0/122", prefixStr(122)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffe0/123", prefixStr(123)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff0/124", prefixStr(124)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff8/125", prefixStr(125)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffc/126", prefixStr(126)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe/127", prefixStr(127)); + EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128", prefixStr(128)); +} + +TEST(IPPrefixTest, TruncationOther) { + const struct { + const char* ip; + const int cidrLen; + const char* ipTruncated; + } testExpectations[] = { + {"192.0.2.0", 24, "192.0.2.0"}, + {"192.0.2.0", 23, "192.0.2.0"}, + {"192.0.2.0", 22, "192.0.0.0"}, + {"192.0.2.0", 1, "128.0.0.0"}, + {"2001:db8:cafe:d00d::", 56, "2001:db8:cafe:d000::"}, + {"2001:db8:cafe:d00d::", 48, "2001:db8:cafe::"}, + {"2001:db8:cafe:d00d::", 47, "2001:db8:cafe::"}, + {"2001:db8:cafe:d00d::", 46, "2001:db8:cafc::"}, + }; + + for (const auto& expectation : testExpectations) { + IPAddress ip; + EXPECT_TRUE(IPAddress::forString(expectation.ip, &ip)) + << "Failed to parse IP address " << expectation.ip; + + IPAddress ipTruncated; + EXPECT_TRUE(IPAddress::forString(expectation.ipTruncated, &ipTruncated)) + << "Failed to parse IP address " << expectation.ipTruncated; + + IPPrefix prefix(ip, expectation.cidrLen); + + EXPECT_EQ(expectation.cidrLen, prefix.length()) + << "Unexpected cidrLen " << expectation.cidrLen; + EXPECT_EQ(ipTruncated, prefix.ip()) + << "Unexpected IP truncation: " << prefix.ip() << ", expected: " << ipTruncated; + } +} + +TEST(IPPrefixTest, containsPrefix) { + const struct { + const char* prefix; + const char* otherPrefix; + const bool expected; + std::string asParameters() const { + return fmt::format("prefix={}, other={}, expect={}", prefix, otherPrefix, expected); + } + } testExpectations[] = { + {"192.0.0.0/8", "192.0.0.0/8", true}, + {"192.1.0.0/16", "192.1.0.0/16", true}, + {"192.1.2.0/24", "192.1.2.0/24", true}, + {"192.1.2.3/32", "192.1.2.3/32", true}, + {"0.0.0.0/0", "192.0.0.0/8", true}, + {"0.0.0.0/0", "192.1.0.0/16", true}, + {"0.0.0.0/0", "192.1.2.0/24", true}, + {"0.0.0.0/0", "192.1.2.3/32", true}, + {"192.0.0.0/8", "192.1.0.0/16", true}, + {"192.0.0.0/8", "192.1.2.0/24", true}, + {"192.0.0.0/8", "192.1.2.5/32", true}, + {"192.1.0.0/16", "192.1.2.0/24", true}, + {"192.1.0.0/16", "192.1.3.6/32", true}, + {"192.5.6.0/24", "192.5.6.7/32", true}, + {"192.1.2.3/32", "192.1.2.0/24", false}, + {"192.1.2.3/32", "192.1.0.0/16", false}, + {"192.1.2.3/32", "192.0.0.0/8", false}, + {"192.1.2.3/32", "0.0.0.0/0", false}, + {"192.1.2.0/24", "192.1.0.0/16", false}, + {"192.1.2.0/24", "192.0.0.0/8", false}, + {"192.1.2.0/24", "0.0.0.0/0", false}, + {"192.9.0.0/16", "192.0.0.0/8", false}, + {"192.9.0.0/16", "0.0.0.0/0", false}, + {"192.0.0.0/8", "0.0.0.0/0", false}, + {"192.0.0.0/8", "191.0.0.0/8", false}, + {"191.0.0.0/8", "192.0.0.0/8", false}, + {"192.8.0.0/16", "192.7.0.0/16", false}, + {"192.7.0.0/16", "192.8.0.0/16", false}, + {"192.8.6.0/24", "192.7.5.0/24", false}, + {"192.7.5.0/24", "192.8.6.0/24", false}, + {"192.8.6.100/32", "192.8.6.200/32", false}, + {"192.8.6.200/32", "192.8.6.100/32", false}, + {"192.0.0.0/8", "192.0.0.0/12", true}, + {"192.0.0.0/12", "192.0.0.0/8", false}, + {"2001::/16", "2001::/16", true}, + {"2001:db8::/32", "2001:db8::/32", true}, + {"2001:db8:cafe::/48", "2001:db8:cafe::/48", true}, + {"2001:db8:cafe:d00d::/64", "2001:db8:cafe:d00d::/64", true}, + {"2001:db8:cafe:d00d:fec0::/80", "2001:db8:cafe:d00d:fec0::/80", true}, + {"2001:db8:cafe:d00d:fec0:de::/96", "2001:db8:cafe:d00d:fec0:de::/96", true}, + {"2001:db8:cafe:d00d:fec0:de:ac::/112", "2001:db8:cafe:d00d:fec0:de:ac::/112", true}, + {"2001:db8::cafe:0:1/128", "2001:db8::cafe:0:1/128", true}, + {"2001::/16", "2001:db8::/32", true}, + {"2001::/16", "2001:db8:cafe::/48", true}, + {"2001::/16", "2001:db8:cafe:d00d::/64", true}, + {"2001::/16", "2001:db8:cafe:d00d:fec0::/80", true}, + {"2001::/16", "2001:db8:cafe:d00d:fec0:de::/96", true}, + {"2001::/16", "2001:db8:cafe:d00d:fec0:de:ac::/112", true}, + {"2001::/16", "2001:db8:cafe:d00d:fec0:de:ac:dd/128", true}, + {"::/0", "2001::/16", true}, + {"::/0", "2001:db8::/32", true}, + {"::/0", "2001:db8:cafe::/48", true}, + {"::/0", "2001:db8:cafe:d00d::/64", true}, + {"::/0", "2001:db8:cafe:d00d:fec0::/80", true}, + {"::/0", "2001:db8:cafe:d00d:fec0:de::/96", true}, + {"::/0", "2001:db8:cafe:d00d:fec0:de:ac::/112", true}, + {"::/0", "2001:db8:cafe:d00d:fec0:de:ac:dd/128", true}, + {"2001:db8::dd/128", "2001::/16", false}, + {"2001:db8::dd/128", "2001:db8::/32", false}, + {"2001:db8::dd/128", "2001:db8:cafe::/48", false}, + {"2001:db8::dd/128", "2001:db8:cafe:d00d::/64", false}, + {"2001:db8::dd/128", "2001:db8:cafe:d00d:fec0::/80", false}, + {"2001:db8::dd/128", "2001:db8:cafe:d00d:fec0:de::/96", false}, + {"2001:db8::dd/128", "2001:db8:cafe:d00d:fec0:de:ac::/112", false}, + {"2001:db7::/32", "2001:db8::/32", false}, + {"2001:db8::/32", "2001:db7::/32", false}, + {"2001:db8:caff::/48", "2001:db8:cafe::/48", false}, + {"2001:db8:cafe::/48", "2001:db8:caff::/48", false}, + {"2001:db8:cafe:a00d::/64", "2001:db8:cafe:d00d::/64", false}, + {"2001:db8:cafe:d00d::/64", "2001:db8:cafe:a00d::/64", false}, + {"2001:db8:cafe:d00d:fec1::/80", "2001:db8:cafe:d00d:fec0::/80", false}, + {"2001:db8:cafe:d00d:fec0::/80", "2001:db8:cafe:d00d:fec1::/80", false}, + {"2001:db8:cafe:d00d:fec0:dd::/96", "2001:db8:cafe:d00d:fec0:ae::/96", false}, + {"2001:db8:cafe:d00d:fec0:ae::/96", "2001:db8:cafe:d00d:fec0:dd::/96", false}, + {"2001:db8:cafe:d00d:fec0:de:aa::/112", "2001:db8:cafe:d00d:fec0:de:ac::/112", false}, + {"2001:db8:cafe:d00d:fec0:de:ac::/112", "2001:db8:cafe:d00d:fec0:de:aa::/112", false}, + {"2001:db8::cafe:0:123/128", "2001:db8::cafe:0:456/128", false}, + {"2001:db8::cafe:0:456/128", "2001:db8::cafe:0:123/128", false}, + {"2001:db8::/32", "2001:db8::/64", true}, + {"2001:db8::/64", "2001:db8::/32", false}, + {"::/0", "0.0.0.0/0", false}, + {"::/0", "1.0.0.0/8", false}, + {"::/0", "1.2.0.0/16", false}, + {"::/0", "1.2.3.0/24", false}, + {"::/0", "1.2.3.4/32", false}, + {"2001::/16", "1.2.3.4/32", false}, + {"2001::db8::/32", "1.2.3.4/32", false}, + {"2001:db8:cafe::/48", "1.2.3.4/32", false}, + {"2001:db8:cafe:d00d::/64", "1.2.3.4/32", false}, + {"2001:db8:cafe:d00d:fec0::/80", "1.2.3.4/32", false}, + {"2001:db8:cafe:d00d:fec0:ae::/96", "1.2.3.4/32", false}, + {"2001:db8:cafe:d00d:fec0:de:aa::/112", "1.2.3.4/32", false}, + {"0.0.0.0/0", "::/0", false}, + {"0.0.0.0/0", "2001::/16", false}, + {"0.0.0.0/0", "2001::db8::/32", false}, + {"0.0.0.0/0", "2001:db8:cafe::/48", false}, + {"0.0.0.0/0", "2001:db8:cafe:d00d::/64", false}, + {"0.0.0.0/0", "2001:db8:cafe:d00d:fec0::/80", false}, + {"0.0.0.0/0", "2001:db8:cafe:d00d:fec0:ae::/96", false}, + {"0.0.0.0/0", "2001:db8:cafe:d00d:fec0:de:aa::/112", false}, + {"1.2.3.4/32", "2001:db8:cafe:d00d:fec0:de:aa::/112", false}, + {"1.2.3.0/24", "2001:db8:cafe:d00d:fec0:de:aa::/112", false}, + {"1.2.0.0/16", "2001:db8:cafe:d00d:fec0:de:aa::/112", false}, + {"1.0.0.0/8", "2001:db8:cafe:d00d:fec0:de:aa::/112", false}, + }; + + for (const auto& expectation : testExpectations) { + SCOPED_TRACE(expectation.asParameters()); + IPPrefix a = IPPrefix::forString(expectation.prefix); + IPPrefix b = IPPrefix::forString(expectation.otherPrefix); + EXPECT_EQ(expectation.expected, a.contains(b)); + } +} + +TEST(IPPrefixTest, containsAddress) { + const struct { + const char* prefix; + const char* address; + const bool expected; + std::string asParameters() const { + return fmt::format("prefix={}, address={}, expect={}", prefix, address, expected); + } + } testExpectations[] = { + {"0.0.0.0/0", "255.255.255.255", true}, + {"0.0.0.0/0", "1.2.3.4", true}, + {"0.0.0.0/0", "1.2.3.0", true}, + {"0.0.0.0/0", "1.2.0.0", true}, + {"0.0.0.0/0", "1.0.0.0", true}, + {"0.0.0.0/0", "0.0.0.0", true}, + {"0.0.0.0/0", "2001:4868:4860::8888", false}, + {"0.0.0.0/0", "::/0", false}, + {"192.0.2.0/23", "192.0.2.0", true}, + {"192.0.2.0/23", "192.0.2.43", true}, + {"192.0.2.0/23", "192.0.3.21", true}, + {"192.0.2.0/23", "192.0.0.21", false}, + {"192.0.2.0/23", "8.8.8.8", false}, + {"192.0.2.0/23", "2001:4868:4860::8888", false}, + {"192.0.2.0/23", "::/0", false}, + {"1.2.3.4/32", "1.2.3.4", true}, + {"1.2.3.4/32", "1.2.3.5", false}, + {"10.0.0.0/8", "10.2.0.0", true}, + {"10.0.0.0/8", "10.2.3.5", true}, + {"10.0.0.0/8", "10.0.0.0", true}, + {"10.0.0.0/8", "10.255.255.254", true}, + {"10.0.0.0/8", "11.0.0.0", false}, + {"::/0", "2001:db8:f000::ace:d00c", true}, + {"::/0", "2002:db8:f00::ace:d00d", true}, + {"::/0", "2001:db7:f00::ace:d00e", true}, + {"::/0", "2001:db8:f01::bad:d00d", true}, + {"::/0", "::", true}, + {"::/0", "0.0.0.0", false}, + {"::/0", "1.2.3.4", false}, + {"2001:db8:f00::ace:d00d/127", "2001:db8:f00::ace:d00c", true}, + {"2001:db8:f00::ace:d00d/127", "2001:db8:f00::ace:d00d", true}, + {"2001:db8:f00::ace:d00d/127", "2001:db8:f00::ace:d00e", false}, + {"2001:db8:f00::ace:d00d/127", "2001:db8:f00::bad:d00d", false}, + {"2001:db8:f00::ace:d00d/127", "2001:4868:4860::8888", false}, + {"2001:db8:f00::ace:d00d/127", "8.8.8.8", false}, + {"2001:db8:f00::ace:d00d/127", "0.0.0.0", false}, + {"2001:db8:f00::ace:d00d/128", "2001:db8:f00::ace:d00d", true}, + {"2001:db8:f00::ace:d00d/128", "2001:db8:f00::ace:d00c", false}, + {"2001::/16", "2001::", true}, + {"2001::/16", "2001:db8:f00::ace:d00d", true}, + {"2001::/16", "2001:db8:f00::bad:d00d", true}, + {"2001::/16", "2001::abc", true}, + {"2001::/16", "2001:ffff:ffff:ffff:ffff:ffff:ffff:ffff", true}, + {"2001::/16", "2000::", false}, + }; + + for (const auto& expectation : testExpectations) { + SCOPED_TRACE(expectation.asParameters()); + IPPrefix a = IPPrefix::forString(expectation.prefix); + IPAddress b = IPAddress::forString(expectation.address); + EXPECT_EQ(expectation.expected, a.contains(b)); + } +} + +TEST(IPPrefixTest, GamutOfOperators) { + const std::vector<OperatorExpectation<IPPrefix>> kExpectations{ + {EQ, IPPrefix(), IPPrefix()}, + {EQ, IPPrefix(IPAddress(IPV4_ANY), 0), IPPrefix(IPAddress(IPV4_ANY), 0)}, + {EQ, IPPrefix(IPAddress(IPV4_ANY), IPV4_ADDR_BITS), IPPrefix(IPAddress(IPV4_ANY))}, + {EQ, IPPrefix(IPAddress(IPV6_ANY), 0), IPPrefix(IPAddress(IPV6_ANY), 0)}, + {EQ, IPPrefix(IPAddress(IPV6_ANY), IPV6_ADDR_BITS), IPPrefix(IPAddress(IPV6_ANY))}, + // Needlessly fully-specified IPv6 link-local address. + {EQ, IPPrefix(IPAddress(FE80_1)), IPPrefix(IPAddress(FE80_1, 0), IPV6_ADDR_BITS)}, + // Different IPv6 link-local addresses within the same /64, no scoped_id: same /64. + {EQ, IPPrefix(IPAddress(FE80_1), 64), IPPrefix(IPAddress(FE80_2), 64)}, + // Different IPv6 link-local address within the same /64, same scoped_id: same /64. + {EQ, IPPrefix(IPAddress(FE80_1, 17), 64), IPPrefix(IPAddress(FE80_2, 17), 64)}, + // Unspecified < IPv4. + {LT, IPPrefix(), IPPrefix(IPAddress(IPV4_ANY), 0)}, + // Same IPv4 base address sorts by prefix length. + {LT, IPPrefix(IPAddress(IPV4_ANY), 0), IPPrefix(IPAddress(IPV4_ANY), 1)}, + {LT, IPPrefix(IPAddress(IPV4_ANY), 1), IPPrefix(IPAddress(IPV4_ANY), IPV4_ADDR_BITS)}, + // Truncation means each base IPv4 address is different. + {LT, IPPrefix(IPAddress(IPV4_ONES), 0), IPPrefix(IPAddress(IPV4_ONES), 1)}, + {LT, IPPrefix(IPAddress(IPV4_ONES), 1), IPPrefix(IPAddress(IPV4_ONES), IPV4_ADDR_BITS)}, + // Sort by base IPv4 addresses first. + {LT, IPPrefix(IPAddress(IPV4_ANY), 0), IPPrefix(IPAddress::forString("0.0.0.1"))}, + {LT, IPPrefix(IPAddress(IPV4_ANY), 1), IPPrefix(IPAddress::forString("0.0.0.1"))}, + {LT, IPPrefix(IPAddress(IPV4_ANY), 24), IPPrefix(IPAddress::forString("0.0.0.1"))}, + // IPv4 < IPv6. + {LT, IPPrefix(IPAddress(IPV4_ANY), 0), IPPrefix(IPAddress(IPV6_ANY), 0)}, + {LT, IPPrefix(IPAddress(IPV4_ONES)), IPPrefix(IPAddress(IPV6_ANY))}, + // Unspecified < IPv6. + {LT, IPPrefix(), IPPrefix(IPAddress(IPV6_ANY), 0)}, + // Same IPv6 base address sorts by prefix length. + {LT, IPPrefix(IPAddress(IPV6_ANY), 0), IPPrefix(IPAddress(IPV6_ANY), 1)}, + {LT, IPPrefix(IPAddress(IPV6_ANY), 1), IPPrefix(IPAddress(IPV6_ANY), IPV6_ADDR_BITS)}, + // Truncation means each base IPv6 address is different. + {LT, IPPrefix(IPAddress(IPV6_ONES), 0), IPPrefix(IPAddress(IPV6_ONES), 1)}, + {LT, IPPrefix(IPAddress(IPV6_ONES), 1), IPPrefix(IPAddress(IPV6_ONES), IPV6_ADDR_BITS)}, + // Different IPv6 link-local address in same /64, different scoped_id: different /64. + {LT, IPPrefix(IPAddress(FE80_1, 17), 64), IPPrefix(IPAddress(FE80_2, 22), 64)}, + {LT, IPPrefix(IPAddress(FE80_1, 17), 64), IPPrefix(IPAddress(FE80_1, 18), 64)}, + {LT, IPPrefix(IPAddress(FE80_1, 18), 64), IPPrefix(IPAddress(FE80_1, 19), 64)}, + }; + + size_t tests_run = 0; + for (const auto& expectation : kExpectations) { + SCOPED_TRACE(expectation.toString()); + EXPECT_NO_FATAL_FAILURE(testGamutOfOperators(expectation)); + tests_run++; + } + EXPECT_EQ(kExpectations.size(), tests_run); +} + +TEST(IPSockAddrTest, GamutOfOperators) { + const std::vector<OperatorExpectation<IPSockAddr>> kExpectations{ + {EQ, IPSockAddr(), IPSockAddr()}, + {EQ, IPSockAddr(IPAddress(IPV4_ANY)), IPSockAddr(IPAddress(IPV4_ANY), 0)}, + {EQ, IPSockAddr(IPAddress(IPV6_ANY)), IPSockAddr(IPAddress(IPV6_ANY), 0)}, + {EQ, IPSockAddr(IPAddress(FE80_1), 80), IPSockAddr(IPAddress(FE80_1), 80)}, + {EQ, IPSockAddr(IPAddress(FE80_1, 17)), IPSockAddr(IPAddress(FE80_1, 17), 0)}, + {LT, IPSockAddr(IPAddress(IPV4_ANY), 0), IPSockAddr(IPAddress(IPV4_ANY), 1)}, + {LT, IPSockAddr(IPAddress(IPV4_ANY), 53), IPSockAddr(IPAddress(IPV4_ANY), 123)}, + {LT, IPSockAddr(IPAddress(IPV4_ONES), 123), IPSockAddr(IPAddress(IPV6_ANY), 53)}, + {LT, IPSockAddr(IPAddress(IPV6_ANY), 0), IPSockAddr(IPAddress(IPV6_ANY), 1)}, + {LT, IPSockAddr(IPAddress(IPV6_ANY), 53), IPSockAddr(IPAddress(IPV6_ANY), 123)}, + {LT, IPSockAddr(IPAddress(FE80_1), 80), IPSockAddr(IPAddress(FE80_1, 17), 80)}, + {LT, IPSockAddr(IPAddress(FE80_1, 17), 80), IPSockAddr(IPAddress(FE80_1, 22), 80)}, + }; + + size_t tests_run = 0; + for (const auto& expectation : kExpectations) { + SCOPED_TRACE(expectation.toString()); + EXPECT_NO_FATAL_FAILURE(testGamutOfOperators(expectation)); + tests_run++; + } + EXPECT_EQ(kExpectations.size(), tests_run); +} + +TEST(IPSockAddrTest, toString) { + EXPECT_EQ("<unspecified>:0", IPSockAddr().toString()); + EXPECT_EQ("0.0.0.0:0", IPSockAddr(IPAddress(IPV4_ANY)).toString()); + EXPECT_EQ("255.255.255.255:67", IPSockAddr(IPAddress(IPV4_ONES), 67).toString()); + EXPECT_EQ("[::]:0", IPSockAddr(IPAddress(IPV6_ANY)).toString()); + EXPECT_EQ("[::1]:53", IPSockAddr(IPAddress(IPV6_LOOPBACK), 53).toString()); + EXPECT_EQ("[fe80::1]:0", IPSockAddr(IPAddress(FE80_1)).toString()); + EXPECT_EQ("[fe80::2%17]:123", IPSockAddr(IPAddress(FE80_2, 17), 123).toString()); +} + +TEST(CompatIPDataTest, ConversionsClearUnneededValues) { + const uint32_t idx = 17; + const IPSockAddr linkLocalNtpSockaddr(IPAddress(FE80_2, idx), 123); + EXPECT_EQ(IPAddress(FE80_2, idx), linkLocalNtpSockaddr.ip()); + // IPSockAddr(IPSockaddr.ip()) see the port cleared. + EXPECT_EQ(0, IPSockAddr(linkLocalNtpSockaddr.ip()).port()); + const IPPrefix linkLocalPrefix(linkLocalNtpSockaddr.ip(), 64); + EXPECT_EQ(IPAddress(FE80, idx), linkLocalPrefix.ip()); + // IPPrefix(IPPrefix.ip()) see the CIDR length cleared. + EXPECT_EQ(IPV6_ADDR_BITS, IPPrefix(linkLocalPrefix.ip()).length()); +} + +} // namespace +} // namespace netdutils +} // namespace android diff --git a/staticlibs/netd/libnetdutils/Log.cpp b/staticlibs/netd/libnetdutils/Log.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d2ce98f094a18b3c0cff7ecfa3802a248332c73d --- /dev/null +++ b/staticlibs/netd/libnetdutils/Log.cpp @@ -0,0 +1,216 @@ +/* + * 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. + */ + +#include "netdutils/Log.h" +#include "netdutils/Slice.h" + +#include <chrono> +#include <ctime> +#include <iomanip> +#include <mutex> +#include <sstream> + +#include <android-base/strings.h> +#include <log/log.h> + +using ::android::base::Join; +using ::android::base::StringPrintf; + +namespace android { +namespace netdutils { + +namespace { + +std::string makeTimestampedEntry(const std::string& entry) { + using ::std::chrono::duration_cast; + using ::std::chrono::milliseconds; + using ::std::chrono::system_clock; + + std::stringstream tsEntry; + const auto now = system_clock::now(); + const auto time_sec = system_clock::to_time_t(now); + tsEntry << std::put_time(std::localtime(&time_sec), "%m-%d %H:%M:%S.") << std::setw(3) + << std::setfill('0') + << duration_cast<milliseconds>(now - system_clock::from_time_t(time_sec)).count() << " " + << entry; + + return tsEntry.str(); +} + +} // namespace + +std::string LogEntry::toString() const { + std::vector<std::string> text; + + if (!mMsg.empty()) text.push_back(mMsg); + if (!mFunc.empty()) { + text.push_back(StringPrintf("%s(%s)", mFunc.c_str(), Join(mArgs, ", ").c_str())); + } + if (!mReturns.empty()) { + text.push_back("->"); + text.push_back(StringPrintf("(%s)", Join(mReturns, ", ").c_str())); + } + if (!mUid.empty()) text.push_back(mUid); + if (!mDuration.empty()) text.push_back(StringPrintf("(%s)", mDuration.c_str())); + + return Join(text, " "); +} + +LogEntry& LogEntry::message(const std::string& message) { + mMsg = message; + return *this; +} + +LogEntry& LogEntry::function(const std::string& function_name) { + mFunc = function_name; + return *this; +} + +LogEntry& LogEntry::prettyFunction(const std::string& pretty_function) { + // __PRETTY_FUNCTION__ generally seems to be of the form: + // + // qualifed::returnType qualified::function(args...) + // + // where the qualified forms include "(anonymous namespace)" in the + // "::"-delimited list and keywords like "virtual" (where applicable). + // + // Here we try to convert strings like: + // + // virtual binder::Status android::net::NetdNativeService::isAlive(bool *) + // netdutils::LogEntry android::netd::(anonymous namespace)::AAA::BBB::function() + // + // into just "NetdNativeService::isAlive" or "BBB::function". Note that + // without imposing convention, how to easily identify any namespace/class + // name boundary is not obvious. + const size_t endFuncName = pretty_function.rfind('('); + const size_t precedingSpace = pretty_function.rfind(' ', endFuncName); + size_t substrStart = (precedingSpace != std::string::npos) ? precedingSpace + 1 : 0; + + const size_t beginFuncName = pretty_function.rfind("::", endFuncName); + if (beginFuncName != std::string::npos && substrStart < beginFuncName) { + const size_t previousNameBoundary = pretty_function.rfind("::", beginFuncName - 1); + if (previousNameBoundary < beginFuncName && substrStart < previousNameBoundary) { + substrStart = previousNameBoundary + 2; + } else { + substrStart = beginFuncName + 2; + } + } + + mFunc = pretty_function.substr(substrStart, endFuncName - substrStart); + return *this; +} + +LogEntry& LogEntry::arg(const std::string& val) { + mArgs.push_back(val.empty() ? "\"\"" : val); + return *this; +} + +template <> +LogEntry& LogEntry::arg<>(bool val) { + mArgs.push_back(val ? "true" : "false"); + return *this; +} + +LogEntry& LogEntry::arg(const std::vector<int32_t>& val) { + mArgs.push_back(StringPrintf("[%s]", Join(val, ", ").c_str())); + return *this; +} + +LogEntry& LogEntry::arg(const std::vector<uint8_t>& val) { + mArgs.push_back('{' + toHex(makeSlice(val)) + '}'); + return *this; +} + +LogEntry& LogEntry::arg(const std::vector<std::string>& val) { + mArgs.push_back(StringPrintf("[%s]", Join(val, ", ").c_str())); + return *this; +} + +LogEntry& LogEntry::returns(const std::string& rval) { + mReturns.push_back(rval); + return *this; +} + +LogEntry& LogEntry::returns(bool rval) { + mReturns.push_back(rval ? "true" : "false"); + return *this; +} + +LogEntry& LogEntry::returns(const Status& status) { + mReturns.push_back(status.msg()); + return *this; +} + +LogEntry& LogEntry::withUid(uid_t uid) { + mUid = StringPrintf("(uid=%d)", uid); + return *this; +} + +LogEntry& LogEntry::withAutomaticDuration() { + using ms = std::chrono::duration<float, std::ratio<1, 1000>>; + + const std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); + std::stringstream duration; + duration << std::setprecision(1) << std::chrono::duration_cast<ms>(end - mStart).count() + << "ms"; + mDuration = duration.str(); + return *this; +} + +LogEntry& LogEntry::withDuration(const std::string& duration) { + mDuration = duration; + return *this; +} + +Log::~Log() { + // TODO: dump the last N entries to the android log for possible posterity. + info(LogEntry().function(__FUNCTION__)); +} + +void Log::forEachEntry(const std::function<void(const std::string&)>& perEntryFn) const { + // We make a (potentially expensive) copy of the log buffer (including + // all strings), in case the |perEntryFn| takes its sweet time. + std::deque<std::string> entries; + { + std::shared_lock<std::shared_mutex> guard(mLock); + entries.assign(mEntries.cbegin(), mEntries.cend()); + } + + for (const std::string& entry : entries) perEntryFn(entry); +} + +void Log::record(Log::Level lvl, const std::string& entry) { + switch (lvl) { + case Level::LOG: + break; + case Level::INFO: + ALOG(LOG_INFO, mTag.c_str(), "%s", entry.c_str()); + break; + case Level::WARN: + ALOG(LOG_WARN, mTag.c_str(), "%s", entry.c_str()); + break; + case Level::ERROR: + ALOG(LOG_ERROR, mTag.c_str(), "%s", entry.c_str()); + break; + } + + std::lock_guard guard(mLock); + mEntries.push_back(makeTimestampedEntry(entry)); + while (mEntries.size() > mMaxEntries) mEntries.pop_front(); +} + +} // namespace netdutils +} // namespace android diff --git a/staticlibs/netd/libnetdutils/LogTest.cpp b/staticlibs/netd/libnetdutils/LogTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..127056091bec5ff8462ffd9a28e2090e2cf12859 --- /dev/null +++ b/staticlibs/netd/libnetdutils/LogTest.cpp @@ -0,0 +1,143 @@ +/* + * 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. + */ + +#include <gtest/gtest.h> + +#include "netdutils/Log.h" + +android::netdutils::LogEntry globalFunctionName() { + return android::netdutils::LogEntry().function(__FUNCTION__); +} + +android::netdutils::LogEntry globalPrettyFunctionName() { + return android::netdutils::LogEntry().prettyFunction(__PRETTY_FUNCTION__); +} + +namespace android { +namespace netdutils { + +namespace { + +LogEntry functionName() { + return LogEntry().function(__FUNCTION__); +} + +LogEntry prettyFunctionName() { + return LogEntry().prettyFunction(__PRETTY_FUNCTION__); +} + +} // namespace + +class AAA { + public: + AAA() = default; + + LogEntry functionName() { + return LogEntry().function(__FUNCTION__); + } + + LogEntry prettyFunctionName() { + return LogEntry().prettyFunction(__PRETTY_FUNCTION__); + } + + class BBB { + public: + BBB() = default; + + LogEntry functionName() { + return LogEntry().function(__FUNCTION__); + } + + LogEntry prettyFunctionName() { + return LogEntry().prettyFunction(__PRETTY_FUNCTION__); + } + }; +}; + +TEST(LogEntryTest, Empty) { + LogEntry empty; + EXPECT_EQ("", empty.toString()); +} + +TEST(LogEntryTest, GlobalFunction) { + EXPECT_EQ("globalFunctionName()", ::globalFunctionName().toString()); +} + +TEST(LogEntryTest, GlobalPrettyFunction) { + EXPECT_EQ("globalPrettyFunctionName()", ::globalPrettyFunctionName().toString()); +} + +TEST(LogEntryTest, UnnamedNamespaceFunction) { + const LogEntry entry = functionName(); + EXPECT_EQ("functionName()", entry.toString()); +} + +TEST(LogEntryTest, UnnamedNamespacePrettyFunction) { + const LogEntry entry = prettyFunctionName(); + EXPECT_EQ("prettyFunctionName()", entry.toString()); +} + +TEST(LogEntryTest, ClassFunction) { + const LogEntry entry = AAA().functionName(); + EXPECT_EQ("functionName()", entry.toString()); +} + +TEST(LogEntryTest, ClassPrettyFunction) { + const LogEntry entry = AAA().prettyFunctionName(); + EXPECT_EQ("AAA::prettyFunctionName()", entry.toString()); +} + +TEST(LogEntryTest, InnerClassFunction) { + const LogEntry entry = AAA::BBB().functionName(); + EXPECT_EQ("functionName()", entry.toString()); +} + +TEST(LogEntryTest, InnerClassPrettyFunction) { + const LogEntry entry = AAA::BBB().prettyFunctionName(); + EXPECT_EQ("BBB::prettyFunctionName()", entry.toString()); +} + +TEST(LogEntryTest, PrintChainedArguments) { + const LogEntry entry = LogEntry() + .function("testFunc") + .arg("hello") + .arg(42) + .arg(true); + EXPECT_EQ("testFunc(hello, 42, true)", entry.toString()); +} + +TEST(LogEntryTest, PrintIntegralTypes) { + const LogEntry entry = LogEntry() + .function("testFunc") + .arg('A') + .arg(100U) + .arg(-1000LL); + EXPECT_EQ("testFunc(65, 100, -1000)", entry.toString()); +} + +TEST(LogEntryTest, PrintHex) { + const std::vector<uint8_t> buf{0xDE, 0xAD, 0xBE, 0xEF}; + const LogEntry entry = LogEntry().function("testFunc").arg(buf); + EXPECT_EQ("testFunc({deadbeef})", entry.toString()); +} + +TEST(LogEntryTest, PrintArgumentPack) { + const LogEntry entry = LogEntry().function("testFunc").args("hello", 42, false); + EXPECT_EQ("testFunc(hello, 42, false)", entry.toString()); +} + +} // namespace netdutils +} // namespace android diff --git a/staticlibs/netd/libnetdutils/MemBlockTest.cpp b/staticlibs/netd/libnetdutils/MemBlockTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6455a7e01bf8df3193a523cc956ec83352268ed2 --- /dev/null +++ b/staticlibs/netd/libnetdutils/MemBlockTest.cpp @@ -0,0 +1,146 @@ +/* + * 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. + */ + +#include <algorithm> +#include <cstdint> +#include <utility> + +#include <gtest/gtest.h> + +#include "netdutils/MemBlock.h" +#include "netdutils/Slice.h" + +namespace android { +namespace netdutils { + +namespace { + +constexpr unsigned DNS_PACKET_SIZE = 512; +constexpr int ARBITRARY_VALUE = 0x55; + +MemBlock makeArbitraryMemBlock(size_t len) { + MemBlock result(len); + // Do some fictional work before returning. + for (Slice slice = result.get(); !slice.empty(); slice = drop(slice, 1)) { + slice.base()[0] = ARBITRARY_VALUE; + } + return result; +} + +void checkAllZeros(Slice slice) { + for (; !slice.empty(); slice = drop(slice, 1)) { + EXPECT_EQ(0U, slice.base()[0]); + } +} + +void checkArbitraryMemBlock(const MemBlock& block, size_t expectedSize) { + Slice slice = block.get(); + EXPECT_EQ(expectedSize, slice.size()); + EXPECT_NE(nullptr, slice.base()); + for (; !slice.empty(); slice = drop(slice, 1)) { + EXPECT_EQ(ARBITRARY_VALUE, slice.base()[0]); + } +} + +void checkHelloMello(Slice dest, Slice src) { + EXPECT_EQ('h', dest.base()[0]); + EXPECT_EQ('e', dest.base()[1]); + EXPECT_EQ('l', dest.base()[2]); + EXPECT_EQ('l', dest.base()[3]); + EXPECT_EQ('o', dest.base()[4]); + + src.base()[0] = 'm'; + EXPECT_EQ('h', dest.base()[0]); +} + +} // namespace + +TEST(MemBlockTest, Empty) { + MemBlock empty; + EXPECT_TRUE(empty.get().empty()); + EXPECT_EQ(nullptr, empty.get().base()); +} + +TEST(MemBlockTest, ExplicitZero) { + MemBlock zero(0); + EXPECT_TRUE(zero.get().empty()); + EXPECT_EQ(nullptr, zero.get().base()); +} + +TEST(MemBlockTest, BasicAllocation) { + MemBlock dnsPacket(DNS_PACKET_SIZE); + Slice slice = dnsPacket.get(); + EXPECT_EQ(DNS_PACKET_SIZE, slice.size()); + // Verify the space is '\0'-initialized. + ASSERT_NO_FATAL_FAILURE(checkAllZeros(slice)); + EXPECT_NE(nullptr, slice.base()); +} + +TEST(MemBlockTest, MoveConstruction) { + MemBlock block(makeArbitraryMemBlock(DNS_PACKET_SIZE)); + ASSERT_NO_FATAL_FAILURE(checkArbitraryMemBlock(block, DNS_PACKET_SIZE)); +} + +TEST(MemBlockTest, MoveAssignmentOrConstruction) { + MemBlock block = makeArbitraryMemBlock(DNS_PACKET_SIZE); + ASSERT_NO_FATAL_FAILURE(checkArbitraryMemBlock(block, DNS_PACKET_SIZE)); +} + +TEST(MemBlockTest, StdMoveAssignment) { + constexpr unsigned SIZE = 10; + + MemBlock block; + EXPECT_TRUE(block.get().empty()); + EXPECT_EQ(nullptr, block.get().base()); + + { + MemBlock block2 = makeArbitraryMemBlock(SIZE); + EXPECT_EQ(SIZE, block2.get().size()); + // More fictional work. + for (unsigned i = 0; i < SIZE; i++) { + block2.get().base()[i] = i; + } + block = std::move(block2); + } + + EXPECT_EQ(SIZE, block.get().size()); + for (unsigned i = 0; i < SIZE; i++) { + EXPECT_EQ(i, block.get().base()[i]); + } +} + +TEST(MemBlockTest, ConstructionFromSlice) { + uint8_t data[] = {'h', 'e', 'l', 'l', 'o'}; + Slice dataSlice(Slice(data, sizeof(data) / sizeof(data[0]))); + + MemBlock dataCopy(dataSlice); + ASSERT_NO_FATAL_FAILURE(checkHelloMello(dataCopy.get(), dataSlice)); +} + +TEST(MemBlockTest, ImplicitCastToSlice) { + uint8_t data[] = {'h', 'e', 'l', 'l', 'o'}; + Slice dataSlice(Slice(data, sizeof(data) / sizeof(data[0]))); + + MemBlock dataCopy(dataSlice.size()); + // NOTE: no explicit MemBlock::get(). + // Verify the space is '\0'-initialized. + ASSERT_NO_FATAL_FAILURE(checkAllZeros(dataCopy)); + copy(dataCopy, dataSlice); + ASSERT_NO_FATAL_FAILURE(checkHelloMello(dataCopy, dataSlice)); +} + +} // namespace netdutils +} // namespace android diff --git a/staticlibs/netd/libnetdutils/Netfilter.cpp b/staticlibs/netd/libnetdutils/Netfilter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bb43de0ab67dd81870f15429549497d09994c2ef --- /dev/null +++ b/staticlibs/netd/libnetdutils/Netfilter.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 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. + */ + +#include <arpa/inet.h> +#include <linux/netfilter.h> +#include <linux/netfilter/nfnetlink.h> +#include <linux/netlink.h> +#include <ios> + +#include "netdutils/Netfilter.h" + +std::ostream& operator<<(std::ostream& os, const nfgenmsg& msg) { + return os << std::hex << "nfgenmsg[" + << "family: 0x" << static_cast<int>(msg.nfgen_family) << ", version: 0x" + << static_cast<int>(msg.version) << ", res_id: 0x" << ntohs(msg.res_id) << "]" + << std::dec; +} diff --git a/staticlibs/netd/libnetdutils/Netlink.cpp b/staticlibs/netd/libnetdutils/Netlink.cpp new file mode 100644 index 0000000000000000000000000000000000000000..824c0f224d26af4a323164bc33f68834c08aef6f --- /dev/null +++ b/staticlibs/netd/libnetdutils/Netlink.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2017 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. + */ + +#include <ios> +#include <linux/netlink.h> + +#include "netdutils/Math.h" +#include "netdutils/Netlink.h" + +namespace android { +namespace netdutils { + +void forEachNetlinkMessage(const Slice buf, + const std::function<void(const nlmsghdr&, const Slice)>& onMsg) { + Slice tail = buf; + while (tail.size() >= sizeof(nlmsghdr)) { + nlmsghdr hdr = {}; + extract(tail, hdr); + const auto len = std::max<size_t>(hdr.nlmsg_len, sizeof(hdr)); + onMsg(hdr, drop(take(tail, len), sizeof(hdr))); + tail = drop(tail, align(len, 2)); + } +} + +void forEachNetlinkAttribute(const Slice buf, + const std::function<void(const nlattr&, const Slice)>& onAttr) { + Slice tail = buf; + while (tail.size() >= sizeof(nlattr)) { + nlattr hdr = {}; + extract(tail, hdr); + const auto len = std::max<size_t>(hdr.nla_len, sizeof(hdr)); + onAttr(hdr, drop(take(tail, len), sizeof(hdr))); + tail = drop(tail, align(len, 2)); + } +} + +} // namespace netdutils +} // namespace android + +bool operator==(const sockaddr_nl& lhs, const sockaddr_nl& rhs) { + return (lhs.nl_family == rhs.nl_family) && (lhs.nl_pid == rhs.nl_pid) && + (lhs.nl_groups == rhs.nl_groups); +} + +bool operator!=(const sockaddr_nl& lhs, const sockaddr_nl& rhs) { + return !(lhs == rhs); +} + +std::ostream& operator<<(std::ostream& os, const nlmsghdr& hdr) { + return os << std::hex << "nlmsghdr[" + << "len: 0x" << hdr.nlmsg_len << ", type: 0x" << hdr.nlmsg_type << ", flags: 0x" + << hdr.nlmsg_flags << ", seq: 0x" << hdr.nlmsg_seq << ", pid: 0x" << hdr.nlmsg_pid + << "]" << std::dec; +} + +std::ostream& operator<<(std::ostream& os, const nlattr& attr) { + return os << std::hex << "nlattr[" + << "len: 0x" << attr.nla_len << ", type: 0x" << attr.nla_type << "]" << std::dec; +} + +std::ostream& operator<<(std::ostream& os, const sockaddr_nl& addr) { + return os << std::hex << "sockaddr_nl[" + << "family: " << addr.nl_family << ", pid: " << addr.nl_pid + << ", groups: " << addr.nl_groups << "]" << std::dec; +} diff --git a/staticlibs/netd/libnetdutils/NetlinkListener.cpp b/staticlibs/netd/libnetdutils/NetlinkListener.cpp new file mode 100644 index 0000000000000000000000000000000000000000..decaa9c88b255ae6bcc5ce82f5a3f2c8a1ee00f2 --- /dev/null +++ b/staticlibs/netd/libnetdutils/NetlinkListener.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2017 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. + */ + +#define LOG_TAG "NetlinkListener" + +#include <sstream> +#include <vector> + +#include <linux/netfilter/nfnetlink.h> + +#include <log/log.h> +#include <netdutils/Misc.h> +#include <netdutils/NetlinkListener.h> +#include <netdutils/Syscalls.h> + +namespace android { +namespace netdutils { + +using netdutils::Fd; +using netdutils::Slice; +using netdutils::Status; +using netdutils::UniqueFd; +using netdutils::findWithDefault; +using netdutils::forEachNetlinkMessage; +using netdutils::makeSlice; +using netdutils::sSyscalls; +using netdutils::status::ok; +using netdutils::statusFromErrno; + +namespace { + +constexpr int kNetlinkMsgErrorType = (NFNL_SUBSYS_NONE << 8) | NLMSG_ERROR; + +constexpr sockaddr_nl kKernelAddr = { + .nl_family = AF_NETLINK, .nl_pad = 0, .nl_pid = 0, .nl_groups = 0, +}; + +const NetlinkListener::DispatchFn kDefaultDispatchFn = [](const nlmsghdr& nlmsg, const Slice) { + std::stringstream ss; + ss << nlmsg; + ALOGE("unhandled netlink message: %s", ss.str().c_str()); +}; + +} // namespace + +NetlinkListener::NetlinkListener(UniqueFd event, UniqueFd sock, const std::string& name) + : mEvent(std::move(event)), mSock(std::move(sock)), mThreadName(name) { + const auto rxErrorHandler = [](const nlmsghdr& nlmsg, const Slice msg) { + std::stringstream ss; + ss << nlmsg << " " << msg << " " << netdutils::toHex(msg, 32); + ALOGE("unhandled netlink message: %s", ss.str().c_str()); + }; + expectOk(NetlinkListener::subscribe(kNetlinkMsgErrorType, rxErrorHandler)); + + mErrorHandler = [& name = mThreadName](const int fd, const int err) { + ALOGE("Error on NetlinkListener(%s) fd=%d: %s", name.c_str(), fd, strerror(err)); + }; + + // Start the thread + mWorker = std::thread([this]() { run().ignoreError(); }); +} + +NetlinkListener::~NetlinkListener() { + const auto& sys = sSyscalls.get(); + const uint64_t data = 1; + // eventfd should never enter an error state unexpectedly + expectOk(sys.write(mEvent, makeSlice(data)).status()); + mWorker.join(); +} + +Status NetlinkListener::send(const Slice msg) { + const auto& sys = sSyscalls.get(); + ASSIGN_OR_RETURN(auto sent, sys.sendto(mSock, msg, 0, kKernelAddr)); + if (sent != msg.size()) { + return statusFromErrno(EMSGSIZE, "unexpect message size"); + } + return ok; +} + +Status NetlinkListener::subscribe(uint16_t type, const DispatchFn& fn) { + std::lock_guard guard(mMutex); + mDispatchMap[type] = fn; + return ok; +} + +Status NetlinkListener::unsubscribe(uint16_t type) { + std::lock_guard guard(mMutex); + mDispatchMap.erase(type); + return ok; +} + +void NetlinkListener::registerSkErrorHandler(const SkErrorHandler& handler) { + mErrorHandler = handler; +} + +Status NetlinkListener::run() { + std::vector<char> rxbuf(4096); + + const auto rxHandler = [this](const nlmsghdr& nlmsg, const Slice& buf) { + std::lock_guard guard(mMutex); + const auto& fn = findWithDefault(mDispatchMap, nlmsg.nlmsg_type, kDefaultDispatchFn); + fn(nlmsg, buf); + }; + + if (mThreadName.length() > 0) { + int ret = pthread_setname_np(pthread_self(), mThreadName.c_str()); + if (ret) { + ALOGE("thread name set failed, name: %s, ret: %s", mThreadName.c_str(), strerror(ret)); + } + } + const auto& sys = sSyscalls.get(); + const std::array<Fd, 2> fds{{{mEvent}, {mSock}}}; + const int events = POLLIN; + const double timeout = 3600; + while (true) { + ASSIGN_OR_RETURN(auto revents, sys.ppoll(fds, events, timeout)); + // After mEvent becomes readable, we should stop servicing mSock and return + if (revents[0] & POLLIN) { + break; + } + if (revents[1] & (POLLIN|POLLERR)) { + auto rx = sys.recvfrom(mSock, makeSlice(rxbuf), 0); + int err = rx.status().code(); + if (err) { + // Ignore errors. The only error we expect to see here is ENOBUFS, and there's + // nothing we can do about that. The recvfrom above will already have cleared the + // error indication and ensured we won't get EPOLLERR again. + // TODO: Consider using NETLINK_NO_ENOBUFS. + mErrorHandler(((Fd) mSock).get(), err); + continue; + } + forEachNetlinkMessage(rx.value(), rxHandler); + } + } + return ok; +} + +} // namespace netdutils +} // namespace android diff --git a/staticlibs/netd/libnetdutils/Slice.cpp b/staticlibs/netd/libnetdutils/Slice.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7a07d477e7b2b9e3e3610a6dae752d474268d638 --- /dev/null +++ b/staticlibs/netd/libnetdutils/Slice.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2017 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. + */ + +#include <sstream> + +#include "netdutils/Slice.h" + +namespace android { +namespace netdutils { +namespace { + +// Convert one byte to a two character hexadecimal string +const std::string toHex(uint8_t byte) { + const std::array<char, 16> kLookup = { + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}}; + return {kLookup[byte >> 4], kLookup[byte & 0xf]}; +} + +} // namespace + +std::string toString(const Slice s) { + return std::string(reinterpret_cast<char*>(s.base()), s.size()); +} + +std::string toHex(const Slice s, int wrap) { + Slice tail = s; + int count = 0; + std::stringstream ss; + while (!tail.empty()) { + uint8_t byte = 0; + extract(tail, byte); + ss << toHex(byte); + if ((++count % wrap) == 0) { + ss << "\n"; + } + tail = drop(tail, 1); + } + return ss.str(); +} + +std::ostream& operator<<(std::ostream& os, const Slice& slice) { + return os << std::hex << "Slice[base: " << reinterpret_cast<void*>(slice.base()) + << ", limit: " << reinterpret_cast<void*>(slice.limit()) << ", size: 0x" + << slice.size() << "]" << std::dec; +} + +} // namespace netdutils +} // namespace android diff --git a/staticlibs/netd/libnetdutils/SliceTest.cpp b/staticlibs/netd/libnetdutils/SliceTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a4969331bdf798cdf3deba9afebec95d460bec66 --- /dev/null +++ b/staticlibs/netd/libnetdutils/SliceTest.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2017 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. + */ + +#include <array> +#include <cstdint> + +#include <gtest/gtest.h> + +#include "netdutils/Slice.h" +#include "netdutils/Status.h" +#include "netdutils/StatusOr.h" + +namespace android { +namespace netdutils { + +class SliceTest : public testing::Test { + protected: + std::array<char, 256> mRaw = {}; +}; + +TEST_F(SliceTest, smoke) { + Slice s1 = makeSlice(mRaw); + Slice s2 = makeSlice(mRaw); + auto p = split(s1, 14); + s2 = p.first; // avoid warn-unused error + std::stringstream ss; + ss << Slice(); + EXPECT_EQ("Slice[base: 0x0, limit: 0x0, size: 0x0]", ss.str()); + constexpr size_t kBytes = 14; + EXPECT_EQ(s1.base(), take(s1, kBytes).base()); + EXPECT_EQ(kBytes, take(s1, kBytes).size()); + EXPECT_EQ(s1.base() + kBytes, drop(s1, kBytes).base()); + EXPECT_EQ(s1.size() - kBytes, drop(s1, kBytes).size()); + double a = 0; + double b = 0; + int c = 0; + EXPECT_EQ(sizeof(a), extract(s1, a)); + EXPECT_EQ(sizeof(a) + sizeof(b), extract(s1, a, b)); + EXPECT_EQ(sizeof(a) + sizeof(b) + sizeof(c), extract(s1, a, b, c)); +} + +TEST_F(SliceTest, constructor) { + // Expect the following lines to compile + Slice s1 = makeSlice(mRaw); + Slice s2(s1); + Slice s3 = s2; + const Slice s4(s3); + const Slice s5 = s4; + s3 = s5; + Slice s6(mRaw.data(), mRaw.size()); + Slice s7(mRaw.data(), mRaw.data() + mRaw.size()); + struct { + int a; + double b; + float c; + } anon; + makeSlice(anon); + EXPECT_EQ(reinterpret_cast<uint8_t*>(mRaw.data()), s1.base()); + EXPECT_EQ(reinterpret_cast<uint8_t*>(mRaw.data()) + mRaw.size(), s1.limit()); + EXPECT_EQ(mRaw.size(), s1.size()); + EXPECT_FALSE(mRaw.empty()); + EXPECT_TRUE(Slice().empty()); + EXPECT_TRUE(Slice(nullptr, static_cast<size_t>(0)).empty()); + EXPECT_TRUE(Slice(nullptr, nullptr).empty()); +} + +TEST_F(SliceTest, extract) { + struct A { + int a, b; + bool operator==(const A& other) const { return a == other.a && b == other.b; } + }; + struct B { + char str[12]; + bool b; + int i; + bool operator==(const B& other) const { + return b == other.b && i == other.i && 0 == strncmp(str, other.str, 12); + } + }; + + A origA1 = {1, 2}; + A origA2 = {3, 4}; + B origB = {"hello world", true, 1234}; + + // Populate buffer for extracting. + Slice buffer = makeSlice(mRaw); + copy(buffer, makeSlice(origA1)); + copy(drop(buffer, sizeof(origA1)), makeSlice(origB)); + copy(drop(buffer, sizeof(origA1) + sizeof(origB)), makeSlice(origA2)); + + { + // Non-variadic extract + A a1{}; + size_t len = extract(buffer, a1); + EXPECT_EQ(sizeof(A), len); + EXPECT_EQ(origA1, a1); + } + + { + // Variadic extract, 2 destinations + A a1{}; + B b{}; + size_t len = extract(buffer, a1, b); + EXPECT_EQ(sizeof(A) + sizeof(B), len); + EXPECT_EQ(origA1, a1); + EXPECT_EQ(origB, b); + } + + { + // Variadic extract, 3 destinations + A a1{}, a2{}; + B b{}; + size_t len = extract(buffer, a1, b, a2); + EXPECT_EQ(2 * sizeof(A) + sizeof(B), len); + EXPECT_EQ(origA1, a1); + EXPECT_EQ(origB, b); + EXPECT_EQ(origA2, a2); + } +} + +} // namespace netdutils +} // namespace android diff --git a/staticlibs/netd/libnetdutils/Socket.cpp b/staticlibs/netd/libnetdutils/Socket.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e962b6e5d35f5a55e3b7278c7cc69f22d508a3f2 --- /dev/null +++ b/staticlibs/netd/libnetdutils/Socket.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2017 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. + */ + +#include <arpa/inet.h> + +#include "netdutils/Slice.h" +#include "netdutils/Socket.h" + +namespace android { +namespace netdutils { + +StatusOr<std::string> toString(const in6_addr& addr) { + std::array<char, INET6_ADDRSTRLEN> out = {}; + auto* rv = inet_ntop(AF_INET6, &addr, out.data(), out.size()); + if (rv == nullptr) { + return statusFromErrno(errno, "inet_ntop() failed"); + } + return std::string(out.data()); +} + +} // namespace netdutils +} // namespace android diff --git a/staticlibs/netd/libnetdutils/SocketOption.cpp b/staticlibs/netd/libnetdutils/SocketOption.cpp new file mode 100644 index 0000000000000000000000000000000000000000..023df6e8b9407f96afe47755fb0073a57f061f8f --- /dev/null +++ b/staticlibs/netd/libnetdutils/SocketOption.cpp @@ -0,0 +1,54 @@ +/* + * 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. + */ + +#include "netdutils/SocketOption.h" + +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <sys/socket.h> +#include <utility> + +#include "netdutils/Syscalls.h" + +namespace android { +namespace netdutils { + +Status enableSockopt(Fd sock, int level, int optname) { + auto& sys = sSyscalls.get(); + const int on = 1; + return sys.setsockopt(sock, level, optname, &on, sizeof(on)); +} + +Status enableTcpKeepAlives(Fd sock, unsigned idleTime, unsigned numProbes, unsigned probeInterval) { + RETURN_IF_NOT_OK(enableSockopt(sock, SOL_SOCKET, SO_KEEPALIVE)); + + auto& sys = sSyscalls.get(); + if (idleTime != 0) { + RETURN_IF_NOT_OK(sys.setsockopt(sock, SOL_TCP, TCP_KEEPIDLE, &idleTime, sizeof(idleTime))); + } + if (numProbes != 0) { + RETURN_IF_NOT_OK(sys.setsockopt(sock, SOL_TCP, TCP_KEEPCNT, &numProbes, sizeof(numProbes))); + } + if (probeInterval != 0) { + RETURN_IF_NOT_OK(sys.setsockopt(sock, SOL_TCP, TCP_KEEPINTVL, &probeInterval, + sizeof(probeInterval))); + } + + return status::ok; +} + +} // namespace netdutils +} // namespace android diff --git a/staticlibs/netd/libnetdutils/Status.cpp b/staticlibs/netd/libnetdutils/Status.cpp new file mode 100644 index 0000000000000000000000000000000000000000..acd8f11a36d42cbfc1d9d146414a69a32809a3a4 --- /dev/null +++ b/staticlibs/netd/libnetdutils/Status.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2017 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. + */ + +#include "netdutils/Status.h" + +#include <sstream> + +#include "android-base/stringprintf.h" + +namespace android { +namespace netdutils { + +Status statusFromErrno(int err, const std::string& msg) { + return Status(err, base::StringPrintf("[%s] : %s", strerror(err), msg.c_str())); +} + +bool equalToErrno(const Status& status, int err) { + return status.code() == err; +} + +std::string toString(const Status& status) { + std::stringstream ss; + ss << status; + return ss.str(); +} + +std::ostream& operator<<(std::ostream& os, const Status& s) { + return os << "Status[code: " << s.code() << ", msg: \"" << s.msg() << "\"]"; +} + +} // namespace netdutils +} // namespace android diff --git a/staticlibs/netd/libnetdutils/StatusTest.cpp b/staticlibs/netd/libnetdutils/StatusTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4cfc3bbbc3a436eeee0d99f874a81e2ea292e1cd --- /dev/null +++ b/staticlibs/netd/libnetdutils/StatusTest.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2017 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. + */ + +#include "netdutils/Status.h" +#include "netdutils/StatusOr.h" + +#include <sstream> + +#include <gtest/gtest.h> + +namespace android { +namespace netdutils { +namespace { + +TEST(StatusTest, valueSemantics) { + // Default constructor + EXPECT_EQ(status::ok, Status()); + + // Copy constructor + Status status1(1); + Status status2(status1); // NOLINT(performance-unnecessary-copy-initialization) + EXPECT_EQ(1, status2.code()); + + // Copy assignment + Status status3; + status3 = status2; + EXPECT_EQ(1, status3.code()); + + // Same with const objects + const Status status4(4); + const Status status5(status4); // NOLINT(performance-unnecessary-copy-initialization) + Status status6; + status6 = status5; + EXPECT_EQ(4, status6.code()); +} + +TEST(StatusTest, errorMessages) { + Status s(42, "for tea too"); + EXPECT_EQ(42, s.code()); + EXPECT_FALSE(s.ok()); + EXPECT_EQ(s.msg(), "for tea too"); +} + +TEST(StatusOrTest, moveSemantics) { + // Status objects should be cheaply movable. + EXPECT_TRUE(std::is_nothrow_move_constructible<Status>::value); + EXPECT_TRUE(std::is_nothrow_move_assignable<Status>::value); + + // Should move from a temporary Status (twice) + Status s(Status(Status(42, "move me"))); + EXPECT_EQ(42, s.code()); + EXPECT_EQ(s.msg(), "move me"); + + Status s2(666, "EDAEMON"); + EXPECT_NE(s, s2); + s = s2; // Invokes the move-assignment operator. + EXPECT_EQ(666, s.code()); + EXPECT_EQ(s.msg(), "EDAEMON"); + EXPECT_EQ(s, s2); + + // A moved-from Status can be re-used. + s2 = s; + + // Now both objects are valid. + EXPECT_EQ(666, s.code()); + EXPECT_EQ(s.msg(), "EDAEMON"); + EXPECT_EQ(s, s2); +} + +TEST(StatusTest, ignoredStatus) { + statusFromErrno(ENOTTY, "Not a typewriter, what did you expect?").ignoreError(); +} + +TEST(StatusOrTest, ostream) { + { + StatusOr<int> so(11); + std::stringstream ss; + ss << so; + // TODO: Fix StatusOr to optionally output "value:". + EXPECT_EQ("StatusOr[status: Status[code: 0, msg: \"\"]]", ss.str()); + } + { + StatusOr<int> err(status::undefined); + std::stringstream ss; + ss << err; + EXPECT_EQ("StatusOr[status: Status[code: 2147483647, msg: \"undefined\"]]", ss.str()); + } +} + +} // namespace +} // namespace netdutils +} // namespace android diff --git a/staticlibs/netd/libnetdutils/Syscalls.cpp b/staticlibs/netd/libnetdutils/Syscalls.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7e1a2426350ac58a0a1845b9dff38faaa56e963b --- /dev/null +++ b/staticlibs/netd/libnetdutils/Syscalls.cpp @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2017 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. + */ + +#include "netdutils/Syscalls.h" + +#include <atomic> +#include <type_traits> +#include <utility> + +namespace android { +namespace netdutils { +namespace { + +// Retry syscall fn as long as it returns -1 with errno == EINTR +template <typename FnT, typename... Params> +typename std::invoke_result<FnT, Params...>::type syscallRetry(FnT fn, Params&&... params) { + auto rv = fn(std::forward<Params>(params)...); + while ((rv == -1) && (errno == EINTR)) { + rv = fn(std::forward<Params>(params)...); + } + return rv; +} + +} // namespace + +// Production implementation of Syscalls that forwards to libc syscalls. +class RealSyscalls final : public Syscalls { + public: + ~RealSyscalls() override = default; + + StatusOr<UniqueFd> open(const std::string& pathname, int flags, mode_t mode) const override { + UniqueFd fd(::open(pathname.c_str(), flags, mode)); + if (!isWellFormed(fd)) { + return statusFromErrno(errno, "open(\"" + pathname + "\"...) failed"); + } + return fd; + } + + StatusOr<UniqueFd> socket(int domain, int type, int protocol) const override { + UniqueFd sock(::socket(domain, type, protocol)); + if (!isWellFormed(sock)) { + return statusFromErrno(errno, "socket() failed"); + } + return sock; + } + + Status getsockname(Fd sock, sockaddr* addr, socklen_t* addrlen) const override { + auto rv = ::getsockname(sock.get(), addr, addrlen); + if (rv == -1) { + return statusFromErrno(errno, "getsockname() failed"); + } + return status::ok; + } + + Status getsockopt(Fd sock, int level, int optname, void* optval, + socklen_t* optlen) const override { + auto rv = ::getsockopt(sock.get(), level, optname, optval, optlen); + if (rv == -1) { + return statusFromErrno(errno, "getsockopt() failed"); + } + return status::ok; + } + + Status setsockopt(Fd sock, int level, int optname, const void* optval, + socklen_t optlen) const override { + auto rv = ::setsockopt(sock.get(), level, optname, optval, optlen); + if (rv == -1) { + return statusFromErrno(errno, "setsockopt() failed"); + } + return status::ok; + } + + Status bind(Fd sock, const sockaddr* addr, socklen_t addrlen) const override { + auto rv = ::bind(sock.get(), addr, addrlen); + if (rv == -1) { + return statusFromErrno(errno, "bind() failed"); + } + return status::ok; + } + + Status connect(Fd sock, const sockaddr* addr, socklen_t addrlen) const override { + auto rv = syscallRetry(::connect, sock.get(), addr, addrlen); + if (rv == -1) { + return statusFromErrno(errno, "connect() failed"); + } + return status::ok; + } + + StatusOr<ifreq> ioctl(Fd sock, unsigned long request, ifreq* ifr) const override { + auto rv = ::ioctl(sock.get(), request, ifr); + if (rv == -1) { + return statusFromErrno(errno, "ioctl() failed"); + } + return *ifr; + } + + StatusOr<UniqueFd> eventfd(unsigned int initval, int flags) const override { + UniqueFd fd(::eventfd(initval, flags)); + if (!isWellFormed(fd)) { + return statusFromErrno(errno, "eventfd() failed"); + } + return fd; + } + + StatusOr<int> ppoll(pollfd* fds, nfds_t nfds, double timeout) const override { + timespec ts = {}; + ts.tv_sec = timeout; + ts.tv_nsec = (timeout - ts.tv_sec) * 1e9; + auto rv = syscallRetry(::ppoll, fds, nfds, &ts, nullptr); + if (rv == -1) { + return statusFromErrno(errno, "ppoll() failed"); + } + return rv; + } + + StatusOr<size_t> writev(Fd fd, const std::vector<iovec>& iov) const override { + auto rv = syscallRetry(::writev, fd.get(), iov.data(), iov.size()); + if (rv == -1) { + return statusFromErrno(errno, "writev() failed"); + } + return rv; + } + + StatusOr<size_t> write(Fd fd, const Slice buf) const override { + auto rv = syscallRetry(::write, fd.get(), buf.base(), buf.size()); + if (rv == -1) { + return statusFromErrno(errno, "write() failed"); + } + return static_cast<size_t>(rv); + } + + StatusOr<Slice> read(Fd fd, const Slice buf) const override { + auto rv = syscallRetry(::read, fd.get(), buf.base(), buf.size()); + if (rv == -1) { + return statusFromErrno(errno, "read() failed"); + } + return Slice(buf.base(), rv); + } + + StatusOr<size_t> sendto(Fd sock, const Slice buf, int flags, const sockaddr* dst, + socklen_t dstlen) const override { + auto rv = syscallRetry(::sendto, sock.get(), buf.base(), buf.size(), flags, dst, dstlen); + if (rv == -1) { + return statusFromErrno(errno, "sendto() failed"); + } + return static_cast<size_t>(rv); + } + + StatusOr<Slice> recvfrom(Fd sock, const Slice dst, int flags, sockaddr* src, + socklen_t* srclen) const override { + auto rv = syscallRetry(::recvfrom, sock.get(), dst.base(), dst.size(), flags, src, srclen); + if (rv == -1) { + return statusFromErrno(errno, "recvfrom() failed"); + } + if (rv == 0) { + return status::eof; + } + return take(dst, rv); + } + + Status shutdown(Fd fd, int how) const override { + auto rv = ::shutdown(fd.get(), how); + if (rv == -1) { + return statusFromErrno(errno, "shutdown() failed"); + } + return status::ok; + } + + Status close(Fd fd) const override { + auto rv = ::close(fd.get()); + if (rv == -1) { + return statusFromErrno(errno, "close() failed"); + } + return status::ok; + } + + StatusOr<UniqueFile> fopen(const std::string& path, const std::string& mode) const override { + UniqueFile file(::fopen(path.c_str(), mode.c_str())); + if (file == nullptr) { + return statusFromErrno(errno, "fopen(\"" + path + "\", \"" + mode + "\") failed"); + } + return file; + } + + StatusOr<pid_t> fork() const override { + pid_t rv = ::fork(); + if (rv == -1) { + return statusFromErrno(errno, "fork() failed"); + } + return rv; + } + + StatusOr<int> vfprintf(FILE* file, const char* format, va_list ap) const override { + auto rv = ::vfprintf(file, format, ap); + if (rv == -1) { + return statusFromErrno(errno, "vfprintf() failed"); + } + return rv; + } + + StatusOr<int> vfscanf(FILE* file, const char* format, va_list ap) const override { + auto rv = ::vfscanf(file, format, ap); + if (rv == -1) { + return statusFromErrno(errno, "vfscanf() failed"); + } + return rv; + } + + Status fclose(FILE* file) const override { + auto rv = ::fclose(file); + if (rv == -1) { + return statusFromErrno(errno, "fclose() failed"); + } + return status::ok; + } +}; + +SyscallsHolder::~SyscallsHolder() { + delete &get(); +} + +Syscalls& SyscallsHolder::get() { + while (true) { + // memory_order_relaxed gives the compiler and hardware more + // freedom. If we get a stale value (this should only happen + // early in the execution of a program) the exchange code below + // will loop until we get the most current value. + auto* syscalls = mSyscalls.load(std::memory_order_relaxed); + // Common case returns existing syscalls + if (syscalls) { + return *syscalls; + } + + // This code will execute on first get() + std::unique_ptr<Syscalls> tmp(new RealSyscalls()); + Syscalls* expected = nullptr; + bool success = mSyscalls.compare_exchange_strong(expected, tmp.get()); + if (success) { + // Ownership was transferred to mSyscalls already, must release() + return *tmp.release(); + } + } +} + +Syscalls& SyscallsHolder::swap(Syscalls& syscalls) { + return *mSyscalls.exchange(&syscalls); +} + +SyscallsHolder sSyscalls; + +} // namespace netdutils +} // namespace android diff --git a/staticlibs/netd/libnetdutils/SyscallsTest.cpp b/staticlibs/netd/libnetdutils/SyscallsTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..78ffab5e258b663559ff97a49147ba47e5226c6a --- /dev/null +++ b/staticlibs/netd/libnetdutils/SyscallsTest.cpp @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2017 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. + */ + +#include <array> +#include <cstdint> +#include <memory> + +#include <gtest/gtest.h> + +#include "netdutils/Handle.h" +#include "netdutils/Math.h" +#include "netdutils/MockSyscalls.h" +#include "netdutils/Netfilter.h" +#include "netdutils/Netlink.h" +#include "netdutils/Slice.h" +#include "netdutils/Status.h" +#include "netdutils/StatusOr.h" +#include "netdutils/Syscalls.h" + +using testing::_; +using testing::ByMove; +using testing::Invoke; +using testing::Return; +using testing::StrictMock; + +namespace android { +namespace netdutils { + +class SyscallsTest : public testing::Test { + protected: + StrictMock<ScopedMockSyscalls> mSyscalls; +}; + +TEST(syscalls, scopedMock) { + auto& old = sSyscalls.get(); + { + StrictMock<ScopedMockSyscalls> s; + EXPECT_EQ(&s, &sSyscalls.get()); + } + EXPECT_EQ(&old, &sSyscalls.get()); +} + +TEST_F(SyscallsTest, open) { + const char kPath[] = "/test/path/please/ignore"; + constexpr Fd kFd(40); + constexpr int kFlags = 883; + constexpr mode_t kMode = 37373; + const auto& sys = sSyscalls.get(); + EXPECT_CALL(mSyscalls, open(kPath, kFlags, kMode)).WillOnce(Return(ByMove(UniqueFd(kFd)))); + EXPECT_CALL(mSyscalls, close(kFd)).WillOnce(Return(status::ok)); + auto result = sys.open(kPath, kFlags, kMode); + EXPECT_EQ(status::ok, result.status()); + EXPECT_EQ(kFd, result.value()); +} + +TEST_F(SyscallsTest, getsockname) { + constexpr Fd kFd(40); + sockaddr_nl expected = {}; + auto& sys = sSyscalls.get(); + + // Success + EXPECT_CALL(mSyscalls, getsockname(kFd, _, _)) + .WillOnce(Invoke([expected](Fd, sockaddr* addr, socklen_t* addrlen) { + memcpy(addr, &expected, sizeof(expected)); + EXPECT_EQ(*addrlen, static_cast<socklen_t>(sizeof(expected))); + return status::ok; + })); + const auto result = sys.getsockname<sockaddr_nl>(kFd); + EXPECT_TRUE(isOk(result)); + EXPECT_EQ(expected, result.value()); + + // Failure + const Status kError = statusFromErrno(EINVAL, "test"); + EXPECT_CALL(mSyscalls, getsockname(kFd, _, _)).WillOnce(Return(kError)); + EXPECT_EQ(kError, sys.getsockname<sockaddr_nl>(kFd).status()); +} + +TEST_F(SyscallsTest, setsockopt) { + constexpr Fd kFd(40); + constexpr int kLevel = 50; + constexpr int kOptname = 70; + sockaddr_nl expected = {}; + auto& sys = sSyscalls.get(); + + // Success + EXPECT_CALL(mSyscalls, setsockopt(kFd, kLevel, kOptname, &expected, sizeof(expected))) + .WillOnce(Return(status::ok)); + EXPECT_EQ(status::ok, sys.setsockopt(kFd, kLevel, kOptname, expected)); + + // Failure + const Status kError = statusFromErrno(EINVAL, "test"); + EXPECT_CALL(mSyscalls, setsockopt(kFd, kLevel, kOptname, &expected, sizeof(expected))) + .WillOnce(Return(kError)); + EXPECT_EQ(kError, sys.setsockopt(kFd, kLevel, kOptname, expected)); +} + +TEST_F(SyscallsTest, getsockopt) { + constexpr Fd kFd(40); + constexpr int kLevel = 50; + constexpr int kOptname = 70; + sockaddr_nl expected = {}; + socklen_t optLen = 0; + auto& sys = sSyscalls.get(); + + // Success + EXPECT_CALL(mSyscalls, getsockopt(kFd, kLevel, kOptname, &expected, &optLen)) + .WillOnce(Return(status::ok)); + EXPECT_EQ(status::ok, sys.getsockopt(kFd, kLevel, kOptname, &expected, &optLen)); + + // Failure + const Status kError = statusFromErrno(EINVAL, "test"); + EXPECT_CALL(mSyscalls, getsockopt(kFd, kLevel, kOptname, &expected, &optLen)) + .WillOnce(Return(kError)); + EXPECT_EQ(kError, sys.getsockopt(kFd, kLevel, kOptname, &expected, &optLen)); +} + +TEST_F(SyscallsTest, bind) { + constexpr Fd kFd(40); + sockaddr_nl expected = {}; + auto& sys = sSyscalls.get(); + + // Success + EXPECT_CALL(mSyscalls, bind(kFd, asSockaddrPtr(&expected), sizeof(expected))) + .WillOnce(Return(status::ok)); + EXPECT_EQ(status::ok, sys.bind(kFd, expected)); + + // Failure + const Status kError = statusFromErrno(EINVAL, "test"); + EXPECT_CALL(mSyscalls, bind(kFd, asSockaddrPtr(&expected), sizeof(expected))) + .WillOnce(Return(kError)); + EXPECT_EQ(kError, sys.bind(kFd, expected)); +} + +TEST_F(SyscallsTest, connect) { + constexpr Fd kFd(40); + sockaddr_nl expected = {}; + auto& sys = sSyscalls.get(); + + // Success + EXPECT_CALL(mSyscalls, connect(kFd, asSockaddrPtr(&expected), sizeof(expected))) + .WillOnce(Return(status::ok)); + EXPECT_EQ(status::ok, sys.connect(kFd, expected)); + + // Failure + const Status kError = statusFromErrno(EINVAL, "test"); + EXPECT_CALL(mSyscalls, connect(kFd, asSockaddrPtr(&expected), sizeof(expected))) + .WillOnce(Return(kError)); + EXPECT_EQ(kError, sys.connect(kFd, expected)); +} + +TEST_F(SyscallsTest, sendto) { + constexpr Fd kFd(40); + constexpr int kFlags = 0; + std::array<char, 10> payload; + const auto slice = makeSlice(payload); + sockaddr_nl expected = {}; + auto& sys = sSyscalls.get(); + + // Success + EXPECT_CALL(mSyscalls, sendto(kFd, slice, kFlags, asSockaddrPtr(&expected), sizeof(expected))) + .WillOnce(Return(slice.size())); + EXPECT_EQ(status::ok, sys.sendto(kFd, slice, kFlags, expected)); +} + +TEST_F(SyscallsTest, recvfrom) { + constexpr Fd kFd(40); + constexpr int kFlags = 0; + std::array<char, 10> payload; + const auto dst = makeSlice(payload); + const auto used = take(dst, 8); + sockaddr_nl expected = {}; + auto& sys = sSyscalls.get(); + + // Success + EXPECT_CALL(mSyscalls, recvfrom(kFd, dst, kFlags, _, _)) + .WillOnce(Invoke( + [expected, used](Fd, const Slice, int, sockaddr* src, socklen_t* srclen) { + *srclen = sizeof(expected); + memcpy(src, &expected, *srclen); + return used; + })); + auto result = sys.recvfrom<sockaddr_nl>(kFd, dst, kFlags); + EXPECT_EQ(status::ok, result.status()); + EXPECT_EQ(used, result.value().first); + EXPECT_EQ(expected, result.value().second); +} + +} // namespace netdutils +} // namespace android diff --git a/staticlibs/netd/libnetdutils/ThreadUtilTest.cpp b/staticlibs/netd/libnetdutils/ThreadUtilTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8fad8b8e5a3a547c89745b6a6a10d046b4c9e3bd --- /dev/null +++ b/staticlibs/netd/libnetdutils/ThreadUtilTest.cpp @@ -0,0 +1,122 @@ +/* + * 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. + */ + +#include <string> + +#include <android-base/expected.h> +#include <gtest/gtest.h> +#include <netdutils/ThreadUtil.h> + +namespace android::netdutils { + +namespace { + +android::base::expected<std::string, int> getThreadName() { + char name[16] = {}; + if (const int ret = pthread_getname_np(pthread_self(), name, sizeof(name)); ret != 0) { + return android::base::unexpected(ret); + } + return std::string(name); +} + +class NoopRun { + public: + explicit NoopRun(const std::string& name = "") : mName(name) { instanceNum++; } + + // Destructor happens in the thread. + ~NoopRun() { + if (checkName) { + auto expected = getThreadName(); + EXPECT_TRUE(expected.has_value()); + EXPECT_EQ(mExpectedName, expected.value()); + } + instanceNum--; + } + + void run() {} + + std::string threadName() { return mName; } + + // Set the expected thread name which will be used to check if it matches the actual thread + // name which is returned from the system call. The check will happen in the destructor. + void setExpectedName(const std::string& expectedName) { + checkName = true; + mExpectedName = expectedName; + } + + static bool waitForAllReleased(int timeoutMs) { + constexpr int intervalMs = 20; + int limit = timeoutMs / intervalMs; + for (int i = 1; i < limit; i++) { + if (instanceNum == 0) { + return true; + } + usleep(intervalMs * 1000); + } + return false; + } + + // To track how many instances are alive. + static std::atomic<int> instanceNum; + + private: + std::string mName; + std::string mExpectedName; + bool checkName = false; +}; + +std::atomic<int> NoopRun::instanceNum; + +} // namespace + +TEST(ThreadUtilTest, objectReleased) { + NoopRun::instanceNum = 0; + NoopRun* obj = new NoopRun(); + EXPECT_EQ(1, NoopRun::instanceNum); + threadLaunch(obj); + + // Wait for the object released along with the thread exited. + EXPECT_TRUE(NoopRun::waitForAllReleased(1000)); + EXPECT_EQ(0, NoopRun::instanceNum); +} + +TEST(ThreadUtilTest, SetThreadName) { + NoopRun::instanceNum = 0; + + // Test thread name empty. + NoopRun* obj1 = new NoopRun(); + obj1->setExpectedName(""); + + // Test normal case. + NoopRun* obj2 = new NoopRun("TestName"); + obj2->setExpectedName("TestName"); + + // Test thread name too long. + std::string name("TestNameTooooLong"); + NoopRun* obj3 = new NoopRun(name); + obj3->setExpectedName(name.substr(0, 15)); + + // Thread names are examined in their destructors. + EXPECT_EQ(3, NoopRun::instanceNum); + threadLaunch(obj1); + threadLaunch(obj2); + threadLaunch(obj3); + + EXPECT_TRUE(NoopRun::waitForAllReleased(1000)); + EXPECT_EQ(0, NoopRun::instanceNum); +} + +} // namespace android::netdutils diff --git a/staticlibs/netd/libnetdutils/UniqueFd.cpp b/staticlibs/netd/libnetdutils/UniqueFd.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1cb30edde280d8f762eb7cec93275985434fec51 --- /dev/null +++ b/staticlibs/netd/libnetdutils/UniqueFd.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2017 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. + */ + +#include <algorithm> + +#include "netdutils/UniqueFd.h" +#include "netdutils/Syscalls.h" + +namespace android { +namespace netdutils { + +void UniqueFd::reset(Fd fd) { + auto& sys = sSyscalls.get(); + std::swap(fd, mFd); + if (isWellFormed(fd)) { + expectOk(sys.close(fd)); + } +} + +std::ostream& operator<<(std::ostream& os, const UniqueFd& fd) { + return os << "UniqueFd[" << static_cast<Fd>(fd) << "]"; +} + +} // namespace netdutils +} // namespace android diff --git a/staticlibs/netd/libnetdutils/UniqueFile.cpp b/staticlibs/netd/libnetdutils/UniqueFile.cpp new file mode 100644 index 0000000000000000000000000000000000000000..21e877980522fc6c778bb3836204a47a89f4ae70 --- /dev/null +++ b/staticlibs/netd/libnetdutils/UniqueFile.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2017 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. + */ + +#include <algorithm> + +#include "netdutils/Syscalls.h" +#include "netdutils/UniqueFile.h" + +namespace android { +namespace netdutils { + +void UniqueFileDtor::operator()(FILE* file) const { + const auto& sys = sSyscalls.get(); + sys.fclose(file).ignoreError(); +} + +} // namespace netdutils +} // namespace android diff --git a/staticlibs/netd/libnetdutils/Utils.cpp b/staticlibs/netd/libnetdutils/Utils.cpp new file mode 100644 index 0000000000000000000000000000000000000000..16ec8825f6317d41d0220608529650a35815c90b --- /dev/null +++ b/staticlibs/netd/libnetdutils/Utils.cpp @@ -0,0 +1,61 @@ +/* + * 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. + */ + +#include <map> + +#include <net/if.h> + +#include "dirent.h" +#include "netdutils/Status.h" +#include "netdutils/Utils.h" + +namespace android { +namespace netdutils { + +StatusOr<std::vector<std::string>> getIfaceNames() { + std::vector<std::string> ifaceNames; + DIR* d; + struct dirent* de; + + if (!(d = opendir("/sys/class/net"))) { + return statusFromErrno(errno, "Cannot open iface directory"); + } + while ((de = readdir(d))) { + if ((de->d_type != DT_DIR) && (de->d_type != DT_LNK)) continue; + if (de->d_name[0] == '.') continue; + ifaceNames.push_back(std::string(de->d_name)); + } + closedir(d); + return ifaceNames; +} + +StatusOr<std::map<std::string, uint32_t>> getIfaceList() { + std::map<std::string, uint32_t> ifacePairs; + + ASSIGN_OR_RETURN(auto ifaceNames, getIfaceNames()); + + for (const auto& name : ifaceNames) { + uint32_t ifaceIndex = if_nametoindex(name.c_str()); + if (ifaceIndex) { + ifacePairs.insert(std::pair<std::string, uint32_t>(name, ifaceIndex)); + } + } + return ifacePairs; +} + +} // namespace netdutils +} // namespace android diff --git a/staticlibs/netd/libnetdutils/include/netdutils/BackoffSequence.h b/staticlibs/netd/libnetdutils/include/netdutils/BackoffSequence.h new file mode 100644 index 0000000000000000000000000000000000000000..a52e72d6ddf60352d4e1ddc928204f7f919cb55c --- /dev/null +++ b/staticlibs/netd/libnetdutils/include/netdutils/BackoffSequence.h @@ -0,0 +1,153 @@ +/* + * 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. + */ + +#ifndef NETDUTILS_BACKOFFSEQUENCE_H +#define NETDUTILS_BACKOFFSEQUENCE_H + +#include <stdint.h> +#include <algorithm> +#include <chrono> +#include <limits> + +namespace android { +namespace netdutils { + +// Encapsulate some RFC 3315 section 14 -style backoff mechanics. +// +// https://tools.ietf.org/html/rfc3315#section-14 +template<typename time_type = std::chrono::seconds, typename counter_type = uint32_t> +class BackoffSequence { + public: + struct Parameters { + time_type initialRetransTime{TIME_UNITY}; + counter_type maxRetransCount{0U}; + time_type maxRetransTime{TIME_ZERO}; + time_type maxRetransDuration{TIME_ZERO}; + time_type endOfSequenceIndicator{TIME_ZERO}; + }; + + BackoffSequence() : BackoffSequence(Parameters{}) {} + BackoffSequence(const BackoffSequence &) = default; + BackoffSequence(BackoffSequence &&) = default; + BackoffSequence& operator=(const BackoffSequence &) = default; + BackoffSequence& operator=(BackoffSequence &&) = default; + + bool hasNextTimeout() const noexcept { + return !maxRetransCountExceed() && !maxRetransDurationExceeded(); + } + + // Returns 0 when the sequence is exhausted. + time_type getNextTimeout() { + if (!hasNextTimeout()) return getEndOfSequenceIndicator(); + + mRetransTime = getNextTimeoutAfter(mRetransTime); + + mRetransCount++; + mTotalRetransDuration += mRetransTime; + return mRetransTime; + } + + time_type getEndOfSequenceIndicator() const noexcept { + return mParams.endOfSequenceIndicator; + } + + class Builder { + public: + Builder() {} + + constexpr Builder& withInitialRetransmissionTime(time_type irt) { + mParams.initialRetransTime = irt; + return *this; + } + constexpr Builder& withMaximumRetransmissionCount(counter_type mrc) { + mParams.maxRetransCount = mrc; + return *this; + } + constexpr Builder& withMaximumRetransmissionTime(time_type mrt) { + mParams.maxRetransTime = mrt; + return *this; + } + constexpr Builder& withMaximumRetransmissionDuration(time_type mrd) { + mParams.maxRetransDuration = mrd; + return *this; + } + constexpr Builder& withEndOfSequenceIndicator(time_type eos) { + mParams.endOfSequenceIndicator = eos; + return *this; + } + + constexpr BackoffSequence build() const { + return BackoffSequence(mParams); + } + + private: + Parameters mParams; + }; + + private: + static constexpr int PER_ITERATION_SCALING_FACTOR = 2; + static constexpr time_type TIME_ZERO = time_type(); + static constexpr time_type TIME_UNITY = time_type(1); + + constexpr BackoffSequence(const struct Parameters ¶ms) + : mParams(params), + mRetransCount(0), + mRetransTime(TIME_ZERO), + mTotalRetransDuration(TIME_ZERO) {} + + constexpr bool maxRetransCountExceed() const { + return (mParams.maxRetransCount > 0) && (mRetransCount >= mParams.maxRetransCount); + } + + constexpr bool maxRetransDurationExceeded() const { + return (mParams.maxRetransDuration > TIME_ZERO) && + (mTotalRetransDuration >= mParams.maxRetransDuration); + } + + time_type getNextTimeoutAfter(time_type lastTimeout) const { + // TODO: Support proper random jitter. Also, consider supporting some + // per-iteration scaling factor other than doubling. + time_type nextTimeout = (lastTimeout > TIME_ZERO) + ? PER_ITERATION_SCALING_FACTOR * lastTimeout + : mParams.initialRetransTime; + + // Check if overflow occurred. + if (nextTimeout < lastTimeout) { + nextTimeout = std::numeric_limits<time_type>::max(); + } + + // Cap to maximum allowed, if necessary. + if (mParams.maxRetransTime > TIME_ZERO) { + nextTimeout = std::min(nextTimeout, mParams.maxRetransTime); + } + + // Don't overflow the maximum total duration. + if (mParams.maxRetransDuration > TIME_ZERO) { + nextTimeout = std::min(nextTimeout, mParams.maxRetransDuration - lastTimeout); + } + return nextTimeout; + } + + const Parameters mParams; + counter_type mRetransCount; + time_type mRetransTime; + time_type mTotalRetransDuration; +}; + +} // namespace netdutils +} // namespace android + +#endif /* NETDUTILS_BACKOFFSEQUENCE_H */ diff --git a/staticlibs/netd/libnetdutils/include/netdutils/DumpWriter.h b/staticlibs/netd/libnetdutils/include/netdutils/DumpWriter.h new file mode 100644 index 0000000000000000000000000000000000000000..a50b5e60a3c4110690ebb6321bf4cd815310e8ea --- /dev/null +++ b/staticlibs/netd/libnetdutils/include/netdutils/DumpWriter.h @@ -0,0 +1,68 @@ +/* + * 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. + */ + +#ifndef NETDUTILS_DUMPWRITER_H_ +#define NETDUTILS_DUMPWRITER_H_ + +#include <string> + +namespace android { +namespace netdutils { + +class DumpWriter { + public: + DumpWriter(int fd); + + void incIndent(); + void decIndent(); + + void println(const std::string& line); + template <size_t n> + void println(const char line[n]) { + println(std::string(line)); + } + // Hint to the compiler that it should apply printf validation of + // arguments (beginning at position 3) of the format (specified in + // position 2). Note that position 1 is the implicit "this" argument. + void println(const char* fmt, ...) __attribute__((__format__(__printf__, 2, 3))); + void blankline() { println(""); } + + private: + uint8_t mIndentLevel; + int mFd; +}; + +class ScopedIndent { + public: + ScopedIndent() = delete; + ScopedIndent(const ScopedIndent&) = delete; + ScopedIndent(ScopedIndent&&) = delete; + explicit ScopedIndent(DumpWriter& dw) : mDw(dw) { mDw.incIndent(); } + ~ScopedIndent() { mDw.decIndent(); } + ScopedIndent& operator=(const ScopedIndent&) = delete; + ScopedIndent& operator=(ScopedIndent&&) = delete; + + // TODO: consider additional {inc,dec}Indent methods and a counter that + // can be used to unwind all pending increments on exit. + + private: + DumpWriter& mDw; +}; + +} // namespace netdutils +} // namespace android + +#endif // NETDUTILS_DUMPWRITER_H_ diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Fd.h b/staticlibs/netd/libnetdutils/include/netdutils/Fd.h new file mode 100644 index 0000000000000000000000000000000000000000..7db40870f15e82046c9cced26199b252b678f8b0 --- /dev/null +++ b/staticlibs/netd/libnetdutils/include/netdutils/Fd.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef NETUTILS_FD_H +#define NETUTILS_FD_H + +#include <ostream> + +#include "netdutils/Status.h" + +namespace android { +namespace netdutils { + +// Strongly typed wrapper for file descriptors with value semantics. +// This class should typically hold unowned file descriptors. +class Fd { + public: + constexpr Fd() = default; + + constexpr Fd(int fd) : mFd(fd) {} + + int get() const { return mFd; } + + bool operator==(const Fd& other) const { return get() == other.get(); } + bool operator!=(const Fd& other) const { return get() != other.get(); } + + private: + int mFd = -1; +}; + +// Return true if fd appears valid (non-negative) +inline bool isWellFormed(const Fd fd) { + return fd.get() >= 0; +} + +std::ostream& operator<<(std::ostream& os, const Fd& fd); + +} // namespace netdutils +} // namespace android + +#endif /* NETUTILS_FD_H */ diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Handle.h b/staticlibs/netd/libnetdutils/include/netdutils/Handle.h new file mode 100644 index 0000000000000000000000000000000000000000..82083d414e3224a1aff03c318416d1968819726b --- /dev/null +++ b/staticlibs/netd/libnetdutils/include/netdutils/Handle.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef NETUTILS_HANDLE_H +#define NETUTILS_HANDLE_H + +#include <ostream> + +namespace android { +namespace netdutils { + +// Opaque, strongly typed wrapper for integer-like handles. +// Explicitly avoids implementing arithmetic operations. +// +// This class is intended to avoid common errors when reordering +// arguments to functions, typos and other cases where plain integer +// types would silently cover up the mistake. +// +// usage: +// DEFINE_HANDLE(ProductId, uint64_t); +// DEFINE_HANDLE(ThumbnailHash, uint64_t); +// void foo(ProductId p, ThumbnailHash th) {...} +// +// void test() { +// ProductId p(88); +// ThumbnailHash th1(100), th2(200); +// +// foo(p, th1); <- ok! +// foo(th1, p); <- disallowed! +// th1 += 10; <- disallowed! +// p = th2; <- disallowed! +// assert(th1 != th2); <- ok! +// } +template <typename T, typename TagT> +class Handle { + public: + constexpr Handle() = default; + constexpr Handle(const T& value) : mValue(value) {} + + const T get() const { return mValue; } + + bool operator==(const Handle& that) const { return get() == that.get(); } + bool operator!=(const Handle& that) const { return get() != that.get(); } + + private: + T mValue; +}; + +#define DEFINE_HANDLE(name, type) \ + struct _##name##Tag {}; \ + using name = ::android::netdutils::Handle<type, _##name##Tag>; + +template <typename T, typename TagT> +inline std::ostream& operator<<(std::ostream& os, const Handle<T, TagT>& handle) { + return os << handle.get(); +} + +} // namespace netdutils +} // namespace android + +#endif /* NETUTILS_HANDLE_H */ diff --git a/staticlibs/netd/libnetdutils/include/netdutils/InternetAddresses.h b/staticlibs/netd/libnetdutils/include/netdutils/InternetAddresses.h new file mode 100644 index 0000000000000000000000000000000000000000..d10cec7d5d2314f89cb92018291fb32160cd37a2 --- /dev/null +++ b/staticlibs/netd/libnetdutils/include/netdutils/InternetAddresses.h @@ -0,0 +1,331 @@ +/* + * 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. + */ + +#pragma once + +#include <netdb.h> +#include <netinet/in.h> +#include <stdint.h> +#include <cstring> +#include <limits> +#include <string> + +#include "netdutils/NetworkConstants.h" + +namespace android { +namespace netdutils { + +namespace internal_ { + +// A structure to hold data for dealing with Internet addresses (IPAddress) and +// related types such as IPSockAddr and IPPrefix. +struct compact_ipdata { + uint8_t family{AF_UNSPEC}; + uint8_t cidrlen{0U}; // written and read in host-byte order + in_port_t port{0U}; // written and read in host-byte order + uint32_t scope_id{0U}; + union { + in_addr v4; + in6_addr v6; + } ip{.v6 = IN6ADDR_ANY_INIT}; // written and read in network-byte order + + // Classes that use compact_ipdata and this method should be sure to clear + // (i.e. zero or make uniform) any fields not relevant to the class. + friend bool operator==(const compact_ipdata& a, const compact_ipdata& b) { + if ((a.family != b.family) || (a.cidrlen != b.cidrlen) || (a.port != b.port) || + (a.scope_id != b.scope_id)) { + return false; + } + switch (a.family) { + case AF_UNSPEC: + // After the above checks, two AF_UNSPEC objects can be + // considered equal, for convenience. + return true; + case AF_INET: { + const in_addr v4a = a.ip.v4; + const in_addr v4b = b.ip.v4; + return (v4a.s_addr == v4b.s_addr); + } + case AF_INET6: { + const in6_addr v6a = a.ip.v6; + const in6_addr v6b = b.ip.v6; + return IN6_ARE_ADDR_EQUAL(&v6a, &v6b); + } + } + return false; + } + + // Classes that use compact_ipdata and this method should be sure to clear + // (i.e. zero or make uniform) any fields not relevant to the class. + friend bool operator!=(const compact_ipdata& a, const compact_ipdata& b) { return !(a == b); } + + // Classes that use compact_ipdata and this method should be sure to clear + // (i.e. zero or make uniform) any fields not relevant to the class. + friend bool operator<(const compact_ipdata& a, const compact_ipdata& b) { + if (a.family != b.family) return (a.family < b.family); + switch (a.family) { + case AF_INET: { + const in_addr v4a = a.ip.v4; + const in_addr v4b = b.ip.v4; + if (v4a.s_addr != v4b.s_addr) return (ntohl(v4a.s_addr) < ntohl(v4b.s_addr)); + break; + } + case AF_INET6: { + const in6_addr v6a = a.ip.v6; + const in6_addr v6b = b.ip.v6; + const int cmp = std::memcmp(v6a.s6_addr, v6b.s6_addr, IPV6_ADDR_LEN); + if (cmp != 0) return cmp < 0; + break; + } + } + if (a.cidrlen != b.cidrlen) return (a.cidrlen < b.cidrlen); + if (a.port != b.port) return (a.port < b.port); + return (a.scope_id < b.scope_id); + } +}; + +static_assert(AF_UNSPEC <= std::numeric_limits<uint8_t>::max(), "AF_UNSPEC value too large"); +static_assert(AF_INET <= std::numeric_limits<uint8_t>::max(), "AF_INET value too large"); +static_assert(AF_INET6 <= std::numeric_limits<uint8_t>::max(), "AF_INET6 value too large"); +static_assert(sizeof(compact_ipdata) == 24U, "compact_ipdata unexpectedly large"); + +} // namespace internal_ + +struct AddrinfoDeleter { + void operator()(struct addrinfo* p) const { + if (p != nullptr) { + freeaddrinfo(p); + } + } +}; + +typedef std::unique_ptr<struct addrinfo, struct AddrinfoDeleter> ScopedAddrinfo; + +inline bool usesScopedIds(const in6_addr& ipv6) { + return (IN6_IS_ADDR_LINKLOCAL(&ipv6) || IN6_IS_ADDR_MC_LINKLOCAL(&ipv6)); +} + +class IPPrefix; +class IPSockAddr; + +class IPAddress { + public: + static bool forString(const std::string& repr, IPAddress* ip); + static IPAddress forString(const std::string& repr) { + IPAddress ip; + if (!forString(repr, &ip)) return IPAddress(); + return ip; + } + + IPAddress() = default; + IPAddress(const IPAddress&) = default; + IPAddress(IPAddress&&) = default; + + explicit IPAddress(const in_addr& ipv4) + : mData({AF_INET, IPV4_ADDR_BITS, 0U, 0U, {.v4 = ipv4}}) {} + explicit IPAddress(const in6_addr& ipv6) + : mData({AF_INET6, IPV6_ADDR_BITS, 0U, 0U, {.v6 = ipv6}}) {} + IPAddress(const in6_addr& ipv6, uint32_t scope_id) + : mData({AF_INET6, + IPV6_ADDR_BITS, + 0U, + // Sanity check: scoped_ids only for link-local addresses. + usesScopedIds(ipv6) ? scope_id : 0U, + {.v6 = ipv6}}) {} + IPAddress(const IPAddress& ip, uint32_t scope_id) : IPAddress(ip) { + mData.scope_id = (family() == AF_INET6 && usesScopedIds(mData.ip.v6)) ? scope_id : 0U; + } + + IPAddress& operator=(const IPAddress&) = default; + IPAddress& operator=(IPAddress&&) = default; + + constexpr sa_family_t family() const noexcept { return mData.family; } + constexpr uint32_t scope_id() const noexcept { return mData.scope_id; } + + std::string toString() const noexcept; + + friend std::ostream& operator<<(std::ostream& os, const IPAddress& ip) { + os << ip.toString(); + return os; + } + friend bool operator==(const IPAddress& a, const IPAddress& b) { return (a.mData == b.mData); } + friend bool operator!=(const IPAddress& a, const IPAddress& b) { return (a.mData != b.mData); } + friend bool operator<(const IPAddress& a, const IPAddress& b) { return (a.mData < b.mData); } + friend bool operator>(const IPAddress& a, const IPAddress& b) { return (b.mData < a.mData); } + friend bool operator<=(const IPAddress& a, const IPAddress& b) { return (a < b) || (a == b); } + friend bool operator>=(const IPAddress& a, const IPAddress& b) { return (b < a) || (a == b); } + + private: + friend class IPPrefix; + friend class IPSockAddr; + + explicit IPAddress(const internal_::compact_ipdata& ipdata) : mData(ipdata) { + mData.port = 0U; + switch (mData.family) { + case AF_INET: + mData.cidrlen = IPV4_ADDR_BITS; + mData.scope_id = 0U; + break; + case AF_INET6: + mData.cidrlen = IPV6_ADDR_BITS; + if (usesScopedIds(ipdata.ip.v6)) mData.scope_id = ipdata.scope_id; + break; + default: + mData.cidrlen = 0U; + mData.scope_id = 0U; + break; + } + } + + internal_::compact_ipdata mData{}; +}; + +class IPPrefix { + public: + static bool forString(const std::string& repr, IPPrefix* prefix); + static IPPrefix forString(const std::string& repr) { + IPPrefix prefix; + if (!forString(repr, &prefix)) return IPPrefix(); + return prefix; + } + + IPPrefix() = default; + IPPrefix(const IPPrefix&) = default; + IPPrefix(IPPrefix&&) = default; + + explicit IPPrefix(const IPAddress& ip) : mData(ip.mData) {} + + // Truncate the IP address |ip| at length |length|. Lengths greater than + // the address-family-relevant maximum, along with negative values, are + // interpreted as if the address-family-relevant maximum had been given. + IPPrefix(const IPAddress& ip, int length); + + IPPrefix& operator=(const IPPrefix&) = default; + IPPrefix& operator=(IPPrefix&&) = default; + + constexpr sa_family_t family() const noexcept { return mData.family; } + IPAddress ip() const noexcept { return IPAddress(mData); } + in_addr addr4() const noexcept { return mData.ip.v4; } + in6_addr addr6() const noexcept { return mData.ip.v6; } + constexpr int length() const noexcept { return mData.cidrlen; } + bool contains(const IPPrefix& other) { + return length() <= other.length() && IPPrefix(other.ip(), length()).ip() == ip(); + } + bool contains(const IPAddress& other) { + return IPPrefix(other, length()).ip() == ip(); + } + + bool isUninitialized() const noexcept; + std::string toString() const noexcept; + + friend std::ostream& operator<<(std::ostream& os, const IPPrefix& prefix) { + os << prefix.toString(); + return os; + } + friend bool operator==(const IPPrefix& a, const IPPrefix& b) { return (a.mData == b.mData); } + friend bool operator!=(const IPPrefix& a, const IPPrefix& b) { return (a.mData != b.mData); } + friend bool operator<(const IPPrefix& a, const IPPrefix& b) { return (a.mData < b.mData); } + friend bool operator>(const IPPrefix& a, const IPPrefix& b) { return (b.mData < a.mData); } + friend bool operator<=(const IPPrefix& a, const IPPrefix& b) { return (a < b) || (a == b); } + friend bool operator>=(const IPPrefix& a, const IPPrefix& b) { return (b < a) || (a == b); } + + private: + internal_::compact_ipdata mData{}; +}; + +// An Internet socket address. +// +// Cannot represent other types of socket addresses (e.g. UNIX socket address, et cetera). +class IPSockAddr { + public: + // TODO: static forString + + static IPSockAddr toIPSockAddr(const std::string& repr, in_port_t port) { + return IPSockAddr(IPAddress::forString(repr), port); + } + static IPSockAddr toIPSockAddr(const sockaddr& sa) { + switch (sa.sa_family) { + case AF_INET: + return IPSockAddr(*reinterpret_cast<const sockaddr_in*>(&sa)); + case AF_INET6: + return IPSockAddr(*reinterpret_cast<const sockaddr_in6*>(&sa)); + default: + return IPSockAddr(); + } + } + static IPSockAddr toIPSockAddr(const sockaddr_storage& ss) { + return toIPSockAddr(*reinterpret_cast<const sockaddr*>(&ss)); + } + + IPSockAddr() = default; + IPSockAddr(const IPSockAddr&) = default; + IPSockAddr(IPSockAddr&&) = default; + + explicit IPSockAddr(const IPAddress& ip) : mData(ip.mData) {} + IPSockAddr(const IPAddress& ip, in_port_t port) : mData(ip.mData) { mData.port = port; } + explicit IPSockAddr(const sockaddr_in& ipv4sa) + : IPSockAddr(IPAddress(ipv4sa.sin_addr), ntohs(ipv4sa.sin_port)) {} + explicit IPSockAddr(const sockaddr_in6& ipv6sa) + : IPSockAddr(IPAddress(ipv6sa.sin6_addr, ipv6sa.sin6_scope_id), ntohs(ipv6sa.sin6_port)) {} + + IPSockAddr& operator=(const IPSockAddr&) = default; + IPSockAddr& operator=(IPSockAddr&&) = default; + + constexpr sa_family_t family() const noexcept { return mData.family; } + IPAddress ip() const noexcept { return IPAddress(mData); } + constexpr in_port_t port() const noexcept { return mData.port; } + + // Implicit conversion to sockaddr_storage. + operator sockaddr_storage() const noexcept { + sockaddr_storage ss; + ss.ss_family = mData.family; + switch (mData.family) { + case AF_INET: + reinterpret_cast<sockaddr_in*>(&ss)->sin_addr = mData.ip.v4; + reinterpret_cast<sockaddr_in*>(&ss)->sin_port = htons(mData.port); + break; + case AF_INET6: + reinterpret_cast<sockaddr_in6*>(&ss)->sin6_addr = mData.ip.v6; + reinterpret_cast<sockaddr_in6*>(&ss)->sin6_port = htons(mData.port); + reinterpret_cast<sockaddr_in6*>(&ss)->sin6_scope_id = mData.scope_id; + break; + } + return ss; + } + + std::string toString() const noexcept; + + friend std::ostream& operator<<(std::ostream& os, const IPSockAddr& prefix) { + os << prefix.toString(); + return os; + } + friend bool operator==(const IPSockAddr& a, const IPSockAddr& b) { + return (a.mData == b.mData); + } + friend bool operator!=(const IPSockAddr& a, const IPSockAddr& b) { + return (a.mData != b.mData); + } + friend bool operator<(const IPSockAddr& a, const IPSockAddr& b) { return (a.mData < b.mData); } + friend bool operator>(const IPSockAddr& a, const IPSockAddr& b) { return (b.mData < a.mData); } + friend bool operator<=(const IPSockAddr& a, const IPSockAddr& b) { return (a < b) || (a == b); } + friend bool operator>=(const IPSockAddr& a, const IPSockAddr& b) { return (b < a) || (a == b); } + + private: + internal_::compact_ipdata mData{}; +}; + +} // namespace netdutils +} // namespace android diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Log.h b/staticlibs/netd/libnetdutils/include/netdutils/Log.h new file mode 100644 index 0000000000000000000000000000000000000000..77ae6494244d46f5f0938de5fe64792aed0d7d54 --- /dev/null +++ b/staticlibs/netd/libnetdutils/include/netdutils/Log.h @@ -0,0 +1,211 @@ +/* + * 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. + */ + +#ifndef NETUTILS_LOG_H +#define NETUTILS_LOG_H + +#include <chrono> +#include <deque> +#include <shared_mutex> +#include <string> +#include <type_traits> +#include <vector> + +#include <android-base/stringprintf.h> +#include <android-base/thread_annotations.h> + +#include <netdutils/Status.h> + +namespace android { +namespace netdutils { + +class LogEntry { + public: + LogEntry() = default; + LogEntry(const LogEntry&) = default; + LogEntry(LogEntry&&) = default; + ~LogEntry() = default; + LogEntry& operator=(const LogEntry&) = default; + LogEntry& operator=(LogEntry&&) = default; + + std::string toString() const; + + /// + // Helper methods that make it easy to build up a LogEntry message. + // If performance becomes a factor the implementations could be inlined. + /// + LogEntry& message(const std::string& message); + + // For calling with __FUNCTION__. + LogEntry& function(const std::string& function_name); + // For calling with __PRETTY_FUNCTION__. + LogEntry& prettyFunction(const std::string& pretty_function); + + // Convenience methods for each of the common types of function arguments. + LogEntry& arg(const std::string& val); + // Intended for binary buffers, formats as hex + LogEntry& arg(const std::vector<uint8_t>& val); + LogEntry& arg(const std::vector<int32_t>& val); + LogEntry& arg(const std::vector<std::string>& val); + template <typename IntT, typename = std::enable_if_t<std::is_arithmetic_v<IntT>>> + LogEntry& arg(IntT val) { + mArgs.push_back(std::to_string(val)); + return *this; + } + // Not using a plain overload here to avoid the implicit conversion from + // any pointer to bool, which causes string literals to print as 'true'. + template <> + LogEntry& arg<>(bool val); + + template <typename... Args> + LogEntry& args(const Args&... a) { + // Cleverness ahead: we throw away the initializer_list filled with + // zeroes, all we care about is calling arg() for each argument. + (void) std::initializer_list<int>{(arg(a), 0)...}; + return *this; + } + + // Some things can return more than one value, or have multiple output + // parameters, so each of these adds to the mReturns vector. + LogEntry& returns(const std::string& rval); + LogEntry& returns(const Status& status); + LogEntry& returns(bool rval); + template <class T> + LogEntry& returns(T val) { + mReturns.push_back(std::to_string(val)); + return *this; + } + + LogEntry& withUid(uid_t uid); + + // Append the duration computed since the creation of this instance. + LogEntry& withAutomaticDuration(); + // Append the string-ified duration computed by some other means. + LogEntry& withDuration(const std::string& duration); + + private: + std::chrono::steady_clock::time_point mStart = std::chrono::steady_clock::now(); + std::string mMsg{}; + std::string mFunc{}; + std::vector<std::string> mArgs{}; + std::vector<std::string> mReturns{}; + std::string mUid{}; + std::string mDuration{}; +}; + +class Log { + public: + Log() = delete; + Log(const std::string& tag) : Log(tag, MAX_ENTRIES) {} + Log(const std::string& tag, size_t maxEntries) : mTag(tag), mMaxEntries(maxEntries) {} + Log(const Log&) = delete; + Log(Log&&) = delete; + ~Log(); + Log& operator=(const Log&) = delete; + Log& operator=(Log&&) = delete; + + LogEntry newEntry() const { return LogEntry(); } + + // Record a log entry in internal storage only. + void log(const std::string& entry) { record(Level::LOG, entry); } + template <size_t n> + void log(const char entry[n]) { log(std::string(entry)); } + void log(const LogEntry& entry) { log(entry.toString()); } + void log(const char* fmt, ...) __attribute__((__format__(__printf__, 2, 3))) { + using ::android::base::StringAppendV; + std::string result; + va_list ap; + va_start(ap, fmt); + StringAppendV(&result, fmt, ap); + va_end(ap); + log(result); + } + + // Record a log entry in internal storage and to ALOGI as well. + void info(const std::string& entry) { record(Level::INFO, entry); } + template <size_t n> + void info(const char entry[n]) { info(std::string(entry)); } + void info(const LogEntry& entry) { info(entry.toString()); } + void info(const char* fmt, ...) __attribute__((__format__(__printf__, 2, 3))) { + using ::android::base::StringAppendV; + std::string result; + va_list ap; + va_start(ap, fmt); + StringAppendV(&result, fmt, ap); + va_end(ap); + info(result); + } + + // Record a log entry in internal storage and to ALOGW as well. + void warn(const std::string& entry) { record(Level::WARN, entry); } + template <size_t n> + void warn(const char entry[n]) { warn(std::string(entry)); } + void warn(const LogEntry& entry) { warn(entry.toString()); } + void warn(const char* fmt, ...) __attribute__((__format__(__printf__, 2, 3))) { + using ::android::base::StringAppendV; + std::string result; + va_list ap; + va_start(ap, fmt); + StringAppendV(&result, fmt, ap); + va_end(ap); + warn(result); + } + + // Record a log entry in internal storage and to ALOGE as well. + void error(const std::string& entry) { record(Level::ERROR, entry); } + template <size_t n> + void error(const char entry[n]) { error(std::string(entry)); } + void error(const LogEntry& entry) { error(entry.toString()); } + void error(const char* fmt, ...) __attribute__((__format__(__printf__, 2, 3))) { + using ::android::base::StringAppendV; + std::string result; + va_list ap; + va_start(ap, fmt); + StringAppendV(&result, fmt, ap); + va_end(ap); + error(result); + } + + // Iterates over every entry in the log in chronological order. Operates + // on a copy of the log entries, and so perEntryFn may itself call one of + // the logging functions if needed. + void forEachEntry(const std::function<void(const std::string&)>& perEntryFn) const; + + private: + static constexpr const size_t MAX_ENTRIES = 750U; + const std::string mTag; + const size_t mMaxEntries; + + // The LOG level adds an entry to mEntries but does not output the message + // to the system log. All other levels append to mEntries and output to the + // the system log. + enum class Level { + LOG, + INFO, + WARN, + ERROR, + }; + + void record(Level lvl, const std::string& entry); + + mutable std::shared_mutex mLock; + std::deque<const std::string> mEntries; // GUARDED_BY(mLock), when supported +}; + +} // namespace netdutils +} // namespace android + +#endif /* NETUTILS_LOG_H */ diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Math.h b/staticlibs/netd/libnetdutils/include/netdutils/Math.h new file mode 100644 index 0000000000000000000000000000000000000000..c41fbf588221f1da99800094fc5e4b50ff2ae3bd --- /dev/null +++ b/staticlibs/netd/libnetdutils/include/netdutils/Math.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef NETUTILS_MATH_H +#define NETUTILS_MATH_H + +#include <algorithm> +#include <cstdint> + +namespace android { +namespace netdutils { + +template <class T> +inline constexpr const T mask(const int shift) { + return (1 << shift) - 1; +} + +// Align x up to the nearest integer multiple of 2^shift +template <class T> +inline constexpr const T align(const T& x, const int shift) { + return (x + mask<T>(shift)) & ~mask<T>(shift); +} + +} // namespace netdutils +} // namespace android + +#endif /* NETUTILS_MATH_H */ diff --git a/staticlibs/netd/libnetdutils/include/netdutils/MemBlock.h b/staticlibs/netd/libnetdutils/include/netdutils/MemBlock.h new file mode 100644 index 0000000000000000000000000000000000000000..fd4d612d38bd35aa5d0b72314ea3ea94464a61c4 --- /dev/null +++ b/staticlibs/netd/libnetdutils/include/netdutils/MemBlock.h @@ -0,0 +1,67 @@ +/* + * 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. + */ + +#ifndef NETUTILS_MEMBLOCK_H +#define NETUTILS_MEMBLOCK_H + +#include <memory> +#include "netdutils/Slice.h" + +namespace android { +namespace netdutils { + +// A class to encapsulate self-deleting byte arrays while preserving access +// to the underlying length (without the length being part of the type, e.g. +// std::array<>). By design, the only interface to the underlying bytes is +// via Slice, to encourage safer memory access usage. +// +// No thread-safety guarantees whatsoever. +class MemBlock { + public: + MemBlock() : MemBlock(0U) {} + explicit MemBlock(size_t len) + : mData((len > 0U) ? new uint8_t[len]{} : nullptr), + mLen(len) {} + // Allocate memory of size src.size() and copy src into this MemBlock. + explicit MemBlock(Slice src) : MemBlock(src.size()) { + copy(get(), src); + } + + // No copy construction or assignment. + MemBlock(const MemBlock&) = delete; + MemBlock& operator=(const MemBlock&) = delete; + + // Move construction and assignment are okay. + MemBlock(MemBlock&&) = default; + MemBlock& operator=(MemBlock&&) = default; + + // Even though this method is const, the memory wrapped by the + // returned Slice is mutable. + Slice get() const noexcept { return Slice(mData.get(), mLen); } + + // Implicit cast to Slice. + // NOLINTNEXTLINE(google-explicit-constructor) + operator const Slice() const noexcept { return get(); } + + private: + std::unique_ptr<uint8_t[]> mData; + size_t mLen; +}; + +} // namespace netdutils +} // namespace android + +#endif /* NETUTILS_MEMBLOCK_H */ diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Misc.h b/staticlibs/netd/libnetdutils/include/netdutils/Misc.h new file mode 100644 index 0000000000000000000000000000000000000000..d344f81fc3997e26fc9a508fb9abf5d7433fb937 --- /dev/null +++ b/staticlibs/netd/libnetdutils/include/netdutils/Misc.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef NETUTILS_MISC_H +#define NETUTILS_MISC_H + +#include <map> + +namespace android { +namespace netdutils { + +// Lookup key in map, returing a default value if key is not found +template <typename U, typename V> +inline const V& findWithDefault(const std::map<U, V>& map, const U& key, const V& dflt) { + auto it = map.find(key); + return (it == map.end()) ? dflt : it->second; +} + +// Movable, copiable, scoped lambda (or std::function) runner. Useful +// for running arbitrary cleanup or logging code when exiting a scope. +// +// Compare to defer in golang. +template <typename FnT> +class Cleanup { + public: + Cleanup() = delete; + explicit Cleanup(FnT fn) : mFn(fn) {} + ~Cleanup() { if (!mReleased) mFn(); } + + void release() { mReleased = true; } + + private: + bool mReleased{false}; + FnT mFn; +}; + +// Helper to make a new Cleanup. Avoids complex or impossible syntax +// when wrapping lambdas. +// +// Usage: +// auto cleanup = makeCleanup([](){ your_code_here; }); +template <typename FnT> +Cleanup<FnT> makeCleanup(FnT fn) { + return Cleanup<FnT>(fn); +} + +} // namespace netdutils +} // namespace android + +#endif /* NETUTILS_MISC_H */ diff --git a/staticlibs/netd/libnetdutils/include/netdutils/MockSyscalls.h b/staticlibs/netd/libnetdutils/include/netdutils/MockSyscalls.h new file mode 100644 index 0000000000000000000000000000000000000000..f57b55ca9d8eb031382528f655e049eb469bc412 --- /dev/null +++ b/staticlibs/netd/libnetdutils/include/netdutils/MockSyscalls.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef NETUTILS_MOCK_SYSCALLS_H +#define NETUTILS_MOCK_SYSCALLS_H + +#include <atomic> +#include <cassert> +#include <memory> + +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include "netdutils/Syscalls.h" + +namespace android { +namespace netdutils { + +class MockSyscalls : public Syscalls { + public: + virtual ~MockSyscalls() = default; + // Use Return(ByMove(...)) to deal with movable return types. + MOCK_CONST_METHOD3(open, + StatusOr<UniqueFd>(const std::string& pathname, int flags, mode_t mode)); + MOCK_CONST_METHOD3(socket, StatusOr<UniqueFd>(int domain, int type, int protocol)); + MOCK_CONST_METHOD3(getsockname, Status(Fd sock, sockaddr* addr, socklen_t* addrlen)); + MOCK_CONST_METHOD5(getsockopt, Status(Fd sock, int level, int optname, void* optval, + socklen_t *optlen)); + MOCK_CONST_METHOD5(setsockopt, Status(Fd sock, int level, int optname, const void* optval, + socklen_t optlen)); + + MOCK_CONST_METHOD3(bind, Status(Fd sock, const sockaddr* addr, socklen_t addrlen)); + MOCK_CONST_METHOD3(connect, Status(Fd sock, const sockaddr* addr, socklen_t addrlen)); + MOCK_CONST_METHOD3(ioctl, StatusOr<ifreq>(Fd sock, unsigned long request, ifreq* ifr)); + + // Use Return(ByMove(...)) to deal with movable return types. + MOCK_CONST_METHOD2(eventfd, StatusOr<UniqueFd>(unsigned int initval, int flags)); + MOCK_CONST_METHOD3(ppoll, StatusOr<int>(pollfd* fds, nfds_t nfds, double timeout)); + + MOCK_CONST_METHOD2(writev, StatusOr<size_t>(Fd fd, const std::vector<iovec>& iov)); + MOCK_CONST_METHOD2(write, StatusOr<size_t>(Fd fd, const Slice buf)); + MOCK_CONST_METHOD2(read, StatusOr<Slice>(Fd fd, const Slice buf)); + MOCK_CONST_METHOD5(sendto, StatusOr<size_t>(Fd sock, const Slice buf, int flags, + const sockaddr* dst, socklen_t dstlen)); + MOCK_CONST_METHOD5(recvfrom, StatusOr<Slice>(Fd sock, const Slice dst, int flags, sockaddr* src, + socklen_t* srclen)); + MOCK_CONST_METHOD2(shutdown, Status(Fd fd, int how)); + MOCK_CONST_METHOD1(close, Status(Fd fd)); + + MOCK_CONST_METHOD2(fopen, + StatusOr<UniqueFile>(const std::string& path, const std::string& mode)); + MOCK_CONST_METHOD3(vfprintf, StatusOr<int>(FILE* file, const char* format, va_list ap)); + MOCK_CONST_METHOD3(vfscanf, StatusOr<int>(FILE* file, const char* format, va_list ap)); + MOCK_CONST_METHOD1(fclose, Status(FILE* file)); + MOCK_CONST_METHOD0(fork, StatusOr<pid_t>()); +}; + +// For the lifetime of this mock, replace the contents of sSyscalls +// with a pointer to this mock. Behavior is undefined if multiple +// ScopedMockSyscalls instances exist concurrently. +class ScopedMockSyscalls : public MockSyscalls { + public: + ScopedMockSyscalls() : mOld(sSyscalls.swap(*this)) { assert((mRefcount++) == 1); } + virtual ~ScopedMockSyscalls() { + sSyscalls.swap(mOld); + assert((mRefcount--) == 0); + } + + private: + std::atomic<int> mRefcount{0}; + Syscalls& mOld; +}; + +} // namespace netdutils +} // namespace android + +#endif /* NETUTILS_MOCK_SYSCALLS_H */ diff --git a/staticlibs/netd/libnetdutils/include/netdutils/NetNativeTestBase.h b/staticlibs/netd/libnetdutils/include/netdutils/NetNativeTestBase.h new file mode 100644 index 0000000000000000000000000000000000000000..c8b30c6266a3ea7e7495e02a70b42a3ac1928422 --- /dev/null +++ b/staticlibs/netd/libnetdutils/include/netdutils/NetNativeTestBase.h @@ -0,0 +1,46 @@ +/* + * 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. + * + */ +#pragma once + +#include <android-base/format.h> +#include <android-base/logging.h> +#include "gtest/gtest.h" + +using ::testing::TestInfo; +using ::testing::UnitTest; + +#define DBG 1 + +/* + * Test base class for net native tests to support common usage. + */ +class NetNativeTestBase : public ::testing::Test { + public: + // TODO: update the logging when gtest supports logging the life cycle on each test. + NetNativeTestBase() { + if (DBG) LOG(INFO) << getTestCaseLog(true); + } + ~NetNativeTestBase() { + if (DBG) LOG(INFO) << getTestCaseLog(false); + } + + std::string getTestCaseLog(bool running) { + const TestInfo* const test_info = UnitTest::GetInstance()->current_test_info(); + return fmt::format("{}: {}#{}", (running ? "started" : "finished"), + test_info->test_suite_name(), test_info->name()); + } +}; diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Netfilter.h b/staticlibs/netd/libnetdutils/include/netdutils/Netfilter.h new file mode 100644 index 0000000000000000000000000000000000000000..22736f192b66799edb174457ad9b9fd82924035d --- /dev/null +++ b/staticlibs/netd/libnetdutils/include/netdutils/Netfilter.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef NETUTILS_NETFILTER_H +#define NETUTILS_NETFILTER_H + +#include <ostream> + +#include <linux/netfilter.h> +#include <linux/netfilter/nfnetlink.h> +#include <linux/netlink.h> + +std::ostream& operator<<(std::ostream& os, const nfgenmsg& msg); + +#endif /* NETUTILS_NETFILTER_H */ diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Netlink.h b/staticlibs/netd/libnetdutils/include/netdutils/Netlink.h new file mode 100644 index 0000000000000000000000000000000000000000..ee5183ab478ec570acb78cb2844ae5d803df23ab --- /dev/null +++ b/staticlibs/netd/libnetdutils/include/netdutils/Netlink.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef NETUTILS_NETLINK_H +#define NETUTILS_NETLINK_H + +#include <functional> +#include <ostream> +#include <linux/netlink.h> + +#include "netdutils/Slice.h" + +namespace android { +namespace netdutils { + +// Invoke onMsg once for each netlink message in buf. onMsg will be +// invoked with an aligned and deserialized header along with a Slice +// containing the message payload. +// +// Assume that the first message begins at offset zero within buf. +void forEachNetlinkMessage(const Slice buf, + const std::function<void(const nlmsghdr&, const Slice)>& onMsg); + +// Invoke onAttr once for each netlink attribute in buf. onAttr will be +// invoked with an aligned and deserialized header along with a Slice +// containing the attribute payload. +// +// Assume that the first attribute begins at offset zero within buf. +void forEachNetlinkAttribute(const Slice buf, + const std::function<void(const nlattr&, const Slice)>& onAttr); + +} // namespace netdutils +} // namespace android + +bool operator==(const sockaddr_nl& lhs, const sockaddr_nl& rhs); +bool operator!=(const sockaddr_nl& lhs, const sockaddr_nl& rhs); + +std::ostream& operator<<(std::ostream& os, const nlmsghdr& hdr); +std::ostream& operator<<(std::ostream& os, const nlattr& attr); +std::ostream& operator<<(std::ostream& os, const sockaddr_nl& addr); + +#endif /* NETUTILS_NETLINK_H */ diff --git a/staticlibs/netd/libnetdutils/include/netdutils/NetlinkListener.h b/staticlibs/netd/libnetdutils/include/netdutils/NetlinkListener.h new file mode 100644 index 0000000000000000000000000000000000000000..97f7bb2357663f008c139ec3282ca43467fd4bde --- /dev/null +++ b/staticlibs/netd/libnetdutils/include/netdutils/NetlinkListener.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef NETLINK_LISTENER_H +#define NETLINK_LISTENER_H + +#include <functional> +#include <map> +#include <mutex> +#include <thread> + +#include <android-base/thread_annotations.h> +#include <netdutils/Netlink.h> +#include <netdutils/Slice.h> +#include <netdutils/Status.h> +#include <netdutils/UniqueFd.h> + +namespace android { +namespace netdutils { + +class NetlinkListenerInterface { + public: + using DispatchFn = std::function<void(const nlmsghdr& nlmsg, const netdutils::Slice msg)>; + + using SkErrorHandler = std::function<void(const int fd, const int err)>; + + virtual ~NetlinkListenerInterface() = default; + + // Send message to the kernel using the underlying netlink socket + virtual netdutils::Status send(const netdutils::Slice msg) = 0; + + // Deliver future messages with nlmsghdr.nlmsg_type == type to fn. + // + // Threadsafe. + // All dispatch functions invoked on a single service thread. + // subscribe() and join() must not be called from the stack of fn(). + virtual netdutils::Status subscribe(uint16_t type, const DispatchFn& fn) = 0; + + // Halt delivery of future messages with nlmsghdr.nlmsg_type == type. + // Threadsafe. + virtual netdutils::Status unsubscribe(uint16_t type) = 0; + + virtual void registerSkErrorHandler(const SkErrorHandler& handler) = 0; +}; + +// NetlinkListener manages a netlink socket and associated blocking +// service thread. +// +// This class is written in a generic way to allow multiple different +// netlink subsystems to share this common infrastructure. If multiple +// subsystems share the same message delivery requirements (drops ok, +// no drops) they may share a single listener by calling subscribe() +// with multiple types. +// +// This class is suitable for moderate performance message +// processing. In particular it avoids extra copies of received +// message data and allows client code to control which message +// attributes are processed. +// +// Note that NetlinkListener is capable of processing multiple batched +// netlink messages in a single system call. This is useful to +// netfilter extensions that allow batching of events like NFLOG. +class NetlinkListener : public NetlinkListenerInterface { + public: + NetlinkListener(netdutils::UniqueFd event, netdutils::UniqueFd sock, const std::string& name); + + ~NetlinkListener() override; + + netdutils::Status send(const netdutils::Slice msg) override; + + netdutils::Status subscribe(uint16_t type, const DispatchFn& fn) override EXCLUDES(mMutex); + + netdutils::Status unsubscribe(uint16_t type) override EXCLUDES(mMutex); + + void registerSkErrorHandler(const SkErrorHandler& handler) override; + + private: + netdutils::Status run(); + + const netdutils::UniqueFd mEvent; + const netdutils::UniqueFd mSock; + const std::string mThreadName; + std::mutex mMutex; + std::map<uint16_t, DispatchFn> mDispatchMap GUARDED_BY(mMutex); + std::thread mWorker; + SkErrorHandler mErrorHandler; +}; + +} // namespace netdutils +} // namespace android + +#endif /* NETLINK_LISTENER_H */ diff --git a/staticlibs/netd/libnetdutils/include/netdutils/NetworkConstants.h b/staticlibs/netd/libnetdutils/include/netdutils/NetworkConstants.h new file mode 100644 index 0000000000000000000000000000000000000000..dead9a17bd6b7c2ef1125241638842ff77871d56 --- /dev/null +++ b/staticlibs/netd/libnetdutils/include/netdutils/NetworkConstants.h @@ -0,0 +1,32 @@ +/* + * 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. + */ + +#pragma once + +namespace android { +namespace netdutils { + +// See also NetworkConstants.java in frameworks/base. +constexpr int IPV4_ADDR_LEN = 4; +constexpr int IPV4_ADDR_BITS = 32; +constexpr int IPV6_ADDR_LEN = 16; +constexpr int IPV6_ADDR_BITS = 128; + +// Referred from SHA256_DIGEST_LENGTH in boringssl +constexpr size_t SHA256_SIZE = 32; + +} // namespace netdutils +} // namespace android diff --git a/staticlibs/netd/libnetdutils/include/netdutils/ResponseCode.h b/staticlibs/netd/libnetdutils/include/netdutils/ResponseCode.h new file mode 100644 index 0000000000000000000000000000000000000000..c170684cf376b3d956cc9bf3e06d625583ae9e5a --- /dev/null +++ b/staticlibs/netd/libnetdutils/include/netdutils/ResponseCode.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2008 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. + */ + +#ifndef NETDUTILS_RESPONSECODE_H +#define NETDUTILS_RESPONSECODE_H + +namespace android { +namespace netdutils { + +class ResponseCode { + // Keep in sync with + // frameworks/base/services/java/com/android/server/NetworkManagementService.java + public: + // 100 series - Requestion action was initiated; expect another reply + // before proceeding with a new command. + // clang-format off + static constexpr int ActionInitiated = 100; + static constexpr int InterfaceListResult = 110; + static constexpr int TetherInterfaceListResult = 111; + static constexpr int TetherDnsFwdTgtListResult = 112; + static constexpr int TtyListResult = 113; + static constexpr int TetheringStatsListResult = 114; + static constexpr int TetherDnsFwdNetIdResult = 115; + + // 200 series - Requested action has been successfully completed + static constexpr int CommandOkay = 200; + static constexpr int TetherStatusResult = 210; + static constexpr int IpFwdStatusResult = 211; + static constexpr int InterfaceGetCfgResult = 213; + // Formerly: int SoftapStatusResult = 214; + static constexpr int UsbRNDISStatusResult = 215; + static constexpr int InterfaceRxCounterResult = 216; + static constexpr int InterfaceTxCounterResult = 217; + static constexpr int InterfaceRxThrottleResult = 218; + static constexpr int InterfaceTxThrottleResult = 219; + static constexpr int QuotaCounterResult = 220; + static constexpr int TetheringStatsResult = 221; + // NOTE: keep synced with bionic/libc/dns/net/gethnamaddr.c + static constexpr int DnsProxyQueryResult = 222; + static constexpr int ClatdStatusResult = 223; + + // 400 series - The command was accepted but the requested action + // did not take place. + static constexpr int OperationFailed = 400; + static constexpr int DnsProxyOperationFailed = 401; + static constexpr int ServiceStartFailed = 402; + static constexpr int ServiceStopFailed = 403; + + // 500 series - The command was not accepted and the requested + // action did not take place. + static constexpr int CommandSyntaxError = 500; + static constexpr int CommandParameterError = 501; + + // 600 series - Unsolicited broadcasts + static constexpr int InterfaceChange = 600; + static constexpr int BandwidthControl = 601; + static constexpr int ServiceDiscoveryFailed = 602; + static constexpr int ServiceDiscoveryServiceAdded = 603; + static constexpr int ServiceDiscoveryServiceRemoved = 604; + static constexpr int ServiceRegistrationFailed = 605; + static constexpr int ServiceRegistrationSucceeded = 606; + static constexpr int ServiceResolveFailed = 607; + static constexpr int ServiceResolveSuccess = 608; + static constexpr int ServiceSetHostnameFailed = 609; + static constexpr int ServiceSetHostnameSuccess = 610; + static constexpr int ServiceGetAddrInfoFailed = 611; + static constexpr int ServiceGetAddrInfoSuccess = 612; + static constexpr int InterfaceClassActivity = 613; + static constexpr int InterfaceAddressChange = 614; + static constexpr int InterfaceDnsInfo = 615; + static constexpr int RouteChange = 616; + static constexpr int StrictCleartext = 617; + // clang-format on +}; + +} // namespace netdutils +} // namespace android + +#endif // NETDUTILS_RESPONSECODE_H diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Slice.h b/staticlibs/netd/libnetdutils/include/netdutils/Slice.h new file mode 100644 index 0000000000000000000000000000000000000000..717fbd1fbbb0041baa0eb11f4dfbce59f0cc7443 --- /dev/null +++ b/staticlibs/netd/libnetdutils/include/netdutils/Slice.h @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef NETUTILS_SLICE_H +#define NETUTILS_SLICE_H + +#include <algorithm> +#include <array> +#include <cstring> +#include <ostream> +#include <tuple> +#include <vector> + +namespace android { +namespace netdutils { + +// Immutable wrapper for a linear region of unowned bytes. +// Slice represents memory as a half-closed interval [base, limit). +// +// Note that without manually invoking the Slice() constructor, it is +// impossible to increase the size of a slice. This guarantees that +// applications that properly use the slice API will never access +// memory outside of a slice. +// +// Note that const Slice still wraps mutable memory, however copy +// assignment and move assignment to slice are disabled. +class Slice { + public: + Slice() = default; + + // Create a slice beginning at base and continuing to but not including limit + Slice(void* base, void* limit) : mBase(toUint8(base)), mLimit(toUint8(limit)) {} + + // Create a slice beginning at base and continuing for size bytes + Slice(void* base, size_t size) : Slice(base, toUint8(base) + size) {} + + // Return the address of the first byte in this slice + uint8_t* base() const { return mBase; } + + // Return the address of the first byte following this slice + uint8_t* limit() const { return mLimit; } + + // Return the size of this slice in bytes + size_t size() const { return limit() - base(); } + + // Return true if size() == 0 + bool empty() const { return base() == limit(); } + + private: + static uint8_t* toUint8(void* ptr) { return reinterpret_cast<uint8_t*>(ptr); } + + uint8_t* mBase = nullptr; + uint8_t* mLimit = nullptr; +}; + +// Return slice representation of ref which must be a POD type +template <typename T> +inline const Slice makeSlice(const T& ref) { + static_assert(std::is_pod<T>::value, "value must be a POD type"); + static_assert(!std::is_pointer<T>::value, "value must not be a pointer type"); + return {const_cast<T*>(&ref), sizeof(ref)}; +} + +// Return slice representation of string data() +inline const Slice makeSlice(const std::string& s) { + using ValueT = std::string::value_type; + return {const_cast<ValueT*>(s.data()), s.size() * sizeof(ValueT)}; +} + +// Return slice representation of vector data() +template <typename T> +inline const Slice makeSlice(const std::vector<T>& v) { + return {const_cast<T*>(v.data()), v.size() * sizeof(T)}; +} + +// Return slice representation of array data() +template <typename U, size_t V> +inline const Slice makeSlice(const std::array<U, V>& a) { + return {const_cast<U*>(a.data()), a.size() * sizeof(U)}; +} + +// Return prefix and suffix of Slice s ending and starting at position cut +inline std::pair<const Slice, const Slice> split(const Slice s, size_t cut) { + const size_t tmp = std::min(cut, s.size()); + return {{s.base(), s.base() + tmp}, {s.base() + tmp, s.limit()}}; +} + +// Return prefix of Slice s ending at position cut +inline const Slice take(const Slice s, size_t cut) { + return std::get<0>(split(s, cut)); +} + +// Return suffix of Slice s starting at position cut +inline const Slice drop(const Slice s, size_t cut) { + return std::get<1>(split(s, cut)); +} + +// Copy from src into dst. Bytes copied is the lesser of dst.size() and src.size() +inline size_t copy(const Slice dst, const Slice src) { + const auto min = std::min(dst.size(), src.size()); + memcpy(dst.base(), src.base(), min); + return min; +} + +// Base case for variadic extract below +template <typename Head> +inline size_t extract(const Slice src, Head& head) { + return copy(makeSlice(head), src); +} + +// Copy from src into one or more pointers to POD data. If src.size() +// is less than the sum of all data pointers a suffix of data will be +// left unmodified. Return the number of bytes copied. +template <typename Head, typename... Tail> +inline size_t extract(const Slice src, Head& head, Tail&... tail) { + const auto extracted = extract(src, head); + return extracted + extract(drop(src, extracted), tail...); +} + +// Return a string containing a copy of the contents of s +std::string toString(const Slice s); + +// Return a string containing a hexadecimal representation of the contents of s. +// This function inserts a newline into its output every wrap bytes. +std::string toHex(const Slice s, int wrap = INT_MAX); + +inline bool operator==(const Slice& lhs, const Slice& rhs) { + return (lhs.base() == rhs.base()) && (lhs.limit() == rhs.limit()); +} + +inline bool operator!=(const Slice& lhs, const Slice& rhs) { + return !(lhs == rhs); +} + +std::ostream& operator<<(std::ostream& os, const Slice& slice); + +// Return suffix of Slice s starting at the first match of byte c. If no matched +// byte, return an empty Slice. +inline const Slice findFirstMatching(const Slice s, uint8_t c) { + uint8_t* match = (uint8_t*)memchr(s.base(), c, s.size()); + if (!match) return Slice(); + return drop(s, match - s.base()); +} + +} // namespace netdutils +} // namespace android + +#endif /* NETUTILS_SLICE_H */ diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Socket.h b/staticlibs/netd/libnetdutils/include/netdutils/Socket.h new file mode 100644 index 0000000000000000000000000000000000000000..e5aaab9c64b3b2d80c98a5c91a68900e9b83ae34 --- /dev/null +++ b/staticlibs/netd/libnetdutils/include/netdutils/Socket.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef NETDUTILS_SOCKET_H +#define NETDUTILS_SOCKET_H + +#include <netinet/in.h> +#include <sys/socket.h> +#include <string> + +#include "netdutils/StatusOr.h" + +namespace android { +namespace netdutils { + +inline sockaddr* asSockaddrPtr(void* addr) { + return reinterpret_cast<sockaddr*>(addr); +} + +inline const sockaddr* asSockaddrPtr(const void* addr) { + return reinterpret_cast<const sockaddr*>(addr); +} + +// Return a string representation of addr or Status if there was a +// failure during conversion. +StatusOr<std::string> toString(const in6_addr& addr); + +} // namespace netdutils +} // namespace android + +#endif /* NETDUTILS_SOCKET_H */ diff --git a/staticlibs/netd/libnetdutils/include/netdutils/SocketOption.h b/staticlibs/netd/libnetdutils/include/netdutils/SocketOption.h new file mode 100644 index 0000000000000000000000000000000000000000..3b0aab75d4e6618fe03586521690b37ab5cf5866 --- /dev/null +++ b/staticlibs/netd/libnetdutils/include/netdutils/SocketOption.h @@ -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 permissions and + * limitations under the License. + */ + +#ifndef NETDUTILS_SOCKETOPTION_H +#define NETDUTILS_SOCKETOPTION_H + +#include <netinet/in.h> +#include <sys/socket.h> +#include <string> + +#include "netdutils/Fd.h" +#include "netdutils/Status.h" + +namespace android { +namespace netdutils { + +// Turn on simple "boolean" socket options. +// +// This is simple wrapper for options that are enabled via code of the form: +// +// int on = 1; +// setsockopt(..., &on, sizeof(on)); +Status enableSockopt(Fd sock, int level, int optname); + +// Turn on TCP keepalives, and set keepalive parameters for this socket. +// +// A parameter value of zero does not set that parameter. +// +// Typical system defaults are: +// +// idleTime (in seconds) +// $ cat /proc/sys/net/ipv4/tcp_keepalive_time +// 7200 +// +// numProbes +// $ cat /proc/sys/net/ipv4/tcp_keepalive_probes +// 9 +// +// probeInterval (in seconds) +// $ cat /proc/sys/net/ipv4/tcp_keepalive_intvl +// 75 +Status enableTcpKeepAlives(Fd sock, unsigned idleTime, unsigned numProbes, unsigned probeInterval); + +} // namespace netdutils +} // namespace android + +#endif /* NETDUTILS_SOCKETOPTION_H */ diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Status.h b/staticlibs/netd/libnetdutils/include/netdutils/Status.h new file mode 100644 index 0000000000000000000000000000000000000000..7b0bd4729daeb6a02d5d84c4fa9e0f0df6a7cb9e --- /dev/null +++ b/staticlibs/netd/libnetdutils/include/netdutils/Status.h @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef NETUTILS_STATUS_H +#define NETUTILS_STATUS_H + +#include <cassert> +#include <limits> +#include <ostream> + +#include <android-base/result.h> + +namespace android { +namespace netdutils { + +// Simple status implementation suitable for use on the stack in low +// or moderate performance code. This can definitely be improved but +// for now short string optimization is expected to keep the common +// success case fast. +// +// Status is implicitly movable via the default noexcept move constructor +// and noexcept move-assignment operator. +class [[nodiscard]] Status { + public: + Status() = default; + explicit Status(int code) : mCode(code) {} + + // Constructs an error Status, |code| must be non-zero. + Status(int code, std::string msg) : mCode(code), mMsg(std::move(msg)) { assert(!ok()); } + + Status(android::base::Result<void> result) + : mCode(result.ok() ? 0 : static_cast<int>(result.error().code())), + mMsg(result.ok() ? "" : result.error().message()) {} + + int code() const { return mCode; } + + bool ok() const { return code() == 0; } + + const std::string& msg() const { return mMsg; } + + // Explicitly ignores the Status without triggering [[nodiscard]] errors. + void ignoreError() const {} + + bool operator==(const Status& other) const { return code() == other.code(); } + bool operator!=(const Status& other) const { return !(*this == other); } + + private: + int mCode = 0; + std::string mMsg; +}; + +namespace status { + +const Status ok{0}; +// EOF is not part of errno space, we'll place it far above the +// highest existing value. +const Status eof{0x10001, "end of file"}; +const Status undefined{std::numeric_limits<int>::max(), "undefined"}; + +} // namespace status + +// Return true if status is "OK". This is sometimes preferable to +// status.ok() when we want to check the state of Status-like objects +// that implicitly cast to Status. +inline bool isOk(const Status& status) { + return status.ok(); +} + +// For use only in tests. Used for both Status and Status-like objects. See also isOk(). +#define EXPECT_OK(status) EXPECT_TRUE(isOk(status)) +#define ASSERT_OK(status) ASSERT_TRUE(isOk(status)) + +// Documents that status is expected to be ok. This function may log +// (or assert when running in debug mode) if status has an unexpected value. +inline void expectOk(const Status& /*status*/) { + // TODO: put something here, for now this function serves solely as documentation. +} + +// Convert POSIX errno to a Status object. +// If Status is extended to have more features, this mapping may +// become more complex. +Status statusFromErrno(int err, const std::string& msg); + +// Helper that checks Status-like object (notably StatusOr) against a +// value in the errno space. +bool equalToErrno(const Status& status, int err); + +// Helper that converts Status-like object (notably StatusOr) to a +// message. +std::string toString(const Status& status); + +std::ostream& operator<<(std::ostream& os, const Status& s); + +// Evaluate 'stmt' to a Status object and if it results in an error, return that +// error. Use 'tmp' as a variable name to avoid shadowing any variables named +// tmp. +#define RETURN_IF_NOT_OK_IMPL(tmp, stmt) \ + do { \ + ::android::netdutils::Status tmp = (stmt); \ + if (!isOk(tmp)) { \ + return tmp; \ + } \ + } while (false) + +// Create a unique variable name to avoid shadowing local variables. +#define RETURN_IF_NOT_OK_CONCAT(line, stmt) RETURN_IF_NOT_OK_IMPL(__CONCAT(_status_, line), stmt) + +// Macro to allow exception-like handling of error return values. +// +// If the evaluation of stmt results in an error, return that error +// from current function. +// +// Example usage: +// Status bar() { ... } +// +// RETURN_IF_NOT_OK(status); +// RETURN_IF_NOT_OK(bar()); +#define RETURN_IF_NOT_OK(stmt) RETURN_IF_NOT_OK_CONCAT(__LINE__, stmt) + +} // namespace netdutils +} // namespace android + +#endif /* NETUTILS_STATUS_H */ diff --git a/staticlibs/netd/libnetdutils/include/netdutils/StatusOr.h b/staticlibs/netd/libnetdutils/include/netdutils/StatusOr.h new file mode 100644 index 0000000000000000000000000000000000000000..c7aa4e4ae01ee1a54f28bb5c795a8e4a5c1d94c6 --- /dev/null +++ b/staticlibs/netd/libnetdutils/include/netdutils/StatusOr.h @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef NETUTILS_STATUSOR_H +#define NETUTILS_STATUSOR_H + +#include <cassert> +#include "netdutils/Status.h" + +namespace android { +namespace netdutils { + +// Wrapper around a combination of Status and application value type. +// T may be any copyable or movable type. +template <typename T> +class [[nodiscard]] StatusOr { + public: + // Constructs a new StatusOr with status::undefined status. + // This is marked 'explicit' to try to catch cases like 'return {};', + // where people think StatusOr<std::vector<int>> will be initialized + // with an empty vector, instead of a status::undefined. + explicit StatusOr() = default; + + // Implicit copy constructor and construction from T. + // NOLINTNEXTLINE(google-explicit-constructor) + StatusOr(Status status) : mStatus(std::move(status)) { assert(!isOk(mStatus)); } + + // Implicit construction from T. It is convenient and sensible to be able + // to do 'return T()' when the return type is StatusOr<T>. + // NOLINTNEXTLINE(google-explicit-constructor) + StatusOr(const T& value) : mStatus(status::ok), mValue(value) {} + // NOLINTNEXTLINE(google-explicit-constructor) + StatusOr(T&& value) : mStatus(status::ok), mValue(std::move(value)) {} + + // Move constructor ok (if T supports move) + StatusOr(StatusOr&&) noexcept = default; + // Move assignment ok (if T supports move) + StatusOr& operator=(StatusOr&&) noexcept = default; + // Copy constructor ok (if T supports copy) + StatusOr(const StatusOr&) = default; + // Copy assignment ok (if T supports copy) + StatusOr& operator=(const StatusOr&) = default; + + // Returns a const reference to wrapped type. + // It is an error to call value() when !isOk(status()) + const T& value() const & { return mValue; } + const T&& value() const && { return mValue; } + + // Returns an rvalue reference to wrapped type + // It is an error to call value() when !isOk(status()) + // + // If T is expensive to copy but supports efficient move, it can be moved + // out of a StatusOr as follows: + // T value = std::move(statusor).value(); + T& value() & { return mValue; } + T&& value() && { return mValue; } + + // Returns the Status object assigned at construction time. + const Status status() const { return mStatus; } + + // Explicitly ignores the Status without triggering [[nodiscard]] errors. + void ignoreError() const {} + + // Implicit cast to Status. + // NOLINTNEXTLINE(google-explicit-constructor) + operator Status() const { return status(); } + + private: + Status mStatus = status::undefined; + T mValue; +}; + +template <typename T> +inline std::ostream& operator<<(std::ostream& os, const StatusOr<T>& s) { + return os << "StatusOr[status: " << s.status() << "]"; +} + +#define ASSIGN_OR_RETURN_IMPL(tmp, lhs, stmt) \ + auto tmp = (stmt); \ + RETURN_IF_NOT_OK(tmp); \ + lhs = std::move(tmp.value()); + +#define ASSIGN_OR_RETURN_CONCAT(line, lhs, stmt) \ + ASSIGN_OR_RETURN_IMPL(__CONCAT(_status_or_, line), lhs, stmt) + +// Macro to allow exception-like handling of error return values. +// +// If the evaluation of stmt results in an error, return that error +// from the current function. Otherwise, assign the result to lhs. +// +// This macro supports both move and copy assignment operators. lhs +// may be either a new local variable or an existing non-const +// variable accessible in the current scope. +// +// Example usage: +// StatusOr<MyType> foo() { ... } +// +// ASSIGN_OR_RETURN(auto myVar, foo()); +// ASSIGN_OR_RETURN(myExistingVar, foo()); +// ASSIGN_OR_RETURN(myMemberVar, foo()); +#define ASSIGN_OR_RETURN(lhs, stmt) ASSIGN_OR_RETURN_CONCAT(__LINE__, lhs, stmt) + +} // namespace netdutils +} // namespace android + +#endif /* NETUTILS_STATUSOR_H */ diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Stopwatch.h b/staticlibs/netd/libnetdutils/include/netdutils/Stopwatch.h new file mode 100644 index 0000000000000000000000000000000000000000..e7b4326455635b45cb8f9180bd199c4f7c8b7c87 --- /dev/null +++ b/staticlibs/netd/libnetdutils/include/netdutils/Stopwatch.h @@ -0,0 +1,53 @@ +/* + * 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. + */ + +#ifndef NETDUTILS_STOPWATCH_H +#define NETDUTILS_STOPWATCH_H + +#include <chrono> + +namespace android { +namespace netdutils { + +class Stopwatch { + private: + using clock = std::chrono::steady_clock; + using time_point = std::chrono::time_point<clock>; + + public: + Stopwatch() : mStart(clock::now()) {} + virtual ~Stopwatch() = default; + + int64_t timeTakenUs() const { return getElapsedUs(clock::now()); } + int64_t getTimeAndResetUs() { + const auto& now = clock::now(); + int64_t elapsed = getElapsedUs(now); + mStart = now; + return elapsed; + } + + private: + time_point mStart; + + int64_t getElapsedUs(const time_point& now) const { + return (std::chrono::duration_cast<std::chrono::microseconds>(now - mStart)).count(); + } +}; + +} // namespace netdutils +} // namespace android + +#endif // NETDUTILS_STOPWATCH_H diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Syscalls.h b/staticlibs/netd/libnetdutils/include/netdutils/Syscalls.h new file mode 100644 index 0000000000000000000000000000000000000000..36fcd853289cab13d07f86860be143e51f3aeee8 --- /dev/null +++ b/staticlibs/netd/libnetdutils/include/netdutils/Syscalls.h @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef NETDUTILS_SYSCALLS_H +#define NETDUTILS_SYSCALLS_H + +#include <memory> + +#include <net/if.h> +#include <poll.h> +#include <sys/eventfd.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/uio.h> +#include <unistd.h> + +#include "netdutils/Fd.h" +#include "netdutils/Slice.h" +#include "netdutils/Socket.h" +#include "netdutils/Status.h" +#include "netdutils/StatusOr.h" +#include "netdutils/UniqueFd.h" +#include "netdutils/UniqueFile.h" + +namespace android { +namespace netdutils { + +class Syscalls { + public: + virtual ~Syscalls() = default; + + virtual StatusOr<UniqueFd> open(const std::string& pathname, int flags, + mode_t mode = 0) const = 0; + + virtual StatusOr<UniqueFd> socket(int domain, int type, int protocol) const = 0; + + virtual Status getsockname(Fd sock, sockaddr* addr, socklen_t* addrlen) const = 0; + + virtual Status getsockopt(Fd sock, int level, int optname, void *optval, + socklen_t *optlen) const = 0; + + virtual Status setsockopt(Fd sock, int level, int optname, const void* optval, + socklen_t optlen) const = 0; + + virtual Status bind(Fd sock, const sockaddr* addr, socklen_t addrlen) const = 0; + + virtual Status connect(Fd sock, const sockaddr* addr, socklen_t addrlen) const = 0; + + virtual StatusOr<ifreq> ioctl(Fd sock, unsigned long request, ifreq* ifr) const = 0; + + virtual StatusOr<UniqueFd> eventfd(unsigned int initval, int flags) const = 0; + + virtual StatusOr<int> ppoll(pollfd* fds, nfds_t nfds, double timeout) const = 0; + + virtual StatusOr<size_t> writev(Fd fd, const std::vector<iovec>& iov) const = 0; + + virtual StatusOr<size_t> write(Fd fd, const Slice buf) const = 0; + + virtual StatusOr<Slice> read(Fd fd, const Slice buf) const = 0; + + virtual StatusOr<size_t> sendto(Fd sock, const Slice buf, int flags, const sockaddr* dst, + socklen_t dstlen) const = 0; + + virtual StatusOr<Slice> recvfrom(Fd sock, const Slice dst, int flags, sockaddr* src, + socklen_t* srclen) const = 0; + + virtual Status shutdown(Fd fd, int how) const = 0; + + virtual Status close(Fd fd) const = 0; + + virtual StatusOr<UniqueFile> fopen(const std::string& path, const std::string& mode) const = 0; + + virtual StatusOr<int> vfprintf(FILE* file, const char* format, va_list ap) const = 0; + + virtual StatusOr<int> vfscanf(FILE* file, const char* format, va_list ap) const = 0; + + virtual Status fclose(FILE* file) const = 0; + + virtual StatusOr<pid_t> fork() const = 0; + + // va_args helpers + // va_start doesn't work when the preceding argument is a reference + // type so we're forced to use const char*. + StatusOr<int> fprintf(FILE* file, const char* format, ...) const { + va_list ap; + va_start(ap, format); + auto result = vfprintf(file, format, ap); + va_end(ap); + return result; + } + + // va_start doesn't work when the preceding argument is a reference + // type so we're forced to use const char*. + StatusOr<int> fscanf(FILE* file, const char* format, ...) const { + va_list ap; + va_start(ap, format); + auto result = vfscanf(file, format, ap); + va_end(ap); + return result; + } + + // Templated helpers that forward directly to methods declared above + template <typename SockaddrT> + StatusOr<SockaddrT> getsockname(Fd sock) const { + SockaddrT addr = {}; + socklen_t addrlen = sizeof(addr); + RETURN_IF_NOT_OK(getsockname(sock, asSockaddrPtr(&addr), &addrlen)); + return addr; + } + + template <typename SockoptT> + Status getsockopt(Fd sock, int level, int optname, void* optval, socklen_t* optlen) const { + return getsockopt(sock, level, optname, optval, optlen); + } + + template <typename SockoptT> + Status setsockopt(Fd sock, int level, int optname, const SockoptT& opt) const { + return setsockopt(sock, level, optname, &opt, sizeof(opt)); + } + + template <typename SockaddrT> + Status bind(Fd sock, const SockaddrT& addr) const { + return bind(sock, asSockaddrPtr(&addr), sizeof(addr)); + } + + template <typename SockaddrT> + Status connect(Fd sock, const SockaddrT& addr) const { + return connect(sock, asSockaddrPtr(&addr), sizeof(addr)); + } + + template <size_t size> + StatusOr<std::array<uint16_t, size>> ppoll(const std::array<Fd, size>& fds, uint16_t events, + double timeout) const { + std::array<pollfd, size> tmp; + for (size_t i = 0; i < size; ++i) { + tmp[i].fd = fds[i].get(); + tmp[i].events = events; + tmp[i].revents = 0; + } + RETURN_IF_NOT_OK(ppoll(tmp.data(), tmp.size(), timeout).status()); + std::array<uint16_t, size> out; + for (size_t i = 0; i < size; ++i) { + out[i] = tmp[i].revents; + } + return out; + } + + template <typename SockaddrT> + StatusOr<size_t> sendto(Fd sock, const Slice buf, int flags, const SockaddrT& dst) const { + return sendto(sock, buf, flags, asSockaddrPtr(&dst), sizeof(dst)); + } + + // Ignore src sockaddr + StatusOr<Slice> recvfrom(Fd sock, const Slice dst, int flags) const { + return recvfrom(sock, dst, flags, nullptr, nullptr); + } + + template <typename SockaddrT> + StatusOr<std::pair<Slice, SockaddrT>> recvfrom(Fd sock, const Slice dst, int flags) const { + SockaddrT addr = {}; + socklen_t addrlen = sizeof(addr); + ASSIGN_OR_RETURN(auto used, recvfrom(sock, dst, flags, asSockaddrPtr(&addr), &addrlen)); + return std::make_pair(used, addr); + } +}; + +// Specialized singleton that supports zero initialization and runtime +// override of contained pointer. +class SyscallsHolder { + public: + ~SyscallsHolder(); + + // Return a pointer to an unowned instance of Syscalls. + Syscalls& get(); + + // Testing only: set the value returned by getSyscalls. Return the old value. + // Callers are responsible for restoring the previous value returned + // by getSyscalls to avoid leaks. + Syscalls& swap(Syscalls& syscalls); + + private: + std::atomic<Syscalls*> mSyscalls{nullptr}; +}; + +// Syscalls instance used throughout netdutils +extern SyscallsHolder sSyscalls; + +} // namespace netdutils +} // namespace android + +#endif /* NETDUTILS_SYSCALLS_H */ diff --git a/staticlibs/netd/libnetdutils/include/netdutils/ThreadUtil.h b/staticlibs/netd/libnetdutils/include/netdutils/ThreadUtil.h new file mode 100644 index 0000000000000000000000000000000000000000..62e6f70c90d24a34fb2b089ffb9c7c284264d13a --- /dev/null +++ b/staticlibs/netd/libnetdutils/include/netdutils/ThreadUtil.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef NETDUTILS_THREADUTIL_H +#define NETDUTILS_THREADUTIL_H + +#include <pthread.h> +#include <memory> + +#include <android-base/logging.h> + +namespace android { +namespace netdutils { + +struct scoped_pthread_attr { + scoped_pthread_attr() { pthread_attr_init(&attr); } + ~scoped_pthread_attr() { pthread_attr_destroy(&attr); } + + int detach() { return -pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); } + + pthread_attr_t attr; +}; + +inline void setThreadName(std::string name) { + // MAX_TASK_COMM_LEN=16 is not exported by bionic. + const size_t MAX_TASK_COMM_LEN = 16; + + // Crop name to 16 bytes including the NUL byte, as required by pthread_setname_np() + if (name.size() >= MAX_TASK_COMM_LEN) name.resize(MAX_TASK_COMM_LEN - 1); + + if (int ret = pthread_setname_np(pthread_self(), name.c_str()); ret != 0) { + LOG(WARNING) << "Unable to set thread name to " << name << ": " << strerror(ret); + } +} + +template <typename T> +inline void* runAndDelete(void* obj) { + std::unique_ptr<T> handler(reinterpret_cast<T*>(obj)); + setThreadName(handler->threadName().c_str()); + handler->run(); + return nullptr; +} + +template <typename T> +inline int threadLaunch(T* obj) { + if (obj == nullptr) { + return -EINVAL; + } + + scoped_pthread_attr scoped_attr; + + int rval = scoped_attr.detach(); + if (rval != 0) { + return rval; + } + + pthread_t thread; + rval = pthread_create(&thread, &scoped_attr.attr, &runAndDelete<T>, obj); + if (rval != 0) { + LOG(WARNING) << __func__ << ": pthread_create failed: " << rval; + return -rval; + } + + return rval; +} + +} // namespace netdutils +} // namespace android + +#endif // NETDUTILS_THREADUTIL_H diff --git a/staticlibs/netd/libnetdutils/include/netdutils/UidConstants.h b/staticlibs/netd/libnetdutils/include/netdutils/UidConstants.h new file mode 100644 index 0000000000000000000000000000000000000000..42c10908aacce59edbc2fe965f9cc8079aca2006 --- /dev/null +++ b/staticlibs/netd/libnetdutils/include/netdutils/UidConstants.h @@ -0,0 +1,27 @@ +/* + * 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. + */ + +#ifndef NETDUTILS_UID_CONSTANTS_H +#define NETDUTILS_UID_CONSTANTS_H + +// These are used by both eBPF kernel programs and netd, we cannot put them in NetdConstant.h since +// we have to minimize the number of headers included by the BPF kernel program. +#define MIN_SYSTEM_UID 0 +#define MAX_SYSTEM_UID 9999 + +#define PER_USER_RANGE 100000 + +#endif // NETDUTILS_UID_CONSTANTS_H diff --git a/staticlibs/netd/libnetdutils/include/netdutils/UniqueFd.h b/staticlibs/netd/libnetdutils/include/netdutils/UniqueFd.h new file mode 100644 index 0000000000000000000000000000000000000000..61101f95ac13ddf6b5cc9d483642c8df3db2abc3 --- /dev/null +++ b/staticlibs/netd/libnetdutils/include/netdutils/UniqueFd.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef NETUTILS_UNIQUEFD_H +#define NETUTILS_UNIQUEFD_H + +#include <unistd.h> +#include <ostream> + +#include "netdutils/Fd.h" + +namespace android { +namespace netdutils { + +// Stricter unique_fd implementation that: +// *) Does not implement release() +// *) Does not implicitly cast to int +// *) Uses a strongly typed wrapper (Fd) for the underlying file descriptor +// +// Users of UniqueFd should endeavor to treat this as a completely +// opaque object. The only code that should interpret the wrapped +// value is in Syscalls.h +class UniqueFd { + public: + UniqueFd() = default; + + UniqueFd(Fd fd) : mFd(fd) {} + + ~UniqueFd() { reset(); } + + // Disallow copy + UniqueFd(const UniqueFd&) = delete; + UniqueFd& operator=(const UniqueFd&) = delete; + + // Allow move + UniqueFd(UniqueFd&& other) { std::swap(mFd, other.mFd); } + UniqueFd& operator=(UniqueFd&& other) { + std::swap(mFd, other.mFd); + return *this; + } + + // Cleanup any currently owned Fd, replacing it with the optional + // parameter fd + void reset(Fd fd = Fd()); + + // Implict cast to Fd + operator const Fd &() const { return mFd; } + + private: + Fd mFd; +}; + +std::ostream& operator<<(std::ostream& os, const UniqueFd& fd); + +} // namespace netdutils +} // namespace android + +#endif /* NETUTILS_UNIQUEFD_H */ diff --git a/staticlibs/netd/libnetdutils/include/netdutils/UniqueFile.h b/staticlibs/netd/libnetdutils/include/netdutils/UniqueFile.h new file mode 100644 index 0000000000000000000000000000000000000000..6dd6d6728e8f1cc052af08e2caa1f621de04dbef --- /dev/null +++ b/staticlibs/netd/libnetdutils/include/netdutils/UniqueFile.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef NETDUTILS_UNIQUEFILE_H +#define NETDUTILS_UNIQUEFILE_H + +#include <stdio.h> +#include <memory> + +namespace android { +namespace netdutils { + +struct UniqueFileDtor { + void operator()(FILE* file) const; +}; + +using UniqueFile = std::unique_ptr<FILE, UniqueFileDtor>; + +} // namespace netdutils +} // namespace android + +#endif /* NETDUTILS_UNIQUEFILE_H */ diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Utils.h b/staticlibs/netd/libnetdutils/include/netdutils/Utils.h new file mode 100644 index 0000000000000000000000000000000000000000..83c583b215d6abd3d2493ae16aca3ca6a982a306 --- /dev/null +++ b/staticlibs/netd/libnetdutils/include/netdutils/Utils.h @@ -0,0 +1,32 @@ +/* + * 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. + */ + +#ifndef NETUTILS_UTILS_H +#define NETUTILS_UTILS_H + +#include "netdutils/StatusOr.h" + +namespace android { +namespace netdutils { + +StatusOr<std::vector<std::string>> getIfaceNames(); + +StatusOr<std::map<std::string, uint32_t>> getIfaceList(); + +} // namespace netdutils +} // namespace android + +#endif /* NETUTILS_UTILS_H */ diff --git a/staticlibs/tests/unit/Android.bp b/staticlibs/tests/unit/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..40371e63b2dfc11366a3dc2d3c25b0ec1dafce61 --- /dev/null +++ b/staticlibs/tests/unit/Android.bp @@ -0,0 +1,53 @@ +//######################################################################## +// Build NetworkStaticLibTests package +//######################################################################## + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_library { + name: "NetworkStaticLibTestsLib", + srcs: ["src/**/*.java","src/**/*.kt"], + min_sdk_version: "29", + defaults: ["framework-connectivity-test-defaults"], + static_libs: [ + "androidx.test.rules", + "mockito-target-extended-minus-junit4", + "netd-client", + "net-tests-utils", + "net-utils-framework-common", + "net-utils-device-common", + "net-utils-device-common-async", + "net-utils-device-common-bpf", + "net-utils-device-common-ip", + "net-utils-device-common-wear", + ], + libs: [ + "android.test.runner", + "android.test.base", + ], + visibility: [ + "//frameworks/base/packages/Tethering/tests/integration", + "//packages/modules/Connectivity/tests:__subpackages__", + "//packages/modules/Connectivity/Tethering/tests:__subpackages__", + "//packages/modules/NetworkStack/tests/integration", + ], + lint: { strict_updatability_linting: true }, +} + +android_test { + name: "NetworkStaticLibTests", + certificate: "platform", + static_libs: [ + "NetworkStaticLibTestsLib", + ], + jni_libs: [ + // For mockito extended + "libdexmakerjvmtiagent", + "libstaticjvmtiagent", + ], + jarjar_rules: "jarjar-rules.txt", + test_suites: ["device-tests"], + lint: { strict_updatability_linting: true }, +} diff --git a/staticlibs/tests/unit/AndroidManifest.xml b/staticlibs/tests/unit/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..84a20a277b3e8ebf87e86bcca5952925863209ef --- /dev/null +++ b/staticlibs/tests/unit/AndroidManifest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.frameworks.libnet.tests"> + + <uses-permission android:name="android.permission.INTERNET" /> + + <application android:debuggable="true"> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.frameworks.libnet.tests" + android:label="Network Static Library Tests" /> +</manifest> diff --git a/staticlibs/tests/unit/jarjar-rules.txt b/staticlibs/tests/unit/jarjar-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..e032ae503072bd650a7317ab46e24b2cf0dd2eb1 --- /dev/null +++ b/staticlibs/tests/unit/jarjar-rules.txt @@ -0,0 +1 @@ +rule com.android.net.module.util.** com.android.net.moduletests.util.@1 diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/ArpPacketTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/ArpPacketTest.java new file mode 100644 index 0000000000000000000000000000000000000000..e25d5548929d56592ec1483104dd137b008bec2d --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/ArpPacketTest.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util; + +import static com.android.net.module.util.NetworkStackConstants.ARP_REQUEST; +import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN; +import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST; +import static com.android.testutils.MiscAsserts.assertThrows; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import android.net.InetAddresses; +import android.net.MacAddress; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.net.module.util.arp.ArpPacket; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet4Address; +import java.nio.ByteBuffer; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public final class ArpPacketTest { + + private static final Inet4Address TEST_IPV4_ADDR = + (Inet4Address) InetAddresses.parseNumericAddress("192.168.1.2"); + private static final Inet4Address INADDR_ANY = + (Inet4Address) InetAddresses.parseNumericAddress("0.0.0.0"); + private static final byte[] TEST_SENDER_MAC_ADDR = new byte[] { + 0x00, 0x1a, 0x11, 0x22, 0x33, 0x33 }; + private static final byte[] TEST_TARGET_MAC_ADDR = new byte[] { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + private static final byte[] TEST_ARP_PROBE = new byte[] { + // dst mac address + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + // src mac address + (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33, + // ether type + (byte) 0x08, (byte) 0x06, + // hardware type + (byte) 0x00, (byte) 0x01, + // protocol type + (byte) 0x08, (byte) 0x00, + // hardware address size + (byte) 0x06, + // protocol address size + (byte) 0x04, + // opcode + (byte) 0x00, (byte) 0x01, + // sender mac address + (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33, + // sender IP address + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // target mac address + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // target IP address + (byte) 0xc0, (byte) 0xa8, (byte) 0x01, (byte) 0x02, + }; + + private static final byte[] TEST_ARP_ANNOUNCE = new byte[] { + // dst mac address + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + // src mac address + (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33, + // ether type + (byte) 0x08, (byte) 0x06, + // hardware type + (byte) 0x00, (byte) 0x01, + // protocol type + (byte) 0x08, (byte) 0x00, + // hardware address size + (byte) 0x06, + // protocol address size + (byte) 0x04, + // opcode + (byte) 0x00, (byte) 0x01, + // sender mac address + (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33, + // sender IP address + (byte) 0xc0, (byte) 0xa8, (byte) 0x01, (byte) 0x02, + // target mac address + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // target IP address + (byte) 0xc0, (byte) 0xa8, (byte) 0x01, (byte) 0x02, + }; + + private static final byte[] TEST_ARP_PROBE_TRUNCATED = new byte[] { + // dst mac address + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + // src mac address + (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33, + // ether type + (byte) 0x08, (byte) 0x06, + // hardware type + (byte) 0x00, (byte) 0x01, + // protocol type + (byte) 0x08, (byte) 0x00, + // hardware address size + (byte) 0x06, + // protocol address size + (byte) 0x04, + // opcode + (byte) 0x00, + }; + + private static final byte[] TEST_ARP_PROBE_TRUNCATED_MAC = new byte[] { + // dst mac address + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + // src mac address + (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33, + // ether type + (byte) 0x08, (byte) 0x06, + // hardware type + (byte) 0x00, (byte) 0x01, + // protocol type + (byte) 0x08, (byte) 0x00, + // hardware address size + (byte) 0x06, + // protocol address size + (byte) 0x04, + // opcode + (byte) 0x00, (byte) 0x01, + // sender mac address + (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, + }; + + @Test + public void testBuildArpProbePacket() throws Exception { + final ByteBuffer arpProbe = ArpPacket.buildArpPacket(ETHER_BROADCAST, + TEST_SENDER_MAC_ADDR, TEST_IPV4_ADDR.getAddress(), new byte[ETHER_ADDR_LEN], + INADDR_ANY.getAddress(), (short) ARP_REQUEST); + assertArrayEquals(arpProbe.array(), TEST_ARP_PROBE); + } + + @Test + public void testBuildArpAnnouncePacket() throws Exception { + final ByteBuffer arpAnnounce = ArpPacket.buildArpPacket(ETHER_BROADCAST, + TEST_SENDER_MAC_ADDR, TEST_IPV4_ADDR.getAddress(), new byte[ETHER_ADDR_LEN], + TEST_IPV4_ADDR.getAddress(), (short) ARP_REQUEST); + assertArrayEquals(arpAnnounce.array(), TEST_ARP_ANNOUNCE); + } + + @Test + public void testParseArpProbePacket() throws Exception { + final ArpPacket packet = ArpPacket.parseArpPacket(TEST_ARP_PROBE, TEST_ARP_PROBE.length); + assertEquals(packet.opCode, ARP_REQUEST); + assertEquals(packet.senderHwAddress, MacAddress.fromBytes(TEST_SENDER_MAC_ADDR)); + assertEquals(packet.targetHwAddress, MacAddress.fromBytes(TEST_TARGET_MAC_ADDR)); + assertEquals(packet.senderIp, INADDR_ANY); + assertEquals(packet.targetIp, TEST_IPV4_ADDR); + } + + @Test + public void testParseArpAnnouncePacket() throws Exception { + final ArpPacket packet = ArpPacket.parseArpPacket(TEST_ARP_ANNOUNCE, + TEST_ARP_ANNOUNCE.length); + assertEquals(packet.opCode, ARP_REQUEST); + assertEquals(packet.senderHwAddress, MacAddress.fromBytes(TEST_SENDER_MAC_ADDR)); + assertEquals(packet.targetHwAddress, MacAddress.fromBytes(TEST_TARGET_MAC_ADDR)); + assertEquals(packet.senderIp, TEST_IPV4_ADDR); + assertEquals(packet.targetIp, TEST_IPV4_ADDR); + } + + @Test + public void testParseArpPacket_invalidByteBufferParameters() throws Exception { + assertThrows(ArpPacket.ParseException.class, () -> ArpPacket.parseArpPacket( + TEST_ARP_PROBE, 0)); + } + + @Test + public void testParseArpPacket_truncatedPacket() throws Exception { + assertThrows(ArpPacket.ParseException.class, () -> ArpPacket.parseArpPacket( + TEST_ARP_PROBE_TRUNCATED, TEST_ARP_PROBE_TRUNCATED.length)); + } + + @Test + public void testParseArpPacket_truncatedMacAddress() throws Exception { + assertThrows(ArpPacket.ParseException.class, () -> ArpPacket.parseArpPacket( + TEST_ARP_PROBE_TRUNCATED_MAC, TEST_ARP_PROBE_TRUNCATED.length)); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/BitUtilsTests.kt b/staticlibs/tests/unit/src/com/android/net/module/util/BitUtilsTests.kt new file mode 100644 index 0000000000000000000000000000000000000000..49940ea85cd1e51b443f9cc9c13c59c10093e631 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/BitUtilsTests.kt @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util + +import com.android.net.module.util.BitUtils.appendStringRepresentationOfBitMaskToStringBuilder +import com.android.net.module.util.BitUtils.describeDifferences +import com.android.net.module.util.BitUtils.packBits +import com.android.net.module.util.BitUtils.unpackBits +import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlin.test.assertTrue +import org.junit.Test + +class BitUtilsTests { + @Test + fun testBitPackingTestCase() { + runBitPackingTestCase(0, intArrayOf()) + runBitPackingTestCase(1, intArrayOf(0)) + runBitPackingTestCase(3, intArrayOf(0, 1)) + runBitPackingTestCase(4, intArrayOf(2)) + runBitPackingTestCase(63, intArrayOf(0, 1, 2, 3, 4, 5)) + runBitPackingTestCase(Long.MAX_VALUE.inv(), intArrayOf(63)) + runBitPackingTestCase(Long.MAX_VALUE.inv() + 1, intArrayOf(0, 63)) + runBitPackingTestCase(Long.MAX_VALUE.inv() + 2, intArrayOf(1, 63)) + } + + fun runBitPackingTestCase(packedBits: Long, bits: IntArray) { + assertEquals(packedBits, packBits(bits)) + assertTrue(bits contentEquals unpackBits(packedBits)) + } + + @Test + fun testAppendStringRepresentationOfBitMaskToStringBuilder() { + runTestAppendStringRepresentationOfBitMaskToStringBuilder("", 0) + runTestAppendStringRepresentationOfBitMaskToStringBuilder("BIT0", 0b1) + runTestAppendStringRepresentationOfBitMaskToStringBuilder("BIT1&BIT2&BIT4", 0b10110) + runTestAppendStringRepresentationOfBitMaskToStringBuilder( + "BIT0&BIT60&BIT61&BIT62&BIT63", + (0b11110000_00000000_00000000_00000000 shl 32) + + 0b00000000_00000000_00000000_00000001) + } + + fun runTestAppendStringRepresentationOfBitMaskToStringBuilder(expected: String, bitMask: Long) { + StringBuilder().let { + appendStringRepresentationOfBitMaskToStringBuilder(it, bitMask, { i -> "BIT$i" }, "&") + assertEquals(expected, it.toString()) + } + } + + @Test + fun testDescribeDifferences() { + fun describe(a: Long, b: Long) = describeDifferences(a, b, Integer::toString) + assertNull(describe(0, 0)) + assertNull(describe(5, 5)) + assertNull(describe(Long.MAX_VALUE, Long.MAX_VALUE)) + + assertEquals("+0", describe(0, 1)) + assertEquals("-0", describe(1, 0)) + + assertEquals("+0+2", describe(0, 5)) + assertEquals("+2", describe(1, 5)) + assertEquals("-0+2", describe(1, 4)) + + fun makeField(vararg i: Int) = i.sumOf { 1L shl it } + assertEquals("-0-4-6-9+1+3+11", describe(makeField(0, 4, 6, 9), makeField(1, 3, 11))) + assertEquals("-1-5-9+6+8", describe(makeField(0, 1, 3, 4, 5, 9), makeField(0, 3, 4, 6, 8))) + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/BpfDumpTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/BpfDumpTest.java new file mode 100644 index 0000000000000000000000000000000000000000..a66dacd9d25e9498abfd85cd2330b4922e15b106 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/BpfDumpTest.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util; + +import static android.system.OsConstants.EPERM; +import static android.system.OsConstants.R_OK; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doThrow; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +import android.system.ErrnoException; +import android.system.Os; +import android.util.Pair; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.testutils.TestBpfMap; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoSession; + +import java.io.PrintWriter; +import java.io.StringWriter; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class BpfDumpTest { + private static final int TEST_KEY = 123; + private static final String TEST_KEY_BASE64 = "ewAAAA=="; + private static final int TEST_VAL = 456; + private static final String TEST_VAL_BASE64 = "yAEAAA=="; + private static final String BASE64_DELIMITER = ","; + private static final String TEST_KEY_VAL_BASE64 = + TEST_KEY_BASE64 + BASE64_DELIMITER + TEST_VAL_BASE64; + private static final String INVALID_BASE64_STRING = "Map is null"; + + @Test + public void testToBase64EncodedString() { + final Struct.S32 key = new Struct.S32(TEST_KEY); + final Struct.S32 value = new Struct.S32(TEST_VAL); + + // Verified in python: + // import base64 + // print(base64.b64encode(b'\x7b\x00\x00\x00')) # key: ewAAAA== (TEST_KEY_BASE64) + // print(base64.b64encode(b'\xc8\x01\x00\x00')) # value: yAEAAA== (TEST_VAL_BASE64) + assertEquals("7B000000", HexDump.toHexString(key.writeToBytes())); + assertEquals("C8010000", HexDump.toHexString(value.writeToBytes())); + assertEquals(TEST_KEY_VAL_BASE64, BpfDump.toBase64EncodedString(key, value)); + } + + @Test + public void testFromBase64EncodedString() { + Pair<Struct.S32, Struct.S32> decodedKeyValue = BpfDump.fromBase64EncodedString( + Struct.S32.class, Struct.S32.class, TEST_KEY_VAL_BASE64); + assertEquals(TEST_KEY, decodedKeyValue.first.val); + assertEquals(TEST_VAL, decodedKeyValue.second.val); + } + + private void assertThrowsIllegalArgumentException(final String testStr) { + assertThrows(IllegalArgumentException.class, + () -> BpfDump.fromBase64EncodedString(Struct.S32.class, Struct.S32.class, testStr)); + } + + @Test + public void testFromBase64EncodedStringInvalidString() { + assertThrowsIllegalArgumentException(INVALID_BASE64_STRING); + assertThrowsIllegalArgumentException(TEST_KEY_BASE64); + assertThrowsIllegalArgumentException( + TEST_KEY_BASE64 + BASE64_DELIMITER + INVALID_BASE64_STRING); + assertThrowsIllegalArgumentException( + INVALID_BASE64_STRING + BASE64_DELIMITER + TEST_VAL_BASE64); + assertThrowsIllegalArgumentException( + INVALID_BASE64_STRING + BASE64_DELIMITER + INVALID_BASE64_STRING); + assertThrowsIllegalArgumentException( + TEST_KEY_VAL_BASE64 + BASE64_DELIMITER + TEST_KEY_BASE64); + } + + private String getDumpMap(final IBpfMap<Struct.S32, Struct.S32> map) { + final StringWriter sw = new StringWriter(); + BpfDump.dumpMap(map, new PrintWriter(sw), "mapName", "header", + (key, val) -> "key=" + key.val + ", val=" + val.val); + return sw.toString(); + } + + @Test + public void testDumpMap() throws Exception { + final IBpfMap<Struct.S32, Struct.S32> map = + new TestBpfMap<>(Struct.S32.class, Struct.S32.class); + map.updateEntry(new Struct.S32(123), new Struct.S32(456)); + + final String dump = getDumpMap(map); + assertEquals(dump, "mapName:\n" + + " header\n" + + " key=123, val=456\n"); + } + + @Test + public void testDumpMapMultipleEntries() throws Exception { + final IBpfMap<Struct.S32, Struct.S32> map = + new TestBpfMap<>(Struct.S32.class, Struct.S32.class); + map.updateEntry(new Struct.S32(123), new Struct.S32(456)); + map.updateEntry(new Struct.S32(789), new Struct.S32(123)); + + final String dump = getDumpMap(map); + assertTrue(dump.contains("mapName:")); + assertTrue(dump.contains("header")); + assertTrue(dump.contains("key=123, val=456")); + assertTrue(dump.contains("key=789, val=123")); + } + + private String getDumpMapStatus(final IBpfMap<Struct.S32, Struct.S32> map) { + final StringWriter sw = new StringWriter(); + BpfDump.dumpMapStatus(map, new PrintWriter(sw), "mapName", "mapPath"); + return sw.toString(); + } + + @Test + public void testGetMapStatus() { + final IBpfMap<Struct.S32, Struct.S32> map = + new TestBpfMap<>(Struct.S32.class, Struct.S32.class); + assertEquals("mapName: OK\n", getDumpMapStatus(map)); + } + + @Test + public void testGetMapStatusNull() { + final MockitoSession session = mockitoSession() + .spyStatic(Os.class) + .startMocking(); + try { + // Os.access succeeds + doReturn(true).when(() -> Os.access("mapPath", R_OK)); + assertEquals("mapName: NULL(map is pinned to mapPath)\n", getDumpMapStatus(null)); + + // Os.access throws EPERM + doThrow(new ErrnoException("", EPERM)).when(() -> Os.access("mapPath", R_OK)); + assertEquals("mapName: NULL(map is not pinned to mapPath: Operation not permitted)\n", + getDumpMapStatus(null)); + } finally { + session.finishMocking(); + } + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/ByteUtilsTests.kt b/staticlibs/tests/unit/src/com/android/net/module/util/ByteUtilsTests.kt new file mode 100644 index 0000000000000000000000000000000000000000..e58adad3ccd2dfc7b532c70b94054c7865f27f01 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/ByteUtilsTests.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util + +import com.android.net.module.util.ByteUtils.indexOf +import com.android.net.module.util.ByteUtils.concat +import org.junit.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertNotSame + +class ByteUtilsTests { + private val EMPTY = byteArrayOf() + private val ARRAY1 = byteArrayOf(1) + private val ARRAY234 = byteArrayOf(2, 3, 4) + + @Test + fun testIndexOf() { + assertEquals(-1, indexOf(EMPTY, 1)) + assertEquals(-1, indexOf(ARRAY1, 2)) + assertEquals(-1, indexOf(ARRAY234, 1)) + assertEquals(0, indexOf(byteArrayOf(-1), -1)) + assertEquals(0, indexOf(ARRAY234, 2)) + assertEquals(1, indexOf(ARRAY234, 3)) + assertEquals(2, indexOf(ARRAY234, 4)) + assertEquals(1, indexOf(byteArrayOf(2, 3, 2, 3), 3)) + } + + @Test + fun testConcat() { + assertContentEquals(EMPTY, concat()) + assertContentEquals(EMPTY, concat(EMPTY)) + assertContentEquals(EMPTY, concat(EMPTY, EMPTY, EMPTY)) + assertContentEquals(ARRAY1, concat(ARRAY1)) + assertNotSame(ARRAY1, concat(ARRAY1)) + assertContentEquals(ARRAY1, concat(EMPTY, ARRAY1, EMPTY)) + assertContentEquals(byteArrayOf(1, 1, 1), concat(ARRAY1, ARRAY1, ARRAY1)) + assertContentEquals(byteArrayOf(1, 2, 3, 4), concat(ARRAY1, ARRAY234)) + } +} \ No newline at end of file diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/CleanupTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/CleanupTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..851d09af69da657d13a35b5e72a5ecfebeda2ca2 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/CleanupTest.kt @@ -0,0 +1,223 @@ +/* + * 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. + */ + +package com.android.net.module.util + +import android.util.Log +import com.android.testutils.tryTest +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertTrue +import kotlin.test.fail + +private val TAG = CleanupTest::class.simpleName + +@RunWith(JUnit4::class) +class CleanupTest { + class TestException1 : Exception() + class TestException2 : Exception() + class TestException3 : Exception() + + @Test + fun testNotThrow() { + var x = 1 + val result = tryTest { + x = 2 + Log.e(TAG, "Do nothing") + 6 + } cleanup { + assertTrue(x == 2) + x = 3 + Log.e(TAG, "Do nothing") + } + assertTrue(x == 3) + assertTrue(result == 6) + } + + @Test + fun testThrowTry() { + var x = 1 + val thrown = assertFailsWith<TestException1> { + tryTest { + x = 2 + throw TestException1() + x = 4 + } cleanup { + assertTrue(x == 2) + x = 3 + Log.e(TAG, "Do nothing") + } + } + assertTrue(thrown.suppressedExceptions.isEmpty()) + assertTrue(x == 3) + } + + @Test + fun testThrowCleanup() { + var x = 1 + val thrown = assertFailsWith<TestException2> { + tryTest { + x = 2 + Log.e(TAG, "Do nothing") + } cleanup { + assertTrue(x == 2) + x = 3 + throw TestException2() + x = 4 + } + } + assertTrue(thrown.suppressedExceptions.isEmpty()) + assertTrue(x == 3) + } + + @Test + fun testThrowBoth() { + var x = 1 + val thrown = assertFailsWith<TestException1> { + tryTest { + x = 2 + throw TestException1() + x = 3 + } cleanup { + assertTrue(x == 2) + x = 4 + throw TestException2() + x = 5 + } + } + assertTrue(thrown.suppressedExceptions[0] is TestException2) + assertTrue(x == 4) + } + + @Test + fun testReturn() { + val resultIfSuccess = 11 + val resultIfException = 12 + fun doTestReturn(crash: Boolean) = tryTest { + if (crash) throw RuntimeException() else resultIfSuccess + }.catch<RuntimeException> { + resultIfException + } cleanup {} + + assertTrue(6 == tryTest { 6 } cleanup { Log.e(TAG, "tested") }) + assertEquals(resultIfSuccess, doTestReturn(crash = false)) + assertEquals(resultIfException, doTestReturn(crash = true)) + } + + @Test + fun testCatch() { + var x = 1 + tryTest { + x = 2 + throw TestException1() + x = 3 + }.catch<TestException1> { + x = 4 + }.catch<TestException2> { + x = 5 + } cleanup { + assertTrue(x == 4) + x = 6 + } + assertTrue(x == 6) + } + + @Test + fun testNotCatch() { + var x = 1 + assertFailsWith<TestException1> { + tryTest { + x = 2 + throw TestException1() + }.catch<TestException2> { + fail("Caught TestException2 instead of TestException1") + } cleanup { + assertTrue(x == 2) + x = 3 + } + } + assertTrue(x == 3) + } + + @Test + fun testThrowInCatch() { + var x = 1 + val thrown = assertFailsWith<TestException2> { + tryTest { + x = 2 + throw TestException1() + }.catch<TestException1> { + x = 3 + throw TestException2() + } cleanup { + assertTrue(x == 3) + x = 4 + } + } + assertTrue(x == 4) + assertTrue(thrown.suppressedExceptions.isEmpty()) + } + + @Test + fun testAssertionErrorInCatch() { + var x = 1 + val thrown = assertFailsWith<AssertionError> { + tryTest { + x = 2 + throw TestException1() + }.catch<TestException1> { + x = 3 + fail("Test failure in catch") + } cleanup { + assertTrue(x == 3) + x = 4 + } + } + assertTrue(x == 4) + assertTrue(thrown.suppressedExceptions.isEmpty()) + } + + @Test + fun testMultipleCleanups() { + var x = 1 + val thrown = assertFailsWith<TestException1> { + tryTest { + x = 2 + throw TestException1() + } cleanupStep { + assertTrue(x == 2) + x = 3 + throw TestException2() + x = 4 + } cleanupStep { + assertTrue(x == 3) + x = 5 + throw TestException3() + x = 6 + } cleanup { + assertTrue(x == 5) + x = 7 + } + } + assertEquals(2, thrown.suppressedExceptions.size) + assertTrue(thrown.suppressedExceptions[0] is TestException2) + assertTrue(thrown.suppressedExceptions[1] is TestException3) + assert(x == 7) + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/CleanupTestJava.java b/staticlibs/tests/unit/src/com/android/net/module/util/CleanupTestJava.java new file mode 100644 index 0000000000000000000000000000000000000000..8a13397b212afb05be105b8d03e752cf26584517 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/CleanupTestJava.java @@ -0,0 +1,121 @@ +/* + * 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. + */ + +package com.android.net.module.util; + +import static com.android.testutils.Cleanup.testAndCleanup; +import static com.android.testutils.MiscAsserts.assertThrows; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.util.Log; + +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicInteger; + +public class CleanupTestJava { + private static final String TAG = CleanupTestJava.class.getSimpleName(); + private static final class TestException1 extends Exception {} + private static final class TestException2 extends Exception {} + private static final class TestException3 extends Exception {} + + @Test + public void testNotThrow() { + final AtomicInteger x = new AtomicInteger(1); + final int a = testAndCleanup(() -> { + x.compareAndSet(1, 2); + Log.e(TAG, "Do nothing"); + return 6; + }, () -> { + x.compareAndSet(2, 3); + Log.e(TAG, "Do nothing"); + }); + assertEquals(3, x.get()); + assertEquals(6, a); + } + + @Test + public void testThrowTry() { + final AtomicInteger x = new AtomicInteger(1); + assertThrows(TestException1.class, () -> + testAndCleanup(() -> { + x.compareAndSet(1, 2); + throw new TestException1(); + // Java refuses to call x.set(3) here because this line is unreachable + }, () -> { + x.compareAndSet(2, 3); + Log.e(TAG, "Do nothing"); + }) + ); + assertEquals(3, x.get()); + } + + @Test + public void testThrowCleanup() { + final AtomicInteger x = new AtomicInteger(1); + assertThrows(TestException2.class, () -> + testAndCleanup(() -> { + x.compareAndSet(1, 2); + Log.e(TAG, "Do nothing"); + }, () -> { + x.compareAndSet(2, 3); + throw new TestException2(); + // Java refuses to call x.set(4) here because this line is unreachable + }) + ); + assertEquals(3, x.get()); + } + + @Test + public void testThrowBoth() { + final AtomicInteger x = new AtomicInteger(1); + assertThrows(TestException1.class, () -> + testAndCleanup(() -> { + x.compareAndSet(1, 2); + throw new TestException1(); + }, () -> { + x.compareAndSet(2, 3); + throw new TestException2(); + }) + ); + assertEquals(3, x.get()); + } + + @Test + public void testMultipleCleanups() { + final AtomicInteger x = new AtomicInteger(1); + final TestException1 exception = assertThrows(TestException1.class, () -> + testAndCleanup(() -> { + x.compareAndSet(1, 2); + throw new TestException1(); + }, () -> { + x.compareAndSet(2, 3); + throw new TestException2(); + }, () -> { + x.compareAndSet(3, 4); + throw new TestException3(); + }, () -> { + x.compareAndSet(4, 5); + }) + ); + assertEquals(2, exception.getSuppressed().length); + assertTrue(exception.getSuppressed()[0] instanceof TestException2); + assertTrue(exception.getSuppressed()[1] instanceof TestException3); + assertEquals(5, x.get()); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..e23f99912ecac588b0598ab095c1a3925b8c2786 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt @@ -0,0 +1,182 @@ +/* + * 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. + */ + +package com.android.net.module.util + +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.testutils.assertThrows +import org.junit.Test +import org.junit.runner.RunWith +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertFalse +import kotlin.test.assertNull +import kotlin.test.assertSame +import kotlin.test.assertTrue + +@RunWith(AndroidJUnit4::class) +@SmallTest +class CollectionUtilsTest { + @Test + fun testAny() { + assertTrue(CollectionUtils.any(listOf("A", "B", "C", "D", "E")) { it == "E" }) + assertFalse(CollectionUtils.any(listOf("A", "B", "C", "D", "E")) { it == "F" }) + assertTrue(CollectionUtils.any(listOf("AA", "BBB")) { it.length >= 3 }) + assertFalse(CollectionUtils.any(listOf("A", "BB", "CCC")) { it.length >= 4 }) + assertFalse(CollectionUtils.any(listOf("A", "BB", "CCC")) { it.length < 0 }) + assertFalse(CollectionUtils.any(listOf<String>()) { true }) + assertFalse(CollectionUtils.any(listOf<String>()) { false }) + assertTrue(CollectionUtils.any(listOf("A")) { true }) + assertFalse(CollectionUtils.any(listOf("A")) { false }) + } + + @Test + fun testIndexOf() { + assertEquals(4, CollectionUtils.indexOf(listOf("A", "B", "C", "D", "E")) { it == "E" }) + assertEquals(0, CollectionUtils.indexOf(listOf("A", "B", "C", "D", "E")) { it == "A" }) + assertEquals(1, CollectionUtils.indexOf(listOf("AA", "BBB", "CCCC")) { it.length >= 3 }) + assertEquals(1, CollectionUtils.indexOf(listOf("AA", null, "CCCC")) { it == null }) + assertEquals(1, CollectionUtils.indexOf(listOf(null, "CCCC")) { it != null }) + } + + @Test + fun testIndexOfSubArray() { + val haystack = byteArrayOf(1, 2, 3, 4, 5) + assertEquals(2, CollectionUtils.indexOfSubArray(haystack, byteArrayOf(3, 4))) + assertEquals(3, CollectionUtils.indexOfSubArray(haystack, byteArrayOf(4, 5))) + assertEquals(4, CollectionUtils.indexOfSubArray(haystack, byteArrayOf(5))) + assertEquals(-1, CollectionUtils.indexOfSubArray(haystack, byteArrayOf(3, 2))) + assertEquals(0, CollectionUtils.indexOfSubArray(haystack, byteArrayOf())) + assertEquals(-1, CollectionUtils.indexOfSubArray(byteArrayOf(), byteArrayOf(3, 2))) + assertEquals(0, CollectionUtils.indexOfSubArray(byteArrayOf(), byteArrayOf())) + } + + @Test + fun testAll() { + assertFalse(CollectionUtils.all(listOf("A", "B", "C", "D", "E")) { it != "E" }) + assertTrue(CollectionUtils.all(listOf("A", "B", "C", "D", "E")) { it != "F" }) + assertFalse(CollectionUtils.all(listOf("A", "BB", "CCC")) { it.length > 2 }) + assertTrue(CollectionUtils.all(listOf("A", "BB", "CCC")) { it.length >= 1 }) + assertTrue(CollectionUtils.all(listOf("A", "BB", "CCC")) { it.length < 4 }) + assertTrue(CollectionUtils.all(listOf<String>()) { true }) + assertTrue(CollectionUtils.all(listOf<String>()) { false }) + assertTrue(CollectionUtils.all(listOf(1)) { true }) + assertFalse(CollectionUtils.all(listOf(1)) { false }) + } + + @Test + fun testContains() { + assertTrue(CollectionUtils.contains(shortArrayOf(10, 20, 30), 10)) + assertTrue(CollectionUtils.contains(shortArrayOf(10, 20, 30), 30)) + assertFalse(CollectionUtils.contains(shortArrayOf(10, 20, 30), 40)) + assertFalse(CollectionUtils.contains(null, 10.toShort())) + assertTrue(CollectionUtils.contains(intArrayOf(10, 20, 30), 10)) + assertTrue(CollectionUtils.contains(intArrayOf(10, 20, 30), 30)) + assertFalse(CollectionUtils.contains(intArrayOf(10, 20, 30), 40)) + assertFalse(CollectionUtils.contains(null, 10.toInt())) + assertTrue(CollectionUtils.contains(arrayOf("A", "B", "C"), "A")) + assertTrue(CollectionUtils.contains(arrayOf("A", "B", "C"), "C")) + assertFalse(CollectionUtils.contains(arrayOf("A", "B", "C"), "D")) + assertFalse(CollectionUtils.contains(null, "A")) + + val list = listOf("A", "B", "Ab", "C", "D", "E", "A", "E") + assertTrue(CollectionUtils.contains(list) { it.length == 2 }) + assertFalse(CollectionUtils.contains(list) { it.length < 1 }) + assertTrue(CollectionUtils.contains(list) { it > "A" }) + assertFalse(CollectionUtils.contains(list) { it > "F" }) + } + + @Test + fun testTotal() { + assertEquals(10, CollectionUtils.total(longArrayOf(3, 6, 1))) + assertEquals(10, CollectionUtils.total(longArrayOf(6, 1, 3))) + assertEquals(10, CollectionUtils.total(longArrayOf(1, 3, 6))) + assertEquals(3, CollectionUtils.total(longArrayOf(1, 1, 1))) + assertEquals(0, CollectionUtils.total(null)) + } + + @Test + fun testFindFirstFindLast() { + val listAE = listOf("A", "B", "C", "D", "E") + assertSame(CollectionUtils.findFirst(listAE) { it == "A" }, listAE[0]) + assertSame(CollectionUtils.findFirst(listAE) { it == "B" }, listAE[1]) + assertSame(CollectionUtils.findFirst(listAE) { it == "E" }, listAE[4]) + assertNull(CollectionUtils.findFirst(listAE) { it == "F" }) + assertSame(CollectionUtils.findLast(listAE) { it == "A" }, listAE[0]) + assertSame(CollectionUtils.findLast(listAE) { it == "B" }, listAE[1]) + assertSame(CollectionUtils.findLast(listAE) { it == "E" }, listAE[4]) + assertNull(CollectionUtils.findLast(listAE) { it == "F" }) + + val listMulti = listOf("A", "B", "A", "C", "D", "E", "A", "E") + assertSame(CollectionUtils.findFirst(listMulti) { it == "A" }, listMulti[0]) + assertSame(CollectionUtils.findFirst(listMulti) { it == "B" }, listMulti[1]) + assertSame(CollectionUtils.findFirst(listMulti) { it == "E" }, listMulti[5]) + assertNull(CollectionUtils.findFirst(listMulti) { it == "F" }) + assertSame(CollectionUtils.findLast(listMulti) { it == "A" }, listMulti[6]) + assertSame(CollectionUtils.findLast(listMulti) { it == "B" }, listMulti[1]) + assertSame(CollectionUtils.findLast(listMulti) { it == "E" }, listMulti[7]) + assertNull(CollectionUtils.findLast(listMulti) { it == "F" }) + } + + @Test + fun testMap() { + val listAE = listOf("A", "B", "C", "D", "E", null) + assertEquals(listAE.map { "-$it-" }, CollectionUtils.map(listAE) { "-$it-" }) + } + + @Test + fun testZip() { + val listAE = listOf("A", "B", "C", "D", "E") + val list15 = listOf(1, 2, 3, 4, 5) + // Normal #zip returns kotlin.Pair, not android.util.Pair + assertEquals(list15.zip(listAE).map { android.util.Pair(it.first, it.second) }, + CollectionUtils.zip(list15, listAE)) + val listNull = listOf("A", null, "B", "C", "D") + assertEquals(list15.zip(listNull).map { android.util.Pair(it.first, it.second) }, + CollectionUtils.zip(list15, listNull)) + assertEquals(emptyList<android.util.Pair<Int, Int>>(), + CollectionUtils.zip(emptyList<Int>(), emptyList<Int>())) + assertFailsWith<IllegalArgumentException> { + // Different size + CollectionUtils.zip(listOf(1, 2), list15) + } + } + + @Test + fun testAssoc() { + val listADA = listOf("A", "B", "C", "D", "A") + val list15 = listOf(1, 2, 3, 4, 5) + assertEquals(list15.zip(listADA).toMap(), CollectionUtils.assoc(list15, listADA)) + + // Null key is fine + val assoc = CollectionUtils.assoc(listOf(1, 2, null), listOf("A", "B", "C")) + assertEquals("C", assoc[null]) + + assertFailsWith<IllegalArgumentException> { + // Same key multiple times + CollectionUtils.assoc(listOf("A", "B", "A"), listOf(1, 2, 3)) + } + assertFailsWith<IllegalArgumentException> { + // Same key multiple times, but it's null + CollectionUtils.assoc(listOf(null, "B", null), listOf(1, 2, 3)) + } + assertFailsWith<IllegalArgumentException> { + // Different size + CollectionUtils.assoc(listOf(1, 2), list15) + } + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/ConnectivityUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/ConnectivityUtilsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..8af01965faa10fb71499fe298584ec5dda5662ad --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/ConnectivityUtilsTest.java @@ -0,0 +1,46 @@ +/* + * 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. + */ + +package com.android.net.module.util; + +import static android.net.InetAddresses.parseNumericAddress; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import static com.android.net.module.util.ConnectivityUtils.isIPv6ULA; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Tests for ConnectivityUtils */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ConnectivityUtilsTest { + @Test + public void testIsIPv6ULA() { + assertTrue(isIPv6ULA(parseNumericAddress("fc00::"))); + assertTrue(isIPv6ULA(parseNumericAddress("fc00::1"))); + assertTrue(isIPv6ULA(parseNumericAddress("fc00:1234::5678"))); + assertTrue(isIPv6ULA(parseNumericAddress("fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"))); + + assertFalse(isIPv6ULA(parseNumericAddress("fe00::"))); + assertFalse(isIPv6ULA(parseNumericAddress("2480:1248::123:456"))); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5a96bcbb04a1b0087d838af0195f523d8343be0f --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java @@ -0,0 +1,439 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util; + +import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; +import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; +import static android.provider.DeviceConfig.NAMESPACE_TETHERING; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.net.module.util.FeatureVersions.CONNECTIVITY_MODULE_ID; +import static com.android.net.module.util.FeatureVersions.NETWORK_STACK_MODULE_ID; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.provider.DeviceConfig; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.MockitoSession; + +import java.util.Arrays; + + +/** + * Tests for DeviceConfigUtils. + * + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class DeviceConfigUtilsTest { + private static final String TEST_NAME_SPACE = "connectivity"; + private static final String TEST_EXPERIMENT_FLAG = "experiment_flag"; + private static final int TEST_FLAG_VALUE = 28; + private static final String TEST_FLAG_VALUE_STRING = "28"; + private static final int TEST_DEFAULT_FLAG_VALUE = 0; + private static final int TEST_MAX_FLAG_VALUE = 1000; + private static final int TEST_MIN_FLAG_VALUE = 100; + private static final long TEST_PACKAGE_VERSION = 290000000; + private static final String TEST_PACKAGE_NAME = "test.package.name"; + // The APEX name is the name of the APEX module, as in android.content.pm.ModuleInfo, and is + // used for its mount point in /apex. APEX packages are actually APKs with a different + // file extension, so they have an AndroidManifest: the APEX package name is the package name in + // that manifest, and is reflected in android.content.pm.ApplicationInfo. Contrary to the APEX + // (module) name, different package names are typically used to identify the organization that + // built and signed the APEX modules. + private static final String TEST_APEX_PACKAGE_NAME = "com.prefix.android.tethering"; + private static final String TEST_GO_APEX_PACKAGE_NAME = "com.prefix.android.go.tethering"; + private static final String TEST_CONNRES_PACKAGE_NAME = + "com.prefix.android.connectivity.resources"; + private static final String TEST_NETWORKSTACK_NAME = "com.prefix.android.networkstack"; + private static final String TEST_GO_NETWORKSTACK_NAME = "com.prefix.android.go.networkstack"; + private final PackageInfo mPackageInfo = new PackageInfo(); + private final PackageInfo mApexPackageInfo = new PackageInfo(); + private MockitoSession mSession; + + @Mock private Context mContext; + @Mock private PackageManager mPm; + @Mock private Resources mResources; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mSession = mockitoSession().spyStatic(DeviceConfig.class).startMocking(); + + mPackageInfo.setLongVersionCode(TEST_PACKAGE_VERSION); + mApexPackageInfo.setLongVersionCode(TEST_PACKAGE_VERSION); + + doReturn(mPm).when(mContext).getPackageManager(); + doReturn(TEST_PACKAGE_NAME).when(mContext).getPackageName(); + doThrow(NameNotFoundException.class).when(mPm).getPackageInfo(anyString(), anyInt()); + doReturn(mPackageInfo).when(mPm).getPackageInfo(eq(TEST_PACKAGE_NAME), anyInt()); + doReturn(mApexPackageInfo).when(mPm).getPackageInfo(eq(TEST_APEX_PACKAGE_NAME), anyInt()); + + doReturn(mResources).when(mContext).getResources(); + + final ResolveInfo ri = new ResolveInfo(); + ri.activityInfo = new ActivityInfo(); + ri.activityInfo.applicationInfo = new ApplicationInfo(); + ri.activityInfo.applicationInfo.packageName = TEST_CONNRES_PACKAGE_NAME; + ri.activityInfo.applicationInfo.sourceDir = + "/apex/com.android.tethering/priv-app/ServiceConnectivityResources@version"; + doReturn(Arrays.asList(ri)).when(mPm).queryIntentActivities(argThat( + intent -> intent.getAction().equals(DeviceConfigUtils.RESOURCES_APK_INTENT)), + eq(MATCH_SYSTEM_ONLY)); + } + + @After + public void tearDown() { + mSession.finishMocking(); + DeviceConfigUtils.resetPackageVersionCacheForTest(); + } + + @Test + public void testGetDeviceConfigPropertyInt_Null() { + doReturn(null).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE), + eq(TEST_EXPERIMENT_FLAG))); + assertEquals(TEST_DEFAULT_FLAG_VALUE, DeviceConfigUtils.getDeviceConfigPropertyInt( + TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, + TEST_DEFAULT_FLAG_VALUE /* default value */)); + } + + @Test + public void testGetDeviceConfigPropertyInt_NotNull() { + doReturn(TEST_FLAG_VALUE_STRING).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE), + eq(TEST_EXPERIMENT_FLAG))); + assertEquals(TEST_FLAG_VALUE, DeviceConfigUtils.getDeviceConfigPropertyInt( + TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, + TEST_DEFAULT_FLAG_VALUE /* default value */)); + } + + @Test + public void testGetDeviceConfigPropertyInt_NormalValue() { + doReturn(TEST_FLAG_VALUE_STRING).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE), + eq(TEST_EXPERIMENT_FLAG))); + assertEquals(TEST_FLAG_VALUE, DeviceConfigUtils.getDeviceConfigPropertyInt( + TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, 0 /* minimum value */, + TEST_MAX_FLAG_VALUE /* maximum value */, + TEST_DEFAULT_FLAG_VALUE /* default value */)); + } + + @Test + public void testGetDeviceConfigPropertyInt_NullValue() { + doReturn(null).when(() -> DeviceConfig.getProperty( + eq(TEST_NAME_SPACE), eq(TEST_EXPERIMENT_FLAG))); + assertEquals(TEST_DEFAULT_FLAG_VALUE, DeviceConfigUtils.getDeviceConfigPropertyInt( + TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, 0 /* minimum value */, + TEST_MAX_FLAG_VALUE /* maximum value */, + TEST_DEFAULT_FLAG_VALUE /* default value */)); + } + + @Test + public void testGetDeviceConfigPropertyInt_OverMaximumValue() { + doReturn(Integer.toString(TEST_MAX_FLAG_VALUE + 10)).when(() -> DeviceConfig.getProperty( + eq(TEST_NAME_SPACE), eq(TEST_EXPERIMENT_FLAG))); + assertEquals(TEST_DEFAULT_FLAG_VALUE, DeviceConfigUtils.getDeviceConfigPropertyInt( + TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, TEST_MIN_FLAG_VALUE /* minimum value */, + TEST_MAX_FLAG_VALUE /* maximum value */, + TEST_DEFAULT_FLAG_VALUE /* default value */)); + } + + @Test + public void testGetDeviceConfigPropertyInt_EqualsMaximumValue() { + doReturn(Integer.toString(TEST_MAX_FLAG_VALUE)).when(() -> DeviceConfig.getProperty( + eq(TEST_NAME_SPACE), eq(TEST_EXPERIMENT_FLAG))); + assertEquals(TEST_MAX_FLAG_VALUE, DeviceConfigUtils.getDeviceConfigPropertyInt( + TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, TEST_MIN_FLAG_VALUE /* minimum value */, + TEST_MAX_FLAG_VALUE /* maximum value */, + TEST_DEFAULT_FLAG_VALUE /* default value */)); + } + + @Test + public void testGetDeviceConfigPropertyInt_BelowMinimumValue() { + doReturn(Integer.toString(TEST_MIN_FLAG_VALUE - 10)).when(() -> DeviceConfig.getProperty( + eq(TEST_NAME_SPACE), eq(TEST_EXPERIMENT_FLAG))); + assertEquals(TEST_DEFAULT_FLAG_VALUE, DeviceConfigUtils.getDeviceConfigPropertyInt( + TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, TEST_MIN_FLAG_VALUE /* minimum value */, + TEST_MAX_FLAG_VALUE /* maximum value */, + TEST_DEFAULT_FLAG_VALUE /* default value */)); + } + + @Test + public void testGetDeviceConfigPropertyInt_EqualsMinimumValue() { + doReturn(Integer.toString(TEST_MIN_FLAG_VALUE)).when(() -> DeviceConfig.getProperty( + eq(TEST_NAME_SPACE), eq(TEST_EXPERIMENT_FLAG))); + assertEquals(TEST_MIN_FLAG_VALUE, DeviceConfigUtils.getDeviceConfigPropertyInt( + TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, TEST_MIN_FLAG_VALUE /* minimum value */, + TEST_MAX_FLAG_VALUE /* maximum value */, + TEST_DEFAULT_FLAG_VALUE /* default value */)); + } + + @Test + public void testGetDeviceConfigPropertyBoolean_Null() { + doReturn(null).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE), + eq(TEST_EXPERIMENT_FLAG))); + assertFalse(DeviceConfigUtils.getDeviceConfigPropertyBoolean( + TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, + false /* default value */)); + } + + @Test + public void testGetDeviceConfigPropertyBoolean_NotNull() { + doReturn("true").when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE), + eq(TEST_EXPERIMENT_FLAG))); + assertTrue(DeviceConfigUtils.getDeviceConfigPropertyBoolean( + TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, + false /* default value */)); + } + + @Test + public void testIsNetworkStackFeatureEnabled() { + doReturn(TEST_FLAG_VALUE_STRING).when(() -> DeviceConfig.getProperty(NAMESPACE_CONNECTIVITY, + TEST_EXPERIMENT_FLAG)); + assertTrue(DeviceConfigUtils.isNetworkStackFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG)); + } + + @Test + public void testIsTetheringFeatureEnabled() { + doReturn(TEST_FLAG_VALUE_STRING).when(() -> DeviceConfig.getProperty(NAMESPACE_TETHERING, + TEST_EXPERIMENT_FLAG)); + assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG)); + } + + @Test + public void testFeatureDefaultEnabled() { + doReturn(null).when(() -> DeviceConfig.getProperty(NAMESPACE_CONNECTIVITY, + TEST_EXPERIMENT_FLAG)); + doReturn(null).when(() -> DeviceConfig.getProperty(NAMESPACE_TETHERING, + TEST_EXPERIMENT_FLAG)); + assertFalse(DeviceConfigUtils.isNetworkStackFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG)); + assertFalse(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG)); + } + + @Test + public void testFeatureIsEnabledWithException() throws Exception { + doThrow(NameNotFoundException.class).when(mPm).getPackageInfo(anyString(), anyInt()); + + // Feature should be enabled by flag value "1". + doReturn("1").when(() -> DeviceConfig.getProperty(NAMESPACE_CONNECTIVITY, + TEST_EXPERIMENT_FLAG)); + doReturn("1").when(() -> DeviceConfig.getProperty(NAMESPACE_TETHERING, + TEST_EXPERIMENT_FLAG)); + assertTrue(DeviceConfigUtils.isNetworkStackFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG)); + assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG)); + + // Feature should be disabled by flag value "999999999". + doReturn("999999999").when(() -> DeviceConfig.getProperty(NAMESPACE_CONNECTIVITY, + TEST_EXPERIMENT_FLAG)); + doReturn("999999999").when(() -> DeviceConfig.getProperty(NAMESPACE_TETHERING, + TEST_EXPERIMENT_FLAG)); + assertFalse(DeviceConfigUtils.isNetworkStackFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG)); + assertFalse(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG)); + + // Follow defaultEnabled if the flag is not set + doReturn(null).when(() -> DeviceConfig.getProperty(NAMESPACE_CONNECTIVITY, + TEST_EXPERIMENT_FLAG)); + doReturn(null).when(() -> DeviceConfig.getProperty(NAMESPACE_TETHERING, + TEST_EXPERIMENT_FLAG)); + assertFalse(DeviceConfigUtils.isNetworkStackFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG, + false /* defaultEnabled */)); + assertTrue(DeviceConfigUtils.isNetworkStackFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG, + true /* defaultEnabled */)); + assertFalse(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG)); + } + + @Test + public void testFeatureIsEnabledOnGo() throws Exception { + doThrow(NameNotFoundException.class).when(mPm).getPackageInfo( + eq(TEST_APEX_PACKAGE_NAME), anyInt()); + doReturn(mApexPackageInfo).when(mPm).getPackageInfo( + eq(TEST_GO_APEX_PACKAGE_NAME), anyInt()); + doReturn("0").when(() -> DeviceConfig.getProperty( + NAMESPACE_CONNECTIVITY, TEST_EXPERIMENT_FLAG)); + doReturn("0").when(() -> DeviceConfig.getProperty( + NAMESPACE_TETHERING, TEST_EXPERIMENT_FLAG)); + + assertFalse(DeviceConfigUtils.isNetworkStackFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG)); + assertFalse(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG)); + + doReturn(TEST_FLAG_VALUE_STRING).when(() -> DeviceConfig.getProperty(NAMESPACE_TETHERING, + TEST_EXPERIMENT_FLAG)); + assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG)); + } + + @Test + public void testIsNetworkStackFeatureEnabledCaching() throws Exception { + doReturn(TEST_FLAG_VALUE_STRING).when(() -> DeviceConfig.getProperty(NAMESPACE_CONNECTIVITY, + TEST_EXPERIMENT_FLAG)); + assertTrue(DeviceConfigUtils.isNetworkStackFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG)); + assertTrue(DeviceConfigUtils.isNetworkStackFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG)); + + // Package info is only queried once + verify(mContext, times(1)).getPackageManager(); + verify(mContext, times(1)).getPackageName(); + verify(mPm, times(1)).getPackageInfo(anyString(), anyInt()); + } + + @Test + public void testIsTetheringFeatureEnabledCaching() throws Exception { + doReturn(TEST_FLAG_VALUE_STRING).when(() -> DeviceConfig.getProperty(NAMESPACE_TETHERING, + TEST_EXPERIMENT_FLAG)); + assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG)); + assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG)); + + // Package info is only queried once + verify(mPm, times(1)).getPackageInfo(anyString(), anyInt()); + verify(mContext, never()).getPackageName(); + } + + @Test + public void testGetResBooleanConfig() { + final int someResId = 1234; + doReturn(true).when(mResources).getBoolean(someResId); + assertTrue(DeviceConfigUtils.getResBooleanConfig(mContext, someResId, false)); + doReturn(false).when(mResources).getBoolean(someResId); + assertFalse(DeviceConfigUtils.getResBooleanConfig(mContext, someResId, false)); + doThrow(new Resources.NotFoundException()).when(mResources).getBoolean(someResId); + assertFalse(DeviceConfigUtils.getResBooleanConfig(mContext, someResId, false)); + } + + @Test + public void testGetResIntegerConfig() { + final int someResId = 1234; + doReturn(2097).when(mResources).getInteger(someResId); + assertEquals(2097, DeviceConfigUtils.getResIntegerConfig(mContext, someResId, 2098)); + doThrow(new Resources.NotFoundException()).when(mResources).getInteger(someResId); + assertEquals(2098, DeviceConfigUtils.getResIntegerConfig(mContext, someResId, 2098)); + } + + @Test + public void testGetNetworkStackModuleVersionCaching() throws Exception { + final PackageInfo networkStackPackageInfo = new PackageInfo(); + networkStackPackageInfo.setLongVersionCode(TEST_PACKAGE_VERSION); + doReturn(networkStackPackageInfo).when(mPm).getPackageInfo( + eq(TEST_NETWORKSTACK_NAME), anyInt()); + assertEquals(TEST_PACKAGE_VERSION, + DeviceConfigUtils.getNetworkStackModuleVersion(mContext)); + + assertEquals(TEST_PACKAGE_VERSION, + DeviceConfigUtils.getNetworkStackModuleVersion(mContext)); + // Package info is only queried once + verify(mPm, times(1)).getPackageInfo(anyString(), anyInt()); + verify(mContext, never()).getPackageName(); + } + + @Test + public void testGetNetworkStackModuleVersionOnNonMainline() { + assertEquals(DeviceConfigUtils.DEFAULT_PACKAGE_VERSION, + DeviceConfigUtils.getNetworkStackModuleVersion(mContext)); + } + + @Test + public void testGetNetworkStackModuleVersion() throws Exception { + final PackageInfo networkStackPackageInfo = new PackageInfo(); + final PackageInfo goNetworkStackPackageInfo = new PackageInfo(); + networkStackPackageInfo.setLongVersionCode(TEST_PACKAGE_VERSION); + goNetworkStackPackageInfo.setLongVersionCode(TEST_PACKAGE_VERSION + 1); + doReturn(goNetworkStackPackageInfo).when(mPm).getPackageInfo( + eq(TEST_NETWORKSTACK_NAME), anyInt()); + // Verify the returned value is go module version. + assertEquals(TEST_PACKAGE_VERSION + 1, + DeviceConfigUtils.getNetworkStackModuleVersion(mContext)); + } + + @Test + public void testIsFeatureSupported_networkStackFeature() throws Exception { + // Supported for DEFAULT_PACKAGE_VERSION + assertTrue(DeviceConfigUtils.isFeatureSupported( + mContext, TEST_PACKAGE_VERSION + NETWORK_STACK_MODULE_ID)); + + final PackageInfo networkStackPackageInfo = new PackageInfo(); + networkStackPackageInfo.setLongVersionCode(TEST_PACKAGE_VERSION); + doReturn(networkStackPackageInfo).when(mPm).getPackageInfo( + eq(TEST_NETWORKSTACK_NAME), anyInt()); + + assertTrue(DeviceConfigUtils.isFeatureSupported( + mContext, TEST_PACKAGE_VERSION + NETWORK_STACK_MODULE_ID)); + assertFalse(DeviceConfigUtils.isFeatureSupported( + mContext, TEST_PACKAGE_VERSION + NETWORK_STACK_MODULE_ID + 1)); + } + + @Test + public void testIsFeatureSupported_tetheringFeature() throws Exception { + assertTrue(DeviceConfigUtils.isFeatureSupported( + mContext, TEST_PACKAGE_VERSION + CONNECTIVITY_MODULE_ID)); + // Return false because feature requires a future version. + assertFalse(DeviceConfigUtils.isFeatureSupported( + mContext, 889900000L + CONNECTIVITY_MODULE_ID)); + } + + @Test + public void testIsFeatureSupported_illegalModule() throws Exception { + assertThrows(IllegalArgumentException.class, + () -> DeviceConfigUtils.isFeatureSupported(mContext, TEST_PACKAGE_VERSION)); + } + + @Test + public void testIsTetheringFeatureNotChickenedOut() throws Exception { + doReturn("0").when(() -> DeviceConfig.getProperty( + eq(NAMESPACE_TETHERING), eq(TEST_EXPERIMENT_FLAG))); + assertTrue(DeviceConfigUtils.isTetheringFeatureNotChickenedOut(TEST_EXPERIMENT_FLAG)); + + doReturn(TEST_FLAG_VALUE_STRING).when( + () -> DeviceConfig.getProperty(eq(NAMESPACE_TETHERING), eq(TEST_EXPERIMENT_FLAG))); + assertFalse(DeviceConfigUtils.isTetheringFeatureNotChickenedOut(TEST_EXPERIMENT_FLAG)); + } + + @Test + public void testIsNetworkStackFeatureNotChickenedOut() throws Exception { + doReturn("0").when(() -> DeviceConfig.getProperty( + eq(NAMESPACE_CONNECTIVITY), eq(TEST_EXPERIMENT_FLAG))); + assertTrue(DeviceConfigUtils.isNetworkStackFeatureNotChickenedOut(TEST_EXPERIMENT_FLAG)); + + doReturn(TEST_FLAG_VALUE_STRING).when( + () -> DeviceConfig.getProperty(eq(NAMESPACE_CONNECTIVITY), + eq(TEST_EXPERIMENT_FLAG))); + assertFalse(DeviceConfigUtils.isNetworkStackFeatureNotChickenedOut(TEST_EXPERIMENT_FLAG)); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketTest.java new file mode 100644 index 0000000000000000000000000000000000000000..28e183a95e4f02777d762420ee1339dd1dbefc2a --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketTest.java @@ -0,0 +1,426 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util; + +import static android.net.DnsResolver.CLASS_IN; +import static android.net.DnsResolver.TYPE_A; +import static android.net.DnsResolver.TYPE_AAAA; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import libcore.net.InetAddressUtils; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class DnsPacketTest { + private static final int TEST_DNS_PACKET_ID = 0x7722; + private static final int TEST_DNS_PACKET_FLAGS = 0x8180; + + private void assertHeaderParses(DnsPacket.DnsHeader header, int id, int flag, + int qCount, int aCount, int nsCount, int arCount) { + assertEquals(header.getId(), id); + assertEquals(header.getFlags(), flag); + assertEquals(header.getRecordCount(DnsPacket.QDSECTION), qCount); + assertEquals(header.getRecordCount(DnsPacket.ANSECTION), aCount); + assertEquals(header.getRecordCount(DnsPacket.NSSECTION), nsCount); + assertEquals(header.getRecordCount(DnsPacket.ARSECTION), arCount); + } + + private void assertRecordParses(DnsPacket.DnsRecord record, String dname, + int dtype, int dclass, int ttl, byte[] rr) { + assertEquals(record.dName, dname); + assertEquals(record.nsType, dtype); + assertEquals(record.nsClass, dclass); + assertEquals(record.ttl, ttl); + assertTrue(Arrays.equals(record.getRR(), rr)); + } + + static class TestDnsPacket extends DnsPacket { + TestDnsPacket(byte[] data) throws DnsPacket.ParseException { + super(data); + } + + TestDnsPacket(@NonNull DnsHeader header, @Nullable ArrayList<DnsRecord> qd, + @Nullable ArrayList<DnsRecord> an) { + super(header, qd, an); + } + + public DnsHeader getHeader() { + return mHeader; + } + public List<DnsRecord> getRecordList(int secType) { + return mRecords[secType]; + } + } + + @Test + public void testNullDisallowed() { + try { + new TestDnsPacket(null); + fail("Exception not thrown for null byte array"); + } catch (DnsPacket.ParseException e) { + } + } + + @Test + public void testV4Answer() throws Exception { + final byte[] v4blob = new byte[] { + /* Header */ + 0x55, 0x66, /* Transaction ID */ + (byte) 0x81, (byte) 0x80, /* Flags */ + 0x00, 0x01, /* Questions */ + 0x00, 0x01, /* Answer RRs */ + 0x00, 0x00, /* Authority RRs */ + 0x00, 0x00, /* Additional RRs */ + /* Queries */ + 0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65, + 0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */ + 0x00, 0x01, /* Type */ + 0x00, 0x01, /* Class */ + /* Answers */ + (byte) 0xc0, 0x0c, /* Name */ + 0x00, 0x01, /* Type */ + 0x00, 0x01, /* Class */ + 0x00, 0x00, 0x01, 0x2b, /* TTL */ + 0x00, 0x04, /* Data length */ + (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 /* Address */ + }; + TestDnsPacket packet = new TestDnsPacket(v4blob); + + // Header part + assertHeaderParses(packet.getHeader(), 0x5566, 0x8180, 1, 1, 0, 0); + + // Record part + List<DnsPacket.DnsRecord> qdRecordList = + packet.getRecordList(DnsPacket.QDSECTION); + assertEquals(qdRecordList.size(), 1); + assertRecordParses(qdRecordList.get(0), "www.google.com", 1, 1, 0, null); + + List<DnsPacket.DnsRecord> anRecordList = + packet.getRecordList(DnsPacket.ANSECTION); + assertEquals(anRecordList.size(), 1); + assertRecordParses(anRecordList.get(0), "www.google.com", 1, 1, 0x12b, + new byte[]{ (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 }); + } + + @Test + public void testV6Answer() throws Exception { + final byte[] v6blob = new byte[] { + /* Header */ + 0x77, 0x22, /* Transaction ID */ + (byte) 0x81, (byte) 0x80, /* Flags */ + 0x00, 0x01, /* Questions */ + 0x00, 0x01, /* Answer RRs */ + 0x00, 0x00, /* Authority RRs */ + 0x00, 0x00, /* Additional RRs */ + /* Queries */ + 0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65, + 0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */ + 0x00, 0x1c, /* Type */ + 0x00, 0x01, /* Class */ + /* Answers */ + (byte) 0xc0, 0x0c, /* Name */ + 0x00, 0x1c, /* Type */ + 0x00, 0x01, /* Class */ + 0x00, 0x00, 0x00, 0x37, /* TTL */ + 0x00, 0x10, /* Data length */ + 0x24, 0x04, 0x68, 0x00, 0x40, 0x05, 0x08, 0x0d, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04 /* Address */ + }; + TestDnsPacket packet = new TestDnsPacket(v6blob); + + // Header part + assertHeaderParses(packet.getHeader(), 0x7722, 0x8180, 1, 1, 0, 0); + + // Record part + List<DnsPacket.DnsRecord> qdRecordList = + packet.getRecordList(DnsPacket.QDSECTION); + assertEquals(qdRecordList.size(), 1); + assertRecordParses(qdRecordList.get(0), "www.google.com", 28, 1, 0, null); + + List<DnsPacket.DnsRecord> anRecordList = + packet.getRecordList(DnsPacket.ANSECTION); + assertEquals(anRecordList.size(), 1); + assertRecordParses(anRecordList.get(0), "www.google.com", 28, 1, 0x37, + new byte[]{ 0x24, 0x04, 0x68, 0x00, 0x40, 0x05, 0x08, 0x0d, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04 }); + } + + /** Verifies that the synthesized {@link DnsPacket.DnsHeader} can be parsed correctly. */ + @Test + public void testDnsHeaderSynthesize() { + final DnsPacket.DnsHeader testHeader = new DnsPacket.DnsHeader(TEST_DNS_PACKET_ID, + TEST_DNS_PACKET_FLAGS, 3 /* qcount */, 5 /* ancount */); + final DnsPacket.DnsHeader actualHeader = new DnsPacket.DnsHeader( + ByteBuffer.wrap(testHeader.getBytes())); + assertEquals(testHeader, actualHeader); + } + + /** Verifies that the synthesized {@link DnsPacket.DnsRecord} can be parsed correctly. */ + @Test + public void testDnsRecordSynthesize() throws IOException { + assertDnsRecordRoundTrip( + DnsPacket.DnsRecord.makeAOrAAAARecord(DnsPacket.ANSECTION, + "test.com", CLASS_IN, 5 /* ttl */, + InetAddressUtils.parseNumericAddress("abcd::fedc"))); + assertDnsRecordRoundTrip(DnsPacket.DnsRecord.makeQuestion("test.com", TYPE_AAAA, CLASS_IN)); + assertDnsRecordRoundTrip(DnsPacket.DnsRecord.makeCNameRecord(DnsPacket.ANSECTION, + "test.com", CLASS_IN, 0 /* ttl */, "example.com")); + } + + /** + * Verifies ttl/rData error handling when parsing + * {@link DnsPacket.DnsRecord} from bytes. + */ + @Test + public void testDnsRecordTTLRDataErrorHandling() throws IOException { + // Verify the constructor ignore ttl/rData of questions even if they are supplied. + final byte[] qdWithTTLRData = new byte[]{ + 0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65, + 0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */ + 0x00, 0x00, /* Type */ + 0x00, 0x01, /* Class */ + 0x00, 0x00, 0x01, 0x2b, /* TTL */ + 0x00, 0x04, /* Data length */ + (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 /* Address */}; + final DnsPacket.DnsRecord questionsFromBytes = + DnsPacket.DnsRecord.parse(DnsPacket.QDSECTION, ByteBuffer.wrap(qdWithTTLRData)); + assertEquals(0, questionsFromBytes.ttl); + assertNull(questionsFromBytes.getRR()); + + // Verify ANSECTION must have rData when constructing. + final byte[] anWithoutTTLRData = new byte[]{ + 0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65, + 0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */ + 0x00, 0x01, /* Type */ + 0x00, 0x01, /* Class */}; + assertThrows(BufferUnderflowException.class, () -> + DnsPacket.DnsRecord.parse(DnsPacket.ANSECTION, ByteBuffer.wrap(anWithoutTTLRData))); + } + + private void assertDnsRecordRoundTrip(DnsPacket.DnsRecord before) + throws IOException { + final DnsPacket.DnsRecord after = DnsPacket.DnsRecord.parse(before.rType, + ByteBuffer.wrap(before.getBytes())); + assertEquals(after, before); + } + + /** Verifies that the synthesized {@link DnsPacket} can be parsed correctly. */ + @Test + public void testDnsPacketSynthesize() throws IOException { + // Ipv4 dns response packet generated by scapy: + // dns_r = scapy.DNS( + // id=0xbeef, + // qr=1, + // qd=scapy.DNSQR(qname="hello.example.com"), + // an=scapy.DNSRR(rrname="hello.example.com", type="CNAME", rdata='test.com') / + // scapy.DNSRR(rrname="hello.example.com", rdata='1.2.3.4')) + // scapy.hexdump(dns_r) + // dns_r.show2() + // Note that since the synthesizing does not support name compression yet, the domain + // name of the sample need to be uncompressed when generating. + final byte[] v4BlobUncompressed = new byte[]{ + /* Header */ + (byte) 0xbe, (byte) 0xef, /* Transaction ID */ + (byte) 0x81, 0x00, /* Flags */ + 0x00, 0x01, /* Questions */ + 0x00, 0x02, /* Answer RRs */ + 0x00, 0x00, /* Authority RRs */ + 0x00, 0x00, /* Additional RRs */ + /* Queries */ + 0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x07, 0x65, 0x78, 0x61, + 0x6D, 0x70, 0x6C, 0x65, 0x03, 0x63, 0x6F, 0x6D, 0x00, /* Name: hello.example.com */ + 0x00, 0x01, /* Type */ + 0x00, 0x01, /* Class */ + /* Answers */ + 0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x07, 0x65, 0x78, 0x61, + 0x6D, 0x70, 0x6C, 0x65, 0x03, 0x63, 0x6F, 0x6D, 0x00, /* Name: hello.example.com */ + 0x00, 0x05, /* Type */ + 0x00, 0x01, /* Class */ + 0x00, 0x00, 0x00, 0x00, /* TTL */ + 0x00, 0x0A, /* Data length */ + 0x04, 0x74, 0x65, 0x73, 0x74, 0x03, 0x63, 0x6F, 0x6D, 0x00, /* Alias: test.com */ + 0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x07, 0x65, 0x78, 0x61, + 0x6D, 0x70, 0x6C, 0x65, 0x03, 0x63, 0x6F, 0x6D, 0x00, /* Name: hello.example.com */ + 0x00, 0x01, /* Type */ + 0x00, 0x01, /* Class */ + 0x00, 0x00, 0x00, 0x00, /* TTL */ + 0x00, 0x04, /* Data length */ + 0x01, 0x02, 0x03, 0x04, /* Address: 1.2.3.4 */ + }; + + // Forge one via constructors. + final DnsPacket.DnsHeader testHeader = new DnsPacket.DnsHeader(0xbeef, + 0x8100, 1 /* qcount */, 2 /* ancount */); + final ArrayList<DnsPacket.DnsRecord> qlist = new ArrayList<>(); + final ArrayList<DnsPacket.DnsRecord> alist = new ArrayList<>(); + qlist.add(DnsPacket.DnsRecord.makeQuestion( + "hello.example.com", TYPE_A, CLASS_IN)); + alist.add(DnsPacket.DnsRecord.makeCNameRecord( + DnsPacket.ANSECTION, "hello.example.com", CLASS_IN, 0 /* ttl */, "test.com")); + alist.add(DnsPacket.DnsRecord.makeAOrAAAARecord( + DnsPacket.ANSECTION, "hello.example.com", CLASS_IN, 0 /* ttl */, + InetAddressUtils.parseNumericAddress("1.2.3.4"))); + final TestDnsPacket testPacket = new TestDnsPacket(testHeader, qlist, alist); + + // Assert content equals in both ways. + assertTrue(Arrays.equals(v4BlobUncompressed, testPacket.getBytes())); + assertEquals(new TestDnsPacket(v4BlobUncompressed), testPacket); + } + + @Test + public void testDnsPacketSynthesize_recordCountMismatch() throws IOException { + final DnsPacket.DnsHeader testHeader = new DnsPacket.DnsHeader(0xbeef, + 0x8100, 1 /* qcount */, 1 /* ancount */); + final ArrayList<DnsPacket.DnsRecord> qlist = new ArrayList<>(); + final ArrayList<DnsPacket.DnsRecord> alist = new ArrayList<>(); + qlist.add(DnsPacket.DnsRecord.makeQuestion( + "hello.example.com", TYPE_A, CLASS_IN)); + + // Assert throws if the supplied answer records fewer than the declared count. + assertThrows(IllegalArgumentException.class, () -> + new TestDnsPacket(testHeader, qlist, alist)); + + // Assert throws if the supplied answer records more than the declared count. + alist.add(DnsPacket.DnsRecord.makeCNameRecord( + DnsPacket.ANSECTION, "hello.example.com", CLASS_IN, 0 /* ttl */, "test.com")); + alist.add(DnsPacket.DnsRecord.makeAOrAAAARecord( + DnsPacket.ANSECTION, "hello.example.com", CLASS_IN, 0 /* ttl */, + InetAddressUtils.parseNumericAddress("1.2.3.4"))); + assertThrows(IllegalArgumentException.class, () -> + new TestDnsPacket(testHeader, qlist, alist)); + + // Assert counts matched if the byte buffer still has data when parsing ended. + final byte[] blobTooMuchData = new byte[]{ + /* Header */ + (byte) 0xbe, (byte) 0xef, /* Transaction ID */ + (byte) 0x81, 0x00, /* Flags */ + 0x00, 0x00, /* Questions */ + 0x00, 0x00, /* Answer RRs */ + 0x00, 0x00, /* Authority RRs */ + 0x00, 0x00, /* Additional RRs */ + /* Queries */ + 0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x07, 0x65, 0x78, 0x61, + 0x6D, 0x70, 0x6C, 0x65, 0x03, 0x63, 0x6F, 0x6D, 0x00, /* Name */ + 0x00, 0x01, /* Type */ + 0x00, 0x01, /* Class */ + }; + final TestDnsPacket packetFromTooMuchData = new TestDnsPacket(blobTooMuchData); + for (int i = 0; i < DnsPacket.NUM_SECTIONS; i++) { + assertEquals(0, packetFromTooMuchData.getRecordList(i).size()); + assertEquals(0, packetFromTooMuchData.getHeader().getRecordCount(i)); + } + + // Assert throws if the byte buffer ended when expecting more records. + final byte[] blobNotEnoughData = new byte[]{ + /* Header */ + (byte) 0xbe, (byte) 0xef, /* Transaction ID */ + (byte) 0x81, 0x00, /* Flags */ + 0x00, 0x01, /* Questions */ + 0x00, 0x02, /* Answer RRs */ + 0x00, 0x00, /* Authority RRs */ + 0x00, 0x00, /* Additional RRs */ + /* Queries */ + 0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x07, 0x65, 0x78, 0x61, + 0x6D, 0x70, 0x6C, 0x65, 0x03, 0x63, 0x6F, 0x6D, 0x00, /* Name */ + 0x00, 0x01, /* Type */ + 0x00, 0x01, /* Class */ + /* Answers */ + 0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x07, 0x65, 0x78, 0x61, + 0x6D, 0x70, 0x6C, 0x65, 0x03, 0x63, 0x6F, 0x6D, 0x00, /* Name */ + 0x00, 0x01, /* Type */ + 0x00, 0x01, /* Class */ + 0x00, 0x00, 0x00, 0x00, /* TTL */ + 0x00, 0x04, /* Data length */ + 0x01, 0x02, 0x03, 0x04, /* Address */ + }; + assertThrows(DnsPacket.ParseException.class, () -> new TestDnsPacket(blobNotEnoughData)); + } + + @Test + public void testEqualsAndHashCode() throws IOException { + // Verify DnsHeader equals and hashCode. + final DnsPacket.DnsHeader testHeader = new DnsPacket.DnsHeader(TEST_DNS_PACKET_ID, + TEST_DNS_PACKET_FLAGS, 1 /* qcount */, 1 /* ancount */); + final DnsPacket.DnsHeader emptyHeader = new DnsPacket.DnsHeader(TEST_DNS_PACKET_ID + 1, + TEST_DNS_PACKET_FLAGS + 0x08, 0 /* qcount */, 0 /* ancount */); + final DnsPacket.DnsHeader headerFromBytes = + new DnsPacket.DnsHeader(ByteBuffer.wrap(testHeader.getBytes())); + assertEquals(testHeader, headerFromBytes); + assertEquals(testHeader.hashCode(), headerFromBytes.hashCode()); + assertNotEquals(testHeader, emptyHeader); + assertNotEquals(testHeader.hashCode(), emptyHeader.hashCode()); + assertNotEquals(headerFromBytes, emptyHeader); + assertNotEquals(headerFromBytes.hashCode(), emptyHeader.hashCode()); + + // Verify DnsRecord equals and hashCode. + final DnsPacket.DnsRecord testQuestion = DnsPacket.DnsRecord.makeQuestion( + "test.com", TYPE_AAAA, CLASS_IN); + final DnsPacket.DnsRecord testAnswer = DnsPacket.DnsRecord.makeCNameRecord( + DnsPacket.ANSECTION, "test.com", CLASS_IN, 9, "www.test.com"); + final DnsPacket.DnsRecord questionFromBytes = DnsPacket.DnsRecord.parse(DnsPacket.QDSECTION, + ByteBuffer.wrap(testQuestion.getBytes())); + assertEquals(testQuestion, questionFromBytes); + assertEquals(testQuestion.hashCode(), questionFromBytes.hashCode()); + assertNotEquals(testQuestion, testAnswer); + assertNotEquals(testQuestion.hashCode(), testAnswer.hashCode()); + assertNotEquals(questionFromBytes, testAnswer); + assertNotEquals(questionFromBytes.hashCode(), testAnswer.hashCode()); + + // Verify DnsPacket equals and hashCode. + final ArrayList<DnsPacket.DnsRecord> qlist = new ArrayList<>(); + final ArrayList<DnsPacket.DnsRecord> alist = new ArrayList<>(); + qlist.add(testQuestion); + alist.add(testAnswer); + final TestDnsPacket testPacket = new TestDnsPacket(testHeader, qlist, alist); + final TestDnsPacket emptyPacket = new TestDnsPacket( + emptyHeader, new ArrayList<>(), new ArrayList<>()); + final TestDnsPacket packetFromBytes = new TestDnsPacket(testPacket.getBytes()); + assertEquals(testPacket, packetFromBytes); + assertEquals(testPacket.hashCode(), packetFromBytes.hashCode()); + assertNotEquals(testPacket, emptyPacket); + assertNotEquals(testPacket.hashCode(), emptyPacket.hashCode()); + assertNotEquals(packetFromBytes, emptyPacket); + assertNotEquals(packetFromBytes.hashCode(), emptyPacket.hashCode()); + + // Verify DnsPacket with empty list. + final TestDnsPacket emptyPacketFromBytes = new TestDnsPacket(emptyPacket.getBytes()); + assertEquals(emptyPacket, emptyPacketFromBytes); + assertEquals(emptyPacket.hashCode(), emptyPacketFromBytes.hashCode()); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketUtilsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..9e1ab8249adbf1e8d4bf43cec43f5b95c8ce37c6 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketUtilsTest.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util; + +import static com.android.net.module.util.DnsPacketUtils.DnsRecordParser; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import android.net.ParseException; +import android.os.Build; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.testutils.DevSdkIgnoreRule; + +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.nio.ByteBuffer; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class DnsPacketUtilsTest { + @Rule + public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule(); + + /** + * Verifies that the compressed NAME field in the answer section of the DNS message is parsed + * successfully when name compression is permitted. Additionally, verifies that a + * {@link DnsPacket.ParseException} is thrown in a hypothetical scenario where name compression + * is not expected. + */ + @Test + public void testParsingAnswerSectionNameCompressed() throws Exception { + final byte[] v4blobNameCompressedAnswer = new byte[] { + /* Header */ + 0x55, 0x66, /* Transaction ID */ + (byte) 0x81, (byte) 0x80, /* Flags */ + 0x00, 0x01, /* Questions */ + 0x00, 0x01, /* Answer RRs */ + 0x00, 0x00, /* Authority RRs */ + 0x00, 0x00, /* Additional RRs */ + /* Queries */ + 0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65, + 0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */ + 0x00, 0x01, /* Type */ + 0x00, 0x01, /* Class */ + /* Answers */ + (byte) 0xc0, 0x0c, /* Name */ + 0x00, 0x01, /* Type */ + 0x00, 0x01, /* Class */ + 0x00, 0x00, 0x01, 0x2b, /* TTL */ + 0x00, 0x04, /* Data length */ + (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 /* Address */ + }; + final int answerOffsetBytePosition = 32; + final ByteBuffer nameCompressedBuf = ByteBuffer.wrap(v4blobNameCompressedAnswer); + + nameCompressedBuf.position(answerOffsetBytePosition); + assertThrows(DnsPacket.ParseException.class, () -> DnsRecordParser.parseName( + nameCompressedBuf, /* depth= */ 0, /* isNameCompressionSupported= */false)); + + nameCompressedBuf.position(answerOffsetBytePosition); + String domainName = DnsRecordParser.parseName( + nameCompressedBuf, /* depth= */ 0, /* isNameCompressionSupported= */true); + assertEquals(domainName, "www.google.com"); + } + + /** + * Verifies that an uncompressed NAME field in the answer section of the DNS message is parsed + * successfully irrespective of whether name compression is permitted. + */ + @Test + public void testParsingAnswerSectionNoNameCompression() throws Exception { + final byte[] v4blobNoNameCompression = new byte[] { + /* Header */ + 0x55, 0x66, /* Transaction ID */ + (byte) 0x81, (byte) 0x80, /* Flags */ + 0x00, 0x01, /* Questions */ + 0x00, 0x01, /* Answer RRs */ + 0x00, 0x00, /* Authority RRs */ + 0x00, 0x00, /* Additional RRs */ + /* Queries */ + 0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65, + 0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */ + 0x00, 0x01, /* Type */ + 0x00, 0x01, /* Class */ + /* Answers */ + 0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65, + 0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */ + 0x00, 0x01, /* Type */ + 0x00, 0x01, /* Class */ + 0x00, 0x00, 0x01, 0x2b, /* TTL */ + 0x00, 0x04, /* Data length */ + (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 /* Address */ + }; + final int answerOffsetBytePosition = 32; + final ByteBuffer notNameCompressedBuf = ByteBuffer.wrap(v4blobNoNameCompression); + + notNameCompressedBuf.position(answerOffsetBytePosition); + String domainName = DnsRecordParser.parseName( + notNameCompressedBuf, /* depth= */ 0, /* isNameCompressionSupported= */ true); + assertEquals(domainName, "www.google.com"); + + notNameCompressedBuf.position(answerOffsetBytePosition); + domainName = DnsRecordParser.parseName( + notNameCompressedBuf, /* depth= */ 0, /* isNameCompressionSupported= */ false); + assertEquals(domainName, "www.google.com"); + } + + // Skip test on R- devices since ParseException only available on S+ devices. + @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) + @Test + public void testDomainNameToLabels() throws Exception { + assertArrayEquals( + new byte[]{3, 'w', 'w', 'w', 6, 'g', 'o', 'o', 'g', 'l', 'e', 3, 'c', 'o', 'm', 0}, + DnsRecordParser.domainNameToLabels("www.google.com")); + assertThrows(ParseException.class, () -> + DnsRecordParser.domainNameToLabels("aaa.")); + assertThrows(ParseException.class, () -> + DnsRecordParser.domainNameToLabels("aaa")); + assertThrows(ParseException.class, () -> + DnsRecordParser.domainNameToLabels(".")); + assertThrows(ParseException.class, () -> + DnsRecordParser.domainNameToLabels("")); + } + + @Test + public void testIsHostName() { + Assert.assertTrue(DnsRecordParser.isHostName("www.google.com")); + Assert.assertFalse(DnsRecordParser.isHostName("com")); + Assert.assertFalse(DnsRecordParser.isHostName("1.2.3.4")); + Assert.assertFalse(DnsRecordParser.isHostName("1234::5678")); + Assert.assertFalse(DnsRecordParser.isHostName(null)); + Assert.assertFalse(DnsRecordParser.isHostName("")); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/DomainUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/DomainUtilsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5eaf2add3601b316376e9eec742aafb4e5fba94a --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/DomainUtilsTest.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import libcore.util.HexEncoding; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class DomainUtilsTest { + @Test + public void testEncodeInvalidDomain() { + byte[] buffer = DomainUtils.encode(".google.com"); + assertNull(buffer); + + buffer = DomainUtils.encode("google.com."); + assertNull(buffer); + + buffer = DomainUtils.encode("-google.com"); + assertNull(buffer); + + buffer = DomainUtils.encode("google.com-"); + assertNull(buffer); + + buffer = DomainUtils.encode("google..com"); + assertNull(buffer); + + buffer = DomainUtils.encode("google!.com"); + assertNull(buffer); + + buffer = DomainUtils.encode("google.o"); + assertNull(buffer); + + buffer = DomainUtils.encode("google,com"); + assertNull(buffer); + } + + @Test + public void testEncodeValidDomainNamesWithoutCompression() { + // Single domain: "google.com" + String suffix = "06676F6F676C6503636F6D00"; + byte[] buffer = DomainUtils.encode("google.com"); + //assertNotNull(buffer); + assertEquals(suffix, HexEncoding.encodeToString(buffer)); + + // Single domain: "google-guest.com" + suffix = "0C676F6F676C652D677565737403636F6D00"; + buffer = DomainUtils.encode("google-guest.com"); + assertNotNull(buffer); + assertEquals(suffix, HexEncoding.encodeToString(buffer)); + + // domain search list: "example.corp.google.com", "corp.google.com", "google.com" + suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com + + "04636F727006676F6F676C6503636F6D00" // corp.google.com + + "06676F6F676C6503636F6D00"; // google.com + buffer = DomainUtils.encode(new String[] { + "example.corp.google.com", "corp.google.com", "google.com"}, + false /* compression */); + assertNotNull(buffer); + assertEquals(suffix, HexEncoding.encodeToString(buffer)); + + + // domain search list: "example.corp.google.com", "corp..google.com"(invalid domain), + // "google.com" + suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com + + "06676F6F676C6503636F6D00"; // google.com + buffer = DomainUtils.encode(new String[] { + "example.corp.google.com", "corp..google.com", "google.com"}, + false /* compression */); + assertNotNull(buffer); + assertEquals(suffix, HexEncoding.encodeToString(buffer)); + + // Invalid domain search list: "corp..google.com", "..google.com" + buffer = DomainUtils.encode(new String[] {"corp..google.com", "..google.com"}, + false /* compression */); + assertEquals(0, buffer.length); + } + + @Test + public void testEncodeValidDomainNamesWithCompression() { + // domain search list: "example.corp.google.com", "corp.google.com", "google.com" + String suffix = + "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com + + "C008" // corp.google.com + + "C00D"; // google.com + byte[] buffer = DomainUtils.encode(new String[] { + "example.corp.google.com", "corp.google.com", "google.com"}, true); + assertNotNull(buffer); + assertEquals(suffix, HexEncoding.encodeToString(buffer)); + + // domain search list: "example.corp.google.com", "a.example.corp.google.com", "google.com" + suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com + + "0161C000" // a.example.corp.google.com + + "C00D"; // google.com + buffer = DomainUtils.encode(new String[] { + "example.corp.google.com", "a.example.corp.google.com", "google.com"}, true); + assertNotNull(buffer); + assertEquals(suffix, HexEncoding.encodeToString(buffer)); + + // domain search list: "example.corp.google.com", "google.com", "gle.com" + suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com + + "C00D" // google.com + + "03676C65C014"; // gle.com + buffer = DomainUtils.encode(new String[] { + "example.corp.google.com", "google.com", "gle.com"}, true); + assertNotNull(buffer); + assertEquals(suffix, HexEncoding.encodeToString(buffer)); + + // domain search list: "example.corp.google.com", "google.com", "google" + suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com + + "C00D"; // google.com + buffer = DomainUtils.encode(new String[] { + "example.corp.google.com", "google.com", "google"}, true); + assertNotNull(buffer); + assertEquals(suffix, HexEncoding.encodeToString(buffer)); + + // domain search list: "example.corp.google.com", "..google.com"(invalid domain), "google" + suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00"; // example.corp.google.com + buffer = DomainUtils.encode(new String[] { + "example.corp.google.com", "..google.com", "google"}, true); + assertNotNull(buffer); + assertEquals(suffix, HexEncoding.encodeToString(buffer)); + + // domain search list: "example.corp.google.com", "suffix.example.edu.cn", "edu.cn" + suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com + + "06737566666978076578616D706C650365647502636E00" // suffix.example.edu.cn + + "C028"; // edu.cn + buffer = DomainUtils.encode(new String[] { + "example.corp.google.com", "suffix.example.edu.cn", "edu.cn"}, true); + assertNotNull(buffer); + assertEquals(suffix, HexEncoding.encodeToString(buffer)); + + // domain search list: "google.com", "example.com", "sub.example.com" + suffix = "06676F6F676C6503636F6D00" // google.com + + "076578616D706C65C007" // example.com + + "03737562C00C"; // sub.example.com + buffer = DomainUtils.encode(new String[] { + "google.com", "example.com", "sub.example.com"}, true); + assertNotNull(buffer); + assertEquals(suffix, HexEncoding.encodeToString(buffer)); + } + + @Test + public void testDecodeDomainNames() { + ArrayList<String> suffixStringList; + String suffixes = "06676F6F676C6503636F6D00" // google.com + + "076578616D706C6503636F6D00" // example.com + + "06676F6F676C6500"; // google + List<String> expected = Arrays.asList("google.com", "example.com"); + ByteBuffer buffer = ByteBuffer.wrap(HexEncoding.decode(suffixes)); + suffixStringList = DomainUtils.decode(buffer, false /* compression */); + assertEquals(expected, suffixStringList); + + // include suffix with invalid length: 64 + suffixes = "06676F6F676C6503636F6D00" // google.com + + "406578616D706C6503636F6D00" // example.com(length=64) + + "06676F6F676C6500"; // google + expected = Arrays.asList("google.com"); + buffer = ByteBuffer.wrap(HexEncoding.decode(suffixes)); + suffixStringList = DomainUtils.decode(buffer, false /* compression */); + assertEquals(expected, suffixStringList); + + // include suffix with invalid length: 0 + suffixes = "06676F6F676C6503636F6D00" // google.com + + "076578616D706C6503636F6D00" // example.com + + "00676F6F676C6500"; // google(length=0) + expected = Arrays.asList("google.com", "example.com"); + buffer = ByteBuffer.wrap(HexEncoding.decode(suffixes)); + suffixStringList = DomainUtils.decode(buffer, false /* compression */); + assertEquals(expected, suffixStringList); + + suffixes = + "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com + + "C008" // corp.google.com + + "C00D"; // google.com + expected = Arrays.asList("example.corp.google.com", "corp.google.com", "google.com"); + buffer = ByteBuffer.wrap(HexEncoding.decode(suffixes)); + suffixStringList = DomainUtils.decode(buffer, true /* compression */); + assertEquals(expected, suffixStringList); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/HexDumpTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/HexDumpTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5a15585d20459fda729850d9d1cd1ed437530c94 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/HexDumpTest.java @@ -0,0 +1,72 @@ +/* + * 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. + */ + +package com.android.net.module.util; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class HexDumpTest { + @Test + public void testBytesToHexString() { + assertEquals("abcdef", HexDump.toHexString( + new byte[]{(byte) 0xab, (byte) 0xcd, (byte) 0xef}, false)); + assertEquals("ABCDEF", HexDump.toHexString( + new byte[]{(byte) 0xab, (byte) 0xcd, (byte) 0xef}, true)); + } + + @Test + public void testNullArray() { + assertEquals("(null)", HexDump.dumpHexString(null)); + } + + @Test + public void testHexStringToByteArray() { + assertArrayEquals(new byte[]{(byte) 0xab, (byte) 0xcd, (byte) 0xef}, + HexDump.hexStringToByteArray("abcdef")); + assertArrayEquals(new byte[]{(byte) 0xAB, (byte) 0xCD, (byte) 0xEF}, + HexDump.hexStringToByteArray("ABCDEF")); + } + + @Test + public void testIntegerToByteArray() { + assertArrayEquals(new byte[]{(byte) 0xff, (byte) 0x00, (byte) 0x00, (byte) 0x04}, + HexDump.toByteArray((int) 0xff000004)); + } + + @Test + public void testByteToByteArray() { + assertArrayEquals(new byte[]{(byte) 0x7f}, HexDump.toByteArray((byte) 0x7f)); + } + + @Test + public void testIntegerToHexString() { + assertEquals("FF000004", HexDump.toHexString((int) 0xff000004)); + } + + @Test + public void testByteToHexString() { + assertEquals("7F", HexDump.toHexString((byte) 0x7f)); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/Inet4AddressUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/Inet4AddressUtilsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..702bdaf8dfc53563dfb0c86d036c55ead00dfbf6 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/Inet4AddressUtilsTest.java @@ -0,0 +1,223 @@ +/* + * 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. + */ + +package com.android.net.module.util; + +import static com.android.net.module.util.Inet4AddressUtils.getBroadcastAddress; +import static com.android.net.module.util.Inet4AddressUtils.getImplicitNetmask; +import static com.android.net.module.util.Inet4AddressUtils.getPrefixMaskAsInet4Address; +import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH; +import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTL; +import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH; +import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTL; +import static com.android.net.module.util.Inet4AddressUtils.netmaskToPrefixLength; +import static com.android.net.module.util.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTH; +import static com.android.net.module.util.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTL; +import static com.android.net.module.util.Inet4AddressUtils.trimAddressZeros; + +import static junit.framework.Assert.assertEquals; + +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +import android.net.InetAddresses; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet4Address; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class Inet4AddressUtilsTest { + + @Test + public void testInet4AddressToIntHTL() { + assertEquals(0, inet4AddressToIntHTL(ipv4Address("0.0.0.0"))); + assertEquals(0x000080ff, inet4AddressToIntHTL(ipv4Address("255.128.0.0"))); + assertEquals(0x0080ff0a, inet4AddressToIntHTL(ipv4Address("10.255.128.0"))); + assertEquals(0x00feff0a, inet4AddressToIntHTL(ipv4Address("10.255.254.0"))); + assertEquals(0xfeffa8c0, inet4AddressToIntHTL(ipv4Address("192.168.255.254"))); + assertEquals(0xffffa8c0, inet4AddressToIntHTL(ipv4Address("192.168.255.255"))); + } + + @Test + public void testIntToInet4AddressHTL() { + assertEquals(ipv4Address("0.0.0.0"), intToInet4AddressHTL(0)); + assertEquals(ipv4Address("255.128.0.0"), intToInet4AddressHTL(0x000080ff)); + assertEquals(ipv4Address("10.255.128.0"), intToInet4AddressHTL(0x0080ff0a)); + assertEquals(ipv4Address("10.255.254.0"), intToInet4AddressHTL(0x00feff0a)); + assertEquals(ipv4Address("192.168.255.254"), intToInet4AddressHTL(0xfeffa8c0)); + assertEquals(ipv4Address("192.168.255.255"), intToInet4AddressHTL(0xffffa8c0)); + } + + @Test + public void testInet4AddressToIntHTH() { + assertEquals(0, inet4AddressToIntHTH(ipv4Address("0.0.0.0"))); + assertEquals(0xff800000, inet4AddressToIntHTH(ipv4Address("255.128.0.0"))); + assertEquals(0x0aff8000, inet4AddressToIntHTH(ipv4Address("10.255.128.0"))); + assertEquals(0x0afffe00, inet4AddressToIntHTH(ipv4Address("10.255.254.0"))); + assertEquals(0xc0a8fffe, inet4AddressToIntHTH(ipv4Address("192.168.255.254"))); + assertEquals(0xc0a8ffff, inet4AddressToIntHTH(ipv4Address("192.168.255.255"))); + } + + @Test + public void testIntToInet4AddressHTH() { + assertEquals(ipv4Address("0.0.0.0"), intToInet4AddressHTH(0)); + assertEquals(ipv4Address("255.128.0.0"), intToInet4AddressHTH(0xff800000)); + assertEquals(ipv4Address("10.255.128.0"), intToInet4AddressHTH(0x0aff8000)); + assertEquals(ipv4Address("10.255.254.0"), intToInet4AddressHTH(0x0afffe00)); + assertEquals(ipv4Address("192.168.255.254"), intToInet4AddressHTH(0xc0a8fffe)); + assertEquals(ipv4Address("192.168.255.255"), intToInet4AddressHTH(0xc0a8ffff)); + } + + + @Test + public void testPrefixLengthToV4NetmaskIntHTL() { + assertEquals(0, prefixLengthToV4NetmaskIntHTL(0)); + assertEquals(0x000080ff /* 255.128.0.0 */, prefixLengthToV4NetmaskIntHTL(9)); + assertEquals(0x0080ffff /* 255.255.128.0 */, prefixLengthToV4NetmaskIntHTL(17)); + assertEquals(0x00feffff /* 255.255.254.0 */, prefixLengthToV4NetmaskIntHTL(23)); + assertEquals(0xfeffffff /* 255.255.255.254 */, prefixLengthToV4NetmaskIntHTL(31)); + assertEquals(0xffffffff /* 255.255.255.255 */, prefixLengthToV4NetmaskIntHTL(32)); + } + + @Test + public void testPrefixLengthToV4NetmaskIntHTH() { + assertEquals(0, prefixLengthToV4NetmaskIntHTH(0)); + assertEquals(0xff800000 /* 255.128.0.0 */, prefixLengthToV4NetmaskIntHTH(9)); + assertEquals(0xffff8000 /* 255.255.128.0 */, prefixLengthToV4NetmaskIntHTH(17)); + assertEquals(0xfffffe00 /* 255.255.254.0 */, prefixLengthToV4NetmaskIntHTH(23)); + assertEquals(0xfffffffe /* 255.255.255.254 */, prefixLengthToV4NetmaskIntHTH(31)); + assertEquals(0xffffffff /* 255.255.255.255 */, prefixLengthToV4NetmaskIntHTH(32)); + } + + @Test(expected = IllegalArgumentException.class) + public void testPrefixLengthToV4NetmaskIntHTH_NegativeLength() { + prefixLengthToV4NetmaskIntHTH(-1); + } + + @Test(expected = IllegalArgumentException.class) + public void testPrefixLengthToV4NetmaskIntHTH_LengthTooLarge() { + prefixLengthToV4NetmaskIntHTH(33); + } + + private void checkAddressMasking(String expectedAddr, String addr, int prefixLength) { + final int prefix = prefixLengthToV4NetmaskIntHTH(prefixLength); + final int addrInt = inet4AddressToIntHTH(ipv4Address(addr)); + assertEquals(ipv4Address(expectedAddr), intToInet4AddressHTH(prefix & addrInt)); + } + + @Test + public void testPrefixLengthToV4NetmaskIntHTH_MaskAddr() { + checkAddressMasking("192.168.0.0", "192.168.128.1", 16); + checkAddressMasking("255.240.0.0", "255.255.255.255", 12); + checkAddressMasking("255.255.255.255", "255.255.255.255", 32); + checkAddressMasking("0.0.0.0", "255.255.255.255", 0); + } + + @Test + public void testGetImplicitNetmask() { + assertEquals(8, getImplicitNetmask(ipv4Address("4.2.2.2"))); + assertEquals(8, getImplicitNetmask(ipv4Address("10.5.6.7"))); + assertEquals(16, getImplicitNetmask(ipv4Address("173.194.72.105"))); + assertEquals(16, getImplicitNetmask(ipv4Address("172.23.68.145"))); + assertEquals(24, getImplicitNetmask(ipv4Address("192.0.2.1"))); + assertEquals(24, getImplicitNetmask(ipv4Address("192.168.5.1"))); + assertEquals(32, getImplicitNetmask(ipv4Address("224.0.0.1"))); + assertEquals(32, getImplicitNetmask(ipv4Address("255.6.7.8"))); + } + + private void assertInvalidNetworkMask(Inet4Address addr) { + try { + netmaskToPrefixLength(addr); + fail("Invalid netmask " + addr.getHostAddress() + " did not cause exception"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testNetmaskToPrefixLength() { + assertEquals(0, netmaskToPrefixLength(ipv4Address("0.0.0.0"))); + assertEquals(9, netmaskToPrefixLength(ipv4Address("255.128.0.0"))); + assertEquals(17, netmaskToPrefixLength(ipv4Address("255.255.128.0"))); + assertEquals(23, netmaskToPrefixLength(ipv4Address("255.255.254.0"))); + assertEquals(31, netmaskToPrefixLength(ipv4Address("255.255.255.254"))); + assertEquals(32, netmaskToPrefixLength(ipv4Address("255.255.255.255"))); + + assertInvalidNetworkMask(ipv4Address("0.0.0.1")); + assertInvalidNetworkMask(ipv4Address("255.255.255.253")); + assertInvalidNetworkMask(ipv4Address("255.255.0.255")); + } + + @Test + public void testGetPrefixMaskAsAddress() { + assertEquals("255.255.240.0", getPrefixMaskAsInet4Address(20).getHostAddress()); + assertEquals("255.0.0.0", getPrefixMaskAsInet4Address(8).getHostAddress()); + assertEquals("0.0.0.0", getPrefixMaskAsInet4Address(0).getHostAddress()); + assertEquals("255.255.255.255", getPrefixMaskAsInet4Address(32).getHostAddress()); + } + + @Test + public void testGetBroadcastAddress() { + assertEquals("192.168.15.255", + getBroadcastAddress(ipv4Address("192.168.0.123"), 20).getHostAddress()); + assertEquals("192.255.255.255", + getBroadcastAddress(ipv4Address("192.168.0.123"), 8).getHostAddress()); + assertEquals("192.168.0.123", + getBroadcastAddress(ipv4Address("192.168.0.123"), 32).getHostAddress()); + assertEquals("255.255.255.255", + getBroadcastAddress(ipv4Address("192.168.0.123"), 0).getHostAddress()); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetBroadcastAddress_PrefixTooLarge() { + getBroadcastAddress(ipv4Address("192.168.0.123"), 33); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetBroadcastAddress_NegativePrefix() { + getBroadcastAddress(ipv4Address("192.168.0.123"), -1); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetPrefixMaskAsAddress_PrefixTooLarge() { + getPrefixMaskAsInet4Address(33); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetPrefixMaskAsAddress_NegativePrefix() { + getPrefixMaskAsInet4Address(-1); + } + + @Test + public void testTrimAddressZeros() { + assertNull(trimAddressZeros(null)); + assertEquals("$invalid&", trimAddressZeros("$invalid&")); + assertEquals("example.com", trimAddressZeros("example.com")); + assertEquals("a.b.c.d", trimAddressZeros("a.b.c.d")); + + assertEquals("192.0.2.2", trimAddressZeros("192.000.02.2")); + assertEquals("192.0.2.2", trimAddressZeros("192.0.2.2")); + } + + private Inet4Address ipv4Address(String addr) { + return (Inet4Address) InetAddresses.parseNumericAddress(addr); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/InetAddressUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/InetAddressUtilsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..bb2b9332a49e8c76814858e7084ea037d9951cb1 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/InetAddressUtilsTest.java @@ -0,0 +1,95 @@ +/* + * 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. + */ + +package com.android.net.module.util; + +import static junit.framework.Assert.assertEquals; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.net.InetAddresses; +import android.os.Parcel; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet6Address; +import java.net.InetAddress; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class InetAddressUtilsTest { + + private InetAddress parcelUnparcelAddress(InetAddress addr) { + Parcel p = Parcel.obtain(); + InetAddressUtils.parcelInetAddress(p, addr, 0 /* flags */); + p.setDataPosition(0); + byte[] marshalled = p.marshall(); + p.recycle(); + p = Parcel.obtain(); + p.unmarshall(marshalled, 0, marshalled.length); + p.setDataPosition(0); + InetAddress out = InetAddressUtils.unparcelInetAddress(p); + p.recycle(); + return out; + } + + @Test + public void testParcelUnparcelIpv4Address() throws Exception { + InetAddress ipv4 = InetAddress.getByName("192.0.2.1"); + assertEquals(ipv4, parcelUnparcelAddress(ipv4)); + } + + @Test + public void testParcelUnparcelIpv6Address() throws Exception { + InetAddress ipv6 = InetAddress.getByName("2001:db8::1"); + assertEquals(ipv6, parcelUnparcelAddress(ipv6)); + } + + @Test + public void testParcelUnparcelScopedIpv6Address() throws Exception { + InetAddress ipv6 = InetAddress.getByName("fe80::1%42"); + assertEquals(42, ((Inet6Address) ipv6).getScopeId()); + Inet6Address out = (Inet6Address) parcelUnparcelAddress(ipv6); + assertEquals(ipv6, out); + assertEquals(42, out.getScopeId()); + } + + @Test + public void testWithScopeId() { + final int scopeId = 999; + + final String globalAddrStr = "2401:fa00:49c:484:dc41:e6ff:fefd:f180"; + final Inet6Address globalAddr = (Inet6Address) InetAddresses + .parseNumericAddress(globalAddrStr); + final Inet6Address updatedGlobalAddr = InetAddressUtils.withScopeId(globalAddr, scopeId); + assertFalse(updatedGlobalAddr.isLinkLocalAddress()); + assertEquals(globalAddrStr, updatedGlobalAddr.getHostAddress()); + assertEquals(0, updatedGlobalAddr.getScopeId()); + + final String localAddrStr = "fe80::4735:9628:d038:2087"; + final Inet6Address localAddr = (Inet6Address) InetAddresses + .parseNumericAddress(localAddrStr); + final Inet6Address updatedLocalAddr = InetAddressUtils.withScopeId(localAddr, scopeId); + assertTrue(updatedLocalAddr.isLinkLocalAddress()); + assertEquals(localAddrStr + "%" + scopeId, updatedLocalAddr.getHostAddress()); + assertEquals(scopeId, updatedLocalAddr.getScopeId()); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/InterfaceParamsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/InterfaceParamsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..a1d8c10602fa67f6a42eb452ebccd3830ce076ba --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/InterfaceParamsTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class InterfaceParamsTest { + @Test + public void testNullInterfaceReturnsNull() { + assertNull(InterfaceParams.getByName(null)); + } + + @Test + public void testNonExistentInterfaceReturnsNull() { + assertNull(InterfaceParams.getByName("doesnotexist0")); + } + + @Test + public void testLoopback() { + final InterfaceParams ifParams = InterfaceParams.getByName("lo"); + assertNotNull(ifParams); + assertEquals("lo", ifParams.name); + assertTrue(ifParams.index > 0); + assertNotNull(ifParams.macAddr); + assertFalse(ifParams.hasMacAddress); + assertTrue(ifParams.defaultMtu >= NetworkStackConstants.ETHER_MTU); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/IpRangeTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/IpRangeTest.java new file mode 100644 index 0000000000000000000000000000000000000000..20bbd4a1436414a9b9c6672c7090a590c0d9b59d --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/IpRangeTest.java @@ -0,0 +1,282 @@ +/* + * 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. + */ + +package com.android.net.module.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.annotation.SuppressLint; +import android.net.InetAddresses; +import android.net.IpPrefix; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class IpRangeTest { + + private static InetAddress address(String addr) { + return InetAddresses.parseNumericAddress(addr); + } + + private static final Inet4Address IPV4_ADDR = (Inet4Address) address("192.0.2.4"); + private static final Inet4Address IPV4_RANGE_END = (Inet4Address) address("192.0.3.1"); + private static final Inet6Address IPV6_ADDR = (Inet6Address) address("2001:db8::"); + private static final Inet6Address IPV6_RANGE_END = (Inet6Address) address("2001:db9:010f::"); + + private static final byte[] IPV4_BYTES = IPV4_ADDR.getAddress(); + private static final byte[] IPV6_BYTES = IPV6_ADDR.getAddress(); + + @Test + public void testConstructorBadArguments() { + try { + new IpRange(null, IPV6_ADDR); + fail("Expected NullPointerException: null start address"); + } catch (NullPointerException expected) { + } + + try { + new IpRange(IPV6_ADDR, null); + fail("Expected NullPointerException: null end address"); + } catch (NullPointerException expected) { + } + + try { + new IpRange(null, null); + fail("Expected NullPointerException: null addresses"); + } catch (NullPointerException expected) { + } + + try { + new IpRange(null); + fail("Expected NullPointerException: null start address"); + } catch (NullPointerException expected) { + } + + try { + new IpRange(address("10.10.10.10"), address("1.2.3.4")); + fail("Expected IllegalArgumentException: start address after end address"); + } catch (IllegalArgumentException expected) { + } + + try { + new IpRange(address("ffff::"), address("abcd::")); + fail("Expected IllegalArgumentException: start address after end address"); + } catch (IllegalArgumentException expected) { + } + } + + @SuppressLint("NewApi") + @Test + public void testConstructor() { + IpRange r = new IpRange(new IpPrefix(IPV4_ADDR, 32)); + assertEquals(IPV4_ADDR, r.getStartAddr()); + assertEquals(IPV4_ADDR, r.getEndAddr()); + + r = new IpRange(new IpPrefix(IPV4_ADDR, 16)); + assertEquals(address("192.0.0.0"), r.getStartAddr()); + assertEquals(address("192.0.255.255"), r.getEndAddr()); + + r = new IpRange(IPV4_ADDR, IPV4_RANGE_END); + assertEquals(IPV4_ADDR, r.getStartAddr()); + assertEquals(IPV4_RANGE_END, r.getEndAddr()); + + r = new IpRange(new IpPrefix(IPV6_ADDR, 128)); + assertEquals(IPV6_ADDR, r.getStartAddr()); + assertEquals(IPV6_ADDR, r.getEndAddr()); + + r = new IpRange(new IpPrefix(IPV6_ADDR, 16)); + assertEquals(address("2001::"), r.getStartAddr()); + assertEquals(address("2001:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), r.getEndAddr()); + + r = new IpRange(IPV6_ADDR, IPV6_RANGE_END); + assertEquals(IPV6_ADDR, r.getStartAddr()); + assertEquals(IPV6_RANGE_END, r.getEndAddr()); + } + + @SuppressLint("NewApi") + @Test + public void testContainsRangeEqualRanges() { + final IpRange r1 = new IpRange(new IpPrefix(IPV6_ADDR, 35)); + final IpRange r2 = new IpRange(new IpPrefix(IPV6_ADDR, 35)); + + assertTrue(r1.containsRange(r2)); + assertTrue(r2.containsRange(r1)); + assertEquals(r1, r2); + } + + @SuppressLint("NewApi") + @Test + public void testContainsRangeSubset() { + final IpRange r1 = new IpRange(new IpPrefix(IPV6_ADDR, 64)); + final IpRange r2 = new IpRange(new IpPrefix(address("2001:db8::0101"), 128)); + + assertTrue(r1.containsRange(r2)); + assertFalse(r2.containsRange(r1)); + assertNotEquals(r1, r2); + } + + @SuppressLint("NewApi") + @Test + public void testContainsRangeTruncatesLowerOrderBits() { + final IpRange r1 = new IpRange(new IpPrefix(IPV6_ADDR, 100)); + final IpRange r2 = new IpRange(new IpPrefix(address("2001:db8::0101"), 100)); + + assertTrue(r1.containsRange(r2)); + assertTrue(r2.containsRange(r1)); + assertEquals(r1, r2); + } + + @SuppressLint("NewApi") + @Test + public void testContainsRangeSubsetSameStartAddr() { + final IpRange r1 = new IpRange(new IpPrefix(IPV6_ADDR, 35)); + final IpRange r2 = new IpRange(new IpPrefix(IPV6_ADDR, 39)); + + assertTrue(r1.containsRange(r2)); + assertFalse(r2.containsRange(r1)); + assertNotEquals(r1, r2); + } + + @SuppressLint("NewApi") + @Test + public void testContainsRangeOverlapping() { + final IpRange r1 = new IpRange(new IpPrefix(address("2001:db9::"), 32)); + final IpRange r2 = new IpRange(address("2001:db8::"), address("2001:db9::1")); + + assertFalse(r1.containsRange(r2)); + assertFalse(r2.containsRange(r1)); + assertNotEquals(r1, r2); + } + + @SuppressLint("NewApi") + @Test + public void testOverlapsRangeEqualRanges() { + final IpRange r1 = new IpRange(new IpPrefix(IPV6_ADDR, 35)); + final IpRange r2 = new IpRange(new IpPrefix(IPV6_ADDR, 35)); + + assertTrue(r1.overlapsRange(r2)); + assertTrue(r2.overlapsRange(r1)); + assertEquals(r1, r2); + } + + @SuppressLint("NewApi") + @Test + public void testOverlapsRangeSubset() { + final IpRange r1 = new IpRange(new IpPrefix(IPV6_ADDR, 35)); + final IpRange r2 = new IpRange(new IpPrefix(IPV6_ADDR, 39)); + + assertTrue(r1.overlapsRange(r2)); + assertTrue(r2.overlapsRange(r1)); + assertNotEquals(r1, r2); + } + + @SuppressLint("NewApi") + @Test + public void testOverlapsRangeDisjoint() { + final IpRange r1 = new IpRange(new IpPrefix(IPV6_ADDR, 32)); + final IpRange r2 = new IpRange(new IpPrefix(address("2001:db9::"), 32)); + + assertFalse(r1.overlapsRange(r2)); + assertFalse(r2.overlapsRange(r1)); + assertNotEquals(r1, r2); + } + + @SuppressLint("NewApi") + @Test + public void testOverlapsRangePartialOverlapLow() { + final IpRange r1 = new IpRange(new IpPrefix(address("2001:db9::"), 32)); + final IpRange r2 = new IpRange(address("2001:db8::"), address("2001:db9::1")); + + assertTrue(r1.overlapsRange(r2)); + assertTrue(r2.overlapsRange(r1)); + assertNotEquals(r1, r2); + } + + @SuppressLint("NewApi") + @Test + public void testOverlapsRangePartialOverlapHigh() { + final IpRange r1 = new IpRange(new IpPrefix(address("2001:db7::"), 32)); + final IpRange r2 = new IpRange(address("2001:db7::ffff"), address("2001:db8::")); + + assertTrue(r1.overlapsRange(r2)); + assertTrue(r2.overlapsRange(r1)); + assertNotEquals(r1, r2); + } + + @Test + public void testIpRangeToPrefixesIpv4FullRange() throws Exception { + final IpRange range = new IpRange(address("0.0.0.0"), address("255.255.255.255")); + final List<IpPrefix> prefixes = new ArrayList<>(); + prefixes.add(new IpPrefix("0.0.0.0/0")); + + assertEquals(prefixes, range.asIpPrefixes()); + } + + @Test + public void testIpRangeToPrefixesIpv4() throws Exception { + final IpRange range = new IpRange(IPV4_ADDR, IPV4_RANGE_END); + final List<IpPrefix> prefixes = new ArrayList<>(); + prefixes.add(new IpPrefix("192.0.2.128/25")); + prefixes.add(new IpPrefix("192.0.2.64/26")); + prefixes.add(new IpPrefix("192.0.2.32/27")); + prefixes.add(new IpPrefix("192.0.2.16/28")); + prefixes.add(new IpPrefix("192.0.2.8/29")); + prefixes.add(new IpPrefix("192.0.2.4/30")); + prefixes.add(new IpPrefix("192.0.3.0/31")); + + assertEquals(prefixes, range.asIpPrefixes()); + } + + @Test + public void testIpRangeToPrefixesIpv6FullRange() throws Exception { + final IpRange range = + new IpRange(address("::"), address("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); + final List<IpPrefix> prefixes = new ArrayList<>(); + prefixes.add(new IpPrefix("::/0")); + + assertEquals(prefixes, range.asIpPrefixes()); + } + + @Test + public void testIpRangeToPrefixesIpv6() throws Exception { + final IpRange range = new IpRange(IPV6_ADDR, IPV6_RANGE_END); + final List<IpPrefix> prefixes = new ArrayList<>(); + prefixes.add(new IpPrefix("2001:db8::/32")); + prefixes.add(new IpPrefix("2001:db9::/40")); + prefixes.add(new IpPrefix("2001:db9:100::/45")); + prefixes.add(new IpPrefix("2001:db9:108::/46")); + prefixes.add(new IpPrefix("2001:db9:10c::/47")); + prefixes.add(new IpPrefix("2001:db9:10e::/48")); + prefixes.add(new IpPrefix("2001:db9:10f::/128")); + + assertEquals(prefixes, range.asIpPrefixes()); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/IpUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/IpUtilsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d57023caf18bfcbec41bdfdfc27cc630292fef98 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/IpUtilsTest.java @@ -0,0 +1,225 @@ +/* + * 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. + */ + +package com.android.net.module.util; + +import static com.android.net.module.util.NetworkStackConstants.ICMP_CHECKSUM_OFFSET; +import static com.android.net.module.util.NetworkStackConstants.IPV4_CHECKSUM_OFFSET; + +import static org.junit.Assert.assertEquals; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.nio.ByteBuffer; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class IpUtilsTest { + + private static final int IPV4_HEADER_LENGTH = 20; + private static final int IPV6_HEADER_LENGTH = 40; + private static final int TCP_HEADER_LENGTH = 20; + private static final int UDP_HEADER_LENGTH = 8; + private static final int TCP_CHECKSUM_OFFSET = 16; + private static final int UDP_CHECKSUM_OFFSET = 6; + + private int getUnsignedByte(ByteBuffer buf, int offset) { + return buf.get(offset) & 0xff; + } + + private int getChecksum(ByteBuffer buf, int offset) { + return getUnsignedByte(buf, offset) * 256 + getUnsignedByte(buf, offset + 1); + } + + private void assertChecksumEquals(int expected, short actual) { + assertEquals(Integer.toHexString(expected), Integer.toHexString(actual & 0xffff)); + } + + // Generate test packets using Python code like this:: + // + // from scapy import all as scapy + // + // def JavaPacketDefinition(bytes): + // out = " ByteBuffer packet = ByteBuffer.wrap(new byte[] {\n " + // for i in xrange(len(bytes)): + // out += "(byte) 0x%02x" % ord(bytes[i]) + // if i < len(bytes) - 1: + // if i % 4 == 3: + // out += ",\n " + // else: + // out += ", " + // out += "\n });" + // return out + // + // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2") / + // scapy.UDP(sport=12345, dport=7) / + // "hello") + // print JavaPacketDefinition(str(packet)) + + @Test + public void testEmptyAndZeroBufferChecksum() throws Exception { + ByteBuffer packet = ByteBuffer.wrap(new byte[] { (byte) 0x00, (byte) 0x00, }); + // the following should *not* return 0xFFFF + assertEquals(0, IpUtils.checksum(packet, 0, 0, 0)); + assertEquals(0, IpUtils.checksum(packet, 0, 0, 1)); + assertEquals(0, IpUtils.checksum(packet, 0, 0, 2)); + assertEquals(0, IpUtils.checksum(packet, 0, 1, 2)); + assertEquals(0, IpUtils.checksum(packet, 0xFFFF, 0, 0)); + assertEquals(0, IpUtils.checksum(packet, 0xFFFF, 0, 1)); + assertEquals(0, IpUtils.checksum(packet, 0xFFFF, 0, 2)); + assertEquals(0, IpUtils.checksum(packet, 0xFFFF, 1, 2)); + } + + @Test + public void testIpv6TcpChecksum() throws Exception { + // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80) / + // scapy.TCP(sport=12345, dport=7, + // seq=1692871236, ack=128376451, flags=16, + // window=32768) / + // "hello, world") + ByteBuffer packet = ByteBuffer.wrap(new byte[] { + (byte) 0x68, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x20, (byte) 0x06, (byte) 0x40, + (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, + (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02, + (byte) 0x30, (byte) 0x39, (byte) 0x00, (byte) 0x07, + (byte) 0x64, (byte) 0xe7, (byte) 0x2a, (byte) 0x44, + (byte) 0x07, (byte) 0xa6, (byte) 0xde, (byte) 0x83, + (byte) 0x50, (byte) 0x10, (byte) 0x80, (byte) 0x00, + (byte) 0xee, (byte) 0x71, (byte) 0x00, (byte) 0x00, + (byte) 0x68, (byte) 0x65, (byte) 0x6c, (byte) 0x6c, + (byte) 0x6f, (byte) 0x2c, (byte) 0x20, (byte) 0x77, + (byte) 0x6f, (byte) 0x72, (byte) 0x6c, (byte) 0x64 + }); + + // Check that a valid packet has checksum 0. + int transportLen = packet.limit() - IPV6_HEADER_LENGTH; + assertEquals(0, IpUtils.tcpChecksum(packet, 0, IPV6_HEADER_LENGTH, transportLen)); + + // Check that we can calculate the checksum from scratch. + int sumOffset = IPV6_HEADER_LENGTH + TCP_CHECKSUM_OFFSET; + int sum = getUnsignedByte(packet, sumOffset) * 256 + getUnsignedByte(packet, sumOffset + 1); + assertEquals(0xee71, sum); + + packet.put(sumOffset, (byte) 0); + packet.put(sumOffset + 1, (byte) 0); + assertChecksumEquals(sum, IpUtils.tcpChecksum(packet, 0, IPV6_HEADER_LENGTH, transportLen)); + + // Check that writing the checksum back into the packet results in a valid packet. + packet.putShort( + sumOffset, + IpUtils.tcpChecksum(packet, 0, IPV6_HEADER_LENGTH, transportLen)); + assertEquals(0, IpUtils.tcpChecksum(packet, 0, IPV6_HEADER_LENGTH, transportLen)); + } + + @Test + public void testIpv4UdpChecksum() { + // packet = (scapy.IP(src="192.0.2.1", dst="192.0.2.2", tos=0x40) / + // scapy.UDP(sport=32012, dport=4500) / + // "\xff") + ByteBuffer packet = ByteBuffer.wrap(new byte[] { + (byte) 0x45, (byte) 0x40, (byte) 0x00, (byte) 0x1d, + (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, + (byte) 0x40, (byte) 0x11, (byte) 0xf6, (byte) 0x8b, + (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01, + (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x02, + (byte) 0x7d, (byte) 0x0c, (byte) 0x11, (byte) 0x94, + (byte) 0x00, (byte) 0x09, (byte) 0xee, (byte) 0x36, + (byte) 0xff + }); + + // Check that a valid packet has IP checksum 0 and UDP checksum 0xffff (0 is not a valid + // UDP checksum, so the udpChecksum rewrites 0 to 0xffff). + assertEquals(0, IpUtils.ipChecksum(packet, 0)); + assertEquals((short) 0xffff, IpUtils.udpChecksum(packet, 0, IPV4_HEADER_LENGTH)); + + // Check that we can calculate the checksums from scratch. + final int ipSumOffset = IPV4_CHECKSUM_OFFSET; + final int ipSum = getChecksum(packet, ipSumOffset); + assertEquals(0xf68b, ipSum); + + packet.put(ipSumOffset, (byte) 0); + packet.put(ipSumOffset + 1, (byte) 0); + assertChecksumEquals(ipSum, IpUtils.ipChecksum(packet, 0)); + + final int udpSumOffset = IPV4_HEADER_LENGTH + UDP_CHECKSUM_OFFSET; + final int udpSum = getChecksum(packet, udpSumOffset); + assertEquals(0xee36, udpSum); + + packet.put(udpSumOffset, (byte) 0); + packet.put(udpSumOffset + 1, (byte) 0); + assertChecksumEquals(udpSum, IpUtils.udpChecksum(packet, 0, IPV4_HEADER_LENGTH)); + + // Check that writing the checksums back into the packet results in a valid packet. + packet.putShort(ipSumOffset, IpUtils.ipChecksum(packet, 0)); + packet.putShort(udpSumOffset, IpUtils.udpChecksum(packet, 0, IPV4_HEADER_LENGTH)); + assertEquals(0, IpUtils.ipChecksum(packet, 0)); + assertEquals((short) 0xffff, IpUtils.udpChecksum(packet, 0, IPV4_HEADER_LENGTH)); + } + + @Test + public void testIpv4IcmpChecksum() throws Exception { + // packet = (scapy.IP(src="192.0.2.1", dst="192.0.2.2", tos=0x40) / + // scapy.ICMP(type=0x8, id=0x1234, seq=0x5678) / + // "hello, world") + ByteBuffer packet = ByteBuffer.wrap(new byte[] { + /* IPv4 */ + (byte) 0x45, (byte) 0x40, (byte) 0x00, (byte) 0x28, + (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, + (byte) 0x40, (byte) 0x01, (byte) 0xf6, (byte) 0x90, + (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01, + (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x02, + /* ICMP */ + (byte) 0x08, /* type: echo-request */ + (byte) 0x00, /* code: 0 */ + (byte) 0x4f, (byte) 0x07, /* chksum: 0x4f07 */ + (byte) 0x12, (byte) 0x34, /* id: 0x1234 */ + (byte) 0x56, (byte) 0x78, /* seq: 0x5678 */ + (byte) 0x68, (byte) 0x65, (byte) 0x6c, (byte) 0x6c, /* data: hello, world */ + (byte) 0x6f, (byte) 0x2c, (byte) 0x20, (byte) 0x77, + (byte) 0x6f, (byte) 0x72, (byte) 0x6c, (byte) 0x64 + }); + + // Check that a valid packet has checksum 0. + int transportLen = packet.limit() - IPV4_HEADER_LENGTH; + assertEquals(0, IpUtils.icmpChecksum(packet, IPV4_HEADER_LENGTH, transportLen)); + + // Check that we can calculate the checksum from scratch. + int sumOffset = IPV4_HEADER_LENGTH + ICMP_CHECKSUM_OFFSET; + int sum = getUnsignedByte(packet, sumOffset) * 256 + getUnsignedByte(packet, sumOffset + 1); + assertEquals(0x4f07, sum); + + packet.put(sumOffset, (byte) 0); + packet.put(sumOffset + 1, (byte) 0); + assertChecksumEquals(sum, IpUtils.icmpChecksum(packet, IPV4_HEADER_LENGTH, transportLen)); + + // Check that writing the checksum back into the packet results in a valid packet. + packet.putShort( + sumOffset, + IpUtils.icmpChecksum(packet, IPV4_HEADER_LENGTH, transportLen)); + assertEquals(0, IpUtils.icmpChecksum(packet, IPV4_HEADER_LENGTH, transportLen)); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/Ipv6UtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/Ipv6UtilsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..4866a489f36093d33e1b5eeea849ce7dcf210c95 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/Ipv6UtilsTest.java @@ -0,0 +1,168 @@ +/* + * 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. + */ + +package com.android.net.module.util; + +import static org.junit.Assert.assertEquals; + +import android.net.InetAddresses; +import android.net.IpPrefix; +import android.net.MacAddress; +import android.system.OsConstants; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.net.module.util.structs.EthernetHeader; +import com.android.net.module.util.structs.Icmpv6Header; +import com.android.net.module.util.structs.Ipv6Header; +import com.android.net.module.util.structs.PrefixInformationOption; +import com.android.net.module.util.structs.RaHeader; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet6Address; +import java.nio.ByteBuffer; +import java.util.Arrays; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class Ipv6UtilsTest { + + private static final MacAddress MAC1 = MacAddress.fromString("11:22:33:44:55:66"); + private static final MacAddress MAC2 = MacAddress.fromString("aa:bb:cc:dd:ee:ff"); + private static final Inet6Address LINK_LOCAL = addr("fe80::1"); + private static final Inet6Address ROUTER_LINK_LOCAL = addr("fe80::cafe:d00d"); + private static final Inet6Address ALL_ROUTERS = + NetworkStackConstants.IPV6_ADDR_ALL_ROUTERS_MULTICAST; + private static final Inet6Address ALL_NODES = + NetworkStackConstants.IPV6_ADDR_ALL_NODES_MULTICAST; + + @Test + public void testBuildRsPacket() { + ByteBuffer b = Ipv6Utils.buildRsPacket(MAC1, MAC2, LINK_LOCAL, ALL_ROUTERS /* no opts */); + + EthernetHeader eth = Struct.parse(EthernetHeader.class, b); + assertEquals(MAC1, eth.srcMac); + assertEquals(MAC2, eth.dstMac); + + Ipv6Header ipv6 = Struct.parse(Ipv6Header.class, b); + assertEquals(255, ipv6.hopLimit); + assertEquals(OsConstants.IPPROTO_ICMPV6, ipv6.nextHeader); + assertEquals(LINK_LOCAL, ipv6.srcIp); + assertEquals(ALL_ROUTERS, ipv6.dstIp); + + Icmpv6Header icmpv6 = Struct.parse(Icmpv6Header.class, b); + assertEquals(NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION, icmpv6.type); + assertEquals(0, icmpv6.code); + } + + @Test + public void testBuildRaPacket() { + final byte pioFlags = + NetworkStackConstants.PIO_FLAG_AUTONOMOUS | NetworkStackConstants.PIO_FLAG_ON_LINK; + ByteBuffer pio1 = PrefixInformationOption.build(new IpPrefix("2001:db8:1::/64"), + pioFlags, 3600 /* validLifetime */, 1800 /* preferredLifetime */); + ByteBuffer pio2 = PrefixInformationOption.build(new IpPrefix("fdcd:a17f:6502:1::/64"), + pioFlags, 86400 /* validLifetime */, 86400 /* preferredLifetime */); + + ByteBuffer b = Ipv6Utils.buildRaPacket(MAC2, MAC1, ROUTER_LINK_LOCAL, ALL_NODES, + (byte) 0 /* flags */, 7200 /* lifetime */, + 30_000 /* reachableTime */, 750 /* retransTimer */, + pio1, pio2); + + EthernetHeader eth = Struct.parse(EthernetHeader.class, b); + assertEquals(MAC2, eth.srcMac); + assertEquals(MAC1, eth.dstMac); + + Ipv6Header ipv6 = Struct.parse(Ipv6Header.class, b); + assertEquals(255, ipv6.hopLimit); + assertEquals(OsConstants.IPPROTO_ICMPV6, ipv6.nextHeader); + assertEquals(ROUTER_LINK_LOCAL, ipv6.srcIp); + assertEquals(ALL_NODES, ipv6.dstIp); + + Icmpv6Header icmpv6 = Struct.parse(Icmpv6Header.class, b); + assertEquals(NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT, icmpv6.type); + assertEquals(0, icmpv6.code); + + RaHeader ra = Struct.parse(RaHeader.class, b); + assertEquals(0, ra.hopLimit); // Hop limit: unspecified. + assertEquals(0, ra.flags); + assertEquals(7200, ra.lifetime); + assertEquals(30_000, ra.reachableTime); + assertEquals(750, ra.retransTimer); + + PrefixInformationOption pio = Struct.parse(PrefixInformationOption.class, b); + assertPioEquals(pio, "2001:db8:1::/64", pioFlags, 3600, 1800); + pio = Struct.parse(PrefixInformationOption.class, b); + assertPioEquals(pio, "fdcd:a17f:6502:1::/64", pioFlags, 86400, 86400); + } + + @Test + public void testBuildEchoRequestPacket() { + final ByteBuffer b = Ipv6Utils.buildEchoRequestPacket(MAC2, MAC1, LINK_LOCAL, ALL_NODES); + + EthernetHeader eth = Struct.parse(EthernetHeader.class, b); + assertEquals(MAC2, eth.srcMac); + assertEquals(MAC1, eth.dstMac); + + Ipv6Header ipv6 = Struct.parse(Ipv6Header.class, b); + assertEquals(255, ipv6.hopLimit); + assertEquals(OsConstants.IPPROTO_ICMPV6, ipv6.nextHeader); + assertEquals(LINK_LOCAL, ipv6.srcIp); + assertEquals(ALL_NODES, ipv6.dstIp); + + Icmpv6Header icmpv6 = Struct.parse(Icmpv6Header.class, b); + assertEquals(NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE, icmpv6.type); + assertEquals(0, icmpv6.code); + } + + @Test + public void testBuildEchoReplyPacket() { + final ByteBuffer b = Ipv6Utils.buildEchoReplyPacket(LINK_LOCAL, ALL_NODES); + + Ipv6Header ipv6 = Struct.parse(Ipv6Header.class, b); + assertEquals(255, ipv6.hopLimit); + assertEquals(OsConstants.IPPROTO_ICMPV6, ipv6.nextHeader); + assertEquals(LINK_LOCAL, ipv6.srcIp); + assertEquals(ALL_NODES, ipv6.dstIp); + + Icmpv6Header icmpv6 = Struct.parse(Icmpv6Header.class, b); + assertEquals(NetworkStackConstants.ICMPV6_ECHO_REPLY_TYPE, icmpv6.type); + assertEquals(0, icmpv6.code); + } + + private void assertPioEquals(PrefixInformationOption pio, String prefix, byte flags, + long valid, long preferred) { + assertEquals(NetworkStackConstants.ICMPV6_ND_OPTION_PIO, pio.type); + assertEquals(4, pio.length); + assertEquals(flags, pio.flags); + assertEquals(valid, pio.validLifetime); + assertEquals(preferred, pio.preferredLifetime); + IpPrefix expected = new IpPrefix(prefix); + IpPrefix actual = new IpPrefix(pio.prefix, pio.prefixLen); + assertEquals(expected, actual); + } + + private static Inet6Address addr(String addr) { + return (Inet6Address) InetAddresses.parseNumericAddress(addr); + } + + private byte[] slice(byte[] array, int length) { + return Arrays.copyOf(array, length); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/JniUtilTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/JniUtilTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..757408778042eeae416a9b5ed1709a0cbeea2b78 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/JniUtilTest.kt @@ -0,0 +1,38 @@ +/* + * 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. + */ + +package com.android.net.module.util + +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import kotlin.test.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SmallTest +public final class JniUtilTest { + private val TEST_JAVA_UTIL_NAME = "java_util_jni" + private val TEST_ORG_JUNIT_NAME = "org_junit_jni" + + @Test + fun testGetJniLibraryName() { + assertEquals(TEST_JAVA_UTIL_NAME, + JniUtil.getJniLibraryName(java.util.Set::class.java.getPackage())) + assertEquals(TEST_ORG_JUNIT_NAME, + JniUtil.getJniLibraryName(org.junit.Before::class.java.getPackage())) + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/LinkPropertiesUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/LinkPropertiesUtilsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..80ab61853249a1aa70c4f8f5f6fac13f7b09f7f8 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/LinkPropertiesUtilsTest.java @@ -0,0 +1,326 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util; + +import static com.android.testutils.MiscAsserts.assertSameElements; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.annotation.SuppressLint; +import android.net.InetAddresses; +import android.net.IpPrefix; +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.ProxyInfo; +import android.net.RouteInfo; +import android.util.ArraySet; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.net.module.util.LinkPropertiesUtils.CompareOrUpdateResult; +import com.android.net.module.util.LinkPropertiesUtils.CompareResult; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; + +@RunWith(AndroidJUnit4.class) +public final class LinkPropertiesUtilsTest { + @SuppressLint("NewApi") + private static final IpPrefix PREFIX = new IpPrefix(toInetAddress("75.208.6.0"), 24); + private static final InetAddress V4_ADDR = toInetAddress("75.208.6.1"); + private static final InetAddress V6_ADDR = toInetAddress( + "2001:0db8:85a3:0000:0000:8a2e:0370:7334"); + private static final InetAddress DNS1 = toInetAddress("75.208.7.1"); + private static final InetAddress DNS2 = toInetAddress("69.78.7.1"); + + private static final InetAddress GATEWAY1 = toInetAddress("75.208.8.1"); + private static final InetAddress GATEWAY2 = toInetAddress("69.78.8.1"); + + private static final String IF_NAME = "wlan0"; + private static final LinkAddress V4_LINKADDR = new LinkAddress(V4_ADDR, 32); + private static final LinkAddress V6_LINKADDR = new LinkAddress(V6_ADDR, 128); + private static final RouteInfo RT_INFO1 = new RouteInfo(PREFIX, GATEWAY1, IF_NAME); + private static final RouteInfo RT_INFO2 = new RouteInfo(PREFIX, GATEWAY2, IF_NAME); + private static final String TEST_DOMAIN = "link.properties.com"; + + private static InetAddress toInetAddress(String addrString) { + return InetAddresses.parseNumericAddress(addrString); + } + + private LinkProperties createTestObject() { + final LinkProperties lp = new LinkProperties(); + lp.setInterfaceName(IF_NAME); + lp.addLinkAddress(V4_LINKADDR); + lp.addLinkAddress(V6_LINKADDR); + lp.addDnsServer(DNS1); + lp.addDnsServer(DNS2); + lp.setDomains(TEST_DOMAIN); + lp.addRoute(RT_INFO1); + lp.addRoute(RT_INFO2); + lp.setHttpProxy(ProxyInfo.buildDirectProxy("test", 8888)); + return lp; + } + + @Test + public void testLinkPropertiesIdenticalEqual() { + final LinkProperties source = createTestObject(); + final LinkProperties target = new LinkProperties(source); + + assertTrue(LinkPropertiesUtils.isIdenticalInterfaceName(source, target)); + assertTrue(LinkPropertiesUtils.isIdenticalInterfaceName(target, source)); + + assertTrue(LinkPropertiesUtils.isIdenticalAddresses(source, target)); + assertTrue(LinkPropertiesUtils.isIdenticalAddresses(target, source)); + + assertTrue(LinkPropertiesUtils.isIdenticalAllLinkAddresses(source, target)); + assertTrue(LinkPropertiesUtils.isIdenticalAllLinkAddresses(target, source)); + + assertTrue(LinkPropertiesUtils.isIdenticalDnses(source, target)); + assertTrue(LinkPropertiesUtils.isIdenticalDnses(target, source)); + + assertTrue(LinkPropertiesUtils.isIdenticalRoutes(source, target)); + assertTrue(LinkPropertiesUtils.isIdenticalRoutes(target, source)); + + assertTrue(LinkPropertiesUtils.isIdenticalHttpProxy(source, target)); + assertTrue(LinkPropertiesUtils.isIdenticalHttpProxy(target, source)); + + // Test different interface name. + target.setInterfaceName("lo"); + assertFalse(LinkPropertiesUtils.isIdenticalInterfaceName(source, target)); + assertFalse(LinkPropertiesUtils.isIdenticalInterfaceName(target, source)); + // Restore interface name + target.setInterfaceName(IF_NAME); + + // Compare addresses.size() not equals. + final LinkAddress testLinkAddr = new LinkAddress(toInetAddress("75.208.6.2"), 32); + target.addLinkAddress(testLinkAddr); + assertFalse(LinkPropertiesUtils.isIdenticalAddresses(source, target)); + assertFalse(LinkPropertiesUtils.isIdenticalAddresses(target, source)); + + assertFalse(LinkPropertiesUtils.isIdenticalAllLinkAddresses(source, target)); + assertFalse(LinkPropertiesUtils.isIdenticalAllLinkAddresses(target, source)); + + // Currently, target contains V4_LINKADDR, V6_LINKADDR and testLinkAddr. + // Compare addresses.size() equals but contains different address. + target.removeLinkAddress(V4_LINKADDR); + assertEquals(source.getAddresses().size(), target.getAddresses().size()); + assertFalse(LinkPropertiesUtils.isIdenticalAddresses(source, target)); + assertFalse(LinkPropertiesUtils.isIdenticalAddresses(target, source)); + assertFalse(LinkPropertiesUtils.isIdenticalAllLinkAddresses(source, target)); + assertFalse(LinkPropertiesUtils.isIdenticalAllLinkAddresses(target, source)); + // Restore link address + target.addLinkAddress(V4_LINKADDR); + target.removeLinkAddress(testLinkAddr); + + // Compare size not equals. + target.addDnsServer(toInetAddress("75.208.10.1")); + assertFalse(LinkPropertiesUtils.isIdenticalDnses(source, target)); + assertFalse(LinkPropertiesUtils.isIdenticalDnses(target, source)); + + // Compare the same servers but target has different domains. + target.removeDnsServer(toInetAddress("75.208.10.1")); + target.setDomains("test.com"); + assertFalse(LinkPropertiesUtils.isIdenticalDnses(source, target)); + assertFalse(LinkPropertiesUtils.isIdenticalDnses(target, source)); + + // Test null domain. + target.setDomains(null); + assertFalse(LinkPropertiesUtils.isIdenticalDnses(source, target)); + assertFalse(LinkPropertiesUtils.isIdenticalDnses(target, source)); + // Restore domain + target.setDomains(TEST_DOMAIN); + + // Compare size not equals. + final RouteInfo testRoute = new RouteInfo(toInetAddress("75.208.7.1")); + target.addRoute(testRoute); + assertFalse(LinkPropertiesUtils.isIdenticalRoutes(source, target)); + assertFalse(LinkPropertiesUtils.isIdenticalRoutes(target, source)); + + // Currently, target contains RT_INFO1, RT_INFO2 and testRoute. + // Compare size equals but different routes. + target.removeRoute(RT_INFO1); + assertEquals(source.getRoutes().size(), target.getRoutes().size()); + assertFalse(LinkPropertiesUtils.isIdenticalRoutes(source, target)); + assertFalse(LinkPropertiesUtils.isIdenticalRoutes(target, source)); + // Restore route + target.addRoute(RT_INFO1); + target.removeRoute(testRoute); + + // Test different proxy. + target.setHttpProxy(ProxyInfo.buildDirectProxy("hello", 8888)); + assertFalse(LinkPropertiesUtils.isIdenticalHttpProxy(source, target)); + assertFalse(LinkPropertiesUtils.isIdenticalHttpProxy(target, source)); + + // Test null proxy. + target.setHttpProxy(null); + assertFalse(LinkPropertiesUtils.isIdenticalHttpProxy(source, target)); + assertFalse(LinkPropertiesUtils.isIdenticalHttpProxy(target, source)); + + final LinkProperties stacked = new LinkProperties(); + stacked.setInterfaceName("v4-" + target.getInterfaceName()); + stacked.addLinkAddress(testLinkAddr); + target.addStackedLink(stacked); + assertFalse(LinkPropertiesUtils.isIdenticalAllLinkAddresses(source, target)); + assertFalse(LinkPropertiesUtils.isIdenticalAllLinkAddresses(target, source)); + } + + private <T> void compareResult(List<T> oldItems, List<T> newItems, List<T> expectRemoved, + List<T> expectAdded) { + CompareResult<T> result = new CompareResult<>(oldItems, newItems); + assertEquals(new ArraySet<>(expectAdded), new ArraySet<>(result.added)); + assertEquals(new ArraySet<>(expectRemoved), (new ArraySet<>(result.removed))); + } + + @Test + public void testCompareResult() { + // Either adding or removing items + compareResult(Arrays.asList(1, 2, 3, 4), Arrays.asList(1), + Arrays.asList(2, 3, 4), new ArrayList<>()); + compareResult(Arrays.asList(1, 2), Arrays.asList(3, 2, 1, 4), + new ArrayList<>(), Arrays.asList(3, 4)); + + + // adding and removing items at the same time + compareResult(Arrays.asList(1, 2, 3, 4), Arrays.asList(2, 3, 4, 5), + Arrays.asList(1), Arrays.asList(5)); + compareResult(Arrays.asList(1, 2, 3), Arrays.asList(4, 5, 6), + Arrays.asList(1, 2, 3), Arrays.asList(4, 5, 6)); + + // null cases + compareResult(Arrays.asList(1, 2, 3), null, Arrays.asList(1, 2, 3), new ArrayList<>()); + compareResult(null, Arrays.asList(3, 2, 1), new ArrayList<>(), Arrays.asList(1, 2, 3)); + compareResult(null, null, new ArrayList<>(), new ArrayList<>()); + + // Some more tests with strings + final ArrayList<String> list1 = new ArrayList<>(); + list1.add("string1"); + + final ArrayList<String> list2 = new ArrayList<>(list1); + final CompareResult<String> cr1 = new CompareResult<>(list1, list2); + assertTrue(cr1.added.isEmpty()); + assertTrue(cr1.removed.isEmpty()); + + list2.add("string2"); + final CompareResult<String> cr2 = new CompareResult<>(list1, list2); + assertEquals(Arrays.asList("string2"), cr2.added); + assertTrue(cr2.removed.isEmpty()); + + list2.remove("string1"); + final CompareResult<String> cr3 = new CompareResult<>(list1, list2); + assertEquals(Arrays.asList("string2"), cr3.added); + assertEquals(Arrays.asList("string1"), cr3.removed); + + list1.add("string2"); + final CompareResult<String> cr4 = new CompareResult<>(list1, list2); + assertTrue(cr4.added.isEmpty()); + assertEquals(Arrays.asList("string1"), cr3.removed); + } + + @Test + public void testCompareAddresses() { + final LinkProperties source = createTestObject(); + final LinkProperties target = new LinkProperties(source); + final InetAddress addr1 = toInetAddress("75.208.6.2"); + final LinkAddress linkAddr1 = new LinkAddress(addr1, 32); + + CompareResult<LinkAddress> results = LinkPropertiesUtils.compareAddresses(source, target); + assertEquals(0, results.removed.size()); + assertEquals(0, results.added.size()); + + source.addLinkAddress(linkAddr1); + results = LinkPropertiesUtils.compareAddresses(source, target); + assertEquals(1, results.removed.size()); + assertEquals(linkAddr1, results.removed.get(0)); + assertEquals(0, results.added.size()); + + final InetAddress addr2 = toInetAddress("75.208.6.3"); + final LinkAddress linkAddr2 = new LinkAddress(addr2, 32); + + target.addLinkAddress(linkAddr2); + results = LinkPropertiesUtils.compareAddresses(source, target); + assertEquals(linkAddr1, results.removed.get(0)); + assertEquals(linkAddr2, results.added.get(0)); + } + + private void assertCompareOrUpdateResult(CompareOrUpdateResult result, + List<String> expectedAdded, List<String> expectedRemoved, + List<String> expectedUpdated) { + assertSameElements(expectedAdded, result.added); + assertSameElements(expectedRemoved, result.removed); + assertSameElements(expectedUpdated, result.updated); + } + + private List<String> strArray(String... strs) { + return Arrays.asList(strs); + } + + @Test + public void testCompareOrUpdateResult() { + // As the item type, use a simple string. An item is defined to be an update of another item + // if the string starts with the same alphabetical characters. + // Extracting the key from the object is just a regexp. + Function<String, String> extractPrefix = (s) -> s.replaceFirst("^([a-z]+).*", "$1"); + assertEquals("goodbye", extractPrefix.apply("goodbye1234")); + + List<String> oldItems = strArray("hello123", "goodbye5678", "howareyou669"); + List<String> newItems = strArray("hello123", "goodbye000", "verywell"); + + final List<String> emptyList = new ArrayList<>(); + + // Items -> empty: everything removed. + CompareOrUpdateResult<String, String> result = + new CompareOrUpdateResult<String, String>(oldItems, emptyList, extractPrefix); + assertCompareOrUpdateResult(result, + emptyList, strArray("hello123", "howareyou669", "goodbye5678"), emptyList); + + // Empty -> items: everything added. + result = new CompareOrUpdateResult<String, String>(emptyList, newItems, extractPrefix); + assertCompareOrUpdateResult(result, + strArray("hello123", "goodbye000", "verywell"), emptyList, emptyList); + + // Empty -> empty: no change. + result = new CompareOrUpdateResult<String, String>(newItems, newItems, extractPrefix); + assertCompareOrUpdateResult(result, emptyList, emptyList, emptyList); + + // Added, removed, updated at the same time. + result = new CompareOrUpdateResult<>(oldItems, newItems, extractPrefix); + assertCompareOrUpdateResult(result, + strArray("verywell"), strArray("howareyou669"), strArray("goodbye000")); + + // Null -> items: everything added. + result = new CompareOrUpdateResult<String, String>(null, newItems, extractPrefix); + assertCompareOrUpdateResult(result, + strArray("hello123", "goodbye000", "verywell"), emptyList, emptyList); + + // Items -> null: everything removed. + result = new CompareOrUpdateResult<String, String>(oldItems, null, extractPrefix); + assertCompareOrUpdateResult(result, + emptyList, strArray("hello123", "howareyou669", "goodbye5678"), emptyList); + + // Null -> null: all lists empty. + result = new CompareOrUpdateResult<String, String>(null, null, extractPrefix); + assertCompareOrUpdateResult(result, emptyList, emptyList, emptyList); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/LocationPermissionCheckerTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/LocationPermissionCheckerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..84018a52baf25327d0243da30268fd9b8776081e --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/LocationPermissionCheckerTest.java @@ -0,0 +1,315 @@ +/* + * 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. + */ +package com.android.net.module.util; + +import static android.Manifest.permission.NETWORK_SETTINGS; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.Manifest; +import android.app.AppOpsManager; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.location.LocationManager; +import android.os.Binder; +import android.os.Build; +import android.os.Process; +import android.os.UserHandle; +import android.os.UserManager; + +import androidx.annotation.RequiresApi; + +import com.android.testutils.DevSdkIgnoreRule; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.util.HashMap; + +/** Unit tests for {@link LocationPermissionChecker}. */ +@RequiresApi(Build.VERSION_CODES.R) +public class LocationPermissionCheckerTest { + @Rule + public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule( + Build.VERSION_CODES.Q /* ignoreClassUpTo */); + + // Mock objects for testing + @Mock private Context mMockContext; + @Mock private PackageManager mMockPkgMgr; + @Mock private ApplicationInfo mMockApplInfo; + @Mock private AppOpsManager mMockAppOps; + @Mock private UserManager mMockUserManager; + @Mock private LocationManager mLocationManager; + + private static final String TEST_PKG_NAME = "com.google.somePackage"; + private static final String TEST_FEATURE_ID = "com.google.someFeature"; + private static final int MANAGED_PROFILE_UID = 1100000; + private static final int OTHER_USER_UID = 1200000; + + private final String mInteractAcrossUsersFullPermission = + "android.permission.INTERACT_ACROSS_USERS_FULL"; + private final String mManifestStringCoarse = + Manifest.permission.ACCESS_COARSE_LOCATION; + private final String mManifestStringFine = + Manifest.permission.ACCESS_FINE_LOCATION; + + // Test variables + private int mWifiScanAllowApps; + private int mUid; + private int mCoarseLocationPermission; + private int mAllowCoarseLocationApps; + private int mFineLocationPermission; + private int mAllowFineLocationApps; + private int mNetworkSettingsPermission; + private int mCurrentUid; + private boolean mIsLocationEnabled; + private boolean mThrowSecurityException; + private Answer<Integer> mReturnPermission; + private HashMap<String, Integer> mPermissionsList = new HashMap<String, Integer>(); + private LocationPermissionChecker mChecker; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + initTestVars(); + } + + private void setupMocks() throws Exception { + when(mMockPkgMgr.getApplicationInfoAsUser(eq(TEST_PKG_NAME), eq(0), any())) + .thenReturn(mMockApplInfo); + when(mMockContext.getPackageManager()).thenReturn(mMockPkgMgr); + when(mMockAppOps.noteOp(AppOpsManager.OPSTR_WIFI_SCAN, mUid, TEST_PKG_NAME, + TEST_FEATURE_ID, null)).thenReturn(mWifiScanAllowApps); + when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_COARSE_LOCATION), eq(mUid), + eq(TEST_PKG_NAME), eq(TEST_FEATURE_ID), nullable(String.class))) + .thenReturn(mAllowCoarseLocationApps); + when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_FINE_LOCATION), eq(mUid), + eq(TEST_PKG_NAME), eq(TEST_FEATURE_ID), nullable(String.class))) + .thenReturn(mAllowFineLocationApps); + if (mThrowSecurityException) { + doThrow(new SecurityException("Package " + TEST_PKG_NAME + " doesn't belong" + + " to application bound to user " + mUid)) + .when(mMockAppOps).checkPackage(mUid, TEST_PKG_NAME); + } + mockSystemService(Context.APP_OPS_SERVICE, AppOpsManager.class, mMockAppOps); + mockSystemService(Context.USER_SERVICE, UserManager.class, mMockUserManager); + mockSystemService(Context.LOCATION_SERVICE, LocationManager.class, mLocationManager); + } + + private <T> void mockSystemService(String name, Class<T> clazz, T service) { + when(mMockContext.getSystemService(name)).thenReturn(service); + when(mMockContext.getSystemServiceName(clazz)).thenReturn(name); + // Do not use mockito extended final method mocking + when(mMockContext.getSystemService(clazz)).thenCallRealMethod(); + } + + private void setupTestCase() throws Exception { + setupMocks(); + setupMockInterface(); + mChecker = new LocationPermissionChecker(mMockContext) { + @Override + protected int getCurrentUser() { + // Get the user ID of the process running the test rather than the foreground user + // id: ActivityManager.getCurrentUser() requires privileged permissions. + return UserHandle.getUserHandleForUid(Process.myUid()).getIdentifier(); + } + }; + } + + private void initTestVars() { + mPermissionsList.clear(); + mReturnPermission = createPermissionAnswer(); + mWifiScanAllowApps = AppOpsManager.MODE_ERRORED; + mUid = OTHER_USER_UID; + mThrowSecurityException = true; + mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.M; + mIsLocationEnabled = false; + mCurrentUid = Process.myUid(); + mCoarseLocationPermission = PackageManager.PERMISSION_DENIED; + mFineLocationPermission = PackageManager.PERMISSION_DENIED; + mAllowCoarseLocationApps = AppOpsManager.MODE_ERRORED; + mAllowFineLocationApps = AppOpsManager.MODE_ERRORED; + mNetworkSettingsPermission = PackageManager.PERMISSION_DENIED; + } + + private void setupMockInterface() { + Binder.restoreCallingIdentity((((long) mUid) << 32) | Binder.getCallingPid()); + doAnswer(mReturnPermission).when(mMockContext).checkPermission( + anyString(), anyInt(), anyInt()); + when(mMockUserManager.isSameProfileGroup(UserHandle.SYSTEM, + UserHandle.getUserHandleForUid(MANAGED_PROFILE_UID))) + .thenReturn(true); + when(mMockContext.checkPermission(mManifestStringCoarse, -1, mUid)) + .thenReturn(mCoarseLocationPermission); + when(mMockContext.checkPermission(mManifestStringFine, -1, mUid)) + .thenReturn(mFineLocationPermission); + when(mMockContext.checkPermission(NETWORK_SETTINGS, -1, mUid)) + .thenReturn(mNetworkSettingsPermission); + when(mLocationManager.isLocationEnabledForUser(any())).thenReturn(mIsLocationEnabled); + } + + private Answer<Integer> createPermissionAnswer() { + return new Answer<Integer>() { + @Override + public Integer answer(InvocationOnMock invocation) { + int myUid = (int) invocation.getArguments()[1]; + String myPermission = (String) invocation.getArguments()[0]; + mPermissionsList.get(myPermission); + if (mPermissionsList.containsKey(myPermission)) { + int uid = mPermissionsList.get(myPermission); + if (myUid == uid) { + return PackageManager.PERMISSION_GRANTED; + } + } + return PackageManager.PERMISSION_DENIED; + } + }; + } + + @Test + public void testEnforceLocationPermission_HasAllPermissions_BeforeQ() throws Exception { + mIsLocationEnabled = true; + mThrowSecurityException = false; + mCoarseLocationPermission = PackageManager.PERMISSION_GRANTED; + mAllowCoarseLocationApps = AppOpsManager.MODE_ALLOWED; + mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED; + mUid = mCurrentUid; + setupTestCase(); + + final int result = + mChecker.checkLocationPermissionWithDetailInfo( + TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null); + assertEquals(LocationPermissionChecker.SUCCEEDED, result); + } + + @Test + public void testEnforceLocationPermission_HasAllPermissions_AfterQ() throws Exception { + mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.Q; + mIsLocationEnabled = true; + mThrowSecurityException = false; + mUid = mCurrentUid; + mFineLocationPermission = PackageManager.PERMISSION_GRANTED; + mAllowFineLocationApps = AppOpsManager.MODE_ALLOWED; + mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED; + setupTestCase(); + + final int result = + mChecker.checkLocationPermissionWithDetailInfo( + TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null); + assertEquals(LocationPermissionChecker.SUCCEEDED, result); + } + + @Test + public void testEnforceLocationPermission_PkgNameAndUidMismatch() throws Exception { + mThrowSecurityException = true; + mIsLocationEnabled = true; + mFineLocationPermission = PackageManager.PERMISSION_GRANTED; + mAllowFineLocationApps = AppOpsManager.MODE_ALLOWED; + mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED; + setupTestCase(); + + assertThrows(SecurityException.class, + () -> mChecker.checkLocationPermissionWithDetailInfo( + TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null)); + } + + @Test + public void testenforceCanAccessScanResults_NoCoarseLocationPermission() throws Exception { + mThrowSecurityException = false; + mIsLocationEnabled = true; + setupTestCase(); + + final int result = + mChecker.checkLocationPermissionWithDetailInfo( + TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null); + assertEquals(LocationPermissionChecker.ERROR_LOCATION_PERMISSION_MISSING, result); + } + + @Test + public void testenforceCanAccessScanResults_NoFineLocationPermission() throws Exception { + mThrowSecurityException = false; + mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.Q; + mIsLocationEnabled = true; + mCoarseLocationPermission = PackageManager.PERMISSION_GRANTED; + mAllowFineLocationApps = AppOpsManager.MODE_ERRORED; + mUid = MANAGED_PROFILE_UID; + setupTestCase(); + + final int result = + mChecker.checkLocationPermissionWithDetailInfo( + TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null); + assertEquals(LocationPermissionChecker.ERROR_LOCATION_PERMISSION_MISSING, result); + verify(mMockAppOps, never()).noteOp(anyInt(), anyInt(), anyString()); + } + + @Test + public void testenforceCanAccessScanResults_LocationModeDisabled() throws Exception { + mThrowSecurityException = false; + mUid = MANAGED_PROFILE_UID; + mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED; + mPermissionsList.put(mInteractAcrossUsersFullPermission, mUid); + mIsLocationEnabled = false; + + setupTestCase(); + + final int result = + mChecker.checkLocationPermissionWithDetailInfo( + TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null); + assertEquals(LocationPermissionChecker.ERROR_LOCATION_MODE_OFF, result); + } + + @Test + public void testenforceCanAccessScanResults_LocationModeDisabledHasNetworkSettings() + throws Exception { + mThrowSecurityException = false; + mIsLocationEnabled = false; + mNetworkSettingsPermission = PackageManager.PERMISSION_GRANTED; + setupTestCase(); + + final int result = + mChecker.checkLocationPermissionWithDetailInfo( + TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null); + assertEquals(LocationPermissionChecker.SUCCEEDED, result); + } + + + private static void assertThrows(Class<? extends Exception> exceptionClass, Runnable r) { + try { + r.run(); + Assert.fail("Expected " + exceptionClass + " to be thrown."); + } catch (Exception exception) { + assertTrue(exceptionClass.isInstance(exception)); + } + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/MacAddressUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/MacAddressUtilsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..25507563678a1319413edd4c6a7ddcd6321d2a4f --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/MacAddressUtilsTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.net.MacAddress; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public final class MacAddressUtilsTest { + + // Matches WifiInfo.DEFAULT_MAC_ADDRESS + private static final MacAddress DEFAULT_MAC_ADDRESS = + MacAddress.fromString("02:00:00:00:00:00"); + + @Test + public void testIsMulticastAddress() { + MacAddress[] multicastAddresses = { + // broadcast address + MacAddress.fromString("ff:ff:ff:ff:ff:ff"), + MacAddress.fromString("07:00:d3:56:8a:c4"), + MacAddress.fromString("33:33:aa:bb:cc:dd"), + }; + MacAddress[] unicastAddresses = { + // all zero address + MacAddress.fromString("00:00:00:00:00:00"), + MacAddress.fromString("00:01:44:55:66:77"), + MacAddress.fromString("08:00:22:33:44:55"), + MacAddress.fromString("06:00:00:00:00:00"), + }; + + for (MacAddress mac : multicastAddresses) { + String msg = mac.toString() + " expected to be a multicast address"; + assertTrue(msg, MacAddressUtils.isMulticastAddress(mac)); + } + for (MacAddress mac : unicastAddresses) { + String msg = mac.toString() + " expected not to be a multicast address"; + assertFalse(msg, MacAddressUtils.isMulticastAddress(mac)); + } + } + + @Test + public void testMacAddressRandomGeneration() { + final int iterations = 1000; + + for (int i = 0; i < iterations; i++) { + MacAddress mac = MacAddressUtils.createRandomUnicastAddress(); + String stringRepr = mac.toString(); + + assertTrue(stringRepr + " expected to be a locally assigned address", + mac.isLocallyAssigned()); + assertEquals(MacAddress.TYPE_UNICAST, mac.getAddressType()); + assertFalse(mac.equals(DEFAULT_MAC_ADDRESS)); + } + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/NetUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/NetUtilsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..9e635c2124bbf467e495c9d4e64ab5acac3e1aaf --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/NetUtilsTest.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util; + +import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.net.InetAddresses; +import android.net.IpPrefix; +import android.net.RouteInfo; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.testutils.DevSdkIgnoreRule; +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +public final class NetUtilsTest { + @Rule + public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule(); + + private static final InetAddress V4_ADDR1 = toInetAddress("75.208.7.1"); + private static final InetAddress V4_ADDR2 = toInetAddress("75.208.7.2"); + private static final InetAddress V6_ADDR1 = toInetAddress("2001:0db8:85a3::8a2e:0370:7334"); + private static final InetAddress V6_ADDR2 = toInetAddress("2001:0db8:85a3::8a2e:0370:7335"); + + private static final InetAddress V4_GATEWAY = toInetAddress("75.208.8.1"); + private static final InetAddress V6_GATEWAY = toInetAddress("fe80::6:0000:613"); + + private static final InetAddress V4_DEST = toInetAddress("75.208.8.15"); + private static final InetAddress V6_DEST = toInetAddress("2001:db8:cafe::123"); + + private static final RouteInfo V4_EXPECTED = new RouteInfo(new IpPrefix("75.208.8.0/24"), + V4_GATEWAY, "wlan0"); + private static final RouteInfo V6_EXPECTED = new RouteInfo(new IpPrefix("2001:db8:cafe::/64"), + V6_GATEWAY, "wlan0"); + + private static InetAddress toInetAddress(String addr) { + return InetAddresses.parseNumericAddress(addr); + } + + @Test + public void testAddressTypeMatches() { + assertTrue(NetUtils.addressTypeMatches(V4_ADDR1, V4_ADDR2)); + assertTrue(NetUtils.addressTypeMatches(V6_ADDR1, V6_ADDR2)); + assertFalse(NetUtils.addressTypeMatches(V4_ADDR1, V6_ADDR1)); + assertFalse(NetUtils.addressTypeMatches(V6_ADDR1, V4_ADDR1)); + } + + @Test + public void testSelectBestRoute() { + final List<RouteInfo> routes = new ArrayList<>(); + + RouteInfo route = NetUtils.selectBestRoute(null, V4_DEST); + assertNull(route); + route = NetUtils.selectBestRoute(routes, null); + assertNull(route); + + route = NetUtils.selectBestRoute(routes, V4_DEST); + assertNull(route); + + routes.add(V4_EXPECTED); + // "75.208.0.0/16" is not an expected result since it is not the longest prefix. + routes.add(new RouteInfo(new IpPrefix("75.208.0.0/16"), V4_GATEWAY, "wlan0")); + routes.add(new RouteInfo(new IpPrefix("75.208.7.0/24"), V4_GATEWAY, "wlan0")); + + routes.add(V6_EXPECTED); + // "2001:db8::/32" is not an expected result since it is not the longest prefix. + routes.add(new RouteInfo(new IpPrefix("2001:db8::/32"), V6_GATEWAY, "wlan0")); + routes.add(new RouteInfo(new IpPrefix("2001:db8:beef::/64"), V6_GATEWAY, "wlan0")); + + // Verify expected v4 route is selected + route = NetUtils.selectBestRoute(routes, V4_DEST); + assertEquals(V4_EXPECTED, route); + + // Verify expected v6 route is selected + route = NetUtils.selectBestRoute(routes, V6_DEST); + assertEquals(V6_EXPECTED, route); + + // Remove expected v4 route + routes.remove(V4_EXPECTED); + route = NetUtils.selectBestRoute(routes, V4_DEST); + assertNotEquals(V4_EXPECTED, route); + + // Remove expected v6 route + routes.remove(V6_EXPECTED); + route = NetUtils.selectBestRoute(routes, V4_DEST); + assertNotEquals(V6_EXPECTED, route); + } + + @Test @IgnoreUpTo(SC_V2) + public void testSelectBestRouteWithExcludedRoutes() { + final List<RouteInfo> routes = new ArrayList<>(); + + routes.add(V4_EXPECTED); + routes.add(new RouteInfo(new IpPrefix("75.208.0.0/16"), V4_GATEWAY, "wlan0")); + routes.add(new RouteInfo(new IpPrefix("75.208.7.0/24"), V4_GATEWAY, "wlan0")); + + routes.add(V6_EXPECTED); + routes.add(new RouteInfo(new IpPrefix("2001:db8::/32"), V6_GATEWAY, "wlan0")); + routes.add(new RouteInfo(new IpPrefix("2001:db8:beef::/64"), V6_GATEWAY, "wlan0")); + + // After adding excluded v4 route with longer prefix, expected result is null. + routes.add(new RouteInfo(new IpPrefix("75.208.8.0/28"), null /* gateway */, "wlan0", + RouteInfo.RTN_THROW)); + RouteInfo route = NetUtils.selectBestRoute(routes, V4_DEST); + assertNull(route); + + // After adding excluded v6 route with longer prefix, expected result is null. + routes.add(new RouteInfo(new IpPrefix("2001:db8:cafe::/96"), null /* gateway */, "wlan0", + RouteInfo.RTN_THROW)); + route = NetUtils.selectBestRoute(routes, V6_DEST); + assertNull(route); + } +} + diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/NetworkCapabilitiesUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/NetworkCapabilitiesUtilsTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..958f45f46ed642b42006e2c25b20b1750fb4ee64 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/NetworkCapabilitiesUtilsTest.kt @@ -0,0 +1,127 @@ +/* + * 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. + */ + +package com.android.net.module.util + +import android.annotation.TargetApi +import android.net.NetworkCapabilities +import android.net.NetworkCapabilities.NET_CAPABILITY_BIP +import android.net.NetworkCapabilities.NET_CAPABILITY_CBS +import android.net.NetworkCapabilities.NET_CAPABILITY_EIMS +import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET +import android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID +import android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH +import android.net.NetworkCapabilities.TRANSPORT_CELLULAR +import android.net.NetworkCapabilities.TRANSPORT_ETHERNET +import android.net.NetworkCapabilities.TRANSPORT_TEST +import android.net.NetworkCapabilities.TRANSPORT_VPN +import android.net.NetworkCapabilities.TRANSPORT_WIFI +import android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE +import android.os.Build +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.modules.utils.build.SdkLevel +import com.android.net.module.util.NetworkCapabilitiesUtils.RESTRICTED_CAPABILITIES +import com.android.net.module.util.NetworkCapabilitiesUtils.UNRESTRICTED_CAPABILITIES +import com.android.net.module.util.NetworkCapabilitiesUtils.getDisplayTransport +import org.junit.Test +import org.junit.runner.RunWith +import java.lang.IllegalArgumentException +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +@RunWith(AndroidJUnit4::class) +@SmallTest +class NetworkCapabilitiesUtilsTest { + + @Test + fun testGetAccountingTransport() { + assertEquals(TRANSPORT_WIFI, getDisplayTransport(intArrayOf(TRANSPORT_WIFI))) + assertEquals(TRANSPORT_CELLULAR, getDisplayTransport(intArrayOf(TRANSPORT_CELLULAR))) + assertEquals(TRANSPORT_BLUETOOTH, getDisplayTransport(intArrayOf(TRANSPORT_BLUETOOTH))) + assertEquals(TRANSPORT_ETHERNET, getDisplayTransport(intArrayOf(TRANSPORT_ETHERNET))) + assertEquals(TRANSPORT_WIFI_AWARE, getDisplayTransport(intArrayOf(TRANSPORT_WIFI_AWARE))) + + assertEquals(TRANSPORT_VPN, getDisplayTransport( + intArrayOf(TRANSPORT_VPN, TRANSPORT_WIFI))) + assertEquals(TRANSPORT_VPN, getDisplayTransport( + intArrayOf(TRANSPORT_CELLULAR, TRANSPORT_VPN))) + + assertEquals(TRANSPORT_WIFI, getDisplayTransport( + intArrayOf(TRANSPORT_ETHERNET, TRANSPORT_WIFI))) + assertEquals(TRANSPORT_ETHERNET, getDisplayTransport( + intArrayOf(TRANSPORT_ETHERNET, TRANSPORT_TEST))) + + assertFailsWith(IllegalArgumentException::class) { + getDisplayTransport(intArrayOf()) + } + } + + // NetworkCapabilities constructor and Builder are not available until R. Mark TargetApi to + // ignore the linter error since it's used in only unit test. + @Test @TargetApi(Build.VERSION_CODES.R) + fun testInferRestrictedCapability() { + val nc = NetworkCapabilities() + // Default capabilities don't have restricted capability. + assertFalse(NetworkCapabilitiesUtils.inferRestrictedCapability(nc)) + // If there is a force restricted capability, then the network capabilities is restricted. + nc.addCapability(NET_CAPABILITY_OEM_PAID) + nc.addCapability(NET_CAPABILITY_INTERNET) + assertTrue(NetworkCapabilitiesUtils.inferRestrictedCapability(nc)) + // Except for the force restricted capability, if there is any unrestricted capability in + // capabilities, then the network capabilities is not restricted. + nc.removeCapability(NET_CAPABILITY_OEM_PAID) + nc.addCapability(NET_CAPABILITY_CBS) + assertFalse(NetworkCapabilitiesUtils.inferRestrictedCapability(nc)) + // Except for the force restricted capability, the network capabilities will only be treated + // as restricted when there is no any unrestricted capability. + nc.removeCapability(NET_CAPABILITY_INTERNET) + assertTrue(NetworkCapabilitiesUtils.inferRestrictedCapability(nc)) + if (!SdkLevel.isAtLeastS()) return + // BIP deserves its specific test because it's the first capability over 30, meaning the + // shift will overflow + nc.removeCapability(NET_CAPABILITY_CBS) + nc.addCapability(NET_CAPABILITY_BIP) + assertTrue(NetworkCapabilitiesUtils.inferRestrictedCapability(nc)) + } + + @Test + fun testRestrictedUnrestrictedCapabilities() { + // verify EIMS is restricted + assertEquals((1 shl NET_CAPABILITY_EIMS).toLong() and RESTRICTED_CAPABILITIES, + (1 shl NET_CAPABILITY_EIMS).toLong()) + + // verify CBS is also restricted + assertEquals((1 shl NET_CAPABILITY_CBS).toLong() and RESTRICTED_CAPABILITIES, + (1 shl NET_CAPABILITY_CBS).toLong()) + + // verify BIP is also restricted + // BIP is not available in R and before, but the BIP constant is inlined so + // this test can still run on R. + assertEquals((1L shl NET_CAPABILITY_BIP) and RESTRICTED_CAPABILITIES, + (1L shl NET_CAPABILITY_BIP)) + + // verify default is not restricted + assertEquals((1 shl NET_CAPABILITY_INTERNET).toLong() and RESTRICTED_CAPABILITIES, 0) + + assertTrue(RESTRICTED_CAPABILITIES > 0) + + // just to see + assertEquals(RESTRICTED_CAPABILITIES and UNRESTRICTED_CAPABILITIES, 0) + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/NetworkIdentityUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/NetworkIdentityUtilsTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..2904e1239c92e9aa49c45802194781cd15f2fa8c --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/NetworkIdentityUtilsTest.kt @@ -0,0 +1,49 @@ +/* + * 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. + */ + +package com.android.net.module.util + +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.net.module.util.NetworkIdentityUtils.scrubSubscriberId +import com.android.net.module.util.NetworkIdentityUtils.scrubSubscriberIds +import com.android.testutils.assertContainsStringsExactly +import org.junit.Test +import org.junit.runner.RunWith +import kotlin.test.assertEquals +import kotlin.test.assertNull + +@RunWith(AndroidJUnit4::class) +@SmallTest +class NetworkIdentityUtilsTest { + @Test + fun testScrubSubscriberId() { + assertEquals("123456...", scrubSubscriberId("1234567890123")) + assertEquals("123456...", scrubSubscriberId("1234567")) + assertEquals("123...", scrubSubscriberId("123")) + assertEquals("...", scrubSubscriberId("")) + assertEquals("null", scrubSubscriberId(null)) + } + + @Test + fun testScrubSubscriberIds() { + assertContainsStringsExactly(scrubSubscriberIds(arrayOf("1234567", "", null))!!, + "123456...", "...", "null") + assertContainsStringsExactly(scrubSubscriberIds(arrayOf("12345"))!!, "12345...") + assertContainsStringsExactly(scrubSubscriberIds(arrayOf())!!) + assertNull(scrubSubscriberIds(null)) + } +} \ No newline at end of file diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/NetworkStatsUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/NetworkStatsUtilsTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..2785ea9029e7faa934e8f3582052cba27f2983e3 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/NetworkStatsUtilsTest.kt @@ -0,0 +1,142 @@ +/* + * 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. + */ + +package com.android.net.module.util + +import android.net.NetworkStats +import android.text.TextUtils +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import org.junit.Test +import org.junit.runner.RunWith +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.mock + +@RunWith(AndroidJUnit4::class) +@SmallTest +class NetworkStatsUtilsTest { + @Test + fun testMultiplySafeByRational() { + // Verify basic cases that the method equals to a * b / c. + assertEquals(3 * 5 / 2, NetworkStatsUtils.multiplySafeByRational(3, 5, 2)) + + // Verify input with zeros. + assertEquals(0 * 7 / 3, NetworkStatsUtils.multiplySafeByRational(0, 7, 3)) + assertEquals(7 * 0 / 3, NetworkStatsUtils.multiplySafeByRational(7, 0, 3)) + assertEquals(0 * 0 / 1, NetworkStatsUtils.multiplySafeByRational(0, 0, 1)) + assertEquals(0, NetworkStatsUtils.multiplySafeByRational(0, Long.MAX_VALUE, Long.MAX_VALUE)) + assertEquals(0, NetworkStatsUtils.multiplySafeByRational(Long.MAX_VALUE, 0, Long.MAX_VALUE)) + assertFailsWith<ArithmeticException> { + NetworkStatsUtils.multiplySafeByRational(7, 3, 0) + } + assertFailsWith<ArithmeticException> { + NetworkStatsUtils.multiplySafeByRational(0, 0, 0) + } + + // Verify cases where a * b overflows. + assertEquals(101, NetworkStatsUtils.multiplySafeByRational( + 101, Long.MAX_VALUE, Long.MAX_VALUE)) + assertEquals(721, NetworkStatsUtils.multiplySafeByRational( + Long.MAX_VALUE, 721, Long.MAX_VALUE)) + assertEquals(Long.MAX_VALUE, NetworkStatsUtils.multiplySafeByRational( + Long.MAX_VALUE, Long.MAX_VALUE, Long.MAX_VALUE)) + assertFailsWith<ArithmeticException> { + NetworkStatsUtils.multiplySafeByRational(Long.MAX_VALUE, Long.MAX_VALUE, 0) + } + } + + @Test + fun testConstrain() { + assertFailsWith<IllegalArgumentException> { + NetworkStatsUtils.constrain(5, 6, 3) // low > high + } + assertEquals(3, NetworkStatsUtils.constrain(5, 1, 3)) + assertEquals(3, NetworkStatsUtils.constrain(3, 1, 3)) + assertEquals(2, NetworkStatsUtils.constrain(2, 1, 3)) + assertEquals(1, NetworkStatsUtils.constrain(1, 1, 3)) + assertEquals(1, NetworkStatsUtils.constrain(0, 1, 3)) + + assertEquals(11, NetworkStatsUtils.constrain(15, 11, 11)) + assertEquals(11, NetworkStatsUtils.constrain(11, 11, 11)) + assertEquals(11, NetworkStatsUtils.constrain(1, 11, 11)) + } + + @Test + fun testBucketToEntry() { + val bucket = makeMockBucket(android.app.usage.NetworkStats.Bucket.UID_ALL, + android.app.usage.NetworkStats.Bucket.TAG_NONE, + android.app.usage.NetworkStats.Bucket.STATE_DEFAULT, + android.app.usage.NetworkStats.Bucket.METERED_YES, + android.app.usage.NetworkStats.Bucket.ROAMING_NO, + android.app.usage.NetworkStats.Bucket.DEFAULT_NETWORK_ALL, 1024, 8, 2048, 12) + val entry = NetworkStatsUtils.fromBucket(bucket) + val expectedEntry = NetworkStats.Entry(null /* IFACE_ALL */, NetworkStats.UID_ALL, + NetworkStats.SET_DEFAULT, NetworkStats.TAG_NONE, NetworkStats.METERED_YES, + NetworkStats.ROAMING_NO, NetworkStats.DEFAULT_NETWORK_ALL, 1024, 8, 2048, 12, + 0 /* operations */) + + // TODO: Use assertEquals once all downstreams accept null iface in + // NetworkStats.Entry#equals. + assertEntryEquals(expectedEntry, entry) + } + + private fun makeMockBucket( + uid: Int, + tag: Int, + state: Int, + metered: Int, + roaming: Int, + defaultNetwork: Int, + rxBytes: Long, + rxPackets: Long, + txBytes: Long, + txPackets: Long + ): android.app.usage.NetworkStats.Bucket { + val ret: android.app.usage.NetworkStats.Bucket = + mock(android.app.usage.NetworkStats.Bucket::class.java) + doReturn(uid).`when`(ret).getUid() + doReturn(tag).`when`(ret).getTag() + doReturn(state).`when`(ret).getState() + doReturn(metered).`when`(ret).getMetered() + doReturn(roaming).`when`(ret).getRoaming() + doReturn(defaultNetwork).`when`(ret).getDefaultNetworkStatus() + doReturn(rxBytes).`when`(ret).getRxBytes() + doReturn(rxPackets).`when`(ret).getRxPackets() + doReturn(txBytes).`when`(ret).getTxBytes() + doReturn(txPackets).`when`(ret).getTxPackets() + return ret + } + + /** + * Assert that the two {@link NetworkStats.Entry} are equals. + */ + private fun assertEntryEquals(left: NetworkStats.Entry, right: NetworkStats.Entry) { + TextUtils.equals(left.iface, right.iface) + assertEquals(left.uid, right.uid) + assertEquals(left.set, right.set) + assertEquals(left.tag, right.tag) + assertEquals(left.metered, right.metered) + assertEquals(left.roaming, right.roaming) + assertEquals(left.defaultNetwork, right.defaultNetwork) + assertEquals(left.rxBytes, right.rxBytes) + assertEquals(left.rxPackets, right.rxPackets) + assertEquals(left.txBytes, right.txBytes) + assertEquals(left.txPackets, right.txPackets) + assertEquals(left.operations, right.operations) + } +} \ No newline at end of file diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java new file mode 100644 index 0000000000000000000000000000000000000000..e40cd6b6bfc549c5547857770d65750a6d244ae9 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java @@ -0,0 +1,935 @@ +/* + * 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. + */ + +package com.android.net.module.util; + +import static android.system.OsConstants.IPPROTO_IP; +import static android.system.OsConstants.IPPROTO_IPV6; +import static android.system.OsConstants.IPPROTO_TCP; +import static android.system.OsConstants.IPPROTO_UDP; + +import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV4; +import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6; +import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN; +import static com.android.net.module.util.NetworkStackConstants.TCPHDR_ACK; +import static com.android.net.module.util.NetworkStackConstants.TCP_HEADER_MIN_LEN; +import static com.android.net.module.util.NetworkStackConstants.UDP_HEADER_LEN; +import static com.android.testutils.MiscAsserts.assertThrows; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import android.net.InetAddresses; +import android.net.MacAddress; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.net.module.util.structs.EthernetHeader; +import com.android.net.module.util.structs.Ipv4Header; +import com.android.net.module.util.structs.Ipv6Header; +import com.android.net.module.util.structs.TcpHeader; +import com.android.net.module.util.structs.UdpHeader; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.nio.ByteBuffer; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class PacketBuilderTest { + private static final MacAddress SRC_MAC = MacAddress.fromString("11:22:33:44:55:66"); + private static final MacAddress DST_MAC = MacAddress.fromString("aa:bb:cc:dd:ee:ff"); + private static final Inet4Address IPV4_SRC_ADDR = addr4("192.0.2.1"); + private static final Inet4Address IPV4_DST_ADDR = addr4("198.51.100.1"); + private static final Inet6Address IPV6_SRC_ADDR = addr6("2001:db8::1"); + private static final Inet6Address IPV6_DST_ADDR = addr6("2001:db8::2"); + private static final short SRC_PORT = 9876; + private static final short DST_PORT = 433; + private static final short SEQ_NO = 13579; + private static final short ACK_NO = 24680; + private static final byte TYPE_OF_SERVICE = 0; + private static final short ID = 27149; + private static final short FLAGS_AND_FRAGMENT_OFFSET = (short) 0x4000; // flags=DF, offset=0 + private static final byte TIME_TO_LIVE = (byte) 0x40; + private static final short WINDOW = (short) 0x2000; + private static final short URGENT_POINTER = 0; + // version=6, traffic class=0x80, flowlabel=0x515ca; + private static final int VERSION_TRAFFICCLASS_FLOWLABEL = 0x680515ca; + private static final short HOP_LIMIT = 0x40; + private static final ByteBuffer DATA = ByteBuffer.wrap(new byte[] { + (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef + }); + + private static final byte[] TEST_PACKET_ETHERHDR_IPV4HDR_TCPHDR = + new byte[] { + // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff", + // type='IPv4') / + // scapy.IP(src="192.0.2.1", dst="198.51.100.1", + // tos=0, id=27149, flags='DF') / + // scapy.TCP(sport=9876, dport=433, seq=13579, ack=24680, + // flags='A', window=8192, urgptr=0)) + // Ether header + (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd, + (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22, + (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66, + (byte) 0x08, (byte) 0x00, + // IPv4 header + (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x28, + (byte) 0x6a, (byte) 0x0d, (byte) 0x40, (byte) 0x00, + (byte) 0x40, (byte) 0x06, (byte) 0xe4, (byte) 0x8c, + (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01, + (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01, + // TCP header + (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1, + (byte) 0x00, (byte) 0x00, (byte) 0x35, (byte) 0x0b, + (byte) 0x00, (byte) 0x00, (byte) 0x60, (byte) 0x68, + (byte) 0x50, (byte) 0x10, (byte) 0x20, (byte) 0x00, + (byte) 0xe5, (byte) 0xe5, (byte) 0x00, (byte) 0x00 + }; + + private static final byte[] TEST_PACKET_ETHERHDR_IPV4HDR_TCPHDR_DATA = + new byte[] { + // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff", + // type='IPv4') / + // scapy.IP(src="192.0.2.1", dst="198.51.100.1", + // tos=0, id=27149, flags='DF') / + // scapy.TCP(sport=9876, dport=433, seq=13579, ack=24680, + // flags='A', window=8192, urgptr=0) / + // b'\xde\xad\xbe\xef') + // Ether header + (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd, + (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22, + (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66, + (byte) 0x08, (byte) 0x00, + // IPv4 header + (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x2c, + (byte) 0x6a, (byte) 0x0d, (byte) 0x40, (byte) 0x00, + (byte) 0x40, (byte) 0x06, (byte) 0xe4, (byte) 0x88, + (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01, + (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01, + // TCP header + (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1, + (byte) 0x00, (byte) 0x00, (byte) 0x35, (byte) 0x0b, + (byte) 0x00, (byte) 0x00, (byte) 0x60, (byte) 0x68, + (byte) 0x50, (byte) 0x10, (byte) 0x20, (byte) 0x00, + (byte) 0x48, (byte) 0x44, (byte) 0x00, (byte) 0x00, + // Data + (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef + }; + + private static final byte[] TEST_PACKET_IPV4HDR_TCPHDR = + new byte[] { + // packet = (scapy.IP(src="192.0.2.1", dst="198.51.100.1", + // tos=0, id=27149, flags='DF') / + // scapy.TCP(sport=9876, dport=433, seq=13579, ack=24680, + // flags='A', window=8192, urgptr=0)) + // IPv4 header + (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x28, + (byte) 0x6a, (byte) 0x0d, (byte) 0x40, (byte) 0x00, + (byte) 0x40, (byte) 0x06, (byte) 0xe4, (byte) 0x8c, + (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01, + (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01, + // TCP header + (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1, + (byte) 0x00, (byte) 0x00, (byte) 0x35, (byte) 0x0b, + (byte) 0x00, (byte) 0x00, (byte) 0x60, (byte) 0x68, + (byte) 0x50, (byte) 0x10, (byte) 0x20, (byte) 0x00, + (byte) 0xe5, (byte) 0xe5, (byte) 0x00, (byte) 0x00 + }; + + private static final byte[] TEST_PACKET_IPV4HDR_TCPHDR_DATA = + new byte[] { + // packet = (scapy.IP(src="192.0.2.1", dst="198.51.100.1", + // tos=0, id=27149, flags='DF') / + // scapy.TCP(sport=9876, dport=433, seq=13579, ack=24680, + // flags='A', window=8192, urgptr=0) / + // b'\xde\xad\xbe\xef') + // IPv4 header + (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x2c, + (byte) 0x6a, (byte) 0x0d, (byte) 0x40, (byte) 0x00, + (byte) 0x40, (byte) 0x06, (byte) 0xe4, (byte) 0x88, + (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01, + (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01, + // TCP header + (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1, + (byte) 0x00, (byte) 0x00, (byte) 0x35, (byte) 0x0b, + (byte) 0x00, (byte) 0x00, (byte) 0x60, (byte) 0x68, + (byte) 0x50, (byte) 0x10, (byte) 0x20, (byte) 0x00, + (byte) 0x48, (byte) 0x44, (byte) 0x00, (byte) 0x00, + // Data + (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef + }; + + private static final byte[] TEST_PACKET_ETHERHDR_IPV4HDR_UDPHDR = + new byte[] { + // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff", + // type='IPv4') / + // scapy.IP(src="192.0.2.1", dst="198.51.100.1", + // tos=0, id=27149, flags='DF') / + // scapy.UDP(sport=9876, dport=433)) + // Ether header + (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd, + (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22, + (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66, + (byte) 0x08, (byte) 0x00, + // IP header + (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x1c, + (byte) 0x6a, (byte) 0x0d, (byte) 0x40, (byte) 0x00, + (byte) 0x40, (byte) 0x11, (byte) 0xe4, (byte) 0x8d, + (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01, + (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01, + // UDP header + (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1, + (byte) 0x00, (byte) 0x08, (byte) 0xeb, (byte) 0x62 + }; + + private static final byte[] TEST_PACKET_ETHERHDR_IPV4HDR_UDPHDR_DATA = + new byte[] { + // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff", + // type='IPv4') / + // scapy.IP(src="192.0.2.1", dst="198.51.100.1", + // tos=0, id=27149, flags='DF') / + // scapy.UDP(sport=9876, dport=433) / + // b'\xde\xad\xbe\xef') + // Ether header + (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd, + (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22, + (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66, + (byte) 0x08, (byte) 0x00, + // IP header + (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x20, + (byte) 0x6a, (byte) 0x0d, (byte) 0x40, (byte) 0x00, + (byte) 0x40, (byte) 0x11, (byte) 0xe4, (byte) 0x89, + (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01, + (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01, + // UDP header + (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1, + (byte) 0x00, (byte) 0x0c, (byte) 0x4d, (byte) 0xbd, + // Data + (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef + }; + + private static final byte[] TEST_PACKET_IPV4HDR_UDPHDR = + new byte[] { + // packet = (scapy.IP(src="192.0.2.1", dst="198.51.100.1", + // tos=0, id=27149, flags='DF') / + // scapy.UDP(sport=9876, dport=433)) + // IP header + (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x1c, + (byte) 0x6a, (byte) 0x0d, (byte) 0x40, (byte) 0x00, + (byte) 0x40, (byte) 0x11, (byte) 0xe4, (byte) 0x8d, + (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01, + (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01, + // UDP header + (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1, + (byte) 0x00, (byte) 0x08, (byte) 0xeb, (byte) 0x62 + }; + + private static final byte[] TEST_PACKET_IPV4HDR_UDPHDR_DATA = + new byte[] { + // packet = (scapy.IP(src="192.0.2.1", dst="198.51.100.1", + // tos=0, id=27149, flags='DF') / + // scapy.UDP(sport=9876, dport=433) / + // b'\xde\xad\xbe\xef') + // IP header + (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x20, + (byte) 0x6a, (byte) 0x0d, (byte) 0x40, (byte) 0x00, + (byte) 0x40, (byte) 0x11, (byte) 0xe4, (byte) 0x89, + (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01, + (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01, + // UDP header + (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1, + (byte) 0x00, (byte) 0x0c, (byte) 0x4d, (byte) 0xbd, + // Data + (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef + }; + + private static final byte[] TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR = + new byte[] { + // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff", + // type='IPv6') / + // scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80, + // fl=0x515ca, hlim=0x40) / + // scapy.UDP(sport=9876, dport=433)) + // Ether header + (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd, + (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22, + (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66, + (byte) 0x86, (byte) 0xdd, + // IP header + (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca, + (byte) 0x00, (byte) 0x08, (byte) 0x11, (byte) 0x40, + (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, + (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02, + // UDP header + (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1, + (byte) 0x00, (byte) 0x08, (byte) 0x7c, (byte) 0x24 + }; + + private static final byte[] TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA = + new byte[] { + // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff", + // type='IPv6') / + // scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80, + // fl=0x515ca, hlim=0x40) / + // scapy.UDP(sport=9876, dport=433) / + // b'\xde\xad\xbe\xef') + // Ether header + (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd, + (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22, + (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66, + (byte) 0x86, (byte) 0xdd, + // IP header + (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca, + (byte) 0x00, (byte) 0x0c, (byte) 0x11, (byte) 0x40, + (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, + (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02, + // UDP header + (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1, + (byte) 0x00, (byte) 0x0c, (byte) 0xde, (byte) 0x7e, + // Data + (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef + }; + + private static final byte[] TEST_PACKET_ETHERHDR_IPV6HDR_TCPHDR_DATA = + new byte[] { + // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff", + // type='IPv6') / + // scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80, + // fl=0x515ca, hlim=0x40) / + // scapy.TCP(sport=9876, dport=433, seq=13579, ack=24680, + // flags='A', window=8192, urgptr=0) / + // b'\xde\xad\xbe\xef') + // Ether header + (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd, + (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22, + (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66, + (byte) 0x86, (byte) 0xdd, + // IPv6 header + (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca, + (byte) 0x00, (byte) 0x18, (byte) 0x06, (byte) 0x40, + (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, + (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02, + // TCP header + (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1, + (byte) 0x00, (byte) 0x00, (byte) 0x35, (byte) 0x0b, + (byte) 0x00, (byte) 0x00, (byte) 0x60, (byte) 0x68, + (byte) 0x50, (byte) 0x10, (byte) 0x20, (byte) 0x00, + (byte) 0xd9, (byte) 0x05, (byte) 0x00, (byte) 0x00, + // Data + (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef + }; + + private static final byte[] TEST_PACKET_ETHERHDR_IPV6HDR_TCPHDR = + new byte[] { + // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff", + // type='IPv6') / + // scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80, + // fl=0x515ca, hlim=0x40) / + // scapy.TCP(sport=9876, dport=433, seq=13579, ack=24680, + // flags='A', window=8192, urgptr=0)) + // Ether header + (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd, + (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22, + (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66, + (byte) 0x86, (byte) 0xdd, + // IPv6 header + (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca, + (byte) 0x00, (byte) 0x14, (byte) 0x06, (byte) 0x40, + (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, + (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02, + // TCP header + (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1, + (byte) 0x00, (byte) 0x00, (byte) 0x35, (byte) 0x0b, + (byte) 0x00, (byte) 0x00, (byte) 0x60, (byte) 0x68, + (byte) 0x50, (byte) 0x10, (byte) 0x20, (byte) 0x00, + (byte) 0x76, (byte) 0xa7, (byte) 0x00, (byte) 0x00 + }; + + private static final byte[] TEST_PACKET_IPV6HDR_TCPHDR_DATA = + new byte[] { + // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80, + // fl=0x515ca, hlim=0x40) / + // scapy.TCP(sport=9876, dport=433, seq=13579, ack=24680, + // flags='A', window=8192, urgptr=0) / + // b'\xde\xad\xbe\xef') + // IPv6 header + (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca, + (byte) 0x00, (byte) 0x18, (byte) 0x06, (byte) 0x40, + (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, + (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02, + // TCP header + (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1, + (byte) 0x00, (byte) 0x00, (byte) 0x35, (byte) 0x0b, + (byte) 0x00, (byte) 0x00, (byte) 0x60, (byte) 0x68, + (byte) 0x50, (byte) 0x10, (byte) 0x20, (byte) 0x00, + (byte) 0xd9, (byte) 0x05, (byte) 0x00, (byte) 0x00, + // Data + (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef + }; + + private static final byte[] TEST_PACKET_IPV6HDR_TCPHDR = + new byte[] { + // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80, + // fl=0x515ca, hlim=0x40) / + // scapy.TCP(sport=9876, dport=433, seq=13579, ack=24680, + // flags='A', window=8192, urgptr=0)) + // IPv6 header + (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca, + (byte) 0x00, (byte) 0x14, (byte) 0x06, (byte) 0x40, + (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, + (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02, + // TCP header + (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1, + (byte) 0x00, (byte) 0x00, (byte) 0x35, (byte) 0x0b, + (byte) 0x00, (byte) 0x00, (byte) 0x60, (byte) 0x68, + (byte) 0x50, (byte) 0x10, (byte) 0x20, (byte) 0x00, + (byte) 0x76, (byte) 0xa7, (byte) 0x00, (byte) 0x00 + }; + + private static final byte[] TEST_PACKET_IPV6HDR_UDPHDR = + new byte[] { + // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80, + // fl=0x515ca, hlim=0x40) / + // scapy.UDP(sport=9876, dport=433)) + // IP header + (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca, + (byte) 0x00, (byte) 0x08, (byte) 0x11, (byte) 0x40, + (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, + (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02, + // UDP header + (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1, + (byte) 0x00, (byte) 0x08, (byte) 0x7c, (byte) 0x24 + }; + + private static final byte[] TEST_PACKET_IPV6HDR_UDPHDR_DATA = + new byte[] { + // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80, + // fl=0x515ca, hlim=0x40) / + // scapy.UDP(sport=9876, dport=433) / + // b'\xde\xad\xbe\xef') + // IP header + (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca, + (byte) 0x00, (byte) 0x0c, (byte) 0x11, (byte) 0x40, + (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, + (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02, + // UDP header + (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1, + (byte) 0x00, (byte) 0x0c, (byte) 0xde, (byte) 0x7e, + // Data + (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef + }; + + /** + * Build a packet which has ether header, IP header, TCP/UDP header and data. + * The ethernet header and data are optional. Note that both source mac address and + * destination mac address are required for ethernet header. + * + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Layer 2 header (EthernetHeader) | (optional) + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Layer 3 header (Ipv4Header, Ipv6Header) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Layer 4 header (TcpHeader, UdpHeader) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Payload | (optional) + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * @param srcMac source MAC address. used by L2 ether header. + * @param dstMac destination MAC address. used by L2 ether header. + * @param l3proto the layer 3 protocol. Only {@code IPPROTO_IP} and {@code IPPROTO_IPV6} + * currently supported. + * @param l4proto the layer 4 protocol. Only {@code IPPROTO_TCP} and {@code IPPROTO_UDP} + * currently supported. + * @param payload the payload. + */ + @NonNull + private ByteBuffer buildPacket(@Nullable final MacAddress srcMac, + @Nullable final MacAddress dstMac, final int l3proto, final int l4proto, + @Nullable final ByteBuffer payload) + throws Exception { + if (l3proto != IPPROTO_IP && l3proto != IPPROTO_IPV6) { + fail("Unsupported layer 3 protocol " + l3proto); + } + + if (l4proto != IPPROTO_TCP && l4proto != IPPROTO_UDP) { + fail("Unsupported layer 4 protocol " + l4proto); + } + + final boolean hasEther = (srcMac != null && dstMac != null); + final int payloadLen = (payload == null) ? 0 : payload.limit(); + final ByteBuffer buffer = PacketBuilder.allocate(hasEther, l3proto, l4proto, + payloadLen); + final PacketBuilder packetBuilder = new PacketBuilder(buffer); + + // [1] Build ether header. + if (hasEther) { + final int etherType = (l3proto == IPPROTO_IP) ? ETHER_TYPE_IPV4 : ETHER_TYPE_IPV6; + packetBuilder.writeL2Header(srcMac, dstMac, (short) etherType); + } + + // [2] Build IP header. + if (l3proto == IPPROTO_IP) { + packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET, + TIME_TO_LIVE, (byte) l4proto, IPV4_SRC_ADDR, IPV4_DST_ADDR); + } else if (l3proto == IPPROTO_IPV6) { + packetBuilder.writeIpv6Header(VERSION_TRAFFICCLASS_FLOWLABEL, + (byte) l4proto, HOP_LIMIT, IPV6_SRC_ADDR, IPV6_DST_ADDR); + } + + // [3] Build TCP or UDP header. + if (l4proto == IPPROTO_TCP) { + packetBuilder.writeTcpHeader(SRC_PORT, DST_PORT, SEQ_NO, ACK_NO, + TCPHDR_ACK, WINDOW, URGENT_POINTER); + } else if (l4proto == IPPROTO_UDP) { + packetBuilder.writeUdpHeader(SRC_PORT, DST_PORT); + } + + // [4] Build payload. + if (payload != null) { + buffer.put(payload); + // in case data might be reused by caller, restore the position and + // limit of bytebuffer. + payload.clear(); + } + + return packetBuilder.finalizePacket(); + } + + /** + * Check ethernet header. + * + * @param l3proto the layer 3 protocol. Only {@code IPPROTO_IP} and {@code IPPROTO_IPV6} + * currently supported. + * @param actual the packet to check. + */ + private void checkEtherHeader(final int l3proto, final ByteBuffer actual) { + if (l3proto != IPPROTO_IP && l3proto != IPPROTO_IPV6) { + fail("Unsupported layer 3 protocol " + l3proto); + } + + final EthernetHeader eth = Struct.parse(EthernetHeader.class, actual); + assertEquals(SRC_MAC, eth.srcMac); + assertEquals(DST_MAC, eth.dstMac); + final int expectedEtherType = (l3proto == IPPROTO_IP) ? ETHER_TYPE_IPV4 : ETHER_TYPE_IPV6; + assertEquals(expectedEtherType, eth.etherType); + } + + /** + * Check IPv4 header. + * + * @param l4proto the layer 4 protocol. Only {@code IPPROTO_TCP} and {@code IPPROTO_UDP} + * currently supported. + * @param hasData true if the packet has data payload; false otherwise. + * @param actual the packet to check. + */ + private void checkIpv4Header(final int l4proto, final boolean hasData, + final ByteBuffer actual) { + if (l4proto != IPPROTO_TCP && l4proto != IPPROTO_UDP) { + fail("Unsupported layer 4 protocol " + l4proto); + } + + final Ipv4Header ipv4Header = Struct.parse(Ipv4Header.class, actual); + assertEquals(Ipv4Header.IPHDR_VERSION_IHL, ipv4Header.vi); + assertEquals(TYPE_OF_SERVICE, ipv4Header.tos); + assertEquals(ID, ipv4Header.id); + assertEquals(FLAGS_AND_FRAGMENT_OFFSET, ipv4Header.flagsAndFragmentOffset); + assertEquals(TIME_TO_LIVE, ipv4Header.ttl); + assertEquals(IPV4_SRC_ADDR, ipv4Header.srcIp); + assertEquals(IPV4_DST_ADDR, ipv4Header.dstIp); + + final int dataLength = hasData ? DATA.limit() : 0; + if (l4proto == IPPROTO_TCP) { + assertEquals(IPV4_HEADER_MIN_LEN + TCP_HEADER_MIN_LEN + dataLength, + ipv4Header.totalLength); + assertEquals((byte) IPPROTO_TCP, ipv4Header.protocol); + assertEquals(hasData ? (short) 0xe488 : (short) 0xe48c, ipv4Header.checksum); + } else if (l4proto == IPPROTO_UDP) { + assertEquals(IPV4_HEADER_MIN_LEN + UDP_HEADER_LEN + dataLength, + ipv4Header.totalLength); + assertEquals((byte) IPPROTO_UDP, ipv4Header.protocol); + assertEquals(hasData ? (short) 0xe489 : (short) 0xe48d, ipv4Header.checksum); + } + } + + /** + * Check IPv6 header. + * + * @param l4proto the layer 4 protocol. Only {@code IPPROTO_TCP} and {@code IPPROTO_UDP} + * currently supported. + * @param hasData true if the packet has data payload; false otherwise. + * @param actual the packet to check. + */ + private void checkIpv6Header(final int l4proto, final boolean hasData, + final ByteBuffer actual) { + if (l4proto != IPPROTO_TCP && l4proto != IPPROTO_UDP) { + fail("Unsupported layer 4 protocol " + l4proto); + } + + final Ipv6Header ipv6Header = Struct.parse(Ipv6Header.class, actual); + + assertEquals(VERSION_TRAFFICCLASS_FLOWLABEL, ipv6Header.vtf); + assertEquals(HOP_LIMIT, ipv6Header.hopLimit); + assertEquals(IPV6_SRC_ADDR, ipv6Header.srcIp); + assertEquals(IPV6_DST_ADDR, ipv6Header.dstIp); + + final int dataLength = hasData ? DATA.limit() : 0; + if (l4proto == IPPROTO_TCP) { + assertEquals(TCP_HEADER_MIN_LEN + dataLength, ipv6Header.payloadLength); + assertEquals((byte) IPPROTO_TCP, ipv6Header.nextHeader); + } else if (l4proto == IPPROTO_UDP) { + assertEquals(UDP_HEADER_LEN + dataLength, ipv6Header.payloadLength); + assertEquals((byte) IPPROTO_UDP, ipv6Header.nextHeader); + } + } + + /** + * Check TCP packet. + * + * @param hasEther true if the packet has ether header; false otherwise. + * @param l3proto the layer 3 protocol. Only {@code IPPROTO_IP} and {@code IPPROTO_IPV6} + * currently supported. + * @param hasData true if the packet has data payload; false otherwise. + * @param actual the packet to check. + */ + private void checkTcpPacket(final boolean hasEther, final int l3proto, final boolean hasData, + final ByteBuffer actual) { + if (l3proto != IPPROTO_IP && l3proto != IPPROTO_IPV6) { + fail("Unsupported layer 3 protocol " + l3proto); + } + + // [1] Check ether header. + if (hasEther) { + checkEtherHeader(l3proto, actual); + } + + // [2] Check IP header. + if (l3proto == IPPROTO_IP) { + checkIpv4Header(IPPROTO_TCP, hasData, actual); + } else if (l3proto == IPPROTO_IPV6) { + checkIpv6Header(IPPROTO_TCP, hasData, actual); + } + + // [3] Check TCP header. + final TcpHeader tcpHeader = Struct.parse(TcpHeader.class, actual); + assertEquals(SRC_PORT, tcpHeader.srcPort); + assertEquals(DST_PORT, tcpHeader.dstPort); + assertEquals(SEQ_NO, tcpHeader.seq); + assertEquals(ACK_NO, tcpHeader.ack); + assertEquals((short) 0x5010 /* offset=5(*4bytes), control bits=ACK */, + tcpHeader.dataOffsetAndControlBits); + assertEquals(WINDOW, tcpHeader.window); + assertEquals(URGENT_POINTER, tcpHeader.urgentPointer); + if (l3proto == IPPROTO_IP) { + assertEquals(hasData ? (short) 0x4844 : (short) 0xe5e5, tcpHeader.checksum); + } else if (l3proto == IPPROTO_IPV6) { + assertEquals(hasData ? (short) 0xd905 : (short) 0x76a7, tcpHeader.checksum); + } + + // [4] Check payload. + if (hasData) { + assertEquals(0xdeadbeef, actual.getInt()); + } + } + + /** + * Check UDP packet. + * + * @param hasEther true if the packet has ether header; false otherwise. + * @param l3proto the layer 3 protocol. Only {@code IPPROTO_IP} and {@code IPPROTO_IPV6} + * currently supported. + * @param hasData true if the packet has data payload; false otherwise. + * @param actual the packet to check. + */ + private void checkUdpPacket(final boolean hasEther, final int l3proto, final boolean hasData, + final ByteBuffer actual) { + if (l3proto != IPPROTO_IP && l3proto != IPPROTO_IPV6) { + fail("Unsupported layer 3 protocol " + l3proto); + } + + // [1] Check ether header. + if (hasEther) { + checkEtherHeader(l3proto, actual); + } + + // [2] Check IP header. + if (l3proto == IPPROTO_IP) { + checkIpv4Header(IPPROTO_UDP, hasData, actual); + } else if (l3proto == IPPROTO_IPV6) { + checkIpv6Header(IPPROTO_UDP, hasData, actual); + } + + // [3] Check UDP header. + final UdpHeader udpHeader = Struct.parse(UdpHeader.class, actual); + assertEquals(SRC_PORT, udpHeader.srcPort); + assertEquals(DST_PORT, udpHeader.dstPort); + final int dataLength = hasData ? DATA.limit() : 0; + assertEquals(UDP_HEADER_LEN + dataLength, udpHeader.length); + if (l3proto == IPPROTO_IP) { + assertEquals(hasData ? (short) 0x4dbd : (short) 0xeb62, udpHeader.checksum); + } else if (l3proto == IPPROTO_IPV6) { + assertEquals(hasData ? (short) 0xde7e : (short) 0x7c24, udpHeader.checksum); + } + + // [4] Check payload. + if (hasData) { + assertEquals(0xdeadbeef, actual.getInt()); + } + } + + @Test + public void testBuildPacketEtherIPv4Tcp() throws Exception { + final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IP, IPPROTO_TCP, + null /* data */); + checkTcpPacket(true /* hasEther */, IPPROTO_IP, false /* hasData */, packet); + assertArrayEquals(TEST_PACKET_ETHERHDR_IPV4HDR_TCPHDR, packet.array()); + } + + @Test + public void testBuildPacketEtherIPv4TcpData() throws Exception { + final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IP, IPPROTO_TCP, DATA); + checkTcpPacket(true /* hasEther */, IPPROTO_IP, true /* hasData */, packet); + assertArrayEquals(TEST_PACKET_ETHERHDR_IPV4HDR_TCPHDR_DATA, + packet.array()); + } + + @Test + public void testBuildPacketIPv4Tcp() throws Exception { + final ByteBuffer packet = buildPacket(null /* srcMac */, null /* dstMac */, + IPPROTO_IP, IPPROTO_TCP, null /* data */); + checkTcpPacket(false /* hasEther */, IPPROTO_IP, false /* hasData */, packet); + assertArrayEquals(TEST_PACKET_IPV4HDR_TCPHDR, packet.array()); + } + + @Test + public void testBuildPacketIPv4TcpData() throws Exception { + final ByteBuffer packet = buildPacket(null /* srcMac */, null /* dstMac */, + IPPROTO_IP, IPPROTO_TCP, DATA); + checkTcpPacket(false /* hasEther */, IPPROTO_IP, true /* hasData */, packet); + assertArrayEquals(TEST_PACKET_IPV4HDR_TCPHDR_DATA, packet.array()); + } + + @Test + public void testBuildPacketEtherIPv4Udp() throws Exception { + final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IP, IPPROTO_UDP, + null /* data */); + checkUdpPacket(true /* hasEther */, IPPROTO_IP, false /* hasData */, packet); + assertArrayEquals(TEST_PACKET_ETHERHDR_IPV4HDR_UDPHDR, packet.array()); + } + + @Test + public void testBuildPacketEtherIPv4UdpData() throws Exception { + final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IP, IPPROTO_UDP, DATA); + checkUdpPacket(true /* hasEther */, IPPROTO_IP, true /* hasData */, packet); + assertArrayEquals(TEST_PACKET_ETHERHDR_IPV4HDR_UDPHDR_DATA, packet.array()); + } + + @Test + public void testBuildPacketIPv4Udp() throws Exception { + final ByteBuffer packet = buildPacket(null /* srcMac */, null /* dstMac */, + IPPROTO_IP, IPPROTO_UDP, null /*data*/); + checkUdpPacket(false /* hasEther */, IPPROTO_IP, false /* hasData */, packet); + assertArrayEquals(TEST_PACKET_IPV4HDR_UDPHDR, packet.array()); + } + + @Test + public void testBuildPacketIPv4UdpData() throws Exception { + final ByteBuffer packet = buildPacket(null /* srcMac */, null /* dstMac */, + IPPROTO_IP, IPPROTO_UDP, DATA); + checkUdpPacket(false /* hasEther */, IPPROTO_IP, true /* hasData */, packet); + assertArrayEquals(TEST_PACKET_IPV4HDR_UDPHDR_DATA, packet.array()); + } + + @Test + public void testBuildPacketEtherIPv6TcpData() throws Exception { + final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IPV6, IPPROTO_TCP, DATA); + checkTcpPacket(true /* hasEther */, IPPROTO_IPV6, true /* hasData */, packet); + assertArrayEquals(TEST_PACKET_ETHERHDR_IPV6HDR_TCPHDR_DATA, + packet.array()); + } + + @Test + public void testBuildPacketEtherIPv6Tcp() throws Exception { + final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IPV6, IPPROTO_TCP, + null /*data*/); + checkTcpPacket(true /* hasEther */, IPPROTO_IPV6, false /* hasData */, packet); + assertArrayEquals(TEST_PACKET_ETHERHDR_IPV6HDR_TCPHDR, + packet.array()); + } + + @Test + public void testBuildPacketIPv6TcpData() throws Exception { + final ByteBuffer packet = buildPacket(null /* srcMac */, null /* dstMac */, IPPROTO_IPV6, + IPPROTO_TCP, DATA); + checkTcpPacket(false /* hasEther */, IPPROTO_IPV6, true /* hasData */, packet); + assertArrayEquals(TEST_PACKET_IPV6HDR_TCPHDR_DATA, packet.array()); + } + + @Test + public void testBuildPacketIPv6Tcp() throws Exception { + final ByteBuffer packet = buildPacket(null /* srcMac */, null /* dstMac */, IPPROTO_IPV6, + IPPROTO_TCP, null /*data*/); + checkTcpPacket(false /* hasEther */, IPPROTO_IPV6, false /* hasData */, packet); + assertArrayEquals(TEST_PACKET_IPV6HDR_TCPHDR, packet.array()); + } + + @Test + public void testBuildPacketEtherIPv6Udp() throws Exception { + final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IPV6, IPPROTO_UDP, + null /* data */); + checkUdpPacket(true /* hasEther */, IPPROTO_IPV6, false /* hasData */, packet); + assertArrayEquals(TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR, packet.array()); + } + + @Test + public void testBuildPacketEtherIPv6UdpData() throws Exception { + final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IPV6, IPPROTO_UDP, + DATA); + checkUdpPacket(true /* hasEther */, IPPROTO_IPV6, true /* hasData */, packet); + assertArrayEquals(TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA, packet.array()); + } + + @Test + public void testBuildPacketIPv6Udp() throws Exception { + final ByteBuffer packet = buildPacket(null /* srcMac */, null /* dstMac */, + IPPROTO_IPV6, IPPROTO_UDP, null /*data*/); + checkUdpPacket(false /* hasEther */, IPPROTO_IPV6, false /* hasData */, packet); + assertArrayEquals(TEST_PACKET_IPV6HDR_UDPHDR, packet.array()); + } + + @Test + public void testBuildPacketIPv6UdpData() throws Exception { + final ByteBuffer packet = buildPacket(null /* srcMac */, null /* dstMac */, + IPPROTO_IPV6, IPPROTO_UDP, DATA); + checkUdpPacket(false /* hasEther */, IPPROTO_IPV6, true /* hasData */, packet); + assertArrayEquals(TEST_PACKET_IPV6HDR_UDPHDR_DATA, packet.array()); + } + + @Test + public void testFinalizePacketWithoutIpv4Header() throws Exception { + final ByteBuffer buffer = PacketBuilder.allocate(false /* hasEther */, IPPROTO_IP, + IPPROTO_TCP, 0 /* payloadLen */); + final PacketBuilder packetBuilder = new PacketBuilder(buffer); + packetBuilder.writeTcpHeader(SRC_PORT, DST_PORT, SEQ_NO, ACK_NO, + TCPHDR_ACK, WINDOW, URGENT_POINTER); + assertThrows("java.io.IOException: Packet is missing IPv4 header", IOException.class, + () -> packetBuilder.finalizePacket()); + } + + @Test + public void testFinalizePacketWithoutL4Header() throws Exception { + final ByteBuffer buffer = PacketBuilder.allocate(false /* hasEther */, IPPROTO_IP, + IPPROTO_TCP, 0 /* payloadLen */); + final PacketBuilder packetBuilder = new PacketBuilder(buffer); + packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET, + TIME_TO_LIVE, (byte) IPPROTO_TCP, IPV4_SRC_ADDR, IPV4_DST_ADDR); + assertThrows("java.io.IOException: Packet is missing neither TCP nor UDP header", + IOException.class, () -> packetBuilder.finalizePacket()); + } + + @Test + public void testWriteL2HeaderToInsufficientBuffer() throws Exception { + final PacketBuilder packetBuilder = new PacketBuilder(ByteBuffer.allocate(1)); + assertThrows(IOException.class, + () -> packetBuilder.writeL2Header(SRC_MAC, DST_MAC, (short) ETHER_TYPE_IPV4)); + } + + @Test + public void testWriteIpv4HeaderToInsufficientBuffer() throws Exception { + final PacketBuilder packetBuilder = new PacketBuilder(ByteBuffer.allocate(1)); + assertThrows(IOException.class, + () -> packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET, + TIME_TO_LIVE, (byte) IPPROTO_TCP, IPV4_SRC_ADDR, IPV4_DST_ADDR)); + } + + @Test + public void testWriteTcpHeaderToInsufficientBuffer() throws Exception { + final PacketBuilder packetBuilder = new PacketBuilder(ByteBuffer.allocate(1)); + assertThrows(IOException.class, + () -> packetBuilder.writeTcpHeader(SRC_PORT, DST_PORT, SEQ_NO, ACK_NO, + TCPHDR_ACK, WINDOW, URGENT_POINTER)); + } + + @Test + public void testWriteUdpHeaderToInsufficientBuffer() throws Exception { + final PacketBuilder packetBuilder = new PacketBuilder(ByteBuffer.allocate(1)); + assertThrows(IOException.class, () -> packetBuilder.writeUdpHeader(SRC_PORT, DST_PORT)); + } + + private static Inet4Address addr4(String addr) { + return (Inet4Address) InetAddresses.parseNumericAddress(addr); + } + + private static Inet6Address addr6(String addr) { + return (Inet6Address) InetAddresses.parseNumericAddress(addr); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/PacketReaderTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/PacketReaderTest.java new file mode 100644 index 0000000000000000000000000000000000000000..459801c27265d574ef884429358cf9b09bf75ad0 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/PacketReaderTest.java @@ -0,0 +1,245 @@ +/* + * 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 com.android.net.module.util; + +import static android.system.OsConstants.AF_INET6; +import static android.system.OsConstants.IPPROTO_UDP; +import static android.system.OsConstants.SOCK_DGRAM; +import static android.system.OsConstants.SOCK_NONBLOCK; +import static android.system.OsConstants.SOL_SOCKET; +import static android.system.OsConstants.SO_SNDTIMEO; + +import static com.android.net.module.util.PacketReader.DEFAULT_RECV_BUF_SIZE; +import static com.android.testutils.MiscAsserts.assertThrows; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.os.Handler; +import android.os.HandlerThread; +import android.system.ErrnoException; +import android.system.Os; +import android.system.StructTimeval; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.FileDescriptor; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketException; +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Tests for PacketReader. + * + * @hide + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class PacketReaderTest { + static final InetAddress LOOPBACK6 = Inet6Address.getLoopbackAddress(); + static final StructTimeval TIMEO = StructTimeval.fromMillis(500); + + // TODO : reassigning the latch voids its synchronization properties, which means this + // scheme just doesn't work. Latches almost always need to be final to work. + protected CountDownLatch mLatch; + protected FileDescriptor mLocalSocket; + protected InetSocketAddress mLocalSockName; + protected byte[] mLastRecvBuf; + protected boolean mStopped; + protected HandlerThread mHandlerThread; + protected PacketReader mReceiver; + + class UdpLoopbackReader extends PacketReader { + UdpLoopbackReader(Handler h) { + super(h); + } + + @Override + protected FileDescriptor createFd() { + FileDescriptor s = null; + try { + s = Os.socket(AF_INET6, SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP); + Os.bind(s, LOOPBACK6, 0); + mLocalSockName = (InetSocketAddress) Os.getsockname(s); + Os.setsockoptTimeval(s, SOL_SOCKET, SO_SNDTIMEO, TIMEO); + } catch (ErrnoException | SocketException e) { + closeFd(s); + throw new RuntimeException("Failed to create FD", e); + } + + mLocalSocket = s; + return s; + } + + @Override + protected void handlePacket(byte[] recvbuf, int length) { + mLastRecvBuf = Arrays.copyOf(recvbuf, length); + mLatch.countDown(); + } + + @Override + protected void onStart() { + mStopped = false; + mLatch.countDown(); + } + + @Override + protected void onStop() { + mStopped = true; + mLatch.countDown(); + } + }; + + @Before + public void setUp() { + resetLatch(); + mLocalSocket = null; + mLocalSockName = null; + mLastRecvBuf = null; + mStopped = false; + + mHandlerThread = new HandlerThread(PacketReaderTest.class.getSimpleName()); + mHandlerThread.start(); + } + + @After + public void tearDown() throws Exception { + if (mReceiver != null) { + mHandlerThread.getThreadHandler().post(() -> mReceiver.stop()); + waitForActivity(); + } + mReceiver = null; + mHandlerThread.quit(); + mHandlerThread = null; + } + + void resetLatch() { + mLatch = new CountDownLatch(1); + } + + void waitForActivity() throws Exception { + try { + mLatch.await(1000, TimeUnit.MILLISECONDS); + } finally { + resetLatch(); + } + } + + void sendPacket(byte[] contents) throws Exception { + final DatagramSocket sender = new DatagramSocket(); + sender.connect(mLocalSockName); + sender.send(new DatagramPacket(contents, contents.length)); + sender.close(); + } + + @Test + public void testBasicWorking() throws Exception { + final Handler h = mHandlerThread.getThreadHandler(); + mReceiver = new UdpLoopbackReader(h); + + h.post(() -> mReceiver.start()); + waitForActivity(); + assertTrue(mLocalSockName != null); + assertEquals(LOOPBACK6, mLocalSockName.getAddress()); + assertTrue(0 < mLocalSockName.getPort()); + assertTrue(mLocalSocket != null); + assertFalse(mStopped); + + final byte[] one = "one 1".getBytes("UTF-8"); + sendPacket(one); + waitForActivity(); + assertEquals(1, mReceiver.numPacketsReceived()); + assertTrue(Arrays.equals(one, mLastRecvBuf)); + assertFalse(mStopped); + + final byte[] two = "two 2".getBytes("UTF-8"); + sendPacket(two); + waitForActivity(); + assertEquals(2, mReceiver.numPacketsReceived()); + assertTrue(Arrays.equals(two, mLastRecvBuf)); + assertFalse(mStopped); + + h.post(() -> mReceiver.stop()); + waitForActivity(); + assertEquals(2, mReceiver.numPacketsReceived()); + assertTrue(Arrays.equals(two, mLastRecvBuf)); + assertTrue(mStopped); + mReceiver = null; + } + + class NullPacketReader extends PacketReader { + NullPacketReader(Handler h, int recvbufsize) { + super(h, recvbufsize); + } + + @Override + public FileDescriptor createFd() { + return null; + } + } + + @Test + public void testMinimalRecvBufSize() throws Exception { + final Handler h = mHandlerThread.getThreadHandler(); + + for (int i : new int[] { -1, 0, 1, DEFAULT_RECV_BUF_SIZE - 1 }) { + final PacketReader b = new NullPacketReader(h, i); + assertEquals(DEFAULT_RECV_BUF_SIZE, b.recvBufSize()); + } + } + + @Test + public void testStartingFromWrongThread() throws Exception { + final Handler h = mHandlerThread.getThreadHandler(); + final PacketReader b = new NullPacketReader(h, DEFAULT_RECV_BUF_SIZE); + assertThrows(IllegalStateException.class, () -> b.start()); + } + + @Test + public void testStoppingFromWrongThread() throws Exception { + final Handler h = mHandlerThread.getThreadHandler(); + final PacketReader b = new NullPacketReader(h, DEFAULT_RECV_BUF_SIZE); + assertThrows(IllegalStateException.class, () -> b.stop()); + } + + @Test + public void testSuccessToCreateSocket() throws Exception { + final Handler h = mHandlerThread.getThreadHandler(); + final PacketReader b = new UdpLoopbackReader(h); + h.post(() -> assertTrue(b.start())); + } + + @Test + public void testFailToCreateSocket() throws Exception { + final Handler h = mHandlerThread.getThreadHandler(); + final PacketReader b = new NullPacketReader(h, DEFAULT_RECV_BUF_SIZE); + h.post(() -> assertFalse(b.start())); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/PerUidCounterTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/PerUidCounterTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..321fe59486e870ec067f0926380a7e5190c533da --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/PerUidCounterTest.kt @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util + +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import org.junit.Test +import org.junit.runner.RunWith +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +@RunWith(AndroidJUnit4::class) +@SmallTest +class PerUidCounterTest { + private val UID_A = 1000 + private val UID_B = 1001 + private val UID_C = 1002 + + @Test + fun testCounterMaximum() { + assertFailsWith<IllegalArgumentException> { + PerUidCounter(-1) + } + assertFailsWith<IllegalArgumentException> { + PerUidCounter(0) + } + + val testLimit = 1000 + val testCounter = PerUidCounter(testLimit) + assertEquals(0, testCounter[UID_A]) + repeat(testLimit) { + testCounter.incrementCountOrThrow(UID_A) + } + assertEquals(testLimit, testCounter[UID_A]) + assertFailsWith<IllegalStateException> { + testCounter.incrementCountOrThrow(UID_A) + } + assertEquals(testLimit, testCounter[UID_A]) + } + + @Test + fun testIncrementCountOrThrow() { + val counter = PerUidCounter(3) + + // Verify the counters work independently. + counter.incrementCountOrThrow(UID_A) + counter.incrementCountOrThrow(UID_B) + counter.incrementCountOrThrow(UID_B) + counter.incrementCountOrThrow(UID_A) + counter.incrementCountOrThrow(UID_A) + assertEquals(3, counter[UID_A]) + assertEquals(2, counter[UID_B]) + assertFailsWith<IllegalStateException> { + counter.incrementCountOrThrow(UID_A) + } + counter.incrementCountOrThrow(UID_B) + assertFailsWith<IllegalStateException> { + counter.incrementCountOrThrow(UID_B) + } + + // Verify exception can be triggered again. + assertFailsWith<IllegalStateException> { + counter.incrementCountOrThrow(UID_A) + } + assertFailsWith<IllegalStateException> { + repeat(3) { + counter.incrementCountOrThrow(UID_A) + } + } + assertEquals(3, counter[UID_A]) + assertEquals(3, counter[UID_B]) + assertEquals(0, counter[UID_C]) + } + + @Test + fun testDecrementCountOrThrow() { + val counter = PerUidCounter(3) + + // Verify the count cannot go below zero. + assertFailsWith<IllegalStateException> { + counter.decrementCountOrThrow(UID_A) + } + assertFailsWith<IllegalStateException> { + repeat(5) { + counter.decrementCountOrThrow(UID_A) + } + } + + // Verify the counters work independently. + counter.incrementCountOrThrow(UID_A) + counter.incrementCountOrThrow(UID_B) + assertEquals(1, counter[UID_A]) + assertEquals(1, counter[UID_B]) + assertFailsWith<IllegalStateException> { + repeat(3) { + counter.decrementCountOrThrow(UID_A) + } + } + assertFailsWith<IllegalStateException> { + counter.decrementCountOrThrow(UID_A) + } + assertEquals(0, counter[UID_A]) + assertEquals(1, counter[UID_B]) + + // Verify mixing increment and decrement. + val largeCounter = PerUidCounter(100) + repeat(90) { + largeCounter.incrementCountOrThrow(UID_A) + } + repeat(70) { + largeCounter.decrementCountOrThrow(UID_A) + } + repeat(80) { + largeCounter.incrementCountOrThrow(UID_A) + } + assertFailsWith<IllegalStateException> { + largeCounter.incrementCountOrThrow(UID_A) + } + assertEquals(100, largeCounter[UID_A]) + repeat(100) { + largeCounter.decrementCountOrThrow(UID_A) + } + assertFailsWith<IllegalStateException> { + largeCounter.decrementCountOrThrow(UID_A) + } + assertEquals(0, largeCounter[UID_A]) + } +} \ No newline at end of file diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/PermissionUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/PermissionUtilsTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..028308b3ad903f73ccc136e60bb8f3cd481c57cf --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/PermissionUtilsTest.kt @@ -0,0 +1,170 @@ +/* + * 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. + */ + +package com.android.net.module.util + +import android.Manifest.permission.INTERNET +import android.Manifest.permission.NETWORK_SETTINGS +import android.Manifest.permission.NETWORK_STACK +import android.content.Context +import android.content.pm.PackageManager +import android.content.pm.PackageManager.PERMISSION_DENIED +import android.content.pm.PackageManager.PERMISSION_GRANTED +import android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK +import android.os.Build +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry +import com.android.net.module.util.PermissionUtils.checkAnyPermissionOf +import com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf +import com.android.net.module.util.PermissionUtils.enforceNetworkStackPermission +import com.android.net.module.util.PermissionUtils.enforceNetworkStackPermissionOr +import com.android.net.module.util.PermissionUtils.enforceSystemFeature +import com.android.testutils.DevSdkIgnoreRule +import com.android.testutils.DevSdkIgnoreRunner +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertFalse +import kotlin.test.assertTrue +import org.junit.Assert +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers +import org.mockito.ArgumentMatchers.any +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.mock + +/** Tests for PermissionUtils */ +@RunWith(DevSdkIgnoreRunner::class) +@SmallTest +class PermissionUtilsTest { + @get:Rule + val ignoreRule = DevSdkIgnoreRule() + private val TEST_PERMISSION1 = "android.permission.TEST_PERMISSION1" + private val TEST_PERMISSION2 = "android.permission.TEST_PERMISSION2" + private val mockContext = mock(Context::class.java) + private val mockPackageManager = mock(PackageManager::class.java) + + private val context by lazy { InstrumentationRegistry.getInstrumentation().context } + + @Before + fun setup() { + doReturn(mockPackageManager).`when`(mockContext).packageManager + } + + @Test + fun testEnforceAnyPermissionOf() { + doReturn(PERMISSION_GRANTED).`when`(mockContext) + .checkCallingOrSelfPermission(TEST_PERMISSION1) + doReturn(PERMISSION_DENIED).`when`(mockContext) + .checkCallingOrSelfPermission(TEST_PERMISSION2) + assertTrue(checkAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2)) + enforceAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2) + + doReturn(PERMISSION_DENIED).`when`(mockContext) + .checkCallingOrSelfPermission(TEST_PERMISSION1) + doReturn(PERMISSION_GRANTED).`when`(mockContext) + .checkCallingOrSelfPermission(TEST_PERMISSION2) + assertTrue(checkAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2)) + enforceAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2) + + doReturn(PERMISSION_DENIED).`when`(mockContext).checkCallingOrSelfPermission(any()) + assertFalse(checkAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2)) + assertFailsWith<SecurityException>("Expect fail but permission granted.") { + enforceAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2) + } + } + + @Test + fun testEnforceNetworkStackPermissionOr() { + doReturn(PERMISSION_GRANTED).`when`(mockContext).checkCallingOrSelfPermission(NETWORK_STACK) + doReturn(PERMISSION_DENIED).`when`(mockContext) + .checkCallingOrSelfPermission(PERMISSION_MAINLINE_NETWORK_STACK) + enforceNetworkStackPermission(mockContext) + enforceNetworkStackPermissionOr(mockContext, TEST_PERMISSION1) + + doReturn(PERMISSION_DENIED).`when`(mockContext).checkCallingOrSelfPermission(NETWORK_STACK) + doReturn(PERMISSION_GRANTED).`when`(mockContext) + .checkCallingOrSelfPermission(PERMISSION_MAINLINE_NETWORK_STACK) + enforceNetworkStackPermission(mockContext) + enforceNetworkStackPermissionOr(mockContext, TEST_PERMISSION2) + + doReturn(PERMISSION_DENIED).`when`(mockContext).checkCallingOrSelfPermission(NETWORK_STACK) + doReturn(PERMISSION_DENIED).`when`(mockContext) + .checkCallingOrSelfPermission(PERMISSION_MAINLINE_NETWORK_STACK) + doReturn(PERMISSION_GRANTED).`when`(mockContext) + .checkCallingOrSelfPermission(TEST_PERMISSION1) + assertFailsWith<SecurityException>("Expect fail but permission granted.") { + enforceNetworkStackPermission(mockContext) + } + enforceNetworkStackPermissionOr(mockContext, TEST_PERMISSION1) + + doReturn(PERMISSION_DENIED).`when`(mockContext).checkCallingOrSelfPermission(any()) + assertFailsWith<SecurityException>("Expect fail but permission granted.") { + enforceNetworkStackPermission(mockContext) + } + assertFailsWith<SecurityException>("Expect fail but permission granted.") { + enforceNetworkStackPermissionOr(mockContext, TEST_PERMISSION2) + } + } + + private fun mockHasSystemFeature(featureName: String, hasFeature: Boolean) { + doReturn(hasFeature).`when`(mockPackageManager) + .hasSystemFeature(ArgumentMatchers.eq(featureName)) + } + + @Test + fun testEnforceSystemFeature() { + val systemFeature = "test.system.feature" + val exceptionMessage = "test exception message" + mockHasSystemFeature(featureName = systemFeature, hasFeature = false) + val e = assertFailsWith<UnsupportedOperationException>("Should fail without feature") { + enforceSystemFeature(mockContext, systemFeature, exceptionMessage) + } + assertEquals(exceptionMessage, e.message) + + mockHasSystemFeature(featureName = systemFeature, hasFeature = true) + try { + enforceSystemFeature(mockContext, systemFeature, "") + } catch (e: UnsupportedOperationException) { + Assert.fail("Exception should have not been thrown with system feature enabled") + } + } + + @Test + @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2) + fun testIsSystemSignaturePermission() { + assertTrue( + PermissionUtils.isSystemSignaturePermission( + context, + NETWORK_SETTINGS + ) + ) + assertFalse( + PermissionUtils + .isSystemSignaturePermission(context, PERMISSION_MAINLINE_NETWORK_STACK) + ) + assertFalse( + PermissionUtils + .isSystemSignaturePermission(context, "test_permission") + ) + assertFalse( + PermissionUtils + .isSystemSignaturePermission(context, INTERNET) + ) + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/SharedLogTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/SharedLogTest.java new file mode 100644 index 0000000000000000000000000000000000000000..aa1bfee4ddee1016b80bd6618d1f8d35516429cc --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/SharedLogTest.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class SharedLogTest { + private static final String TIMESTAMP_PATTERN = "\\d{2}:\\d{2}:\\d{2}"; + private static final String TIMESTAMP = "HH:MM:SS"; + private static final String TAG = "top"; + + @Test + public void testBasicOperation() { + final SharedLog logTop = new SharedLog(TAG); + assertTrue(TAG.equals(logTop.getTag())); + + logTop.mark("first post!"); + + final SharedLog logLevel2a = logTop.forSubComponent("twoA"); + final SharedLog logLevel2b = logTop.forSubComponent("twoB"); + logLevel2b.e("2b or not 2b"); + logLevel2b.e("No exception", null); + logLevel2b.e("Wait, here's one", new Exception("Test")); + logLevel2a.w("second post?"); + + final SharedLog logLevel3 = logLevel2a.forSubComponent("three"); + logTop.log("still logging"); + logLevel2b.e(new Exception("Got another exception")); + logLevel3.i("3 >> 2"); + logLevel2a.mark("ok: last post"); + logTop.logf("finished!"); + + final String[] expected = { + " - MARK first post!", + " - [twoB] ERROR 2b or not 2b", + " - [twoB] ERROR No exception", + // No stacktrace in shared log, only in logcat + " - [twoB] ERROR Wait, here's one: Test", + " - [twoA] WARN second post?", + " - still logging", + " - [twoB] ERROR java.lang.Exception: Got another exception", + " - [twoA.three] 3 >> 2", + " - [twoA] MARK ok: last post", + " - finished!", + }; + // Verify the logs are all there and in the correct order. + assertDumpLogs(expected, logTop); + + // In fact, because they all share the same underlying LocalLog, + // every subcomponent SharedLog's dump() is identical. + assertDumpLogs(expected, logLevel2a); + assertDumpLogs(expected, logLevel2b); + assertDumpLogs(expected, logLevel3); + } + + private static void assertDumpLogs(String[] expected, SharedLog log) { + verifyLogLines(expected, dump(log)); + verifyLogLines(reverse(expected), reverseDump(log)); + } + + private static String dump(SharedLog log) { + return getSharedLogString(pw -> log.dump(null /* fd */, pw, null /* args */)); + } + + private static String reverseDump(SharedLog log) { + return getSharedLogString(pw -> log.reverseDump(pw)); + } + + private static String[] reverse(String[] ary) { + final List<String> ls = new ArrayList<>(Arrays.asList(ary)); + Collections.reverse(ls); + return ls.toArray(new String[ary.length]); + } + + private static String getSharedLogString(Consumer<PrintWriter> functor) { + final ByteArrayOutputStream ostream = new ByteArrayOutputStream(); + final PrintWriter pw = new PrintWriter(ostream, true); + functor.accept(pw); + + final String dumpOutput = ostream.toString(); + assertNotNull(dumpOutput); + assertFalse("".equals(dumpOutput)); + return dumpOutput; + } + + private static void verifyLogLines(String[] expected, String gottenLogs) { + final String[] lines = gottenLogs.split("\n"); + assertEquals(expected.length, lines.length); + + for (int i = 0; i < expected.length; i++) { + String got = lines[i]; + String want = expected[i]; + assertTrue(String.format("'%s' did not contain '%s'", got, want), got.endsWith(want)); + assertTrue(String.format("'%s' did not contain a %s timestamp", got, TIMESTAMP), + got.replaceFirst(TIMESTAMP_PATTERN, TIMESTAMP).contains(TIMESTAMP)); + } + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b4da0439538a48f7aa8f4cca0ca39fb4e0f33761 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java @@ -0,0 +1,1075 @@ +/* + * 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. + */ + +package com.android.net.module.util; + +import static com.android.testutils.MiscAsserts.assertThrows; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import android.annotation.SuppressLint; +import android.net.InetAddresses; +import android.net.IpPrefix; +import android.net.MacAddress; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.nio.BufferOverflowException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class StructTest { + + // IPv6, 0 bytes of options, ifindex 15715755: 0x00efcdab, type 134 (RA), code 0, padding. + private static final String HDR_EMPTY = "0a00" + "0000" + "abcdef00" + "8600000000000000"; + + // UBE16: 0xfeff, UBE32: 0xfeffffff, UBE64: 0xfeffffffffffffff, UBE63: 0x7effffffffffffff + private static final String NETWORK_ORDER_MSG = "feff" + "feffffff" + "feffffffffffffff" + + "7effffffffffffff"; + + // S8: 0x7f, S16: 0x7fff, S32: 0x7fffffff, S64: 0x7fffffffffffffff + private static final String SIGNED_DATA = "7f" + "ff7f" + "ffffff7f" + "ffffffffffffff7f"; + + // nS8: 0x81, nS16: 0x8001, nS32: 0x80000001, nS64: 800000000000000001 + private static final String SIGNED_NEGATIVE_DATA = "81" + "0180" + "01000080" + + "0100000000000080"; + + // U8: 0xff, U16: 0xffff, U32: 0xffffffff, U64: 0xffffffffffffffff, U63: 0x7fffffffffffffff, + // U63: 0xffffffffffffffff(-1L) + private static final String UNSIGNED_DATA = "ff" + "ffff" + "ffffffff" + "ffffffffffffffff" + + "ffffffffffffff7f" + "ffffffffffffffff"; + + // PREF64 option, 2001:db8:3:4:5:6::/96, lifetime: 10064 + private static final String OPT_PREF64 = "2750" + "20010db80003000400050006"; + private static final byte[] TEST_PREFIX64 = new byte[]{ + (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, (byte) 0x00, (byte) 0x03, + (byte) 0x00, (byte) 0x04, (byte) 0x00, (byte) 0x05, (byte) 0x00, (byte) 0x06, + }; + + private static final Inet4Address TEST_IPV4_ADDRESS = + (Inet4Address) InetAddresses.parseNumericAddress("192.168.100.1"); + private static final Inet6Address TEST_IPV6_ADDRESS = + (Inet6Address) InetAddresses.parseNumericAddress("2001:db8:3:4:5:6:7:8"); + + private <T> T doParsingMessageTest(final String hexString, final Class<T> clazz, + final ByteOrder order) { + final ByteBuffer buf = toByteBuffer(hexString); + buf.order(order); + return Struct.parse(clazz, buf); + } + + public static class HeaderMsgWithConstructor extends Struct { + static int sType; + static int sLength; + + @Field(order = 0, type = Type.U8, padding = 1) + public final short mFamily; + @Field(order = 1, type = Type.U16) + public final int mLen; + @Field(order = 2, type = Type.S32) + public final int mIfindex; + @Field(order = 3, type = Type.U8) + public final short mIcmpType; + @Field(order = 4, type = Type.U8, padding = 6) + public final short mIcmpCode; + + HeaderMsgWithConstructor(final short family, final int len, final int ifindex, + final short type, final short code) { + mFamily = family; + mLen = len; + mIfindex = ifindex; + mIcmpType = type; + mIcmpCode = code; + } + } + + private void verifyHeaderParsing(final HeaderMsgWithConstructor msg) { + assertEquals(10, msg.mFamily); + assertEquals(0, msg.mLen); + assertEquals(15715755, msg.mIfindex); + assertEquals(134, msg.mIcmpType); + assertEquals(0, msg.mIcmpCode); + + assertEquals(16, Struct.getSize(HeaderMsgWithConstructor.class)); + assertArrayEquals(toByteBuffer(HDR_EMPTY).array(), + msg.writeToBytes(ByteOrder.LITTLE_ENDIAN)); + } + + @Test + public void testClassWithExplicitConstructor() { + final HeaderMsgWithConstructor msg = doParsingMessageTest(HDR_EMPTY, + HeaderMsgWithConstructor.class, ByteOrder.LITTLE_ENDIAN); + verifyHeaderParsing(msg); + } + + public static class HeaderMsgWithoutConstructor extends Struct { + static int sType; + static int sLength; + + @Field(order = 0, type = Type.U8, padding = 1) + public short mFamily; + @Field(order = 1, type = Type.U16) + public int mLen; + @Field(order = 2, type = Type.S32) + public int mIfindex; + @Field(order = 3, type = Type.U8) + public short mIcmpType; + @Field(order = 4, type = Type.U8, padding = 6) + public short mIcmpCode; + } + + @Test + public void testClassWithDefaultConstructor() { + final HeaderMsgWithoutConstructor msg = doParsingMessageTest(HDR_EMPTY, + HeaderMsgWithoutConstructor.class, ByteOrder.LITTLE_ENDIAN); + assertEquals(10, msg.mFamily); + assertEquals(0, msg.mLen); + assertEquals(15715755, msg.mIfindex); + assertEquals(134, msg.mIcmpType); + assertEquals(0, msg.mIcmpCode); + + assertEquals(16, Struct.getSize(HeaderMsgWithoutConstructor.class)); + assertArrayEquals(toByteBuffer(HDR_EMPTY).array(), + msg.writeToBytes(ByteOrder.LITTLE_ENDIAN)); + } + + public static class HeaderMessage { + @Field(order = 0, type = Type.U8, padding = 1) + short mFamily; + @Field(order = 1, type = Type.U16) + int mLen; + @Field(order = 2, type = Type.S32) + int mIfindex; + @Field(order = 3, type = Type.U8) + short mIcmpType; + @Field(order = 4, type = Type.U8, padding = 6) + short mIcmpCode; + } + + @Test + public void testInvalidClass_NotSubClass() { + final ByteBuffer buf = toByteBuffer(HDR_EMPTY); + assertThrows(IllegalArgumentException.class, () -> Struct.parse(HeaderMessage.class, buf)); + } + + public static class HeaderMessageMissingAnnotation extends Struct { + @Field(order = 0, type = Type.U8, padding = 1) + short mFamily; + @Field(order = 1, type = Type.U16) + int mLen; + int mIfindex; + @Field(order = 2, type = Type.U8) + short mIcmpType; + @Field(order = 3, type = Type.U8, padding = 6) + short mIcmpCode; + } + + @Test + public void testInvalidClass_MissingAnnotationField() { + final ByteBuffer buf = toByteBuffer(HDR_EMPTY); + assertThrows(IllegalArgumentException.class, + () -> Struct.parse(HeaderMessageMissingAnnotation.class, buf)); + } + + public static class NetworkOrderMessage extends Struct { + @Field(order = 0, type = Type.UBE16) + public final int mUBE16; + @Field(order = 1, type = Type.UBE32) + public final long mUBE32; + @Field(order = 2, type = Type.UBE64) + public final BigInteger mUBE64; + @Field(order = 3, type = Type.UBE63) + public final long mUBE63; + + NetworkOrderMessage(final int be16, final long be32, final BigInteger be64, + final long be63) { + mUBE16 = be16; + mUBE32 = be32; + mUBE64 = be64; + mUBE63 = be63; + } + } + + @Test + public void testNetworkOrder() { + final NetworkOrderMessage msg = doParsingMessageTest(NETWORK_ORDER_MSG, + NetworkOrderMessage.class, ByteOrder.LITTLE_ENDIAN); + assertEquals(65279, msg.mUBE16); + assertEquals(4278190079L, msg.mUBE32); + assertEquals(new BigInteger("18374686479671623679"), msg.mUBE64); + assertEquals(9151314442816847871L, msg.mUBE63); + + assertEquals(22, Struct.getSize(NetworkOrderMessage.class)); + assertArrayEquals(toByteBuffer(NETWORK_ORDER_MSG).array(), + msg.writeToBytes(ByteOrder.LITTLE_ENDIAN)); + } + + public static class UnsignedDataMessage extends Struct { + @Field(order = 0, type = Type.U8) + public final short mU8; + @Field(order = 1, type = Type.U16) + public final int mU16; + @Field(order = 2, type = Type.U32) + public final long mU32; + @Field(order = 3, type = Type.U64) + public final BigInteger mU64; + @Field(order = 4, type = Type.U63) + public final long mU63; + @Field(order = 5, type = Type.U63) + public final long mLU64; // represent U64 data with U63 type + + UnsignedDataMessage(final short u8, final int u16, final long u32, final BigInteger u64, + final long u63, final long lu64) { + mU8 = u8; + mU16 = u16; + mU32 = u32; + mU64 = u64; + mU63 = u63; + mLU64 = lu64; + } + } + + @Test + public void testUnsignedData() { + final UnsignedDataMessage msg = doParsingMessageTest(UNSIGNED_DATA, + UnsignedDataMessage.class, ByteOrder.LITTLE_ENDIAN); + assertEquals(255, msg.mU8); + assertEquals(65535, msg.mU16); + assertEquals(4294967295L, msg.mU32); + assertEquals(new BigInteger("18446744073709551615"), msg.mU64); + assertEquals(9223372036854775807L, msg.mU63); + assertEquals(-1L, msg.mLU64); + + assertEquals(31, Struct.getSize(UnsignedDataMessage.class)); + assertArrayEquals(toByteBuffer(UNSIGNED_DATA).array(), + msg.writeToBytes(ByteOrder.LITTLE_ENDIAN)); + } + + public static class U64DataMessage extends Struct { + @Field(order = 0, type = Type.U64) long mU64; + } + + @Test + public void testInvalidType_U64WithLongPrimitive() { + assertThrows(IllegalArgumentException.class, + () -> Struct.parse(U64DataMessage.class, toByteBuffer("ffffffffffffffff"))); + } + + // BigInteger U64: 0x0000000000001234, BigInteger UBE64: 0x0000000000001234, BigInteger U64: 0 + private static final String SMALL_VALUE_BIGINTEGER = "3412000000000000" + "0000000000001234" + + "0000000000000000"; + + public static class SmallValueBigInteger extends Struct { + @Field(order = 0, type = Type.U64) public final BigInteger mSmallValue; + @Field(order = 1, type = Type.UBE64) public final BigInteger mBSmallValue; + @Field(order = 2, type = Type.U64) public final BigInteger mZero; + + SmallValueBigInteger(final BigInteger smallValue, final BigInteger bSmallValue, + final BigInteger zero) { + mSmallValue = smallValue; + mBSmallValue = bSmallValue; + mZero = zero; + } + } + + @Test + public void testBigIntegerSmallValueOrZero() { + final SmallValueBigInteger msg = doParsingMessageTest(SMALL_VALUE_BIGINTEGER, + SmallValueBigInteger.class, ByteOrder.LITTLE_ENDIAN); + assertEquals(new BigInteger("4660"), msg.mSmallValue); + assertEquals(new BigInteger("4660"), msg.mBSmallValue); + assertEquals(new BigInteger("0"), msg.mZero); + + assertEquals(24, Struct.getSize(SmallValueBigInteger.class)); + assertArrayEquals(toByteBuffer(SMALL_VALUE_BIGINTEGER).array(), + msg.writeToBytes(ByteOrder.LITTLE_ENDIAN)); + } + + public static class SignedDataMessage extends Struct { + @Field(order = 0, type = Type.S8) + public final byte mS8; + @Field(order = 1, type = Type.S16) + public final short mS16; + @Field(order = 2, type = Type.S32) + public final int mS32; + @Field(order = 3, type = Type.S64) + public final long mS64; + + SignedDataMessage(final byte s8, final short s16, final int s32, final long s64) { + mS8 = s8; + mS16 = s16; + mS32 = s32; + mS64 = s64; + } + } + + @Test + public void testSignedPositiveData() { + final SignedDataMessage msg = doParsingMessageTest(SIGNED_DATA, SignedDataMessage.class, + ByteOrder.LITTLE_ENDIAN); + assertEquals(127, msg.mS8); + assertEquals(32767, msg.mS16); + assertEquals(2147483647, msg.mS32); + assertEquals(9223372036854775807L, msg.mS64); + + assertEquals(15, Struct.getSize(SignedDataMessage.class)); + assertArrayEquals(toByteBuffer(SIGNED_DATA).array(), + msg.writeToBytes(ByteOrder.LITTLE_ENDIAN)); + } + + @Test + public void testSignedNegativeData() { + final SignedDataMessage msg = doParsingMessageTest(SIGNED_NEGATIVE_DATA, + SignedDataMessage.class, ByteOrder.LITTLE_ENDIAN); + assertEquals(-127, msg.mS8); + assertEquals(-32767, msg.mS16); + assertEquals(-2147483647, msg.mS32); + assertEquals(-9223372036854775807L, msg.mS64); + + assertEquals(15, Struct.getSize(SignedDataMessage.class)); + assertArrayEquals(toByteBuffer(SIGNED_NEGATIVE_DATA).array(), + msg.writeToBytes(ByteOrder.LITTLE_ENDIAN)); + } + + public static class HeaderMessageWithDuplicateOrder extends Struct { + @Field(order = 0, type = Type.U8, padding = 1) + short mFamily; + @Field(order = 1, type = Type.U16) + int mLen; + @Field(order = 2, type = Type.S32) + int mIfindex; + @Field(order = 2, type = Type.U8) + short mIcmpType; + @Field(order = 3, type = Type.U8, padding = 6) + short mIcmpCode; + } + + @Test + public void testInvalidClass_DuplicateFieldOrder() { + final ByteBuffer buf = toByteBuffer(HDR_EMPTY); + assertThrows(IllegalArgumentException.class, + () -> Struct.parse(HeaderMessageWithDuplicateOrder.class, buf)); + } + + public static class HeaderMessageWithNegativeOrder extends Struct { + @Field(order = 0, type = Type.U8, padding = 1) + short mFamily; + @Field(order = 1, type = Type.U16) + int mLen; + @Field(order = 2, type = Type.S32) + int mIfindex; + @Field(order = 3, type = Type.U8) + short mIcmpType; + @Field(order = -4, type = Type.U8, padding = 6) + short mIcmpCode; + } + + @Test + public void testInvalidClass_NegativeFieldOrder() { + final ByteBuffer buf = toByteBuffer(HDR_EMPTY); + assertThrows(IllegalArgumentException.class, + () -> Struct.parse(HeaderMessageWithNegativeOrder.class, buf)); + } + + public static class HeaderMessageOutOfIndexBounds extends Struct { + @Field(order = 0, type = Type.U8, padding = 1) + short mFamily; + @Field(order = 1, type = Type.U16) + int mLen; + @Field(order = 2, type = Type.S32) + int mIfindex; + @Field(order = 3, type = Type.U8) + short mIcmpType; + @Field(order = 5, type = Type.U8, padding = 6) + short mIcmpCode; + } + + @Test + public void testInvalidClass_OutOfIndexBounds() { + final ByteBuffer buf = toByteBuffer(HDR_EMPTY); + assertThrows(IllegalArgumentException.class, + () -> Struct.parse(HeaderMessageOutOfIndexBounds.class, buf)); + } + + public static class HeaderMessageMismatchedPrimitiveType extends Struct { + @Field(order = 0, type = Type.U8, padding = 1) + short mFamily; + @Field(order = 1, type = Type.U16) + short mLen; // should be integer + @Field(order = 2, type = Type.S32) + int mIfindex; + @Field(order = 3, type = Type.U8) + short mIcmpType; + @Field(order = 4, type = Type.U8, padding = 6) + short mIcmpCode; + } + + @Test + public void testInvalidClass_MismatchedPrimitiveDataType() { + final ByteBuffer buf = toByteBuffer(HDR_EMPTY); + assertThrows(IllegalArgumentException.class, + () -> Struct.parse(HeaderMessageMismatchedPrimitiveType.class, buf)); + } + + public static class PrefixMessage extends Struct { + @Field(order = 0, type = Type.UBE16) + public final int mLifetime; + @Field(order = 1, type = Type.ByteArray, arraysize = 12) + public final byte[] mPrefix; + + PrefixMessage(final int lifetime, final byte[] prefix) { + mLifetime = lifetime; + mPrefix = prefix; + } + } + + @SuppressLint("NewApi") + private void verifyPrefixByteArrayParsing(final PrefixMessage msg) throws Exception { + // The original PREF64 option message has just 12 bytes for prefix byte array + // (Highest 96 bits of the Prefix), copyOf pads the 128-bits IPv6 address with + // prefix and 4-bytes zeros. + final InetAddress addr = InetAddress.getByAddress(Arrays.copyOf(msg.mPrefix, 16)); + final IpPrefix prefix = new IpPrefix(addr, 96); + assertEquals(10064, msg.mLifetime); + assertTrue(prefix.equals(new IpPrefix("2001:db8:3:4:5:6::/96"))); + + assertEquals(14, Struct.getSize(PrefixMessage.class)); + assertArrayEquals(toByteBuffer(OPT_PREF64).array(), + msg.writeToBytes(ByteOrder.LITTLE_ENDIAN)); + } + + public static class PrefixMessageWithZeroLengthArray extends Struct { + @Field(order = 0, type = Type.UBE16) + final int mLifetime; + @Field(order = 1, type = Type.ByteArray, arraysize = 0) + final byte[] mPrefix; + + PrefixMessageWithZeroLengthArray(final int lifetime, final byte[] prefix) { + mLifetime = lifetime; + mPrefix = prefix; + } + } + + @Test + public void testInvalidClass_ZeroLengthByteArray() { + final ByteBuffer buf = toByteBuffer(OPT_PREF64); + assertThrows(IllegalArgumentException.class, + () -> Struct.parse(PrefixMessageWithZeroLengthArray.class, buf)); + } + + @Test + public void testPrefixArrayField() throws Exception { + final PrefixMessage msg = doParsingMessageTest(OPT_PREF64, PrefixMessage.class, + ByteOrder.LITTLE_ENDIAN); + verifyPrefixByteArrayParsing(msg); + } + + public static class HeaderMessageWithMutableField extends Struct { + @Field(order = 0, type = Type.U8, padding = 1) + final short mFamily; + @Field(order = 1, type = Type.U16) + final int mLen; + @Field(order = 2, type = Type.S32) + int mIfindex; + @Field(order = 3, type = Type.U8) + short mIcmpType; + @Field(order = 4, type = Type.U8, padding = 6) + final short mIcmpCode; + + HeaderMessageWithMutableField(final short family, final int len, final short code) { + mFamily = family; + mLen = len; + mIcmpCode = code; + } + } + + @Test + public void testMixMutableAndImmutableFields() { + final ByteBuffer buf = toByteBuffer(HDR_EMPTY); + assertThrows(IllegalArgumentException.class, + () -> Struct.parse(HeaderMessageWithMutableField.class, buf)); + } + + public static class HeaderMsgWithStaticConstant extends Struct { + private static final String TAG = "HeaderMessage"; + private static final int FIELD_COUNT = 5; + + @Field(order = 0, type = Type.U8, padding = 1) + public final short mFamily; + @Field(order = 1, type = Type.U16) + public final int mLen; + @Field(order = 2, type = Type.S32) + public final int mIfindex; + @Field(order = 3, type = Type.U8) + public final short mIcmpType; + @Field(order = 4, type = Type.U8, padding = 6) + public final short mIcmpCode; + + HeaderMsgWithStaticConstant(final short family, final int len, final int ifindex, + final short type, final short code) { + mFamily = family; + mLen = len; + mIfindex = ifindex; + mIcmpType = type; + mIcmpCode = code; + } + } + + @Test + public void testStaticConstantField() { + final HeaderMsgWithStaticConstant msg = doParsingMessageTest(HDR_EMPTY, + HeaderMsgWithStaticConstant.class, ByteOrder.LITTLE_ENDIAN); + assertEquals(10, msg.mFamily); + assertEquals(0, msg.mLen); + assertEquals(15715755, msg.mIfindex); + assertEquals(134, msg.mIcmpType); + assertEquals(0, msg.mIcmpCode); + + assertEquals(16, Struct.getSize(HeaderMsgWithStaticConstant.class)); + assertArrayEquals(toByteBuffer(HDR_EMPTY).array(), + msg.writeToBytes(ByteOrder.LITTLE_ENDIAN)); + } + + public static class MismatchedConstructor extends Struct { + @Field(order = 0, type = Type.U16) final int mInt1; + @Field(order = 1, type = Type.U16) final int mInt2; + MismatchedConstructor(String int1, String int2) { + mInt1 = Integer.valueOf(int1); + mInt2 = Integer.valueOf(int2); + } + } + + @Test + public void testMisMatchedConstructor() { + final ByteBuffer buf = toByteBuffer("1234" + "5678"); + assertThrows(IllegalArgumentException.class, + () -> Struct.parse(MismatchedConstructor.class, buf)); + } + + public static class ClassWithTwoConstructors extends Struct { + @Field(order = 0, type = Type.U16) public final int mInt1; + @Field(order = 1, type = Type.U16) public final int mInt2; + ClassWithTwoConstructors(String int1, String int2) { + mInt1 = Integer.valueOf(int1); + mInt2 = Integer.valueOf(int2); + } + ClassWithTwoConstructors(int int1, int int2) { + mInt1 = int1; + mInt2 = int2; + } + } + + @Test + public void testClassWithTwoConstructors() { + final ClassWithTwoConstructors msg = doParsingMessageTest("1234" + "5678", + ClassWithTwoConstructors.class, ByteOrder.LITTLE_ENDIAN); + assertEquals(13330 /* 0x3412 */, msg.mInt1); + assertEquals(30806 /* 0x7856 */, msg.mInt2); + + assertEquals(4, Struct.getSize(ClassWithTwoConstructors.class)); + assertArrayEquals(toByteBuffer("1234" + "5678").array(), + msg.writeToBytes(ByteOrder.LITTLE_ENDIAN)); + } + + @Test + public void testInvalidOutputByteBuffer_ZeroCapacity() { + final ByteBuffer output = ByteBuffer.allocate(0); + output.order(ByteOrder.LITTLE_ENDIAN); + final HeaderMsgWithConstructor msg = doParsingMessageTest(HDR_EMPTY, + HeaderMsgWithConstructor.class, ByteOrder.LITTLE_ENDIAN); + assertThrows(BufferOverflowException.class, () -> msg.writeToByteBuffer(output)); + } + + @Test + public void testConsecutiveWrites() { + final HeaderMsgWithConstructor msg1 = doParsingMessageTest(HDR_EMPTY, + HeaderMsgWithConstructor.class, ByteOrder.LITTLE_ENDIAN); + final PrefixMessage msg2 = doParsingMessageTest(OPT_PREF64, PrefixMessage.class, + ByteOrder.LITTLE_ENDIAN); + + int size = Struct.getSize(HeaderMsgWithConstructor.class) + + Struct.getSize(PrefixMessage.class); + final ByteBuffer output = ByteBuffer.allocate(size); + output.order(ByteOrder.LITTLE_ENDIAN); + + msg1.writeToByteBuffer(output); + msg2.writeToByteBuffer(output); + output.flip(); + + final ByteBuffer concat = ByteBuffer.allocate(size).put(toByteBuffer(HDR_EMPTY)) + .put(toByteBuffer(OPT_PREF64)); + assertArrayEquals(output.array(), concat.array()); + } + + @Test + public void testClassesParsedFromCache() throws Exception { + for (int i = 0; i < 100; i++) { + final HeaderMsgWithConstructor msg1 = doParsingMessageTest(HDR_EMPTY, + HeaderMsgWithConstructor.class, ByteOrder.LITTLE_ENDIAN); + verifyHeaderParsing(msg1); + + final PrefixMessage msg2 = doParsingMessageTest(OPT_PREF64, PrefixMessage.class, + ByteOrder.LITTLE_ENDIAN); + verifyPrefixByteArrayParsing(msg2); + } + } + + public static class BigEndianDataMessage extends Struct { + @Field(order = 0, type = Type.S32) public int mInt1; + @Field(order = 1, type = Type.S32) public int mInt2; + @Field(order = 2, type = Type.UBE16) public int mInt3; + @Field(order = 3, type = Type.U16) public int mInt4; + @Field(order = 4, type = Type.U64) public BigInteger mBigInteger1; + @Field(order = 5, type = Type.UBE64) public BigInteger mBigInteger2; + @Field(order = 6, type = Type.S64) public long mLong; + } + + private static final String BIG_ENDIAN_DATA = "00000001" + "fffffffe" + "fffe" + "fffe" + + "ff00004500002301" + "ff00004500002301" + "ff00004500002301"; + + @Test + public void testBigEndianByteBuffer() { + final BigEndianDataMessage msg = doParsingMessageTest(BIG_ENDIAN_DATA, + BigEndianDataMessage.class, ByteOrder.BIG_ENDIAN); + + assertEquals(1, msg.mInt1); + assertEquals(-2, msg.mInt2); + assertEquals(65534, msg.mInt3); + assertEquals(65534, msg.mInt4); + assertEquals(new BigInteger("18374686776024376065"), msg.mBigInteger1); + assertEquals(new BigInteger("18374686776024376065"), msg.mBigInteger2); + assertEquals(0xff00004500002301L, msg.mLong); + + assertEquals(36, Struct.getSize(BigEndianDataMessage.class)); + assertArrayEquals(toByteBuffer(BIG_ENDIAN_DATA).array(), + msg.writeToBytes(ByteOrder.BIG_ENDIAN)); + } + + private ByteBuffer toByteBuffer(final String hexString) { + return ByteBuffer.wrap(HexDump.hexStringToByteArray(hexString)); + } + + public static class MacAddressMessage extends Struct { + public @Field(order = 0, type = Type.EUI48) final MacAddress mMac1; + public @Field(order = 1, type = Type.EUI48) final MacAddress mMac2; + + MacAddressMessage(final MacAddress mac1, final MacAddress mac2) { + this.mMac1 = mac1; + this.mMac2 = mac2; + } + } + + @Test + public void testMacAddressType() { + final MacAddressMessage msg = doParsingMessageTest("001122334455" + "ffffffffffff", + MacAddressMessage.class, ByteOrder.BIG_ENDIAN); + + assertEquals(MacAddress.fromString("00:11:22:33:44:55"), msg.mMac1); + assertEquals(MacAddress.fromString("ff:ff:ff:ff:ff:ff"), msg.mMac2); + + assertEquals(12, Struct.getSize(MacAddressMessage.class)); + assertArrayEquals(toByteBuffer("001122334455" + "ffffffffffff").array(), + msg.writeToBytes(ByteOrder.BIG_ENDIAN)); + } + + public static class BadMacAddressType extends Struct { + @Field(order = 0, type = Type.EUI48) byte[] mMac; + } + + @Test + public void testIncorrectType_EUI48WithByteArray() { + assertThrows(IllegalArgumentException.class, + () -> Struct.parse(BadMacAddressType.class, toByteBuffer("ffffffffffff"))); + } + + @Test + public void testStructToByteArrayRoundTrip() { + final SignedDataMessage littleEndianMsg = doParsingMessageTest(SIGNED_DATA, + SignedDataMessage.class, ByteOrder.LITTLE_ENDIAN); + assertArrayEquals(toByteBuffer(SIGNED_DATA).array(), + littleEndianMsg.writeToBytes(ByteOrder.LITTLE_ENDIAN)); + + final SignedDataMessage bigEndianMsg = doParsingMessageTest(SIGNED_DATA, + SignedDataMessage.class, ByteOrder.BIG_ENDIAN); + assertArrayEquals(toByteBuffer(SIGNED_DATA).array(), + bigEndianMsg.writeToBytes(ByteOrder.BIG_ENDIAN)); + + final SignedDataMessage nativeOrderMsg = ByteOrder.nativeOrder().equals( + ByteOrder.LITTLE_ENDIAN) ? littleEndianMsg : bigEndianMsg; + assertArrayEquals(toByteBuffer(SIGNED_DATA).array(), + nativeOrderMsg.writeToBytes()); + } + + @Test + public void testStructToByteArray() { + final SignedDataMessage msg = new SignedDataMessage((byte) -5, (short) 42, (int) 0xff000004, + (long) 0xff000004ff000005L); + final String leHexString = "fb" + "2a00" + "040000ff" + "050000ff040000ff"; + final String beHexString = "fb" + "002a" + "ff000004" + "ff000004ff000005"; + final String hexString = ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN) + ? leHexString : beHexString; + assertArrayEquals(toByteBuffer(hexString).array(), msg.writeToBytes()); + } + + public static class IpAddressMessage extends Struct { + @Field(order = 0, type = Type.Ipv4Address) public final Inet4Address ipv4Address; + @Field(order = 1, type = Type.Ipv6Address) public final Inet6Address ipv6Address; + + IpAddressMessage(final Inet4Address ipv4Address, final Inet6Address ipv6Address) { + this.ipv4Address = ipv4Address; + this.ipv6Address = ipv6Address; + } + } + + @Test + public void testIpAddressType() { + final IpAddressMessage msg = doParsingMessageTest("c0a86401" + + "20010db8000300040005000600070008", IpAddressMessage.class, ByteOrder.BIG_ENDIAN); + + assertEquals(TEST_IPV4_ADDRESS, msg.ipv4Address); + assertEquals(TEST_IPV6_ADDRESS, msg.ipv6Address); + + assertEquals(20, Struct.getSize(IpAddressMessage.class)); + assertArrayEquals(toByteBuffer("c0a86401" + "20010db8000300040005000600070008").array(), + msg.writeToBytes(ByteOrder.BIG_ENDIAN)); + } + + public static class WrongIpAddressType extends Struct { + @Field(order = 0, type = Type.Ipv4Address) public byte[] ipv4Address; + @Field(order = 1, type = Type.Ipv6Address) public byte[] ipv6Address; + } + + @Test + public void testIncorrectType_IpAddressWithByteArray() { + assertThrows(IllegalArgumentException.class, + () -> Struct.parse(WrongIpAddressType.class, + toByteBuffer("c0a86401" + "20010db8000300040005000600070008"))); + } + + public static class FullTypeMessage extends Struct { + @Field(order = 0, type = Type.U8) public final short u8; + @Field(order = 1, type = Type.U16) public final int u16; + @Field(order = 2, type = Type.U32) public final long u32; + @Field(order = 3, type = Type.U63) public final long u63; + @Field(order = 4, type = Type.U64) public final BigInteger u64; + @Field(order = 5, type = Type.S8) public final byte s8; + @Field(order = 6, type = Type.S16) public final short s16; + @Field(order = 7, type = Type.S32) public final int s32; + @Field(order = 8, type = Type.S64) public final long s64; + @Field(order = 9, type = Type.UBE16) public final int ube16; + @Field(order = 10, type = Type.UBE32) public final long ube32; + @Field(order = 11, type = Type.UBE63) public final long ube63; + @Field(order = 12, type = Type.UBE64) public final BigInteger ube64; + @Field(order = 13, type = Type.ByteArray, arraysize = 12) public final byte[] bytes; + @Field(order = 14, type = Type.EUI48) public final MacAddress eui48; + @Field(order = 15, type = Type.Ipv4Address) public final Inet4Address ipv4Address; + @Field(order = 16, type = Type.Ipv6Address) public final Inet6Address ipv6Address; + + FullTypeMessage(final short u8, final int u16, final long u32, final long u63, + final BigInteger u64, final byte s8, final short s16, final int s32, final long s64, + final int ube16, final long ube32, final long ube63, final BigInteger ube64, + final byte[] bytes, final MacAddress eui48, final Inet4Address ipv4Address, + final Inet6Address ipv6Address) { + this.u8 = u8; + this.u16 = u16; + this.u32 = u32; + this.u63 = u63; + this.u64 = u64; + this.s8 = s8; + this.s16 = s16; + this.s32 = s32; + this.s64 = s64; + this.ube16 = ube16; + this.ube32 = ube32; + this.ube63 = ube63; + this.ube64 = ube64; + this.bytes = bytes; + this.eui48 = eui48; + this.ipv4Address = ipv4Address; + this.ipv6Address = ipv6Address; + } + } + + private static final String FULL_TYPE_DATA = "ff" + "ffff" + "ffffffff" + "7fffffffffffffff" + + "ffffffffffffffff" + "7f" + "7fff" + "7fffffff" + "7fffffffffffffff" + "7fff" + + "7fffffff" + "7fffffffffffffff" + "ffffffffffffffff" + "20010db80003000400050006" + + "001122334455" + "c0a86401" + "20010db8000300040005000600070008"; + private static final String FULL_TYPE_DATA_DIFF_MAC = "ff" + "ffff" + "ffffffff" + + "7fffffffffffffff" + "ffffffffffffffff" + "7f" + "7fff" + "7fffffff" + + "7fffffffffffffff" + "7fff" + "7fffffff" + "7fffffffffffffff" + "ffffffffffffffff" + + "20010db80003000400050006" + "112233445566" + + "c0a86401" + "20010db8000300040005000600070008"; + private static final String FULL_TYPE_DATA_DIFF_LONG = "ff" + "ffff" + "ffffffff" + + "7ffffffffffffffe" + "ffffffffffffffff" + "7f" + "7fff" + "7fffffff" + + "7fffffffffffffff" + "7fff" + "7fffffff" + "7fffffffffffffff" + "ffffffffffffffff" + + "20010db80003000400050006" + "001122334455" + + "c0a86401" + "20010db8000300040005000600070008"; + private static final String FULL_TYPE_DATA_DIFF_INTEGER = "ff" + "ffff" + "ffffffff" + + "7fffffffffffffff" + "ffffffffffffffff" + "7f" + "7fff" + "7fffffff" + + "7fffffffffffffff" + "7fff" + "ffffff7f" + "7fffffffffffffff" + "ffffffffffffffff" + + "20010db80003000400050006" + "001122334455" + + "c0a86401" + "20010db8000300040005000600070008"; + private static final String FULL_TYPE_DATA_DIFF_IPV4 = "ff" + "ffff" + "ffffffff" + + "7fffffffffffffff" + "ffffffffffffffff" + "7f" + "7fff" + "7fffffff" + + "7fffffffffffffff" + "7fff" + "ffffff7f" + "7fffffffffffffff" + "ffffffffffffffff" + + "20010db80003000400050006" + "001122334455" + + "c0a81010" + "20010db8000300040005000600070008"; + private static final String FULL_TYPE_DATA_DIFF_IPV6 = "ff" + "ffff" + "ffffffff" + + "7fffffffffffffff" + "ffffffffffffffff" + "7f" + "7fff" + "7fffffff" + + "7fffffffffffffff" + "7fff" + "ffffff7f" + "7fffffffffffffff" + "ffffffffffffffff" + + "20010db80003000400050006" + "001122334455" + + "c0a86401" + "20010db800030004000500060007000a"; + @Test + public void testStructClass_equals() { + final FullTypeMessage msg = doParsingMessageTest(FULL_TYPE_DATA, FullTypeMessage.class, + ByteOrder.BIG_ENDIAN); + + assertEquals(255, msg.u8); + assertEquals(65535, msg.u16); + assertEquals(4294967295L, msg.u32); + assertEquals(9223372036854775807L, msg.u63); + assertEquals(new BigInteger("18446744073709551615"), msg.u64); + assertEquals(127, msg.s8); + assertEquals(32767, msg.s16); + assertEquals(2147483647, msg.s32); + assertEquals(9223372036854775807L, msg.s64); + assertEquals(32767, msg.ube16); + assertEquals(2147483647, msg.ube32); + assertEquals(9223372036854775807L, msg.ube63); + assertEquals(new BigInteger("18446744073709551615"), msg.ube64); + assertArrayEquals(TEST_PREFIX64, msg.bytes); + assertEquals(MacAddress.fromString("00:11:22:33:44:55"), msg.eui48); + assertEquals(TEST_IPV4_ADDRESS, msg.ipv4Address); + assertEquals(TEST_IPV6_ADDRESS, msg.ipv6Address); + + assertEquals(98, msg.getSize(FullTypeMessage.class)); + assertArrayEquals(toByteBuffer(FULL_TYPE_DATA).array(), + msg.writeToBytes(ByteOrder.BIG_ENDIAN)); + + final FullTypeMessage msg1 = new FullTypeMessage((short) 0xff, (int) 0xffff, + (long) 0xffffffffL, (long) 0x7fffffffffffffffL, + new BigInteger("18446744073709551615"), (byte) 0x7f, (short) 0x7fff, + (int) 0x7fffffff, (long) 0x7fffffffffffffffL, (int) 0x7fff, (long) 0x7fffffffL, + (long) 0x7fffffffffffffffL, new BigInteger("18446744073709551615"), TEST_PREFIX64, + MacAddress.fromString("00:11:22:33:44:55"), TEST_IPV4_ADDRESS, TEST_IPV6_ADDRESS); + assertTrue(msg.equals(msg1)); + } + + public static class FullTypeMessageWithDupType extends Struct { + @Field(order = 0, type = Type.U8) public final short u8; + @Field(order = 1, type = Type.U16) public final int u16; + @Field(order = 2, type = Type.U32) public final long u32; + @Field(order = 3, type = Type.S64) public final long u63; // old: U63, new: S64 + @Field(order = 4, type = Type.UBE64) public final BigInteger u64; // old: U64, new: UBE64 + @Field(order = 5, type = Type.S8) public final byte s8; + @Field(order = 6, type = Type.S16) public final short s16; + @Field(order = 7, type = Type.S32) public final int s32; + @Field(order = 8, type = Type.S64) public final long s64; + @Field(order = 9, type = Type.U16) public final int ube16; // old:UBE16, new: U16 + @Field(order = 10, type = Type.UBE32) public final long ube32; + @Field(order = 11, type = Type.UBE63) public final long ube63; + @Field(order = 12, type = Type.UBE64) public final BigInteger ube64; + @Field(order = 13, type = Type.ByteArray, arraysize = 12) public final byte[] bytes; + @Field(order = 14, type = Type.EUI48) public final MacAddress eui48; + @Field(order = 15, type = Type.Ipv4Address) public final Inet4Address ipv4Address; + @Field(order = 16, type = Type.Ipv6Address) public final Inet6Address ipv6Address; + + FullTypeMessageWithDupType(final short u8, final int u16, final long u32, final long u63, + final BigInteger u64, final byte s8, final short s16, final int s32, final long s64, + final int ube16, final long ube32, final long ube63, final BigInteger ube64, + final byte[] bytes, final MacAddress eui48, final Inet4Address ipv4Address, + final Inet6Address ipv6Address) { + this.u8 = u8; + this.u16 = u16; + this.u32 = u32; + this.u63 = u63; + this.u64 = u64; + this.s8 = s8; + this.s16 = s16; + this.s32 = s32; + this.s64 = s64; + this.ube16 = ube16; + this.ube32 = ube32; + this.ube63 = ube63; + this.ube64 = ube64; + this.bytes = bytes; + this.eui48 = eui48; + this.ipv4Address = ipv4Address; + this.ipv6Address = ipv6Address; + } + } + + @Test + public void testStructClass_notEqualWithDifferentClass() { + final FullTypeMessage msg = doParsingMessageTest(FULL_TYPE_DATA, FullTypeMessage.class, + ByteOrder.BIG_ENDIAN); + final FullTypeMessageWithDupType msg1 = doParsingMessageTest(FULL_TYPE_DATA, + FullTypeMessageWithDupType.class, ByteOrder.BIG_ENDIAN); + + assertFalse(msg.equals(msg1)); + } + + @Test + public void testStructClass_notEqualWithDifferentValue() { + final FullTypeMessage msg = doParsingMessageTest(FULL_TYPE_DATA, FullTypeMessage.class, + ByteOrder.BIG_ENDIAN); + + // With different MAC address. + final FullTypeMessage msg1 = doParsingMessageTest(FULL_TYPE_DATA_DIFF_MAC, + FullTypeMessage.class, ByteOrder.BIG_ENDIAN); + assertNotEquals(msg.eui48, msg1.eui48); + assertFalse(msg.equals(msg1)); + + // With different byte array. + final FullTypeMessage msg2 = doParsingMessageTest(FULL_TYPE_DATA, FullTypeMessage.class, + ByteOrder.BIG_ENDIAN); + msg2.bytes[5] = (byte) 42; // change one byte in the array. + assertFalse(msg.equals(msg2)); + + // With different Long primitive. + final FullTypeMessage msg3 = doParsingMessageTest(FULL_TYPE_DATA_DIFF_LONG, + FullTypeMessage.class, ByteOrder.BIG_ENDIAN); + assertNotEquals(msg.u63, msg3.u63); + assertFalse(msg.equals(msg3)); + + // With different Integer primitive. + final FullTypeMessage msg4 = doParsingMessageTest(FULL_TYPE_DATA_DIFF_INTEGER, + FullTypeMessage.class, ByteOrder.BIG_ENDIAN); + assertNotEquals(msg.ube32, msg4.ube32); + assertFalse(msg.equals(msg4)); + + // With different IPv4 address. + final FullTypeMessage msg5 = doParsingMessageTest(FULL_TYPE_DATA_DIFF_IPV4, + FullTypeMessage.class, ByteOrder.BIG_ENDIAN); + assertNotEquals(msg.ipv4Address, msg5.ipv4Address); + assertFalse(msg.equals(msg5)); + + // With different IPv6 address. + final FullTypeMessage msg6 = doParsingMessageTest(FULL_TYPE_DATA_DIFF_IPV6, + FullTypeMessage.class, ByteOrder.BIG_ENDIAN); + assertNotEquals(msg.ipv6Address, msg6.ipv6Address); + assertFalse(msg.equals(msg6)); + } + + @Test + public void testStructClass_toString() { + final String expected = "u8: 255, u16: 65535, u32: 4294967295," + + " u63: 9223372036854775807, u64: 18446744073709551615, s8: 127, s16: 32767," + + " s32: 2147483647, s64: 9223372036854775807, ube16: 32767, ube32: 2147483647," + + " ube63: 9223372036854775807, ube64: 18446744073709551615," + + " bytes: 0x20010DB80003000400050006," + + " eui48: 00:11:22:33:44:55," + + " ipv4Address: 192.168.100.1," + + " ipv6Address: 2001:db8:3:4:5:6:7:8"; + + final FullTypeMessage msg = doParsingMessageTest(FULL_TYPE_DATA, FullTypeMessage.class, + ByteOrder.BIG_ENDIAN); + assertEquals(expected, msg.toString()); + } + + @Test + public void testStructClass_toStringWithNullMember() { + final String expected = "u8: 255, u16: 65535, u32: 4294967295," + + " u63: 9223372036854775807, u64: null, s8: 127, s16: 32767," + + " s32: 2147483647, s64: 9223372036854775807, ube16: 32767, ube32: 2147483647," + + " ube63: 9223372036854775807, ube64: 18446744073709551615," + + " bytes: null, eui48: null, ipv4Address: 192.168.100.1," + + " ipv6Address: null"; + + final FullTypeMessage msg = new FullTypeMessage((short) 0xff, (int) 0xffff, + (long) 0xffffffffL, (long) 0x7fffffffffffffffL, + null /* u64 */, (byte) 0x7f, (short) 0x7fff, + (int) 0x7fffffff, (long) 0x7fffffffffffffffL, (int) 0x7fff, (long) 0x7fffffffL, + (long) 0x7fffffffffffffffL, new BigInteger("18446744073709551615"), + null /* bytes */, null /* eui48 */, TEST_IPV4_ADDRESS, null /* ipv6Address */); + assertEquals(expected, msg.toString()); + } + + @Test + public void testStructClass_hashcode() { + final FullTypeMessage msg = doParsingMessageTest(FULL_TYPE_DATA, FullTypeMessage.class, + ByteOrder.BIG_ENDIAN); + final FullTypeMessage msg1 = doParsingMessageTest(FULL_TYPE_DATA, FullTypeMessage.class, + ByteOrder.BIG_ENDIAN); + + assertNotEquals(0, msg.hashCode()); + assertNotEquals(0, msg1.hashCode()); + assertTrue(msg.equals(msg1)); + assertEquals(msg.hashCode(), msg1.hashCode()); + } + + public static class InvalidByteArray extends Struct { + @Field(order = 0, type = Type.ByteArray, arraysize = 12) public byte[] bytes; + } + + @Test + public void testStructClass_WrongByteArraySize() { + final InvalidByteArray msg = doParsingMessageTest("20010db80003000400050006", + InvalidByteArray.class, ByteOrder.BIG_ENDIAN); + + // Actual byte array size doesn't match the size declared in the annotation. + msg.bytes = new byte[16]; + assertThrows(IllegalStateException.class, () -> msg.writeToBytes()); + + final ByteBuffer output = ByteBuffer.allocate(Struct.getSize(InvalidByteArray.class)); + output.order(ByteOrder.LITTLE_ENDIAN); + assertThrows(IllegalStateException.class, () -> msg.writeToByteBuffer(output)); + } + + @Test + public void testStructClass_NullByteArray() { + final InvalidByteArray msg = doParsingMessageTest("20010db80003000400050006", + InvalidByteArray.class, ByteOrder.BIG_ENDIAN); + + msg.bytes = null; + assertThrows(NullPointerException.class, () -> msg.writeToBytes()); + + final ByteBuffer output = ByteBuffer.allocate(Struct.getSize(InvalidByteArray.class)); + output.order(ByteOrder.LITTLE_ENDIAN); + assertThrows(NullPointerException.class, () -> msg.writeToByteBuffer(output)); + } + + @Test + public void testStructClass_ParsingByteArrayAfterInitialization() { + InvalidByteArray msg = new InvalidByteArray(); + msg.bytes = new byte[]{(byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03}; + + // Although bytes member has been initialized with the length different with + // annotation size, parsing from ByteBuffer will get bytes member have the + // reference to byte array with correct size. + msg = doParsingMessageTest("20010db80003000400050006", InvalidByteArray.class, + ByteOrder.BIG_ENDIAN); + assertArrayEquals(TEST_PREFIX64, msg.bytes); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/TrackRecordTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/TrackRecordTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..8e320d0d4e4193610104caa33e02c26954aeb2b2 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/TrackRecordTest.kt @@ -0,0 +1,447 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util + +import com.android.testutils.ConcurrentInterpreter +import com.android.testutils.INTERPRET_TIME_UNIT +import com.android.testutils.InterpretException +import com.android.testutils.InterpretMatcher +import com.android.testutils.SyntaxException +import com.android.testutils.__FILE__ +import com.android.testutils.__LINE__ +import com.android.testutils.intArg +import com.android.testutils.strArg +import com.android.testutils.timeArg +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import java.util.concurrent.CyclicBarrier +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicInteger +import kotlin.system.measureTimeMillis +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertFalse +import kotlin.test.assertNotEquals +import kotlin.test.assertNull +import kotlin.test.assertTrue +import kotlin.test.fail + +val TEST_VALUES = listOf(4, 13, 52, 94, 41, 68, 11, 13, 51, 0, 91, 94, 33, 98, 14) +const val ABSENT_VALUE = 2 +// Caution in changing these : some tests rely on the fact that TEST_TIMEOUT > 2 * SHORT_TIMEOUT +// and LONG_TIMEOUT > 2 * TEST_TIMEOUT +const val SHORT_TIMEOUT = 40L // ms +const val TEST_TIMEOUT = 200L // ms +const val LONG_TIMEOUT = 5000L // ms + +@RunWith(JUnit4::class) +class TrackRecordTest { + @Test + fun testAddAndSizeAndGet() { + val repeats = 22 // arbitrary + val record = ArrayTrackRecord<Int>() + assertEquals(0, record.size) + repeat(repeats) { i -> record.add(i + 2) } + assertEquals(repeats, record.size) + record.add(2) + assertEquals(repeats + 1, record.size) + + assertEquals(11, record[9]) + assertEquals(11, record.getOrNull(9)) + assertEquals(2, record[record.size - 1]) + assertEquals(2, record.getOrNull(record.size - 1)) + + assertFailsWith<IndexOutOfBoundsException> { record[800] } + assertFailsWith<IndexOutOfBoundsException> { record[-1] } + assertFailsWith<IndexOutOfBoundsException> { record[repeats + 1] } + assertNull(record.getOrNull(800)) + assertNull(record.getOrNull(-1)) + assertNull(record.getOrNull(repeats + 1)) + assertNull(record.getOrNull(800) { true }) + assertNull(record.getOrNull(-1) { true }) + assertNull(record.getOrNull(repeats + 1) { true }) + } + + @Test + fun testIndexOf() { + val record = ArrayTrackRecord<Int>() + TEST_VALUES.forEach { record.add(it) } + with(record) { + assertEquals(9, indexOf(0)) + assertEquals(9, lastIndexOf(0)) + assertEquals(1, indexOf(13)) + assertEquals(7, lastIndexOf(13)) + assertEquals(3, indexOf(94)) + assertEquals(11, lastIndexOf(94)) + assertEquals(-1, indexOf(ABSENT_VALUE)) + assertEquals(-1, lastIndexOf(ABSENT_VALUE)) + } + } + + @Test + fun testContains() { + val record = ArrayTrackRecord<Int>() + TEST_VALUES.forEach { record.add(it) } + TEST_VALUES.forEach { assertTrue(record.contains(it)) } + assertFalse(record.contains(ABSENT_VALUE)) + assertTrue(record.containsAll(TEST_VALUES)) + assertTrue(record.containsAll(TEST_VALUES.sorted())) + assertTrue(record.containsAll(TEST_VALUES.sortedDescending())) + assertTrue(record.containsAll(TEST_VALUES.distinct())) + assertTrue(record.containsAll(TEST_VALUES.subList(0, TEST_VALUES.size / 2))) + assertTrue(record.containsAll(TEST_VALUES.subList(0, TEST_VALUES.size / 2).sorted())) + assertTrue(record.containsAll(listOf())) + assertFalse(record.containsAll(listOf(ABSENT_VALUE))) + assertFalse(record.containsAll(TEST_VALUES + listOf(ABSENT_VALUE))) + } + + @Test + fun testEmpty() { + val record = ArrayTrackRecord<Int>() + assertTrue(record.isEmpty()) + record.add(1) + assertFalse(record.isEmpty()) + } + + @Test + fun testIterate() { + val record = ArrayTrackRecord<Int>() + record.forEach { fail("Expected nothing to iterate") } + TEST_VALUES.forEach { record.add(it) } + // zip relies on the iterator (this calls extension function Iterable#zip(Iterable)) + record.zip(TEST_VALUES).forEach { assertEquals(it.first, it.second) } + // Also test reverse iteration (to test hasPrevious() and friends) + record.reversed().zip(TEST_VALUES.reversed()).forEach { assertEquals(it.first, it.second) } + } + + @Test + fun testIteratorIsSnapshot() { + val record = ArrayTrackRecord<Int>() + TEST_VALUES.forEach { record.add(it) } + val iterator = record.iterator() + val expectedSize = record.size + record.add(ABSENT_VALUE) + record.add(ABSENT_VALUE) + var measuredSize = 0 + iterator.forEach { + ++measuredSize + assertNotEquals(ABSENT_VALUE, it) + } + assertEquals(expectedSize, measuredSize) + } + + @Test + fun testSublist() { + val record = ArrayTrackRecord<Int>() + TEST_VALUES.forEach { record.add(it) } + assertEquals(record.subList(3, record.size - 3), + TEST_VALUES.subList(3, TEST_VALUES.size - 3)) + } + + fun testPollReturnsImmediately(record: TrackRecord<Int>) { + record.add(4) + val elapsed = measureTimeMillis { assertEquals(4, record.poll(LONG_TIMEOUT, 0)) } + // Should not have waited at all, in fact. + assertTrue(elapsed < LONG_TIMEOUT) + record.add(7) + record.add(9) + // Can poll multiple times for the same position, in whatever order + assertEquals(9, record.poll(0, 2)) + assertEquals(7, record.poll(Long.MAX_VALUE, 1)) + assertEquals(9, record.poll(0, 2)) + assertEquals(4, record.poll(0, 0)) + assertEquals(9, record.poll(0, 2) { it > 5 }) + assertEquals(7, record.poll(0, 0) { it > 5 }) + } + + @Test + fun testPollReturnsImmediately() { + testPollReturnsImmediately(ArrayTrackRecord()) + testPollReturnsImmediately(ArrayTrackRecord<Int>().newReadHead()) + } + + @Test + fun testPollTimesOut() { + val record = ArrayTrackRecord<Int>() + var delay = measureTimeMillis { assertNull(record.poll(SHORT_TIMEOUT, 0)) } + assertTrue(delay >= SHORT_TIMEOUT, "Delay $delay < $SHORT_TIMEOUT") + delay = measureTimeMillis { assertNull(record.poll(SHORT_TIMEOUT, 0) { it < 10 }) } + assertTrue(delay >= SHORT_TIMEOUT) + } + + @Test + fun testConcurrentPollDisallowed() { + val failures = AtomicInteger(0) + val readHead = ArrayTrackRecord<Int>().newReadHead() + val barrier = CyclicBarrier(2) + Thread { + barrier.await(LONG_TIMEOUT, TimeUnit.MILLISECONDS) // barrier 1 + try { + readHead.poll(LONG_TIMEOUT) + } catch (e: ConcurrentModificationException) { + failures.incrementAndGet() + // Unblock the other thread + readHead.add(0) + } + }.start() + barrier.await() // barrier 1 + try { + readHead.poll(LONG_TIMEOUT) + } catch (e: ConcurrentModificationException) { + failures.incrementAndGet() + // Unblock the other thread + readHead.add(0) + } + // One of the threads must have gotten an exception. + assertEquals(failures.get(), 1) + } + + @Test + fun testPollWakesUp() { + val record = ArrayTrackRecord<Int>() + val barrier = CyclicBarrier(2) + Thread { + barrier.await(LONG_TIMEOUT, TimeUnit.MILLISECONDS) // barrier 1 + barrier.await() // barrier 2 + Thread.sleep(SHORT_TIMEOUT * 2) + record.add(31) + }.start() + barrier.await() // barrier 1 + // Should find the element in more than SHORT_TIMEOUT but less than TEST_TIMEOUT + var delay = measureTimeMillis { + barrier.await() // barrier 2 + assertEquals(31, record.poll(TEST_TIMEOUT, 0)) + } + assertTrue(delay in SHORT_TIMEOUT..TEST_TIMEOUT) + // Polling for an element already added in anothe thread (pos 0) : should return immediately + delay = measureTimeMillis { assertEquals(31, record.poll(TEST_TIMEOUT, 0)) } + assertTrue(delay < TEST_TIMEOUT, "Delay $delay > $TEST_TIMEOUT") + // Waiting for an element that never comes + delay = measureTimeMillis { assertNull(record.poll(SHORT_TIMEOUT, 1)) } + assertTrue(delay >= SHORT_TIMEOUT, "Delay $delay < $SHORT_TIMEOUT") + // Polling for an element that doesn't match what is already there + delay = measureTimeMillis { assertNull(record.poll(SHORT_TIMEOUT, 0) { it < 10 }) } + assertTrue(delay >= SHORT_TIMEOUT) + } + + // Just make sure the interpreter actually throws an exception when the spec + // does not conform to the behavior. The interpreter is just a tool to test a + // tool used for a tool for test, let's not have hundreds of tests for it ; + // if it's broken one of the tests using it will break. + @Test + fun testInterpreter() { + val interpretLine = __LINE__ + 2 + try { + TRTInterpreter.interpretTestSpec(useReadHeads = true, spec = """ + add(4) | poll(1, 0) = 5 + """) + fail("This spec should have thrown") + } catch (e: InterpretException) { + assertTrue(e.cause is AssertionError) + assertEquals(interpretLine + 1, e.stackTrace[0].lineNumber) + assertTrue(e.stackTrace[0].fileName.contains(__FILE__)) + assertTrue(e.stackTrace[0].methodName.contains("testInterpreter")) + assertTrue(e.stackTrace[0].methodName.contains("thread1")) + } + } + + @Test + fun testMultipleAdds() { + TRTInterpreter.interpretTestSpec(useReadHeads = false, spec = """ + add(2) | | | + | add(4) | | + | | add(6) | + | | | add(8) + poll(0, 0) = 2 time 0..1 | poll(0, 0) = 2 | poll(0, 0) = 2 | poll(0, 0) = 2 + poll(0, 1) = 4 time 0..1 | poll(0, 1) = 4 | poll(0, 1) = 4 | poll(0, 1) = 4 + poll(0, 2) = 6 time 0..1 | poll(0, 2) = 6 | poll(0, 2) = 6 | poll(0, 2) = 6 + poll(0, 3) = 8 time 0..1 | poll(0, 3) = 8 | poll(0, 3) = 8 | poll(0, 3) = 8 + """) + } + + @Test + fun testConcurrentAdds() { + TRTInterpreter.interpretTestSpec(useReadHeads = false, spec = """ + add(2) | add(4) | add(6) | add(8) + add(1) | add(3) | add(5) | add(7) + poll(0, 1) is even | poll(0, 0) is even | poll(0, 3) is even | poll(0, 2) is even + poll(0, 5) is odd | poll(0, 4) is odd | poll(0, 7) is odd | poll(0, 6) is odd + """) + } + + @Test + fun testMultiplePoll() { + TRTInterpreter.interpretTestSpec(useReadHeads = false, spec = """ + add(4) | poll(1, 0) = 4 + | poll(0, 1) = null time 0..1 + | poll(1, 1) = null time 1..2 + sleep; add(7) | poll(2, 1) = 7 time 1..2 + sleep; add(18) | poll(2, 2) = 18 time 1..2 + """) + } + + @Test + fun testMultiplePollWithPredicate() { + TRTInterpreter.interpretTestSpec(useReadHeads = false, spec = """ + | poll(1, 0) = null | poll(1, 0) = null + add(6) | poll(1, 0) = 6 | + add(11) | poll(1, 0) { > 20 } = null | poll(1, 0) { = 11 } = 11 + | poll(1, 0) { > 8 } = 11 | + """) + } + + @Test + fun testMultipleReadHeads() { + TRTInterpreter.interpretTestSpec(useReadHeads = true, spec = """ + | poll() = null | poll() = null | poll() = null + add(5) | | poll() = 5 | + | poll() = 5 | | + add(8) | poll() = 8 | poll() = 8 | + | | | poll() = 5 + | | | poll() = 8 + | | | poll() = null + | | poll() = null | + """) + } + + @Test + fun testReadHeadPollWithPredicate() { + TRTInterpreter.interpretTestSpec(useReadHeads = true, spec = """ + add(5) | poll() { < 0 } = null + | poll() { > 5 } = null + add(10) | + | poll() { = 5 } = null // The "5" was skipped in the previous line + add(15) | poll() { > 8 } = 15 // The "10" was skipped in the previous line + | poll(1, 0) { > 8 } = 10 // 10 is the first element after pos 0 matching > 8 + """) + } + + @Test + fun testPollImmediatelyAdvancesReadhead() { + TRTInterpreter.interpretTestSpec(useReadHeads = true, spec = """ + add(1) | add(2) | add(3) | add(4) + mark = 0 | poll(0) { > 3 } = 4 | | + poll(0) { > 10 } = null | | | + mark = 4 | | | + poll() = null | | | + """) + } + + @Test + fun testParallelReadHeads() { + TRTInterpreter.interpretTestSpec(useReadHeads = true, spec = """ + mark = 0 | mark = 0 | mark = 0 | mark = 0 + add(2) | | | + | add(4) | | + | | add(6) | + | | | add(8) + poll() = 2 | poll() = 2 | poll() = 2 | poll() = 2 + poll() = 4 | poll() = 4 | poll() = 4 | poll() = 4 + poll() = 6 | poll() = 6 | poll() = 6 | mark = 2 + poll() = 8 | poll() = 8 | mark = 3 | poll() = 6 + mark = 4 | mark = 4 | poll() = 8 | poll() = 8 + """) + } + + @Test + fun testPeek() { + TRTInterpreter.interpretTestSpec(useReadHeads = true, spec = """ + add(2) | | | + | add(4) | | + | | add(6) | + | | | add(8) + peek() = 2 | poll() = 2 | poll() = 2 | peek() = 2 + peek() = 2 | peek() = 4 | poll() = 4 | peek() = 2 + peek() = 2 | peek() = 4 | peek() = 6 | poll() = 2 + peek() = 2 | mark = 1 | mark = 2 | poll() = 4 + mark = 0 | peek() = 4 | peek() = 6 | peek() = 6 + poll() = 2 | poll() = 4 | poll() = 6 | poll() = 6 + poll() = 4 | mark = 2 | poll() = 8 | peek() = 8 + peek() = 6 | peek() = 6 | peek() = null | mark = 3 + """) + } +} + +private object TRTInterpreter : ConcurrentInterpreter<TrackRecord<Int>>(interpretTable) { + fun interpretTestSpec(spec: String, useReadHeads: Boolean) = if (useReadHeads) { + interpretTestSpec(spec, initial = ArrayTrackRecord(), + threadTransform = { (it as ArrayTrackRecord).newReadHead() }) + } else { + interpretTestSpec(spec, ArrayTrackRecord()) + } +} + +/* + * Quick ref of supported expressions : + * sleep(x) : sleeps for x time units and returns Unit ; sleep alone means sleep(1) + * add(x) : calls and returns TrackRecord#add. + * poll(time, pos) [{ predicate }] : calls and returns TrackRecord#poll(x time units, pos). + * Optionally, a predicate may be specified. + * poll() [{ predicate }] : calls and returns ReadHead#poll(1 time unit). Optionally, a predicate + * may be specified. + * EXPR = VALUE : asserts that EXPR equals VALUE. EXPR is interpreted. VALUE can either be the + * string "null" or an int. Returns Unit. + * EXPR time x..y : measures the time taken by EXPR and asserts it took at least x and at most + * y time units. + * predicate must be one of "= x", "< x" or "> x". + */ +private val interpretTable = listOf<InterpretMatcher<TrackRecord<Int>>>( + // Interpret "XXX is odd" : run XXX and assert its return value is odd ("even" works too) + Regex("(.*)\\s+is\\s+(even|odd)") to { i, t, r -> + i.interpret(r.strArg(1), t).also { + assertEquals((it as Int) % 2, if ("even" == r.strArg(2)) 0 else 1) + } + }, + // Interpret "add(XXX)" as TrackRecord#add(int) + Regex("""add\((\d+)\)""") to { i, t, r -> + t.add(r.intArg(1)) + }, + // Interpret "poll(x, y)" as TrackRecord#poll(timeout = x * INTERPRET_TIME_UNIT, pos = y) + // Accepts an optional {} argument for the predicate (see makePredicate for syntax) + Regex("""poll\((\d+),\s*(\d+)\)\s*(\{.*\})?""") to { i, t, r -> + t.poll(r.timeArg(1), r.intArg(2), makePredicate(r.strArg(3))) + }, + // ReadHead#poll. If this throws in the cast, the code is malformed and has passed "poll()" + // in a test that takes a TrackRecord that is not a ReadHead. It's technically possible to get + // the test code to not compile instead of throw, but it's vastly more complex and this will + // fail 100% at runtime any test that would not have compiled. + Regex("""poll\((\d+)?\)\s*(\{.*\})?""") to { i, t, r -> + (if (r.strArg(1).isEmpty()) INTERPRET_TIME_UNIT else r.timeArg(1)).let { time -> + (t as ArrayTrackRecord<Int>.ReadHead).poll(time, makePredicate(r.strArg(2))) + } + }, + // ReadHead#mark. The same remarks apply as with ReadHead#poll. + Regex("mark") to { i, t, _ -> (t as ArrayTrackRecord<Int>.ReadHead).mark }, + // ReadHead#peek. The same remarks apply as with ReadHead#poll. + Regex("peek\\(\\)") to { i, t, _ -> (t as ArrayTrackRecord<Int>.ReadHead).peek() } +) + +// Parses a { = x } or { < x } or { > x } string and returns the corresponding predicate +// Returns an always-true predicate for empty and null arguments +private fun makePredicate(spec: String?): (Int) -> Boolean { + if (spec.isNullOrEmpty()) return { true } + val match = Regex("""\{\s*([<>=])\s*(\d+)\s*\}""").matchEntire(spec) + ?: throw SyntaxException("Predicate \"${spec}\"") + val arg = match.intArg(2) + return when (match.strArg(1)) { + ">" -> { i -> i > arg } + "<" -> { i -> i < arg } + "=" -> { i -> i == arg } + else -> throw RuntimeException("How did \"${spec}\" match this regexp ?") + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/async/BufferedFileTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/async/BufferedFileTest.java new file mode 100644 index 0000000000000000000000000000000000000000..11a74f2c2ed8b1cd3ad87b2cbd3df9cdcb6cb5cb --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/async/BufferedFileTest.java @@ -0,0 +1,376 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.async; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.ignoreStubs; +import static org.mockito.Mockito.reset; +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.os.ParcelFileDescriptor; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.testutils.async.ReadableDataAnswer; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class BufferedFileTest { + @Mock EventManager mockEventManager; + @Mock BufferedFile.Listener mockFileListener; + @Mock AsyncFile mockAsyncFile; + @Mock ParcelFileDescriptor mockParcelFileDescriptor; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + } + + @After + public void tearDown() throws Exception { + verifyNoMoreInteractions(ignoreStubs(mockFileListener, mockAsyncFile, mockEventManager)); + } + + @Test + public void onClosed() throws Exception { + final int inboundBufferSize = 1024; + final int outboundBufferSize = 768; + + final BufferedFile file = createFile(inboundBufferSize, outboundBufferSize); + + file.onClosed(mockAsyncFile); + + verify(mockFileListener).onBufferedFileClosed(); + } + + @Test + public void continueReadingAndClose() throws Exception { + final int inboundBufferSize = 1024; + final int outboundBufferSize = 768; + + final BufferedFile file = createFile(inboundBufferSize, outboundBufferSize); + + assertEquals(inboundBufferSize, file.getInboundBufferFreeSizeForTest()); + assertEquals(outboundBufferSize, file.getOutboundBufferFreeSize()); + + file.continueReading(); + verify(mockAsyncFile).enableReadEvents(true); + + file.close(); + verify(mockAsyncFile).close(); + } + + @Test + public void enqueueOutboundData() throws Exception { + final int inboundBufferSize = 10; + final int outboundBufferSize = 250; + + final BufferedFile file = createFile(inboundBufferSize, outboundBufferSize); + + final byte[] data1 = new byte[101]; + final byte[] data2 = new byte[102]; + data1[0] = (byte) 1; + data2[0] = (byte) 2; + + assertEquals(0, file.getOutboundBufferSize()); + + final int totalLen = data1.length + data2.length; + + when(mockAsyncFile.write(any(), anyInt(), anyInt())).thenReturn(0); + assertTrue(file.enqueueOutboundData(data1, 0, data1.length, null, 0, 0)); + verify(mockAsyncFile).enableWriteEvents(true); + + assertEquals(data1.length, file.getOutboundBufferSize()); + + checkAndResetMocks(); + + final ArgumentCaptor<byte[]> arrayCaptor = ArgumentCaptor.forClass(byte[].class); + final ArgumentCaptor<Integer> posCaptor = ArgumentCaptor.forClass(Integer.class); + final ArgumentCaptor<Integer> lenCaptor = ArgumentCaptor.forClass(Integer.class); + when(mockAsyncFile.write( + arrayCaptor.capture(), posCaptor.capture(), lenCaptor.capture())).thenReturn(totalLen); + + assertTrue(file.enqueueOutboundData(data2, 0, data2.length, null, 0, 0)); + + assertEquals(0, file.getInboundBuffer().size()); + assertEquals(0, file.getOutboundBufferSize()); + + assertEquals(0, posCaptor.getValue().intValue()); + assertEquals(totalLen, lenCaptor.getValue().intValue()); + assertEquals(data1[0], arrayCaptor.getValue()[0]); + assertEquals(data2[0], arrayCaptor.getValue()[data1.length]); + } + + @Test + public void enqueueOutboundData_combined() throws Exception { + final int inboundBufferSize = 10; + final int outboundBufferSize = 250; + + final BufferedFile file = createFile(inboundBufferSize, outboundBufferSize); + + final byte[] data1 = new byte[101]; + final byte[] data2 = new byte[102]; + data1[0] = (byte) 1; + data2[0] = (byte) 2; + + assertEquals(0, file.getOutboundBufferSize()); + + final int totalLen = data1.length + data2.length; + + final ArgumentCaptor<byte[]> arrayCaptor = ArgumentCaptor.forClass(byte[].class); + final ArgumentCaptor<Integer> posCaptor = ArgumentCaptor.forClass(Integer.class); + final ArgumentCaptor<Integer> lenCaptor = ArgumentCaptor.forClass(Integer.class); + when(mockAsyncFile.write( + arrayCaptor.capture(), posCaptor.capture(), lenCaptor.capture())).thenReturn(totalLen); + + assertTrue(file.enqueueOutboundData(data1, 0, data1.length, data2, 0, data2.length)); + + assertEquals(0, file.getInboundBuffer().size()); + assertEquals(0, file.getOutboundBufferSize()); + + assertEquals(0, posCaptor.getValue().intValue()); + assertEquals(totalLen, lenCaptor.getValue().intValue()); + assertEquals(data1[0], arrayCaptor.getValue()[0]); + assertEquals(data2[0], arrayCaptor.getValue()[data1.length]); + } + + @Test + public void enableWriteEvents() throws Exception { + final int inboundBufferSize = 10; + final int outboundBufferSize = 250; + + final BufferedFile file = createFile(inboundBufferSize, outboundBufferSize); + + final byte[] data1 = new byte[101]; + final byte[] data2 = new byte[102]; + final byte[] data3 = new byte[103]; + data1[0] = (byte) 1; + data2[0] = (byte) 2; + data3[0] = (byte) 3; + + assertEquals(0, file.getOutboundBufferSize()); + + // Write first 2 buffers, but fail to flush them, causing async write request. + final int data1And2Len = data1.length + data2.length; + when(mockAsyncFile.write(any(), eq(0), eq(data1And2Len))).thenReturn(0); + assertTrue(file.enqueueOutboundData(data1, 0, data1.length, data2, 0, data2.length)); + assertEquals(0, file.getInboundBuffer().size()); + assertEquals(data1And2Len, file.getOutboundBufferSize()); + verify(mockAsyncFile).enableWriteEvents(true); + + // Try to write 3rd buffers, which won't fit, then fail to flush. + when(mockAsyncFile.write(any(), eq(0), eq(data1And2Len))).thenReturn(0); + assertFalse(file.enqueueOutboundData(data3, 0, data3.length, null, 0, 0)); + assertEquals(0, file.getInboundBuffer().size()); + assertEquals(data1And2Len, file.getOutboundBufferSize()); + verify(mockAsyncFile, times(2)).enableWriteEvents(true); + + checkAndResetMocks(); + + // Simulate writeability event, and successfully flush. + final ArgumentCaptor<byte[]> arrayCaptor = ArgumentCaptor.forClass(byte[].class); + final ArgumentCaptor<Integer> posCaptor = ArgumentCaptor.forClass(Integer.class); + final ArgumentCaptor<Integer> lenCaptor = ArgumentCaptor.forClass(Integer.class); + when(mockAsyncFile.write(arrayCaptor.capture(), + posCaptor.capture(), lenCaptor.capture())).thenReturn(data1And2Len); + file.onWriteReady(mockAsyncFile); + verify(mockAsyncFile).enableWriteEvents(false); + verify(mockFileListener).onBufferedFileOutboundSpace(); + assertEquals(0, file.getOutboundBufferSize()); + + assertEquals(0, posCaptor.getValue().intValue()); + assertEquals(data1And2Len, lenCaptor.getValue().intValue()); + assertEquals(data1[0], arrayCaptor.getValue()[0]); + assertEquals(data2[0], arrayCaptor.getValue()[data1.length]); + + checkAndResetMocks(); + + // Now write, but fail to flush the third buffer. + when(mockAsyncFile.write(arrayCaptor.capture(), + posCaptor.capture(), lenCaptor.capture())).thenReturn(0); + assertTrue(file.enqueueOutboundData(data3, 0, data3.length, null, 0, 0)); + verify(mockAsyncFile).enableWriteEvents(true); + assertEquals(data3.length, file.getOutboundBufferSize()); + + assertEquals(data1And2Len, posCaptor.getValue().intValue()); + assertEquals(outboundBufferSize - data1And2Len, lenCaptor.getValue().intValue()); + assertEquals(data3[0], arrayCaptor.getValue()[data1And2Len]); + } + + @Test + public void read() throws Exception { + final int inboundBufferSize = 250; + final int outboundBufferSize = 10; + + final BufferedFile file = createFile(inboundBufferSize, outboundBufferSize); + + final byte[] data1 = new byte[101]; + final byte[] data2 = new byte[102]; + data1[0] = (byte) 1; + data2[0] = (byte) 2; + + final ReadableDataAnswer dataAnswer = new ReadableDataAnswer(data1, data2); + final ReadableByteBuffer inboundBuffer = file.getInboundBuffer(); + + when(mockAsyncFile.read(any(), anyInt(), anyInt())).thenAnswer(dataAnswer); + file.onReadReady(mockAsyncFile); + verify(mockAsyncFile).enableReadEvents(true); + verify(mockFileListener).onBufferedFileInboundData(eq(data1.length + data2.length)); + + assertEquals(0, file.getOutboundBufferSize()); + assertEquals(data1.length + data2.length, inboundBuffer.size()); + assertEquals((byte) 1, inboundBuffer.peek(0)); + assertEquals((byte) 2, inboundBuffer.peek(data1.length)); + } + + @Test + public void enableReadEvents() throws Exception { + final int inboundBufferSize = 250; + final int outboundBufferSize = 10; + + final BufferedFile file = createFile(inboundBufferSize, outboundBufferSize); + + final byte[] data1 = new byte[101]; + final byte[] data2 = new byte[102]; + final byte[] data3 = new byte[103]; + data1[0] = (byte) 1; + data2[0] = (byte) 2; + data3[0] = (byte) 3; + + final ReadableDataAnswer dataAnswer = new ReadableDataAnswer(data1, data2, data3); + final ReadableByteBuffer inboundBuffer = file.getInboundBuffer(); + + when(mockAsyncFile.read(any(), anyInt(), anyInt())).thenAnswer(dataAnswer); + file.onReadReady(mockAsyncFile); + verify(mockAsyncFile).enableReadEvents(false); + verify(mockFileListener).onBufferedFileInboundData(eq(inboundBufferSize)); + + assertEquals(0, file.getOutboundBufferSize()); + assertEquals(inboundBufferSize, inboundBuffer.size()); + assertEquals((byte) 1, inboundBuffer.peek(0)); + assertEquals((byte) 2, inboundBuffer.peek(data1.length)); + assertEquals((byte) 3, inboundBuffer.peek(data1.length + data2.length)); + + checkAndResetMocks(); + + // Cannot enable read events since the buffer is full. + file.continueReading(); + + checkAndResetMocks(); + + final byte[] tmp = new byte[inboundBufferSize]; + inboundBuffer.readBytes(tmp, 0, data1.length); + assertEquals(inboundBufferSize - data1.length, inboundBuffer.size()); + + file.continueReading(); + + inboundBuffer.readBytes(tmp, 0, data2.length); + assertEquals(inboundBufferSize - data1.length - data2.length, inboundBuffer.size()); + + when(mockAsyncFile.read(any(), anyInt(), anyInt())).thenAnswer(dataAnswer); + file.onReadReady(mockAsyncFile); + verify(mockAsyncFile, times(2)).enableReadEvents(true); + verify(mockFileListener).onBufferedFileInboundData( + eq(data1.length + data2.length + data3.length - inboundBufferSize)); + + assertEquals(data3.length, inboundBuffer.size()); + assertEquals((byte) 3, inboundBuffer.peek(0)); + } + + @Test + public void shutdownReading() throws Exception { + final int inboundBufferSize = 250; + final int outboundBufferSize = 10; + + final BufferedFile file = createFile(inboundBufferSize, outboundBufferSize); + + final byte[] data = new byte[100]; + final ReadableDataAnswer dataAnswer = new ReadableDataAnswer(data); + when(mockAsyncFile.read(any(), anyInt(), anyInt())).thenAnswer(dataAnswer); + + file.shutdownReading(); + file.onReadReady(mockAsyncFile); + + verify(mockAsyncFile).enableReadEvents(false); + + assertEquals(0, file.getInboundBuffer().size()); + assertEquals(data.length, dataAnswer.getRemainingSize()); + } + + @Test + public void shutdownReading_inCallback() throws Exception { + final int inboundBufferSize = 250; + final int outboundBufferSize = 10; + + final BufferedFile file = createFile(inboundBufferSize, outboundBufferSize); + + final byte[] data = new byte[100]; + final ReadableDataAnswer dataAnswer = new ReadableDataAnswer(data); + when(mockAsyncFile.read(any(), anyInt(), anyInt())).thenAnswer(dataAnswer); + + doAnswer(new Answer() { + @Override public Object answer(InvocationOnMock invocation) { + file.shutdownReading(); + return null; + }}).when(mockFileListener).onBufferedFileInboundData(anyInt()); + + file.onReadReady(mockAsyncFile); + + verify(mockAsyncFile).enableReadEvents(false); + + assertEquals(0, file.getInboundBuffer().size()); + assertEquals(0, dataAnswer.getRemainingSize()); + } + + private void checkAndResetMocks() { + verifyNoMoreInteractions(ignoreStubs(mockFileListener, mockAsyncFile, mockEventManager, + mockParcelFileDescriptor)); + reset(mockFileListener, mockAsyncFile, mockEventManager); + } + + private BufferedFile createFile( + int inboundBufferSize, int outboundBufferSize) throws Exception { + when(mockEventManager.registerFile(any(), any())).thenReturn(mockAsyncFile); + return BufferedFile.create( + mockEventManager, + FileHandle.fromFileDescriptor(mockParcelFileDescriptor), + mockFileListener, + inboundBufferSize, + outboundBufferSize); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/async/CircularByteBufferTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/async/CircularByteBufferTest.java new file mode 100644 index 0000000000000000000000000000000000000000..01abee2b4a37ad6a8592a4688bc191d1ec0d90bd --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/async/CircularByteBufferTest.java @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.async; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.fail; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class CircularByteBufferTest { + @Test + public void writeBytes() { + final int capacity = 23; + CircularByteBuffer buffer = new CircularByteBuffer(capacity); + assertEquals(0, buffer.size()); + assertEquals(0, buffer.getDirectReadSize()); + assertEquals(capacity, buffer.freeSize()); + assertEquals(capacity, buffer.getDirectWriteSize()); + + final byte[] writeBuffer = new byte[15]; + buffer.writeBytes(writeBuffer, 0, writeBuffer.length); + + assertEquals(writeBuffer.length, buffer.size()); + assertEquals(writeBuffer.length, buffer.getDirectReadSize()); + assertEquals(capacity - writeBuffer.length, buffer.freeSize()); + assertEquals(capacity - writeBuffer.length, buffer.getDirectWriteSize()); + + buffer.clear(); + assertEquals(0, buffer.size()); + assertEquals(0, buffer.getDirectReadSize()); + assertEquals(capacity, buffer.freeSize()); + assertEquals(capacity, buffer.getDirectWriteSize()); + } + + @Test + public void writeBytes_withRollover() { + doTestReadWriteWithRollover(new BufferAccessor(false, false)); + } + + @Test + public void writeBytes_withFullBuffer() { + doTestReadWriteWithFullBuffer(new BufferAccessor(false, false)); + } + + @Test + public void directWriteBytes_withRollover() { + doTestReadWriteWithRollover(new BufferAccessor(true, true)); + } + + @Test + public void directWriteBytes_withFullBuffer() { + doTestReadWriteWithFullBuffer(new BufferAccessor(true, true)); + } + + private void doTestReadWriteWithFullBuffer(BufferAccessor accessor) { + CircularByteBuffer buffer = doTestReadWrite(accessor, 20, 5, 4); + + assertEquals(0, buffer.size()); + assertEquals(20, buffer.freeSize()); + } + + private void doTestReadWriteWithRollover(BufferAccessor accessor) { + // All buffer sizes are prime numbers to ensure that some read or write + // operations will roll over the end of the internal buffer. + CircularByteBuffer buffer = doTestReadWrite(accessor, 31, 13, 7); + + assertNotEquals(0, buffer.size()); + } + + private CircularByteBuffer doTestReadWrite(BufferAccessor accessor, + final int capacity, final int writeLen, final int readLen) { + CircularByteBuffer buffer = new CircularByteBuffer(capacity); + + final byte[] writeBuffer = new byte[writeLen + 2]; + final byte[] peekBuffer = new byte[readLen + 2]; + final byte[] readBuffer = new byte[readLen + 2]; + + final int numIterations = 1011; + final int maxRemaining = readLen - 1; + + int currentWriteSymbol = 0; + int expectedReadSymbol = 0; + int expectedSize = 0; + int totalWritten = 0; + int totalRead = 0; + + for (int i = 0; i < numIterations; i++) { + // Fill in with write buffers as much as possible. + while (buffer.freeSize() >= writeLen) { + currentWriteSymbol = fillTestBytes(writeBuffer, 1, writeLen, currentWriteSymbol); + accessor.writeBytes(buffer, writeBuffer, 1, writeLen); + + expectedSize += writeLen; + totalWritten += writeLen; + assertEquals(expectedSize, buffer.size()); + assertEquals(capacity - expectedSize, buffer.freeSize()); + } + + // Keep reading into read buffers while there's still data. + while (buffer.size() >= readLen) { + peekBuffer[1] = 0; + peekBuffer[2] = 0; + buffer.peekBytes(2, peekBuffer, 3, readLen - 2); + assertEquals(0, peekBuffer[1]); + assertEquals(0, peekBuffer[2]); + + peekBuffer[2] = buffer.peek(1); + + accessor.readBytes(buffer, readBuffer, 1, readLen); + peekBuffer[1] = readBuffer[1]; + + expectedReadSymbol = checkTestBytes( + readBuffer, 1, readLen, expectedReadSymbol, totalRead); + + assertArrayEquals(peekBuffer, readBuffer); + + expectedSize -= readLen; + totalRead += readLen; + assertEquals(expectedSize, buffer.size()); + assertEquals(capacity - expectedSize, buffer.freeSize()); + } + + if (buffer.size() > maxRemaining) { + fail("Too much data remaining: " + buffer.size()); + } + } + + final int maxWritten = capacity * numIterations; + final int minWritten = maxWritten / 2; + if (totalWritten < minWritten || totalWritten > maxWritten + || (totalWritten - totalRead) > maxRemaining) { + fail("Unexpected counts: read=" + totalRead + ", written=" + totalWritten + + ", minWritten=" + minWritten + ", maxWritten=" + maxWritten); + } + + return buffer; + } + + @Test + public void readBytes_overflow() { + CircularByteBuffer buffer = new CircularByteBuffer(23); + + final byte[] dataBuffer = new byte[15]; + buffer.writeBytes(dataBuffer, 0, dataBuffer.length - 2); + + try { + buffer.readBytes(dataBuffer, 0, dataBuffer.length); + assertTrue(false); + } catch (IllegalArgumentException e) { + // expected + } + + assertEquals(13, buffer.size()); + assertEquals(10, buffer.freeSize()); + } + + @Test + public void writeBytes_overflow() { + CircularByteBuffer buffer = new CircularByteBuffer(23); + + final byte[] dataBuffer = new byte[15]; + buffer.writeBytes(dataBuffer, 0, dataBuffer.length); + + try { + buffer.writeBytes(dataBuffer, 0, dataBuffer.length); + assertTrue(false); + } catch (IllegalArgumentException e) { + // expected + } + + assertEquals(15, buffer.size()); + assertEquals(8, buffer.freeSize()); + } + + private static int fillTestBytes(byte[] buffer, int pos, int len, int startValue) { + for (int i = 0; i < len; i++) { + buffer[pos + i] = (byte) (startValue & 0xFF); + startValue = (startValue + 1) % 256; + } + return startValue; + } + + private static int checkTestBytes( + byte[] buffer, int pos, int len, int startValue, int totalRead) { + for (int i = 0; i < len; i++) { + byte expectedValue = (byte) (startValue & 0xFF); + if (expectedValue != buffer[pos + i]) { + fail("Unexpected byte=" + (((int) buffer[pos + i]) & 0xFF) + + ", expected=" + (((int) expectedValue) & 0xFF) + + ", pos=" + (totalRead + i)); + } + startValue = (startValue + 1) % 256; + } + return startValue; + } + + private static final class BufferAccessor { + private final boolean mDirectRead; + private final boolean mDirectWrite; + + BufferAccessor(boolean directRead, boolean directWrite) { + mDirectRead = directRead; + mDirectWrite = directWrite; + } + + void writeBytes(CircularByteBuffer buffer, byte[] src, int pos, int len) { + if (mDirectWrite) { + while (len > 0) { + if (buffer.getDirectWriteSize() == 0) { + fail("Direct write size is zero: free=" + buffer.freeSize() + + ", size=" + buffer.size()); + } + int copyLen = Math.min(len, buffer.getDirectWriteSize()); + System.arraycopy(src, pos, buffer.getDirectWriteBuffer(), + buffer.getDirectWritePos(), copyLen); + buffer.accountForDirectWrite(copyLen); + len -= copyLen; + pos += copyLen; + } + } else { + buffer.writeBytes(src, pos, len); + } + } + + void readBytes(CircularByteBuffer buffer, byte[] dst, int pos, int len) { + if (mDirectRead) { + while (len > 0) { + if (buffer.getDirectReadSize() == 0) { + fail("Direct read size is zero: free=" + buffer.freeSize() + + ", size=" + buffer.size()); + } + int copyLen = Math.min(len, buffer.getDirectReadSize()); + System.arraycopy( + buffer.getDirectReadBuffer(), buffer.getDirectReadPos(), dst, pos, copyLen); + buffer.accountForDirectRead(copyLen); + len -= copyLen; + pos += copyLen; + } + } else { + buffer.readBytes(dst, pos, len); + } + } + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/ip/ConntrackMonitorTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/ip/ConntrackMonitorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..7ee376c98d2b4aefc395533844a5d6854a329294 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/ip/ConntrackMonitorTest.java @@ -0,0 +1,336 @@ +/* + * 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. + */ + +package com.android.net.module.util.ip; + +import static android.system.OsConstants.AF_UNIX; +import static android.system.OsConstants.IPPROTO_TCP; +import static android.system.OsConstants.SOCK_DGRAM; + +import static com.android.net.module.util.netlink.ConntrackMessage.Tuple; +import static com.android.net.module.util.netlink.ConntrackMessage.TupleIpv4; +import static com.android.net.module.util.netlink.ConntrackMessage.TupleProto; +import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_DELETE; +import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_NEW; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; + +import android.net.InetAddresses; +import android.os.ConditionVariable; +import android.os.Handler; +import android.os.HandlerThread; +import android.system.ErrnoException; +import android.system.Os; + +import androidx.annotation.NonNull; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.net.module.util.SharedLog; +import com.android.net.module.util.ip.ConntrackMonitor.ConntrackEvent; +import com.android.net.module.util.netlink.NetlinkConstants; +import com.android.net.module.util.netlink.NetlinkUtils; + +import libcore.util.HexEncoding; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.FileDescriptor; +import java.io.InterruptedIOException; +import java.net.Inet4Address; + +/** + * Tests for ConntrackMonitor. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ConntrackMonitorTest { + private static final long TIMEOUT_MS = 10_000L; + + @Mock private SharedLog mLog; + @Mock private ConntrackMonitor.ConntrackEventConsumer mConsumer; + + private final HandlerThread mHandlerThread = new HandlerThread( + ConntrackMonitorTest.class.getSimpleName()); + + // Late init since the handler thread has been started. + private Handler mHandler; + private TestConntrackMonitor mConntrackMonitor; + + // A version of [ConntrackMonitor] that reads packets from the socket pair, and instead + // allows the test to write test packets to the socket pair via [sendMessage]. + private class TestConntrackMonitor extends ConntrackMonitor { + TestConntrackMonitor(@NonNull Handler h, @NonNull SharedLog log, + @NonNull ConntrackEventConsumer cb) { + super(h, log, cb); + + mReadFd = new FileDescriptor(); + mWriteFd = new FileDescriptor(); + try { + Os.socketpair(AF_UNIX, SOCK_DGRAM, 0, mWriteFd, mReadFd); + } catch (ErrnoException e) { + fail("Could not create socket pair: " + e); + } + } + + @Override + protected FileDescriptor createFd() { + return mReadFd; + } + + private void sendMessage(byte[] msg) { + mHandler.post(() -> { + try { + NetlinkUtils.sendMessage(mWriteFd, msg, 0 /* offset */, msg.length, + TIMEOUT_MS); + } catch (ErrnoException | InterruptedIOException e) { + fail("Unable to send netfilter message: " + e); + } + }); + } + + private final FileDescriptor mReadFd; + private final FileDescriptor mWriteFd; + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + + // ConntrackMonitor needs to be started from the handler thread. + final ConditionVariable initDone = new ConditionVariable(); + mHandler.post(() -> { + TestConntrackMonitor m = new TestConntrackMonitor(mHandler, mLog, mConsumer); + m.start(); + mConntrackMonitor = m; + + initDone.open(); + }); + if (!initDone.block(TIMEOUT_MS)) { + fail("... init monitor timed-out after " + TIMEOUT_MS + "ms"); + } + } + + @After + public void tearDown() throws Exception { + mHandlerThread.quitSafely(); + } + + public static final String CT_V4NEW_TCP_HEX = + // CHECKSTYLE:OFF IndentationCheck + // struct nlmsghdr + "8C000000" + // length = 140 + "0001" + // type = NFNL_SUBSYS_CTNETLINK (1) << 8 | IPCTNL_MSG_CT_NEW (0) + "0006" + // flags = NLM_F_CREATE (1 << 10) | NLM_F_EXCL (1 << 9) + "00000000" + // seqno = 0 + "00000000" + // pid = 0 + // struct nfgenmsg + "02" + // nfgen_family = AF_INET + "00" + // version = NFNETLINK_V0 + "1234" + // res_id = 0x1234 (big endian) + // struct nlattr + "3400" + // nla_len = 52 + "0180" + // nla_type = nested CTA_TUPLE_ORIG + // struct nlattr + "1400" + // nla_len = 20 + "0180" + // nla_type = nested CTA_TUPLE_IP + "0800 0100 C0A8500C" + // nla_type=CTA_IP_V4_SRC, ip=192.168.80.12 + "0800 0200 8C700874" + // nla_type=CTA_IP_V4_DST, ip=140.112.8.116 + // struct nlattr + "1C00" + // nla_len = 28 + "0280" + // nla_type = nested CTA_TUPLE_PROTO + "0500 0100 06 000000" + // nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6) + "0600 0200 F3F1 0000" + // nla_type=CTA_PROTO_SRC_PORT, port=62449 (big endian) + "0600 0300 01BB 0000" + // nla_type=CTA_PROTO_DST_PORT, port=443 (big endian) + // struct nlattr + "3400" + // nla_len = 52 + "0280" + // nla_type = nested CTA_TUPLE_REPLY + // struct nlattr + "1400" + // nla_len = 20 + "0180" + // nla_type = nested CTA_TUPLE_IP + "0800 0100 8C700874" + // nla_type=CTA_IP_V4_SRC, ip=140.112.8.116 + "0800 0200 6451B301" + // nla_type=CTA_IP_V4_DST, ip=100.81.179.1 + // struct nlattr + "1C00" + // nla_len = 28 + "0280" + // nla_type = nested CTA_TUPLE_PROTO + "0500 0100 06 000000" + // nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6) + "0600 0200 01BB 0000" + // nla_type=CTA_PROTO_SRC_PORT, port=443 (big endian) + "0600 0300 F3F1 0000" + // nla_type=CTA_PROTO_DST_PORT, port=62449 (big endian) + // struct nlattr + "0800" + // nla_len = 8 + "0300" + // nla_type = CTA_STATUS + "0000019e" + // nla_value = 0b110011110 (big endian) + // IPS_SEEN_REPLY (1 << 1) | IPS_ASSURED (1 << 2) | + // IPS_CONFIRMED (1 << 3) | IPS_SRC_NAT (1 << 4) | + // IPS_SRC_NAT_DONE (1 << 7) | IPS_DST_NAT_DONE (1 << 8) + // struct nlattr + "0800" + // nla_len = 8 + "0700" + // nla_type = CTA_TIMEOUT + "00000078"; // nla_value = 120 (big endian) + // CHECKSTYLE:ON IndentationCheck + public static final byte[] CT_V4NEW_TCP_BYTES = + HexEncoding.decode(CT_V4NEW_TCP_HEX.replaceAll(" ", "").toCharArray(), false); + + @NonNull + private ConntrackEvent makeTestConntrackEvent(short msgType, int status, int timeoutSec) { + final Inet4Address privateIp = + (Inet4Address) InetAddresses.parseNumericAddress("192.168.80.12"); + final Inet4Address remoteIp = + (Inet4Address) InetAddresses.parseNumericAddress("140.112.8.116"); + final Inet4Address publicIp = + (Inet4Address) InetAddresses.parseNumericAddress("100.81.179.1"); + + return new ConntrackEvent( + (short) (NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8 | msgType), + new Tuple(new TupleIpv4(privateIp, remoteIp), + new TupleProto((byte) IPPROTO_TCP, (short) 62449, (short) 443)), + new Tuple(new TupleIpv4(remoteIp, publicIp), + new TupleProto((byte) IPPROTO_TCP, (short) 443, (short) 62449)), + status, + timeoutSec); + } + + @Test + public void testConntrackEventNew() throws Exception { + final ConntrackEvent expectedEvent = makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, + 0x19e /* status */, 120 /* timeoutSec */); + mConntrackMonitor.sendMessage(CT_V4NEW_TCP_BYTES); + verify(mConsumer, timeout(TIMEOUT_MS)).accept(eq(expectedEvent)); + } + + @Test + public void testConntrackEventEquals() { + final ConntrackEvent event1 = makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, 1234 /* status */, + 5678 /* timeoutSec*/); + final ConntrackEvent event2 = makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, 1234 /* status */, + 5678 /* timeoutSec*/); + assertEquals(event1, event2); + } + + @Test + public void testConntrackEventNotEquals() { + final ConntrackEvent e = makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, 1234 /* status */, + 5678 /* timeoutSec*/); + + final ConntrackEvent typeNotEqual = new ConntrackEvent((short) (e.msgType + 1) /* diff */, + e.tupleOrig, e.tupleReply, e.status, e.timeoutSec); + assertNotEquals(e, typeNotEqual); + + final ConntrackEvent tupleOrigNotEqual = new ConntrackEvent(e.msgType, + null /* diff */, e.tupleReply, e.status, e.timeoutSec); + assertNotEquals(e, tupleOrigNotEqual); + + final ConntrackEvent tupleReplyNotEqual = new ConntrackEvent(e.msgType, + e.tupleOrig, null /* diff */, e.status, e.timeoutSec); + assertNotEquals(e, tupleReplyNotEqual); + + final ConntrackEvent statusNotEqual = new ConntrackEvent(e.msgType, + e.tupleOrig, e.tupleReply, e.status + 1 /* diff */, e.timeoutSec); + assertNotEquals(e, statusNotEqual); + + final ConntrackEvent timeoutSecNotEqual = new ConntrackEvent(e.msgType, + e.tupleOrig, e.tupleReply, e.status, e.timeoutSec + 1 /* diff */); + assertNotEquals(e, timeoutSecNotEqual); + } + + @Test + public void testToString() { + final ConntrackEvent event = makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, + 0x198 /* status */, 120 /* timeoutSec */); + final String expected = "" + + "ConntrackEvent{" + + "msg_type{IPCTNL_MSG_CT_NEW}, " + + "tuple_orig{Tuple{IPPROTO_TCP: 192.168.80.12:62449 -> 140.112.8.116:443}}, " + + "tuple_reply{Tuple{IPPROTO_TCP: 140.112.8.116:443 -> 100.81.179.1:62449}}, " + + "status{408(IPS_CONFIRMED|IPS_SRC_NAT|IPS_SRC_NAT_DONE|IPS_DST_NAT_DONE)}, " + + "timeout_sec{120}}"; + assertEquals(expected, event.toString()); + } + + public static final String CT_V4DELETE_TCP_HEX = + // CHECKSTYLE:OFF IndentationCheck + // struct nlmsghdr + "84000000" + // length = 132 + "0201" + // type = NFNL_SUBSYS_CTNETLINK (1) << 8 | IPCTNL_MSG_CT_DELETE (2) + "0000" + // flags = 0 + "00000000" + // seqno = 0 + "00000000" + // pid = 0 + // struct nfgenmsg + "02" + // nfgen_family = AF_INET + "00" + // version = NFNETLINK_V0 + "1234" + // res_id = 0x1234 (big endian) + // struct nlattr + "3400" + // nla_len = 52 + "0180" + // nla_type = nested CTA_TUPLE_ORIG + // struct nlattr + "1400" + // nla_len = 20 + "0180" + // nla_type = nested CTA_TUPLE_IP + "0800 0100 C0A8500C" + // nla_type=CTA_IP_V4_SRC, ip=192.168.80.12 + "0800 0200 8C700874" + // nla_type=CTA_IP_V4_DST, ip=140.112.8.116 + // struct nlattr + "1C00" + // nla_len = 28 + "0280" + // nla_type = nested CTA_TUPLE_PROTO + "0500 0100 06 000000" + // nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6) + "0600 0200 F3F1 0000" + // nla_type=CTA_PROTO_SRC_PORT, port=62449 (big endian) + "0600 0300 01BB 0000" + // nla_type=CTA_PROTO_DST_PORT, port=433 (big endian) + // struct nlattr + "3400" + // nla_len = 52 + "0280" + // nla_type = nested CTA_TUPLE_REPLY + // struct nlattr + "1400" + // nla_len = 20 + "0180" + // nla_type = nested CTA_TUPLE_IP + "0800 0100 8C700874" + // nla_type=CTA_IP_V4_SRC, ip=140.112.8.116 + "0800 0200 6451B301" + // nla_type=CTA_IP_V4_DST, ip=100.81.179.1 + // struct nlattr + "1C00" + // nla_len = 28 + "0280" + // nla_type = nested CTA_TUPLE_PROTO + "0500 0100 06 000000" + // nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6) + "0600 0200 01BB 0000" + // nla_type=CTA_PROTO_SRC_PORT, port=433 (big endian) + "0600 0300 F3F1 0000" + // nla_type=CTA_PROTO_DST_PORT, port=62449 (big endian) + // struct nlattr + "0800" + // nla_len = 8 + "0300" + // nla_type = CTA_STATUS + "0000039E"; // nla_value = 0b1110011110 (big endian) + // IPS_SEEN_REPLY (1 << 1) | IPS_ASSURED (1 << 2) | + // IPS_CONFIRMED (1 << 3) | IPS_SRC_NAT (1 << 4) | + // IPS_SRC_NAT_DONE (1 << 7) | IPS_DST_NAT_DONE (1 << 8) | + // IPS_DYING (1 << 9) + // CHECKSTYLE:ON IndentationCheck + public static final byte[] CT_V4DELETE_TCP_BYTES = + HexEncoding.decode(CT_V4DELETE_TCP_HEX.replaceAll(" ", "").toCharArray(), false); + + @Test + public void testConntrackEventDelete() throws Exception { + final ConntrackEvent expectedEvent = + makeTestConntrackEvent(IPCTNL_MSG_CT_DELETE, 0x39e /* status */, + 0 /* timeoutSec (absent) */); + mConntrackMonitor.sendMessage(CT_V4DELETE_TCP_BYTES); + verify(mConsumer, timeout(TIMEOUT_MS)).accept(eq(expectedEvent)); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/ip/InterfaceControllerTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/ip/InterfaceControllerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..dea667d815f75a038ea1d374db88463b49f7e8bd --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/ip/InterfaceControllerTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.ip; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.net.INetd; +import android.net.InetAddresses; +import android.net.InterfaceConfigurationParcel; +import android.net.LinkAddress; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.net.module.util.SharedLog; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class InterfaceControllerTest { + private static final String TEST_IFACE = "testif"; + private static final String TEST_IPV4_ADDR = "192.168.123.28"; + private static final int TEST_PREFIXLENGTH = 31; + + @Mock private INetd mNetd; + @Mock private SharedLog mLog; + @Captor private ArgumentCaptor<InterfaceConfigurationParcel> mConfigCaptor; + + private InterfaceController mController; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mController = new InterfaceController(TEST_IFACE, mNetd, mLog); + + doNothing().when(mNetd).interfaceSetCfg(mConfigCaptor.capture()); + } + + @Test + public void testSetIPv4Address() throws Exception { + mController.setIPv4Address( + new LinkAddress(InetAddresses.parseNumericAddress(TEST_IPV4_ADDR), + TEST_PREFIXLENGTH)); + verify(mNetd, times(1)).interfaceSetCfg(any()); + final InterfaceConfigurationParcel parcel = mConfigCaptor.getValue(); + assertEquals(TEST_IFACE, parcel.ifName); + assertEquals(TEST_IPV4_ADDR, parcel.ipv4Addr); + assertEquals(TEST_PREFIXLENGTH, parcel.prefixLength); + assertEquals("", parcel.hwAddr); + assertArrayEquals(new String[0], parcel.flags); + } + + @Test + public void testClearIPv4Address() throws Exception { + mController.clearIPv4Address(); + verify(mNetd, times(1)).interfaceSetCfg(any()); + final InterfaceConfigurationParcel parcel = mConfigCaptor.getValue(); + assertEquals(TEST_IFACE, parcel.ifName); + assertEquals("0.0.0.0", parcel.ipv4Addr); + assertEquals(0, parcel.prefixLength); + assertEquals("", parcel.hwAddr); + assertArrayEquals(new String[0], parcel.flags); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/ConntrackMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/ConntrackMessageTest.java new file mode 100644 index 0000000000000000000000000000000000000000..f02b4cb6749f82f0d80933a5c21f625c7e88999c --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/ConntrackMessageTest.java @@ -0,0 +1,433 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_NEW; +import static com.android.net.module.util.netlink.NetlinkConstants.NFNL_SUBSYS_CTNETLINK; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; + +import android.system.OsConstants; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import libcore.util.HexEncoding; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ConntrackMessageTest { + private static final boolean USING_LE = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN); + + private short makeCtType(short msgType) { + return (short) (NFNL_SUBSYS_CTNETLINK << 8 | (byte) msgType); + } + + // Example 1: TCP (192.168.43.209, 44333) -> (23.211.13.26, 443) + public static final String CT_V4UPDATE_TCP_HEX = + // struct nlmsghdr + "50000000" + // length = 80 + "0001" + // type = (1 << 8) | 0 + "0501" + // flags + "01000000" + // seqno = 1 + "00000000" + // pid = 0 + // struct nfgenmsg + "02" + // nfgen_family = AF_INET + "00" + // version = NFNETLINK_V0 + "0000" + // res_id + // struct nlattr + "3400" + // nla_len = 52 + "0180" + // nla_type = nested CTA_TUPLE_ORIG + // struct nlattr + "1400" + // nla_len = 20 + "0180" + // nla_type = nested CTA_TUPLE_IP + "0800 0100 C0A82BD1" + // nla_type=CTA_IP_V4_SRC, ip=192.168.43.209 + "0800 0200 17D30D1A" + // nla_type=CTA_IP_V4_DST, ip=23.211.13.26 + // struct nlattr + "1C00" + // nla_len = 28 + "0280" + // nla_type = nested CTA_TUPLE_PROTO + "0500 0100 06 000000" + // nla_type=CTA_PROTO_NUM, proto=6 + "0600 0200 AD2D 0000" + // nla_type=CTA_PROTO_SRC_PORT, port=44333 (big endian) + "0600 0300 01BB 0000" + // nla_type=CTA_PROTO_DST_PORT, port=443 (big endian) + // struct nlattr + "0800" + // nla_len = 8 + "0700" + // nla_type = CTA_TIMEOUT + "00069780"; // nla_value = 432000 (big endian) + public static final byte[] CT_V4UPDATE_TCP_BYTES = + HexEncoding.decode(CT_V4UPDATE_TCP_HEX.replaceAll(" ", "").toCharArray(), false); + + private byte[] makeIPv4TimeoutUpdateRequestTcp() throws Exception { + return ConntrackMessage.newIPv4TimeoutUpdateRequest( + OsConstants.IPPROTO_TCP, + (Inet4Address) InetAddress.getByName("192.168.43.209"), 44333, + (Inet4Address) InetAddress.getByName("23.211.13.26"), 443, + 432000); + } + + // Example 2: UDP (100.96.167.146, 37069) -> (216.58.197.10, 443) + public static final String CT_V4UPDATE_UDP_HEX = + // struct nlmsghdr + "50000000" + // length = 80 + "0001" + // type = (1 << 8) | 0 + "0501" + // flags + "01000000" + // seqno = 1 + "00000000" + // pid = 0 + // struct nfgenmsg + "02" + // nfgen_family = AF_INET + "00" + // version = NFNETLINK_V0 + "0000" + // res_id + // struct nlattr + "3400" + // nla_len = 52 + "0180" + // nla_type = nested CTA_TUPLE_ORIG + // struct nlattr + "1400" + // nla_len = 20 + "0180" + // nla_type = nested CTA_TUPLE_IP + "0800 0100 6460A792" + // nla_type=CTA_IP_V4_SRC, ip=100.96.167.146 + "0800 0200 D83AC50A" + // nla_type=CTA_IP_V4_DST, ip=216.58.197.10 + // struct nlattr + "1C00" + // nla_len = 28 + "0280" + // nla_type = nested CTA_TUPLE_PROTO + "0500 0100 11 000000" + // nla_type=CTA_PROTO_NUM, proto=17 + "0600 0200 90CD 0000" + // nla_type=CTA_PROTO_SRC_PORT, port=37069 (big endian) + "0600 0300 01BB 0000" + // nla_type=CTA_PROTO_DST_PORT, port=443 (big endian) + // struct nlattr + "0800" + // nla_len = 8 + "0700" + // nla_type = CTA_TIMEOUT + "000000B4"; // nla_value = 180 (big endian) + public static final byte[] CT_V4UPDATE_UDP_BYTES = + HexEncoding.decode(CT_V4UPDATE_UDP_HEX.replaceAll(" ", "").toCharArray(), false); + + private byte[] makeIPv4TimeoutUpdateRequestUdp() throws Exception { + return ConntrackMessage.newIPv4TimeoutUpdateRequest( + OsConstants.IPPROTO_UDP, + (Inet4Address) InetAddress.getByName("100.96.167.146"), 37069, + (Inet4Address) InetAddress.getByName("216.58.197.10"), 443, + 180); + } + + @Test + public void testConntrackMakeIPv4TcpTimeoutUpdate() throws Exception { + assumeTrue(USING_LE); + + final byte[] tcp = makeIPv4TimeoutUpdateRequestTcp(); + assertArrayEquals(CT_V4UPDATE_TCP_BYTES, tcp); + } + + @Test + public void testConntrackParseIPv4TcpTimeoutUpdate() throws Exception { + assumeTrue(USING_LE); + + final byte[] tcp = makeIPv4TimeoutUpdateRequestTcp(); + final ByteBuffer byteBuffer = ByteBuffer.wrap(tcp); + byteBuffer.order(ByteOrder.nativeOrder()); + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, OsConstants.NETLINK_NETFILTER); + assertNotNull(msg); + assertTrue(msg instanceof ConntrackMessage); + final ConntrackMessage conntrackMessage = (ConntrackMessage) msg; + + final StructNlMsgHdr hdr = conntrackMessage.getHeader(); + assertNotNull(hdr); + assertEquals(80, hdr.nlmsg_len); + assertEquals(makeCtType(IPCTNL_MSG_CT_NEW), hdr.nlmsg_type); + assertEquals((short) (StructNlMsgHdr.NLM_F_REPLACE | StructNlMsgHdr.NLM_F_REQUEST + | StructNlMsgHdr.NLM_F_ACK), hdr.nlmsg_flags); + assertEquals(1, hdr.nlmsg_seq); + assertEquals(0, hdr.nlmsg_pid); + + final StructNfGenMsg nfmsgHdr = conntrackMessage.nfGenMsg; + assertNotNull(nfmsgHdr); + assertEquals((byte) OsConstants.AF_INET, nfmsgHdr.nfgen_family); + assertEquals((byte) StructNfGenMsg.NFNETLINK_V0, nfmsgHdr.version); + assertEquals((short) 0, nfmsgHdr.res_id); + + assertEquals(InetAddress.parseNumericAddress("192.168.43.209"), + conntrackMessage.tupleOrig.srcIp); + assertEquals(InetAddress.parseNumericAddress("23.211.13.26"), + conntrackMessage.tupleOrig.dstIp); + assertEquals((byte) OsConstants.IPPROTO_TCP, conntrackMessage.tupleOrig.protoNum); + assertEquals((short) 44333, conntrackMessage.tupleOrig.srcPort); + assertEquals((short) 443, conntrackMessage.tupleOrig.dstPort); + + assertNull(conntrackMessage.tupleReply); + + assertEquals(0 /* absent */, conntrackMessage.status); + assertEquals(432000, conntrackMessage.timeoutSec); + } + + @Test + public void testConntrackMakeIPv4UdpTimeoutUpdate() throws Exception { + assumeTrue(USING_LE); + + final byte[] udp = makeIPv4TimeoutUpdateRequestUdp(); + assertArrayEquals(CT_V4UPDATE_UDP_BYTES, udp); + } + + @Test + public void testConntrackParseIPv4UdpTimeoutUpdate() throws Exception { + assumeTrue(USING_LE); + + final byte[] udp = makeIPv4TimeoutUpdateRequestUdp(); + final ByteBuffer byteBuffer = ByteBuffer.wrap(udp); + byteBuffer.order(ByteOrder.nativeOrder()); + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, OsConstants.NETLINK_NETFILTER); + assertNotNull(msg); + assertTrue(msg instanceof ConntrackMessage); + final ConntrackMessage conntrackMessage = (ConntrackMessage) msg; + + final StructNlMsgHdr hdr = conntrackMessage.getHeader(); + assertNotNull(hdr); + assertEquals(80, hdr.nlmsg_len); + assertEquals(makeCtType(IPCTNL_MSG_CT_NEW), hdr.nlmsg_type); + assertEquals((short) (StructNlMsgHdr.NLM_F_REPLACE | StructNlMsgHdr.NLM_F_REQUEST + | StructNlMsgHdr.NLM_F_ACK), hdr.nlmsg_flags); + assertEquals(1, hdr.nlmsg_seq); + assertEquals(0, hdr.nlmsg_pid); + + final StructNfGenMsg nfmsgHdr = conntrackMessage.nfGenMsg; + assertNotNull(nfmsgHdr); + assertEquals((byte) OsConstants.AF_INET, nfmsgHdr.nfgen_family); + assertEquals((byte) StructNfGenMsg.NFNETLINK_V0, nfmsgHdr.version); + assertEquals((short) 0, nfmsgHdr.res_id); + + assertEquals(InetAddress.parseNumericAddress("100.96.167.146"), + conntrackMessage.tupleOrig.srcIp); + assertEquals(InetAddress.parseNumericAddress("216.58.197.10"), + conntrackMessage.tupleOrig.dstIp); + assertEquals((byte) OsConstants.IPPROTO_UDP, conntrackMessage.tupleOrig.protoNum); + assertEquals((short) 37069, conntrackMessage.tupleOrig.srcPort); + assertEquals((short) 443, conntrackMessage.tupleOrig.dstPort); + + assertNull(conntrackMessage.tupleReply); + + assertEquals(0 /* absent */, conntrackMessage.status); + assertEquals(180, conntrackMessage.timeoutSec); + } + + public static final String CT_V4NEW_TCP_HEX = + // CHECKSTYLE:OFF IndentationCheck + // struct nlmsghdr + "8C000000" + // length = 140 + "0001" + // type = NFNL_SUBSYS_CTNETLINK (1) << 8 | IPCTNL_MSG_CT_NEW (0) + "0006" + // flags = NLM_F_CREATE (1 << 10) | NLM_F_EXCL (1 << 9) + "00000000" + // seqno = 0 + "00000000" + // pid = 0 + // struct nfgenmsg + "02" + // nfgen_family = AF_INET + "00" + // version = NFNETLINK_V0 + "1234" + // res_id = 0x1234 (big endian) + // struct nlattr + "3400" + // nla_len = 52 + "0180" + // nla_type = nested CTA_TUPLE_ORIG + // struct nlattr + "1400" + // nla_len = 20 + "0180" + // nla_type = nested CTA_TUPLE_IP + "0800 0100 C0A8500C" + // nla_type=CTA_IP_V4_SRC, ip=192.168.80.12 + "0800 0200 8C700874" + // nla_type=CTA_IP_V4_DST, ip=140.112.8.116 + // struct nlattr + "1C00" + // nla_len = 28 + "0280" + // nla_type = nested CTA_TUPLE_PROTO + "0500 0100 06 000000" + // nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6) + "0600 0200 F3F1 0000" + // nla_type=CTA_PROTO_SRC_PORT, port=62449 (big endian) + "0600 0300 01BB 0000" + // nla_type=CTA_PROTO_DST_PORT, port=443 (big endian) + // struct nlattr + "3400" + // nla_len = 52 + "0280" + // nla_type = nested CTA_TUPLE_REPLY + // struct nlattr + "1400" + // nla_len = 20 + "0180" + // nla_type = nested CTA_TUPLE_IP + "0800 0100 8C700874" + // nla_type=CTA_IP_V4_SRC, ip=140.112.8.116 + "0800 0200 6451B301" + // nla_type=CTA_IP_V4_DST, ip=100.81.179.1 + // struct nlattr + "1C00" + // nla_len = 28 + "0280" + // nla_type = nested CTA_TUPLE_PROTO + "0500 0100 06 000000" + // nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6) + "0600 0200 01BB 0000" + // nla_type=CTA_PROTO_SRC_PORT, port=443 (big endian) + "0600 0300 F3F1 0000" + // nla_type=CTA_PROTO_DST_PORT, port=62449 (big endian) + // struct nlattr + "0800" + // nla_len = 8 + "0300" + // nla_type = CTA_STATUS + "00000198" + // nla_value = 0b110011000 (big endian) + // IPS_CONFIRMED (1 << 3) | IPS_SRC_NAT (1 << 4) | + // IPS_SRC_NAT_DONE (1 << 7) | IPS_DST_NAT_DONE (1 << 8) + // struct nlattr + "0800" + // nla_len = 8 + "0700" + // nla_type = CTA_TIMEOUT + "00000078"; // nla_value = 120 (big endian) + // CHECKSTYLE:ON IndentationCheck + public static final byte[] CT_V4NEW_TCP_BYTES = + HexEncoding.decode(CT_V4NEW_TCP_HEX.replaceAll(" ", "").toCharArray(), false); + + @Test + public void testParseCtNew() { + assumeTrue(USING_LE); + + final ByteBuffer byteBuffer = ByteBuffer.wrap(CT_V4NEW_TCP_BYTES); + byteBuffer.order(ByteOrder.nativeOrder()); + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, OsConstants.NETLINK_NETFILTER); + assertNotNull(msg); + assertTrue(msg instanceof ConntrackMessage); + final ConntrackMessage conntrackMessage = (ConntrackMessage) msg; + + final StructNlMsgHdr hdr = conntrackMessage.getHeader(); + assertNotNull(hdr); + assertEquals(140, hdr.nlmsg_len); + assertEquals(makeCtType(IPCTNL_MSG_CT_NEW), hdr.nlmsg_type); + assertEquals((short) (StructNlMsgHdr.NLM_F_CREATE | StructNlMsgHdr.NLM_F_EXCL), + hdr.nlmsg_flags); + assertEquals(0, hdr.nlmsg_seq); + assertEquals(0, hdr.nlmsg_pid); + + final StructNfGenMsg nfmsgHdr = conntrackMessage.nfGenMsg; + assertNotNull(nfmsgHdr); + assertEquals((byte) OsConstants.AF_INET, nfmsgHdr.nfgen_family); + assertEquals((byte) StructNfGenMsg.NFNETLINK_V0, nfmsgHdr.version); + assertEquals((short) 0x1234, nfmsgHdr.res_id); + + assertEquals(InetAddress.parseNumericAddress("192.168.80.12"), + conntrackMessage.tupleOrig.srcIp); + assertEquals(InetAddress.parseNumericAddress("140.112.8.116"), + conntrackMessage.tupleOrig.dstIp); + assertEquals((byte) OsConstants.IPPROTO_TCP, conntrackMessage.tupleOrig.protoNum); + assertEquals((short) 62449, conntrackMessage.tupleOrig.srcPort); + assertEquals((short) 443, conntrackMessage.tupleOrig.dstPort); + + assertEquals(InetAddress.parseNumericAddress("140.112.8.116"), + conntrackMessage.tupleReply.srcIp); + assertEquals(InetAddress.parseNumericAddress("100.81.179.1"), + conntrackMessage.tupleReply.dstIp); + assertEquals((byte) OsConstants.IPPROTO_TCP, conntrackMessage.tupleReply.protoNum); + assertEquals((short) 443, conntrackMessage.tupleReply.srcPort); + assertEquals((short) 62449, conntrackMessage.tupleReply.dstPort); + + assertEquals(0x198, conntrackMessage.status); + assertEquals(120, conntrackMessage.timeoutSec); + } + + @Test + public void testParseTruncation() { + assumeTrue(USING_LE); + + // Expect no crash while parsing the truncated message which has been truncated to every + // length between 0 and its full length - 1. + for (int len = 0; len < CT_V4NEW_TCP_BYTES.length; len++) { + final byte[] truncated = Arrays.copyOfRange(CT_V4NEW_TCP_BYTES, 0, len); + + final ByteBuffer byteBuffer = ByteBuffer.wrap(truncated); + byteBuffer.order(ByteOrder.nativeOrder()); + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, + OsConstants.NETLINK_NETFILTER); + } + } + + @Test + public void testParseTruncationWithInvalidByte() { + assumeTrue(USING_LE); + + // Expect no crash while parsing the message which is truncated by invalid bytes. The + // message has been truncated to every length between 0 and its full length - 1. + for (byte invalid : new byte[]{(byte) 0x00, (byte) 0xff}) { + for (int len = 0; len < CT_V4NEW_TCP_BYTES.length; len++) { + final byte[] truncated = new byte[CT_V4NEW_TCP_BYTES.length]; + Arrays.fill(truncated, (byte) invalid); + System.arraycopy(CT_V4NEW_TCP_BYTES, 0, truncated, 0, len); + + final ByteBuffer byteBuffer = ByteBuffer.wrap(truncated); + byteBuffer.order(ByteOrder.nativeOrder()); + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, + OsConstants.NETLINK_NETFILTER); + } + } + } + + // Malformed conntrack messages. + public static final String CT_MALFORMED_HEX = + // CHECKSTYLE:OFF IndentationCheck + // <-- nlmsghr -->|<-nfgenmsg->|<-- CTA_TUPLE_ORIG -->| + // CTA_TUPLE_ORIG has no nla_value. + "18000000 0001 0006 00000000 00000000 02 00 0000 0400 0180" + // nested CTA_TUPLE_IP has no nla_value. + + "1C000000 0001 0006 00000000 00000000 02 00 0000 0800 0180 0400 0180" + // nested CTA_IP_V4_SRC has no nla_value. + + "20000000 0001 0006 00000000 00000000 02 00 0000 0C00 0180 0800 0180 0400 0100" + // nested CTA_TUPLE_PROTO has no nla_value. + // <-- nlmsghr -->|<-nfgenmsg->|<-- CTA_TUPLE_ORIG + + "30000000 0001 0006 00000000 00000000 02 00 0000 1C00 0180 1400 0180 0800 0100" + // -->| + + "C0A8500C 0800 0200 8C700874 0400 0280"; + // CHECKSTYLE:ON IndentationCheck + public static final byte[] CT_MALFORMED_BYTES = + HexEncoding.decode(CT_MALFORMED_HEX.replaceAll(" ", "").toCharArray(), false); + + @Test + public void testParseMalformation() { + assumeTrue(USING_LE); + + final ByteBuffer byteBuffer = ByteBuffer.wrap(CT_MALFORMED_BYTES); + byteBuffer.order(ByteOrder.nativeOrder()); + + // Expect no crash while parsing the malformed message. + int messageCount = 0; + while (byteBuffer.remaining() > 0) { + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, + OsConstants.NETLINK_NETFILTER); + messageCount++; + } + assertEquals(4, messageCount); + } + + @Test + public void testToString() { + assumeTrue(USING_LE); + + final ByteBuffer byteBuffer = ByteBuffer.wrap(CT_V4NEW_TCP_BYTES); + byteBuffer.order(ByteOrder.nativeOrder()); + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, OsConstants.NETLINK_NETFILTER); + assertNotNull(msg); + assertTrue(msg instanceof ConntrackMessage); + final ConntrackMessage conntrackMessage = (ConntrackMessage) msg; + + // Bug: "nlmsg_flags{1536(NLM_F_MATCH))" is not correct because StructNlMsgHdr + // #stringForNlMsgFlags can't convert all flags (ex: NLM_F_CREATE) and can't distinguish + // the flags which have the same value (ex: NLM_F_MATCH <0x200> and NLM_F_EXCL <0x200>). + // The flags output string should be "NLM_F_CREATE|NLM_F_EXCL" in this case. + // TODO: correct the flag converted string once #stringForNlMsgFlags does. + final String expected = "" + + "ConntrackMessage{" + + "nlmsghdr{StructNlMsgHdr{ nlmsg_len{140}, nlmsg_type{256(IPCTNL_MSG_CT_NEW)}, " + + "nlmsg_flags{1536(NLM_F_MATCH)}, nlmsg_seq{0}, nlmsg_pid{0} }}, " + + "nfgenmsg{NfGenMsg{ nfgen_family{AF_INET}, version{0}, res_id{4660} }}, " + + "tuple_orig{Tuple{IPPROTO_TCP: 192.168.80.12:62449 -> 140.112.8.116:443}}, " + + "tuple_reply{Tuple{IPPROTO_TCP: 140.112.8.116:443 -> 100.81.179.1:62449}}, " + + "status{408(IPS_CONFIRMED|IPS_SRC_NAT|IPS_SRC_NAT_DONE|IPS_DST_NAT_DONE)}, " + + "timeout_sec{120}}"; + assertEquals(expected, conntrackMessage.toString()); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/InetDiagSocketTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/InetDiagSocketTest.java new file mode 100644 index 0000000000000000000000000000000000000000..65e99f82963fca8e042adbb9060fdf602e9ebf17 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/InetDiagSocketTest.java @@ -0,0 +1,692 @@ +/* + * 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. + */ + +package com.android.net.module.util.netlink; + +import static android.os.Process.ROOT_UID; +import static android.os.Process.SHELL_UID; +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; +import static android.system.OsConstants.IPPROTO_TCP; +import static android.system.OsConstants.IPPROTO_UDP; +import static android.system.OsConstants.NETLINK_INET_DIAG; + +import static com.android.net.module.util.netlink.NetlinkConstants.SOCK_DESTROY; +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_ACK; +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP; +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.net.InetAddresses; +import android.util.ArraySet; +import android.util.Range; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import libcore.util.HexEncoding; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.List; +import java.util.Set; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class InetDiagSocketTest { + // ::FFFF:192.0.2.1 + private static final byte[] SRC_V4_MAPPED_V6_ADDRESS_BYTES = { + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xff, (byte) 0xff, + (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01, + }; + // ::FFFF:192.0.2.2 + private static final byte[] DST_V4_MAPPED_V6_ADDRESS_BYTES = { + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xff, (byte) 0xff, + (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x02, + }; + + // Hexadecimal representation of InetDiagReqV2 request. + private static final String INET_DIAG_REQ_V2_UDP_INET4_HEX = + // struct nlmsghdr + "48000000" + // length = 72 + "1400" + // type = SOCK_DIAG_BY_FAMILY + "0103" + // flags = NLM_F_REQUEST | NLM_F_DUMP + "00000000" + // seqno + "00000000" + // pid (0 == kernel) + // struct inet_diag_req_v2 + "02" + // family = AF_INET + "11" + // protcol = IPPROTO_UDP + "00" + // idiag_ext + "00" + // pad + "ffffffff" + // idiag_states + // inet_diag_sockid + "a5de" + // idiag_sport = 42462 + "b971" + // idiag_dport = 47473 + "0a006402000000000000000000000000" + // idiag_src = 10.0.100.2 + "08080808000000000000000000000000" + // idiag_dst = 8.8.8.8 + "00000000" + // idiag_if + "ffffffffffffffff"; // idiag_cookie = INET_DIAG_NOCOOKIE + private static final byte[] INET_DIAG_REQ_V2_UDP_INET4_BYTES = + HexEncoding.decode(INET_DIAG_REQ_V2_UDP_INET4_HEX.toCharArray(), false); + + @Test + public void testInetDiagReqV2UdpInet4() throws Exception { + InetSocketAddress local = new InetSocketAddress(InetAddress.getByName("10.0.100.2"), + 42462); + InetSocketAddress remote = new InetSocketAddress(InetAddress.getByName("8.8.8.8"), + 47473); + final byte[] msg = InetDiagMessage.inetDiagReqV2(IPPROTO_UDP, local, remote, AF_INET, + (short) (NLM_F_REQUEST | NLM_F_DUMP)); + assertArrayEquals(INET_DIAG_REQ_V2_UDP_INET4_BYTES, msg); + } + + // Hexadecimal representation of InetDiagReqV2 request. + private static final String INET_DIAG_REQ_V2_TCP_INET6_HEX = + // struct nlmsghdr + "48000000" + // length = 72 + "1400" + // type = SOCK_DIAG_BY_FAMILY + "0100" + // flags = NLM_F_REQUEST + "00000000" + // seqno + "00000000" + // pid (0 == kernel) + // struct inet_diag_req_v2 + "0a" + // family = AF_INET6 + "06" + // protcol = IPPROTO_TCP + "00" + // idiag_ext + "00" + // pad + "ffffffff" + // idiag_states + // inet_diag_sockid + "a5de" + // idiag_sport = 42462 + "b971" + // idiag_dport = 47473 + "fe8000000000000086c9b2fffe6aed4b" + // idiag_src = fe80::86c9:b2ff:fe6a:ed4b + "08080808000000000000000000000000" + // idiag_dst = 8.8.8.8 + "00000000" + // idiag_if + "ffffffffffffffff"; // idiag_cookie = INET_DIAG_NOCOOKIE + private static final byte[] INET_DIAG_REQ_V2_TCP_INET6_BYTES = + HexEncoding.decode(INET_DIAG_REQ_V2_TCP_INET6_HEX.toCharArray(), false); + + @Test + public void testInetDiagReqV2TcpInet6() throws Exception { + InetSocketAddress local = new InetSocketAddress( + InetAddress.getByName("fe80::86c9:b2ff:fe6a:ed4b"), 42462); + InetSocketAddress remote = new InetSocketAddress(InetAddress.getByName("8.8.8.8"), + 47473); + byte[] msg = InetDiagMessage.inetDiagReqV2(IPPROTO_TCP, local, remote, AF_INET6, + NLM_F_REQUEST); + + assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_BYTES, msg); + } + + // Hexadecimal representation of InetDiagReqV2 request with extension, INET_DIAG_INFO. + private static final String INET_DIAG_REQ_V2_TCP_INET_INET_DIAG_HEX = + // struct nlmsghdr + "48000000" + // length = 72 + "1400" + // type = SOCK_DIAG_BY_FAMILY + "0100" + // flags = NLM_F_REQUEST + "00000000" + // seqno + "00000000" + // pid (0 == kernel) + // struct inet_diag_req_v2 + "02" + // family = AF_INET + "06" + // protcol = IPPROTO_TCP + "02" + // idiag_ext = INET_DIAG_INFO + "00" + // pad + "ffffffff" + // idiag_states + // inet_diag_sockid + "3039" + // idiag_sport = 12345 + "d431" + // idiag_dport = 54321 + "01020304000000000000000000000000" + // idiag_src = 1.2.3.4 + "08080404000000000000000000000000" + // idiag_dst = 8.8.4.4 + "00000000" + // idiag_if + "ffffffffffffffff"; // idiag_cookie = INET_DIAG_NOCOOKIE + + private static final byte[] INET_DIAG_REQ_V2_TCP_INET_INET_DIAG_BYTES = + HexEncoding.decode(INET_DIAG_REQ_V2_TCP_INET_INET_DIAG_HEX.toCharArray(), false); + private static final int TCP_ALL_STATES = 0xffffffff; + @Test + public void testInetDiagReqV2TcpInetWithExt() throws Exception { + InetSocketAddress local = new InetSocketAddress( + InetAddress.getByName("1.2.3.4"), 12345); + InetSocketAddress remote = new InetSocketAddress(InetAddress.getByName("8.8.4.4"), + 54321); + byte[] msg = InetDiagMessage.inetDiagReqV2(IPPROTO_TCP, local, remote, AF_INET, + NLM_F_REQUEST, 0 /* pad */, 2 /* idiagExt */, TCP_ALL_STATES); + + assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET_INET_DIAG_BYTES, msg); + + local = new InetSocketAddress( + InetAddress.getByName("fe80::86c9:b2ff:fe6a:ed4b"), 42462); + remote = new InetSocketAddress(InetAddress.getByName("8.8.8.8"), + 47473); + msg = InetDiagMessage.inetDiagReqV2(IPPROTO_TCP, local, remote, AF_INET6, + NLM_F_REQUEST, 0 /* pad */, 0 /* idiagExt */, TCP_ALL_STATES); + + assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_BYTES, msg); + } + + // Hexadecimal representation of InetDiagReqV2 request with no socket specified. + private static final String INET_DIAG_REQ_V2_TCP_INET6_NO_ID_SPECIFIED_HEX = + // struct nlmsghdr + "48000000" + // length = 72 + "1400" + // type = SOCK_DIAG_BY_FAMILY + "0100" + // flags = NLM_F_REQUEST + "00000000" + // seqno + "00000000" + // pid (0 == kernel) + // struct inet_diag_req_v2 + "0a" + // family = AF_INET6 + "06" + // protcol = IPPROTO_TCP + "00" + // idiag_ext + "00" + // pad + "ffffffff" + // idiag_states + // inet_diag_sockid + "0000" + // idiag_sport + "0000" + // idiag_dport + "00000000000000000000000000000000" + // idiag_src + "00000000000000000000000000000000" + // idiag_dst + "00000000" + // idiag_if + "0000000000000000"; // idiag_cookie + + private static final byte[] INET_DIAG_REQ_V2_TCP_INET6_NO_ID_SPECIFIED_BYTES = + HexEncoding.decode(INET_DIAG_REQ_V2_TCP_INET6_NO_ID_SPECIFIED_HEX.toCharArray(), false); + + @Test + public void testInetDiagReqV2TcpInet6NoIdSpecified() throws Exception { + InetSocketAddress local = new InetSocketAddress( + InetAddress.getByName("fe80::fe6a:ed4b"), 12345); + InetSocketAddress remote = new InetSocketAddress(InetAddress.getByName("8.8.4.4"), + 54321); + // Verify no socket specified if either local or remote socket address is null. + byte[] msgExt = InetDiagMessage.inetDiagReqV2(IPPROTO_TCP, null, null, AF_INET6, + NLM_F_REQUEST, 0 /* pad */, 0 /* idiagExt */, TCP_ALL_STATES); + byte[] msg; + try { + msg = InetDiagMessage.inetDiagReqV2(IPPROTO_TCP, null, remote, AF_INET6, + NLM_F_REQUEST); + fail("Both remote and local should be null, expected UnknownHostException"); + } catch (IllegalArgumentException e) { + } + + try { + msg = InetDiagMessage.inetDiagReqV2(IPPROTO_TCP, local, null, AF_INET6, + NLM_F_REQUEST, 0 /* pad */, 0 /* idiagExt */, TCP_ALL_STATES); + fail("Both remote and local should be null, expected UnknownHostException"); + } catch (IllegalArgumentException e) { + } + + msg = InetDiagMessage.inetDiagReqV2(IPPROTO_TCP, null, null, AF_INET6, + NLM_F_REQUEST, 0 /* pad */, 0 /* idiagExt */, TCP_ALL_STATES); + assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_NO_ID_SPECIFIED_BYTES, msg); + assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_NO_ID_SPECIFIED_BYTES, msgExt); + } + + // Hexadecimal representation of InetDiagReqV2 request with v4-mapped v6 address + private static final String INET_DIAG_REQ_V2_TCP_INET6_V4_MAPPED_HEX = + // struct nlmsghdr + "48000000" + // length = 72 + "1400" + // type = SOCK_DIAG_BY_FAMILY + "0100" + // flags = NLM_F_REQUEST + "00000000" + // seqno + "00000000" + // pid (0 == kernel) + // struct inet_diag_req_v2 + "0a" + // family = AF_INET6 + "06" + // protcol = IPPROTO_TCP + "00" + // idiag_ext + "00" + // pad + "ffffffff" + // idiag_states + // inet_diag_sockid + "a817" + // idiag_sport = 43031 + "960f" + // idiag_dport = 38415 + "00000000000000000000ffffc0000201" + // idiag_src = ::FFFF:192.0.2.1 + "00000000000000000000ffffc0000202" + // idiag_dst = ::FFFF:192.0.2.2 + "00000000" + // idiag_if + "ffffffffffffffff"; // idiag_cookie = INET_DIAG_NOCOOKIE + + private static final byte[] INET_DIAG_REQ_V2_TCP_INET6_V4_MAPPED_BYTES = + HexEncoding.decode(INET_DIAG_REQ_V2_TCP_INET6_V4_MAPPED_HEX.toCharArray(), false); + + @Test + public void testInetDiagReqV2TcpInet6V4Mapped() throws Exception { + final Inet6Address srcAddr = Inet6Address.getByAddress( + null /* host */, SRC_V4_MAPPED_V6_ADDRESS_BYTES, -1 /* scope_id */); + final Inet6Address dstAddr = Inet6Address.getByAddress( + null /* host */, DST_V4_MAPPED_V6_ADDRESS_BYTES, -1 /* scope_id */); + final byte[] msg = InetDiagMessage.inetDiagReqV2( + IPPROTO_TCP, + new InetSocketAddress(srcAddr, 43031), + new InetSocketAddress(dstAddr, 38415), + AF_INET6, + NLM_F_REQUEST); + assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_V4_MAPPED_BYTES, msg); + } + + // Hexadecimal representation of InetDiagReqV2 request with SOCK_DESTROY + private static final String INET_DIAG_REQ_V2_TCP_INET6_DESTROY_HEX = + // struct nlmsghdr + "48000000" + // length = 72 + "1500" + // type = SOCK_DESTROY + "0500" + // flags = NLM_F_REQUEST | NLM_F_ACK + "00000000" + // seqno + "00000000" + // pid (0 == kernel) + // struct inet_diag_req_v2 + "0a" + // family = AF_INET6 + "06" + // protcol = IPPROTO_TCP + "00" + // idiag_ext + "00" + // pad + "ffffffff" + // idiag_states = TCP_ALL_STATES + // inet_diag_sockid + "a817" + // idiag_sport = 43031 + "960f" + // idiag_dport = 38415 + "20010db8000000000000000000000001" + // idiag_src = 2001:db8::1 + "20010db8000000000000000000000002" + // idiag_dst = 2001:db8::2 + "07000000" + // idiag_if = 7 + "5800000000000000"; // idiag_cookie = 88 + + private static final byte[] INET_DIAG_REQ_V2_TCP_INET6_DESTROY_BYTES = + HexEncoding.decode(INET_DIAG_REQ_V2_TCP_INET6_DESTROY_HEX.toCharArray(), false); + + @Test + public void testInetDiagReqV2TcpInet6Destroy() throws Exception { + final StructInetDiagSockId sockId = new StructInetDiagSockId( + new InetSocketAddress(InetAddresses.parseNumericAddress("2001:db8::1"), 43031), + new InetSocketAddress(InetAddresses.parseNumericAddress("2001:db8::2"), 38415), + 7 /* ifIndex */, + 88 /* cookie */); + final byte[] msg = InetDiagMessage.inetDiagReqV2(IPPROTO_TCP, sockId, AF_INET6, + SOCK_DESTROY, (short) (NLM_F_REQUEST | NLM_F_ACK), 0 /* pad */, 0 /* idiagExt */, + TCP_ALL_STATES); + + assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_DESTROY_BYTES, msg); + } + + private void assertNlMsgHdr(StructNlMsgHdr hdr, short type, short flags, int seq, int pid) { + assertNotNull(hdr); + assertEquals(type, hdr.nlmsg_type); + assertEquals(flags, hdr.nlmsg_flags); + assertEquals(seq, hdr.nlmsg_seq); + assertEquals(pid, hdr.nlmsg_pid); + } + + private void assertInetDiagSockId(StructInetDiagSockId sockId, + InetSocketAddress locSocketAddress, InetSocketAddress remSocketAddress, + int ifIndex, long cookie) { + assertEquals(locSocketAddress, sockId.locSocketAddress); + assertEquals(remSocketAddress, sockId.remSocketAddress); + assertEquals(ifIndex, sockId.ifIndex); + assertEquals(cookie, sockId.cookie); + } + + // Hexadecimal representation of InetDiagMessage + private static final String INET_DIAG_MSG_HEX1 = + // struct nlmsghdr + "58000000" + // length = 88 + "1400" + // type = SOCK_DIAG_BY_FAMILY + "0200" + // flags = NLM_F_MULTI + "00000000" + // seqno + "f5220000" + // pid + // struct inet_diag_msg + "0a" + // family = AF_INET6 + "01" + // idiag_state = 1 + "02" + // idiag_timer = 2 + "ff" + // idiag_retrans = 255 + // inet_diag_sockid + "a817" + // idiag_sport = 43031 + "960f" + // idiag_dport = 38415 + "20010db8000000000000000000000001" + // idiag_src = 2001:db8::1 + "20010db8000000000000000000000002" + // idiag_dst = 2001:db8::2 + "07000000" + // idiag_if = 7 + "5800000000000000" + // idiag_cookie = 88 + "04000000" + // idiag_expires = 4 + "05000000" + // idiag_rqueue = 5 + "06000000" + // idiag_wqueue = 6 + "a3270000" + // idiag_uid = 10147 + "a57e19f0"; // idiag_inode = 4028202661 + + private void assertInetDiagMsg1(final NetlinkMessage msg) { + assertNotNull(msg); + + assertTrue(msg instanceof InetDiagMessage); + final InetDiagMessage inetDiagMsg = (InetDiagMessage) msg; + + assertNlMsgHdr(inetDiagMsg.getHeader(), + NetlinkConstants.SOCK_DIAG_BY_FAMILY, + StructNlMsgHdr.NLM_F_MULTI, + 0 /* seq */, + 8949 /* pid */); + + assertEquals(AF_INET6, inetDiagMsg.inetDiagMsg.idiag_family); + assertEquals(1, inetDiagMsg.inetDiagMsg.idiag_state); + assertEquals(2, inetDiagMsg.inetDiagMsg.idiag_timer); + assertEquals(255, inetDiagMsg.inetDiagMsg.idiag_retrans); + assertInetDiagSockId(inetDiagMsg.inetDiagMsg.id, + new InetSocketAddress(InetAddresses.parseNumericAddress("2001:db8::1"), 43031), + new InetSocketAddress(InetAddresses.parseNumericAddress("2001:db8::2"), 38415), + 7 /* ifIndex */, + 88 /* cookie */); + assertEquals(4, inetDiagMsg.inetDiagMsg.idiag_expires); + assertEquals(5, inetDiagMsg.inetDiagMsg.idiag_rqueue); + assertEquals(6, inetDiagMsg.inetDiagMsg.idiag_wqueue); + assertEquals(10147, inetDiagMsg.inetDiagMsg.idiag_uid); + assertEquals(4028202661L, inetDiagMsg.inetDiagMsg.idiag_inode); + } + + // Hexadecimal representation of InetDiagMessage + private static final String INET_DIAG_MSG_HEX2 = + // struct nlmsghdr + "58000000" + // length = 88 + "1400" + // type = SOCK_DIAG_BY_FAMILY + "0200" + // flags = NLM_F_MULTI + "00000000" + // seqno + "f5220000" + // pid + // struct inet_diag_msg + "0a" + // family = AF_INET6 + "02" + // idiag_state = 2 + "10" + // idiag_timer = 16 + "20" + // idiag_retrans = 32 + // inet_diag_sockid + "a845" + // idiag_sport = 43077 + "01bb" + // idiag_dport = 443 + "20010db8000000000000000000000003" + // idiag_src = 2001:db8::3 + "20010db8000000000000000000000004" + // idiag_dst = 2001:db8::4 + "08000000" + // idiag_if = 8 + "6300000000000000" + // idiag_cookie = 99 + "30000000" + // idiag_expires = 48 + "40000000" + // idiag_rqueue = 64 + "50000000" + // idiag_wqueue = 80 + "39300000" + // idiag_uid = 12345 + "851a0000"; // idiag_inode = 6789 + + private void assertInetDiagMsg2(final NetlinkMessage msg) { + assertNotNull(msg); + + assertTrue(msg instanceof InetDiagMessage); + final InetDiagMessage inetDiagMsg = (InetDiagMessage) msg; + + assertNlMsgHdr(inetDiagMsg.getHeader(), + NetlinkConstants.SOCK_DIAG_BY_FAMILY, + StructNlMsgHdr.NLM_F_MULTI, + 0 /* seq */, + 8949 /* pid */); + + assertEquals(AF_INET6, inetDiagMsg.inetDiagMsg.idiag_family); + assertEquals(2, inetDiagMsg.inetDiagMsg.idiag_state); + assertEquals(16, inetDiagMsg.inetDiagMsg.idiag_timer); + assertEquals(32, inetDiagMsg.inetDiagMsg.idiag_retrans); + assertInetDiagSockId(inetDiagMsg.inetDiagMsg.id, + new InetSocketAddress(InetAddresses.parseNumericAddress("2001:db8::3"), 43077), + new InetSocketAddress(InetAddresses.parseNumericAddress("2001:db8::4"), 443), + 8 /* ifIndex */, + 99 /* cookie */); + assertEquals(48, inetDiagMsg.inetDiagMsg.idiag_expires); + assertEquals(64, inetDiagMsg.inetDiagMsg.idiag_rqueue); + assertEquals(80, inetDiagMsg.inetDiagMsg.idiag_wqueue); + assertEquals(12345, inetDiagMsg.inetDiagMsg.idiag_uid); + assertEquals(6789, inetDiagMsg.inetDiagMsg.idiag_inode); + } + + private static final byte[] INET_DIAG_MSG_BYTES = + HexEncoding.decode(INET_DIAG_MSG_HEX1.toCharArray(), false); + + @Test + public void testParseInetDiagResponse() throws Exception { + final ByteBuffer byteBuffer = ByteBuffer.wrap(INET_DIAG_MSG_BYTES); + byteBuffer.order(ByteOrder.nativeOrder()); + assertInetDiagMsg1(NetlinkMessage.parse(byteBuffer, NETLINK_INET_DIAG)); + } + + + private static final byte[] INET_DIAG_MSG_BYTES_MULTIPLE = + HexEncoding.decode((INET_DIAG_MSG_HEX1 + INET_DIAG_MSG_HEX2).toCharArray(), false); + + @Test + public void testParseInetDiagResponseMultiple() { + final ByteBuffer byteBuffer = ByteBuffer.wrap(INET_DIAG_MSG_BYTES_MULTIPLE); + byteBuffer.order(ByteOrder.nativeOrder()); + assertInetDiagMsg1(NetlinkMessage.parse(byteBuffer, NETLINK_INET_DIAG)); + assertInetDiagMsg2(NetlinkMessage.parse(byteBuffer, NETLINK_INET_DIAG)); + } + + private static final String INET_DIAG_SOCK_ID_V4_MAPPED_V6_HEX = + "a845" + // idiag_sport = 43077 + "01bb" + // idiag_dport = 443 + "00000000000000000000ffffc0000201" + // idiag_src = ::FFFF:192.0.2.1 + "00000000000000000000ffffc0000202" + // idiag_dst = ::FFFF:192.0.2.2 + "08000000" + // idiag_if = 8 + "6300000000000000"; // idiag_cookie = 99 + + private static final byte[] INET_DIAG_SOCK_ID_V4_MAPPED_V6_BYTES = + HexEncoding.decode(INET_DIAG_SOCK_ID_V4_MAPPED_V6_HEX.toCharArray(), false); + + @Test + public void testParseAndPackInetDiagSockIdV4MappedV6() { + final ByteBuffer parseByteBuffer = ByteBuffer.wrap(INET_DIAG_SOCK_ID_V4_MAPPED_V6_BYTES); + parseByteBuffer.order(ByteOrder.nativeOrder()); + final StructInetDiagSockId diagSockId = + StructInetDiagSockId.parse(parseByteBuffer, (short) AF_INET6); + assertNotNull(diagSockId); + + final ByteBuffer packByteBuffer = + ByteBuffer.allocate(INET_DIAG_SOCK_ID_V4_MAPPED_V6_BYTES.length); + diagSockId.pack(packByteBuffer); + + // Move position to the head since ByteBuffer#equals compares the values from the current + // position. + parseByteBuffer.position(0); + packByteBuffer.position(0); + assertEquals(parseByteBuffer, packByteBuffer); + } + + // Hexadecimal representation of InetDiagMessage with v4-mapped v6 address + private static final String INET_DIAG_MSG_V4_MAPPED_V6_HEX = + // struct nlmsghdr + "58000000" + // length = 88 + "1400" + // type = SOCK_DIAG_BY_FAMILY + "0200" + // flags = NLM_F_MULTI + "00000000" + // seqno + "f5220000" + // pid + // struct inet_diag_msg + "0a" + // family = AF_INET6 + "01" + // idiag_state = 1 + "02" + // idiag_timer = 2 + "03" + // idiag_retrans = 3 + // inet_diag_sockid + "a817" + // idiag_sport = 43031 + "960f" + // idiag_dport = 38415 + "00000000000000000000ffffc0000201" + // idiag_src = ::FFFF:192.0.2.1 + "00000000000000000000ffffc0000202" + // idiag_dst = ::FFFF:192.0.2.2 + "07000000" + // idiag_if = 7 + "5800000000000000" + // idiag_cookie = 88 + "04000000" + // idiag_expires = 4 + "05000000" + // idiag_rqueue = 5 + "06000000" + // idiag_wqueue = 6 + "a3270000" + // idiag_uid = 10147 + "A57E1900"; // idiag_inode = 1670821 + + private static final byte[] INET_DIAG_MSG_V4_MAPPED_V6_BYTES = + HexEncoding.decode(INET_DIAG_MSG_V4_MAPPED_V6_HEX.toCharArray(), false); + + @Test + public void testParseInetDiagResponseV4MappedV6() throws Exception { + final ByteBuffer byteBuffer = ByteBuffer.wrap(INET_DIAG_MSG_V4_MAPPED_V6_BYTES); + byteBuffer.order(ByteOrder.nativeOrder()); + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_INET_DIAG); + + assertNotNull(msg); + assertTrue(msg instanceof InetDiagMessage); + final InetDiagMessage inetDiagMsg = (InetDiagMessage) msg; + final Inet6Address srcAddr = Inet6Address.getByAddress( + null /* host */, SRC_V4_MAPPED_V6_ADDRESS_BYTES, -1 /* scope_id */); + final Inet6Address dstAddr = Inet6Address.getByAddress( + null /* host */, DST_V4_MAPPED_V6_ADDRESS_BYTES, -1 /* scope_id */); + assertInetDiagSockId(inetDiagMsg.inetDiagMsg.id, + new InetSocketAddress(srcAddr, 43031), + new InetSocketAddress(dstAddr, 38415), + 7 /* ifIndex */, + 88 /* cookie */); + } + + private void doTestIsLoopback(InetAddress srcAddr, InetAddress dstAddr, boolean expected) { + final InetDiagMessage inetDiagMsg = new InetDiagMessage(new StructNlMsgHdr()); + inetDiagMsg.inetDiagMsg.id = new StructInetDiagSockId( + new InetSocketAddress(srcAddr, 43031), + new InetSocketAddress(dstAddr, 38415) + ); + + assertEquals(expected, InetDiagMessage.isLoopback(inetDiagMsg)); + } + + @Test + public void testIsLoopback() { + doTestIsLoopback( + InetAddresses.parseNumericAddress("127.0.0.1"), + InetAddresses.parseNumericAddress("192.0.2.1"), + true + ); + doTestIsLoopback( + InetAddresses.parseNumericAddress("192.0.2.1"), + InetAddresses.parseNumericAddress("127.7.7.7"), + true + ); + doTestIsLoopback( + InetAddresses.parseNumericAddress("::1"), + InetAddresses.parseNumericAddress("::1"), + true + ); + doTestIsLoopback( + InetAddresses.parseNumericAddress("::1"), + InetAddresses.parseNumericAddress("2001:db8::1"), + true + ); + } + + @Test + public void testIsLoopbackSameSrcDstAddress() { + doTestIsLoopback( + InetAddresses.parseNumericAddress("192.0.2.1"), + InetAddresses.parseNumericAddress("192.0.2.1"), + true + ); + doTestIsLoopback( + InetAddresses.parseNumericAddress("2001:db8::1"), + InetAddresses.parseNumericAddress("2001:db8::1"), + true + ); + } + + @Test + public void testIsLoopbackNonLoopbackSocket() { + doTestIsLoopback( + InetAddresses.parseNumericAddress("192.0.2.1"), + InetAddresses.parseNumericAddress("192.0.2.2"), + false + ); + doTestIsLoopback( + InetAddresses.parseNumericAddress("2001:db8::1"), + InetAddresses.parseNumericAddress("2001:db8::2"), + false + ); + } + + @Test + public void testIsLoopbackV4MappedV6() throws UnknownHostException { + // ::FFFF:127.1.2.3 + final byte[] addrLoopbackByte = { + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xff, (byte) 0xff, + (byte) 0x7f, (byte) 0x01, (byte) 0x02, (byte) 0x03, + }; + // ::FFFF:192.0.2.1 + final byte[] addrNonLoopbackByte1 = { + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xff, (byte) 0xff, + (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01, + }; + // ::FFFF:192.0.2.2 + final byte[] addrNonLoopbackByte2 = { + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xff, (byte) 0xff, + (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x02, + }; + + final Inet6Address addrLoopback = Inet6Address.getByAddress(null, addrLoopbackByte, -1); + final Inet6Address addrNonLoopback1 = + Inet6Address.getByAddress(null, addrNonLoopbackByte1, -1); + final Inet6Address addrNonLoopback2 = + Inet6Address.getByAddress(null, addrNonLoopbackByte2, -1); + + doTestIsLoopback(addrLoopback, addrNonLoopback1, true); + doTestIsLoopback(addrNonLoopback1, addrNonLoopback2, false); + doTestIsLoopback(addrNonLoopback1, addrNonLoopback1, true); + } + + private void doTestContainsUid(final int uid, final Set<Range<Integer>> ranges, + final boolean expected) { + final InetDiagMessage inetDiagMsg = new InetDiagMessage(new StructNlMsgHdr()); + inetDiagMsg.inetDiagMsg.idiag_uid = uid; + assertEquals(expected, InetDiagMessage.containsUid(inetDiagMsg, ranges)); + } + + @Test + public void testContainsUid() { + doTestContainsUid(77 /* uid */, + new ArraySet<>(List.of(new Range<>(0, 100))), + true /* expected */); + doTestContainsUid(77 /* uid */, + new ArraySet<>(List.of(new Range<>(77, 77), new Range<>(100, 200))), + true /* expected */); + + doTestContainsUid(77 /* uid */, + new ArraySet<>(List.of(new Range<>(100, 200))), + false /* expected */); + doTestContainsUid(77 /* uid */, + new ArraySet<>(List.of(new Range<>(0, 76), new Range<>(78, 100))), + false /* expected */); + } + + private void doTestIsAdbSocket(final int uid, final boolean expected) { + final InetDiagMessage inetDiagMsg = new InetDiagMessage(new StructNlMsgHdr()); + inetDiagMsg.inetDiagMsg.idiag_uid = uid; + inetDiagMsg.inetDiagMsg.id = new StructInetDiagSockId( + new InetSocketAddress(InetAddresses.parseNumericAddress("2001:db8::1"), 38417), + new InetSocketAddress(InetAddresses.parseNumericAddress("2001:db8::2"), 38415) + ); + assertEquals(expected, InetDiagMessage.isAdbSocket(inetDiagMsg)); + } + + @Test + public void testIsAdbSocket() { + final int appUid = 10108; + doTestIsAdbSocket(SHELL_UID, true /* expected */); + doTestIsAdbSocket(ROOT_UID, false /* expected */); + doTestIsAdbSocket(appUid, false /* expected */); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NduseroptMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NduseroptMessageTest.java new file mode 100644 index 0000000000000000000000000000000000000000..4fc5ec2e2b9b26737760e88ecda8f59a1aca52e7 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NduseroptMessageTest.java @@ -0,0 +1,295 @@ +/* + * 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. + */ + +package com.android.net.module.util.netlink; + +import static android.net.InetAddresses.parseNumericAddress; +import static android.system.OsConstants.AF_INET6; +import static android.system.OsConstants.NETLINK_ROUTE; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.net.InetAddresses; +import android.net.IpPrefix; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import libcore.util.HexEncoding; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet6Address; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NduseroptMessageTest { + + private static final byte ICMP_TYPE_RA = (byte) 134; + + private static final int IFINDEX1 = 15715755; + private static final int IFINDEX2 = 1431655765; + + // IPv6, 0 bytes of options, interface index 15715755, type 134 (RA), code 0, padding. + private static final String HDR_EMPTY = "0a00" + "0000" + "abcdef00" + "8600000000000000"; + + // IPv6, 16 bytes of options, interface index 1431655765, type 134 (RA), code 0, padding. + private static final String HDR_16BYTE = "0a00" + "1000" + "55555555" + "8600000000000000"; + + // IPv6, 32 bytes of options, interface index 1431655765, type 134 (RA), code 0, padding. + private static final String HDR_32BYTE = "0a00" + "2000" + "55555555" + "8600000000000000"; + + // PREF64 option, 2001:db8:3:4:5:6::/96, lifetime=10064 + private static final String OPT_PREF64 = "2602" + "2750" + "20010db80003000400050006"; + + // Length 20, NDUSEROPT_SRCADDR, fe80:2:3:4:5:6:7:8 + private static final String NLA_SRCADDR = "1400" + "0100" + "fe800002000300040005000600070008"; + + private static final InetAddress SADDR1 = parseNumericAddress("fe80:2:3:4:5:6:7:8%" + IFINDEX1); + private static final InetAddress SADDR2 = parseNumericAddress("fe80:2:3:4:5:6:7:8%" + IFINDEX2); + + private static final String MSG_EMPTY = HDR_EMPTY + NLA_SRCADDR; + private static final String MSG_PREF64 = HDR_16BYTE + OPT_PREF64 + NLA_SRCADDR; + + @Test + public void testParsing() { + NduseroptMessage msg = parseNduseroptMessage(toBuffer(MSG_EMPTY)); + assertMatches(AF_INET6, 0, IFINDEX1, ICMP_TYPE_RA, (byte) 0, SADDR1, msg); + assertNull(msg.option); + + msg = parseNduseroptMessage(toBuffer(MSG_PREF64)); + assertMatches(AF_INET6, 16, IFINDEX2, ICMP_TYPE_RA, (byte) 0, SADDR2, msg); + assertPref64Option("2001:db8:3:4:5:6::/96", msg.option); + } + + @Test + public void testParseWithinNetlinkMessage() throws Exception { + // A NduseroptMessage inside a netlink message. Ensure that it parses the same way both by + // parsing the netlink message via NetlinkMessage.parse() and by parsing the option itself + // with NduseroptMessage.parse(). + final String hexBytes = + "44000000440000000000000000000000" // len=68, RTM_NEWNDUSEROPT + + "0A0010001E0000008600000000000000" // IPv6, opt_bytes=16, ifindex=30, RA + + "260202580064FF9B0000000000000000" // pref64, prefix=64:ff9b::/96, 600 + + "14000100FE800000000000000250B6FFFEB7C499"; // srcaddr=fe80::250:b6ff:feb7:c499 + + ByteBuffer buf = toBuffer(hexBytes); + assertEquals(68, buf.limit()); + buf.order(ByteOrder.nativeOrder()); + + NetlinkMessage nlMsg = NetlinkMessage.parse(buf, NETLINK_ROUTE); + assertNotNull(nlMsg); + assertTrue(nlMsg instanceof NduseroptMessage); + + NduseroptMessage msg = (NduseroptMessage) nlMsg; + InetAddress srcaddr = InetAddress.getByName("fe80::250:b6ff:feb7:c499%30"); + assertMatches(AF_INET6, 16, 30, ICMP_TYPE_RA, (byte) 0, srcaddr, msg); + assertPref64Option("64:ff9b::/96", msg.option); + + final String hexBytesWithoutHeader = hexBytes.substring(StructNlMsgHdr.STRUCT_SIZE * 2); + ByteBuffer bufWithoutHeader = toBuffer(hexBytesWithoutHeader); + assertEquals(52, bufWithoutHeader.limit()); + msg = parseNduseroptMessage(bufWithoutHeader); + assertMatches(AF_INET6, 16, 30, ICMP_TYPE_RA, (byte) 0, srcaddr, msg); + assertPref64Option("64:ff9b::/96", msg.option); + } + + @Test + public void testParseRdnssOptionWithinNetlinkMessage() throws Exception { + final String hexBytes = + "4C000000440000000000000000000000" + + "0A0018001E0000008600000000000000" + + "1903000000001770FD123456789000000000000000000001" // RDNSS option + + "14000100FE800000000000000250B6FFFEB7C499"; + + ByteBuffer buf = toBuffer(hexBytes); + assertEquals(76, buf.limit()); + buf.order(ByteOrder.nativeOrder()); + + NetlinkMessage nlMsg = NetlinkMessage.parse(buf, NETLINK_ROUTE); + assertNotNull(nlMsg); + assertTrue(nlMsg instanceof NduseroptMessage); + + NduseroptMessage msg = (NduseroptMessage) nlMsg; + InetAddress srcaddr = InetAddress.getByName("fe80::250:b6ff:feb7:c499%30"); + assertMatches(AF_INET6, 24, 30, ICMP_TYPE_RA, (byte) 0, srcaddr, msg); + assertRdnssOption(msg.option, 6000 /* lifetime */, + (Inet6Address) InetAddresses.parseNumericAddress("fd12:3456:7890::1")); + } + + @Test + public void testParseTruncatedRdnssOptionWithinNetlinkMessage() throws Exception { + final String truncatedHexBytes = + "38000000440000000000000000000000" + + "0A0018001E0000008600000000000000" + + "1903000000001770FD123456789000000000000000000001"; // RDNSS option + + ByteBuffer buf = toBuffer(truncatedHexBytes); + buf.order(ByteOrder.nativeOrder()); + NetlinkMessage nlMsg = NetlinkMessage.parse(buf, NETLINK_ROUTE); + assertNull(nlMsg); + } + + @Test + public void testParseUnknownOptionWithinNetlinkMessage() throws Exception { + final String hexBytes = + "4C000000440000000000000000000000" + + "0A0018001E0000008600000000000000" + + "310300000000177006676F6F676C652E03636F6D00000000" // DNSSL option: "google.com" + + "14000100FE800000000000000250B6FFFEB7C499"; + + ByteBuffer buf = toBuffer(hexBytes); + assertEquals(76, buf.limit()); + buf.order(ByteOrder.nativeOrder()); + + NetlinkMessage nlMsg = NetlinkMessage.parse(buf, NETLINK_ROUTE); + assertNotNull(nlMsg); + assertTrue(nlMsg instanceof NduseroptMessage); + + NduseroptMessage msg = (NduseroptMessage) nlMsg; + InetAddress srcaddr = InetAddress.getByName("fe80::250:b6ff:feb7:c499%30"); + assertMatches(AF_INET6, 24, 30, ICMP_TYPE_RA, (byte) 0, srcaddr, msg); + assertEquals(NdOption.UNKNOWN, msg.option); + } + + @Test + public void testUnknownOption() { + ByteBuffer buf = toBuffer(MSG_PREF64); + // Replace the PREF64 option type (38) with an unknown option number. + final int optionStart = NduseroptMessage.STRUCT_SIZE; + assertEquals(38, buf.get(optionStart)); + buf.put(optionStart, (byte) 42); + + NduseroptMessage msg = parseNduseroptMessage(buf); + assertMatches(AF_INET6, 16, IFINDEX2, ICMP_TYPE_RA, (byte) 0, SADDR2, msg); + assertEquals(NdOption.UNKNOWN, msg.option); + + buf.flip(); + assertEquals(42, buf.get(optionStart)); + buf.put(optionStart, (byte) 38); + + msg = parseNduseroptMessage(buf); + assertMatches(AF_INET6, 16, IFINDEX2, ICMP_TYPE_RA, (byte) 0, SADDR2, msg); + assertPref64Option("2001:db8:3:4:5:6::/96", msg.option); + } + + @Test + public void testZeroLengthOption() { + // Make sure an unknown option with a 0-byte length is ignored and parsing continues with + // the address, which comes after it. + final String hexString = HDR_16BYTE + "00000000000000000000000000000000" + NLA_SRCADDR; + ByteBuffer buf = toBuffer(hexString); + assertEquals(52, buf.limit()); + NduseroptMessage msg = parseNduseroptMessage(buf); + assertMatches(AF_INET6, 16, IFINDEX2, ICMP_TYPE_RA, (byte) 0, SADDR2, msg); + assertNull(msg.option); + } + + @Test + public void testTooLongOption() { + // Make sure that if an option's length is too long, it's ignored and parsing continues with + // the address, which comes after it. + final String hexString = HDR_16BYTE + "26030000000000000000000000000000" + NLA_SRCADDR; + ByteBuffer buf = toBuffer(hexString); + assertEquals(52, buf.limit()); + NduseroptMessage msg = parseNduseroptMessage(buf); + assertMatches(AF_INET6, 16, IFINDEX2, ICMP_TYPE_RA, (byte) 0, SADDR2, msg); + assertNull(msg.option); + } + + @Test + public void testOptionsTooLong() { + // Header claims 32 bytes of options. Buffer ends before options end. + String hexString = HDR_32BYTE + OPT_PREF64; + ByteBuffer buf = toBuffer(hexString); + assertEquals(32, buf.limit()); + assertNull(NduseroptMessage.parse(toBuffer(hexString), NETLINK_ROUTE)); + + // Header claims 32 bytes of options. Buffer ends at end of options with no source address. + hexString = HDR_32BYTE + OPT_PREF64 + OPT_PREF64; + buf = toBuffer(hexString); + assertEquals(48, buf.limit()); + assertNull(NduseroptMessage.parse(toBuffer(hexString), NETLINK_ROUTE)); + } + + @Test + public void testTruncation() { + final int optLen = MSG_PREF64.length() / 2; // 1 byte = 2 hex chars + for (int len = 0; len < optLen; len++) { + ByteBuffer buf = toBuffer(MSG_PREF64.substring(0, len * 2)); + NduseroptMessage msg = parseNduseroptMessage(buf); + if (len < optLen) { + assertNull(msg); + } else { + assertNotNull(msg); + assertPref64Option("2001:db8:3:4:5:6::/96", msg.option); + } + } + } + + @Test + public void testToString() { + NduseroptMessage msg = parseNduseroptMessage(toBuffer(MSG_PREF64)); + assertNotNull(msg); + assertEquals("Nduseroptmsg(10, 16, 1431655765, 134, 0, fe80:2:3:4:5:6:7:8%1431655765)", + msg.toString()); + } + + // Convenience method to parse a NduseroptMessage that's not part of a netlink message. + private NduseroptMessage parseNduseroptMessage(ByteBuffer buf) { + return NduseroptMessage.parse(null, buf); + } + + private ByteBuffer toBuffer(String hexString) { + return ByteBuffer.wrap(HexEncoding.decode(hexString)); + } + + private void assertMatches(int family, int optsLen, int ifindex, byte icmpType, + byte icmpCode, InetAddress srcaddr, NduseroptMessage msg) { + assertNotNull(msg); + assertEquals(family, msg.family); + assertEquals(ifindex, msg.ifindex); + assertEquals(optsLen, msg.opts_len); + assertEquals(icmpType, msg.icmp_type); + assertEquals(icmpCode, msg.icmp_code); + assertEquals(srcaddr, msg.srcaddr); + } + + private void assertPref64Option(String prefix, NdOption opt) { + assertNotNull(opt); + assertTrue(opt instanceof StructNdOptPref64); + StructNdOptPref64 pref64Opt = (StructNdOptPref64) opt; + assertEquals(new IpPrefix(prefix), pref64Opt.prefix); + } + + private void assertRdnssOption(NdOption opt, long lifetime, Inet6Address... servers) { + assertNotNull(opt); + assertTrue(opt instanceof StructNdOptRdnss); + StructNdOptRdnss rdnss = (StructNdOptRdnss) opt; + assertEquals(StructNdOptRdnss.TYPE, rdnss.type); + assertEquals((byte) (servers.length * 2 + 1), rdnss.header.length); + assertEquals(lifetime, rdnss.header.lifetime); + assertArrayEquals(servers, rdnss.servers); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkConstantsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkConstantsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..143e4d4ac58d13d358ed9b87a4a0cac8ebec5d5f --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkConstantsTest.java @@ -0,0 +1,132 @@ +/* + * 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. + */ + +package com.android.net.module.util.netlink; + +import static android.system.OsConstants.NETLINK_INET_DIAG; +import static android.system.OsConstants.NETLINK_NETFILTER; +import static android.system.OsConstants.NETLINK_ROUTE; + +import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_DELETE; +import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_GET; +import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_GET_CTRZERO; +import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_GET_DYING; +import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_GET_STATS; +import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_GET_STATS_CPU; +import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_GET_UNCONFIRMED; +import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_NEW; +import static com.android.net.module.util.netlink.NetlinkConstants.NFNL_SUBSYS_CTNETLINK; +import static com.android.net.module.util.netlink.NetlinkConstants.NLMSG_DONE; +import static com.android.net.module.util.netlink.NetlinkConstants.NLMSG_ERROR; +import static com.android.net.module.util.netlink.NetlinkConstants.NLMSG_NOOP; +import static com.android.net.module.util.netlink.NetlinkConstants.NLMSG_OVERRUN; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_DELADDR; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_DELLINK; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_DELNEIGH; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_DELROUTE; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_DELRULE; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_GETADDR; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_GETLINK; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_GETNEIGH; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_GETROUTE; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_GETRULE; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWADDR; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWLINK; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWNDUSEROPT; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWNEIGH; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWROUTE; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWRULE; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_SETLINK; +import static com.android.net.module.util.netlink.NetlinkConstants.SOCK_DIAG_BY_FAMILY; +import static com.android.net.module.util.netlink.NetlinkConstants.stringForNlMsgType; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NetlinkConstantsTest { + private static final short UNKNOWN_FAMILY = 1234; + + private short makeCtType(short msgType) { + return (short) (NFNL_SUBSYS_CTNETLINK << 8 | (byte) msgType); + } + + @Test + public void testStringForNlMsgType() { + assertEquals("RTM_NEWLINK", stringForNlMsgType(RTM_NEWLINK, NETLINK_ROUTE)); + assertEquals("RTM_DELLINK", stringForNlMsgType(RTM_DELLINK, NETLINK_ROUTE)); + assertEquals("RTM_GETLINK", stringForNlMsgType(RTM_GETLINK, NETLINK_ROUTE)); + assertEquals("RTM_SETLINK", stringForNlMsgType(RTM_SETLINK, NETLINK_ROUTE)); + assertEquals("RTM_NEWADDR", stringForNlMsgType(RTM_NEWADDR, NETLINK_ROUTE)); + assertEquals("RTM_DELADDR", stringForNlMsgType(RTM_DELADDR, NETLINK_ROUTE)); + assertEquals("RTM_GETADDR", stringForNlMsgType(RTM_GETADDR, NETLINK_ROUTE)); + assertEquals("RTM_NEWROUTE", stringForNlMsgType(RTM_NEWROUTE, NETLINK_ROUTE)); + assertEquals("RTM_DELROUTE", stringForNlMsgType(RTM_DELROUTE, NETLINK_ROUTE)); + assertEquals("RTM_GETROUTE", stringForNlMsgType(RTM_GETROUTE, NETLINK_ROUTE)); + assertEquals("RTM_NEWNEIGH", stringForNlMsgType(RTM_NEWNEIGH, NETLINK_ROUTE)); + assertEquals("RTM_DELNEIGH", stringForNlMsgType(RTM_DELNEIGH, NETLINK_ROUTE)); + assertEquals("RTM_GETNEIGH", stringForNlMsgType(RTM_GETNEIGH, NETLINK_ROUTE)); + assertEquals("RTM_NEWRULE", stringForNlMsgType(RTM_NEWRULE, NETLINK_ROUTE)); + assertEquals("RTM_DELRULE", stringForNlMsgType(RTM_DELRULE, NETLINK_ROUTE)); + assertEquals("RTM_GETRULE", stringForNlMsgType(RTM_GETRULE, NETLINK_ROUTE)); + assertEquals("RTM_NEWNDUSEROPT", stringForNlMsgType(RTM_NEWNDUSEROPT, NETLINK_ROUTE)); + + assertEquals("SOCK_DIAG_BY_FAMILY", + stringForNlMsgType(SOCK_DIAG_BY_FAMILY, NETLINK_INET_DIAG)); + + assertEquals("IPCTNL_MSG_CT_NEW", + stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_NEW), NETLINK_NETFILTER)); + assertEquals("IPCTNL_MSG_CT_GET", + stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_GET), NETLINK_NETFILTER)); + assertEquals("IPCTNL_MSG_CT_DELETE", + stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_DELETE), NETLINK_NETFILTER)); + assertEquals("IPCTNL_MSG_CT_GET_CTRZERO", + stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_GET_CTRZERO), NETLINK_NETFILTER)); + assertEquals("IPCTNL_MSG_CT_GET_STATS_CPU", + stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_GET_STATS_CPU), NETLINK_NETFILTER)); + assertEquals("IPCTNL_MSG_CT_GET_STATS", + stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_GET_STATS), NETLINK_NETFILTER)); + assertEquals("IPCTNL_MSG_CT_GET_DYING", + stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_GET_DYING), NETLINK_NETFILTER)); + assertEquals("IPCTNL_MSG_CT_GET_UNCONFIRMED", + stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_GET_UNCONFIRMED), NETLINK_NETFILTER)); + } + + @Test + public void testStringForNlMsgType_ControlMessage() { + for (int family : new int[]{NETLINK_ROUTE, NETLINK_INET_DIAG, NETLINK_NETFILTER}) { + assertEquals("NLMSG_NOOP", stringForNlMsgType(NLMSG_NOOP, family)); + assertEquals("NLMSG_ERROR", stringForNlMsgType(NLMSG_ERROR, family)); + assertEquals("NLMSG_DONE", stringForNlMsgType(NLMSG_DONE, family)); + assertEquals("NLMSG_OVERRUN", stringForNlMsgType(NLMSG_OVERRUN, family)); + } + } + + @Test + public void testStringForNlMsgType_UnknownFamily() { + assertTrue(stringForNlMsgType(RTM_NEWLINK, UNKNOWN_FAMILY).startsWith("unknown")); + assertTrue(stringForNlMsgType(SOCK_DIAG_BY_FAMILY, UNKNOWN_FAMILY).startsWith("unknown")); + assertTrue(stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_NEW), UNKNOWN_FAMILY) + .startsWith("unknown")); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkErrorMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkErrorMessageTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ab7d9cf56c2de6e90adbb650666a36ea1db6961a --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkErrorMessageTest.java @@ -0,0 +1,91 @@ +/* + * 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 com.android.net.module.util.netlink; + +import static android.system.OsConstants.NETLINK_ROUTE; + +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_ACK; +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REPLACE; +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import libcore.util.HexEncoding; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NetlinkErrorMessageTest { + private static final String TAG = "NetlinkErrorMessageTest"; + + // Hexadecimal representation of packet capture. + public static final String NLM_ERROR_OK_HEX = + // struct nlmsghdr + "24000000" + // length = 36 + "0200" + // type = 2 (NLMSG_ERROR) + "0000" + // flags + "26350000" + // seqno + "64100000" + // pid = userspace process + // error integer + "00000000" + // "errno" (0 == OK) + // struct nlmsghdr + "30000000" + // length (48) of original request + "1C00" + // type = 28 (RTM_NEWNEIGH) + "0501" + // flags (NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE) + "26350000" + // seqno + "00000000"; // pid = kernel + public static final byte[] NLM_ERROR_OK = + HexEncoding.decode(NLM_ERROR_OK_HEX.toCharArray(), false); + + @Test + public void testParseNlmErrorOk() { + final ByteBuffer byteBuffer = ByteBuffer.wrap(NLM_ERROR_OK); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + assertNotNull(msg); + assertTrue(msg instanceof NetlinkErrorMessage); + final NetlinkErrorMessage errorMsg = (NetlinkErrorMessage) msg; + + final StructNlMsgHdr hdr = errorMsg.getHeader(); + assertNotNull(hdr); + assertEquals(36, hdr.nlmsg_len); + assertEquals(NetlinkConstants.NLMSG_ERROR, hdr.nlmsg_type); + assertEquals(0, hdr.nlmsg_flags); + assertEquals(13606, hdr.nlmsg_seq); + assertEquals(4196, hdr.nlmsg_pid); + + final StructNlMsgErr err = errorMsg.getNlMsgError(); + assertNotNull(err); + assertEquals(0, err.error); + assertNotNull(err.msg); + assertEquals(48, err.msg.nlmsg_len); + assertEquals(NetlinkConstants.RTM_NEWNEIGH, err.msg.nlmsg_type); + assertEquals((NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE), err.msg.nlmsg_flags); + assertEquals(13606, err.msg.nlmsg_seq); + assertEquals(0, err.msg.nlmsg_pid); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..3a72dd1ba65ef98ec3f3e34fad5cf2f962a7ad1d --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java @@ -0,0 +1,226 @@ +/* + * 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 com.android.net.module.util.netlink; + +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; +import static android.system.OsConstants.AF_UNSPEC; +import static android.system.OsConstants.EACCES; +import static android.system.OsConstants.NETLINK_ROUTE; + +import static com.android.net.module.util.netlink.NetlinkUtils.DEFAULT_RECV_BUFSIZE; +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP; +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeFalse; + +import android.content.Context; +import android.system.ErrnoException; +import android.system.NetlinkSocketAddress; +import android.system.Os; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.android.modules.utils.build.SdkLevel; +import com.android.net.module.util.Struct; + +import libcore.io.IoUtils; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.FileDescriptor; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.file.Files; +import java.nio.file.Paths; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NetlinkUtilsTest { + private static final String TAG = "NetlinkUtilsTest"; + private static final int TEST_SEQNO = 5; + private static final int TEST_TIMEOUT_MS = 500; + + @Test + public void testGetNeighborsQuery() throws Exception { + final FileDescriptor fd = NetlinkUtils.netlinkSocketForProto(NETLINK_ROUTE); + assertNotNull(fd); + + NetlinkUtils.connectSocketToNetlink(fd); + + final NetlinkSocketAddress localAddr = (NetlinkSocketAddress) Os.getsockname(fd); + assertNotNull(localAddr); + assertEquals(0, localAddr.getGroupsMask()); + assertTrue(0 != localAddr.getPortId()); + + final byte[] req = RtNetlinkNeighborMessage.newGetNeighborsRequest(TEST_SEQNO); + assertNotNull(req); + + final Context ctx = InstrumentationRegistry.getInstrumentation().getContext(); + final int targetSdk = + ctx.getPackageManager() + .getApplicationInfo(ctx.getPackageName(), 0) + .targetSdkVersion; + + // Apps targeting an SDK version > S are not allowed to send RTM_GETNEIGH{TBL} messages + if (SdkLevel.isAtLeastT() && targetSdk > 31) { + var ctxt = new String(Files.readAllBytes(Paths.get("/proc/thread-self/attr/current"))); + assumeFalse("must not be platform app", ctxt.startsWith("u:r:platform_app:s0:")); + try { + NetlinkUtils.sendMessage(fd, req, 0, req.length, TEST_TIMEOUT_MS); + fail("RTM_GETNEIGH is not allowed for apps targeting SDK > 31 on T+ platforms," + + " target SDK version: " + targetSdk); + } catch (ErrnoException e) { + // Expected + assertEquals(e.errno, EACCES); + return; + } + } + + // Check that apps targeting lower API levels / running on older platforms succeed + assertEquals(req.length, + NetlinkUtils.sendMessage(fd, req, 0, req.length, TEST_TIMEOUT_MS)); + + int neighMessageCount = 0; + int doneMessageCount = 0; + + while (doneMessageCount == 0) { + ByteBuffer response = + NetlinkUtils.recvMessage(fd, DEFAULT_RECV_BUFSIZE, TEST_TIMEOUT_MS); + assertNotNull(response); + assertTrue(StructNlMsgHdr.STRUCT_SIZE <= response.limit()); + assertEquals(0, response.position()); + assertEquals(ByteOrder.nativeOrder(), response.order()); + + // Verify the messages at least appears minimally reasonable. + while (response.remaining() > 0) { + final NetlinkMessage msg = NetlinkMessage.parse(response, NETLINK_ROUTE); + assertNotNull(msg); + final StructNlMsgHdr hdr = msg.getHeader(); + assertNotNull(hdr); + + if (hdr.nlmsg_type == NetlinkConstants.NLMSG_DONE) { + doneMessageCount++; + continue; + } + + assertEquals(NetlinkConstants.RTM_NEWNEIGH, hdr.nlmsg_type); + assertTrue(msg instanceof RtNetlinkNeighborMessage); + assertTrue((hdr.nlmsg_flags & StructNlMsgHdr.NLM_F_MULTI) != 0); + assertEquals(TEST_SEQNO, hdr.nlmsg_seq); + assertEquals(localAddr.getPortId(), hdr.nlmsg_pid); + + neighMessageCount++; + } + } + + assertEquals(1, doneMessageCount); + // TODO: make sure this test passes sanely in airplane mode. + assertTrue(neighMessageCount > 0); + + IoUtils.closeQuietly(fd); + } + + @Test + public void testBasicWorkingGetAddrQuery() throws Exception { + final FileDescriptor fd = NetlinkUtils.netlinkSocketForProto(NETLINK_ROUTE); + assertNotNull(fd); + + NetlinkUtils.connectSocketToNetlink(fd); + + final NetlinkSocketAddress localAddr = (NetlinkSocketAddress) Os.getsockname(fd); + assertNotNull(localAddr); + assertEquals(0, localAddr.getGroupsMask()); + assertTrue(0 != localAddr.getPortId()); + + final int testSeqno = 8; + final byte[] req = newGetAddrRequest(testSeqno); + assertNotNull(req); + + final long timeout = 500; + assertEquals(req.length, NetlinkUtils.sendMessage(fd, req, 0, req.length, timeout)); + + int addrMessageCount = 0; + + while (true) { + ByteBuffer response = NetlinkUtils.recvMessage(fd, DEFAULT_RECV_BUFSIZE, timeout); + assertNotNull(response); + assertTrue(StructNlMsgHdr.STRUCT_SIZE <= response.limit()); + assertEquals(0, response.position()); + assertEquals(ByteOrder.nativeOrder(), response.order()); + + final NetlinkMessage msg = NetlinkMessage.parse(response, NETLINK_ROUTE); + assertNotNull(msg); + final StructNlMsgHdr nlmsghdr = msg.getHeader(); + assertNotNull(nlmsghdr); + + if (nlmsghdr.nlmsg_type == NetlinkConstants.NLMSG_DONE) { + break; + } + + assertEquals(NetlinkConstants.RTM_NEWADDR, nlmsghdr.nlmsg_type); + assertTrue((nlmsghdr.nlmsg_flags & StructNlMsgHdr.NLM_F_MULTI) != 0); + assertEquals(testSeqno, nlmsghdr.nlmsg_seq); + assertEquals(localAddr.getPortId(), nlmsghdr.nlmsg_pid); + assertTrue(msg instanceof RtNetlinkAddressMessage); + addrMessageCount++; + + // From the query response we can see the RTM_NEWADDR messages representing for IPv4 + // and IPv6 loopback address: 127.0.0.1 and ::1. + final StructIfaddrMsg ifaMsg = ((RtNetlinkAddressMessage) msg).getIfaddrHeader(); + final InetAddress ipAddress = ((RtNetlinkAddressMessage) msg).getIpAddress(); + assertTrue( + "Non-IP address family: " + ifaMsg.family, + ifaMsg.family == AF_INET || ifaMsg.family == AF_INET6); + assertTrue(ipAddress.isLoopbackAddress()); + } + + assertTrue(addrMessageCount > 0); + + IoUtils.closeQuietly(fd); + } + + /** A convenience method to create an RTM_GETADDR request message. */ + private static byte[] newGetAddrRequest(int seqNo) { + final int length = StructNlMsgHdr.STRUCT_SIZE + Struct.getSize(StructIfaddrMsg.class); + final byte[] bytes = new byte[length]; + final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + byteBuffer.order(ByteOrder.nativeOrder()); + + final StructNlMsgHdr nlmsghdr = new StructNlMsgHdr(); + nlmsghdr.nlmsg_len = length; + nlmsghdr.nlmsg_type = NetlinkConstants.RTM_GETADDR; + nlmsghdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + nlmsghdr.nlmsg_seq = seqNo; + nlmsghdr.pack(byteBuffer); + + final StructIfaddrMsg addrMsg = new StructIfaddrMsg((byte) AF_UNSPEC /* family */, + (short) 0 /* prefixLen */, (short) 0 /* flags */, (short) 0 /* scope */, + 0 /* index */); + addrMsg.pack(byteBuffer); + + return bytes; + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkAddressMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkAddressMessageTest.java new file mode 100644 index 0000000000000000000000000000000000000000..01126d2f82cd81d69bba486e3df9c01cefe7edd9 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkAddressMessageTest.java @@ -0,0 +1,289 @@ +/* + * 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. + */ + +package com.android.net.module.util.netlink; + +import static android.system.OsConstants.IFA_F_PERMANENT; +import static android.system.OsConstants.NETLINK_ROUTE; +import static android.system.OsConstants.RT_SCOPE_LINK; +import static android.system.OsConstants.RT_SCOPE_UNIVERSE; + +import static com.android.testutils.MiscAsserts.assertThrows; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.net.InetAddresses; +import android.system.OsConstants; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.net.module.util.HexDump; + +import libcore.util.HexEncoding; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet6Address; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class RtNetlinkAddressMessageTest { + private static final Inet6Address TEST_LINK_LOCAL = + (Inet6Address) InetAddresses.parseNumericAddress("FE80::2C41:5CFF:FE09:6665"); + private static final Inet6Address TEST_GLOBAL_ADDRESS = + (Inet6Address) InetAddresses.parseNumericAddress("2001:DB8:1::100"); + + // An example of the full RTM_NEWADDR message. + private static final String RTM_NEWADDR_HEX = + "48000000140000000000000000000000" // struct nlmsghr + + "0A4080FD1E000000" // struct ifaddrmsg + + "14000100FE800000000000002C415CFFFE096665" // IFA_ADDRESS + + "14000600100E0000201C00002A70000045700000" // IFA_CACHEINFO + + "0800080080000000"; // IFA_FLAGS + + private ByteBuffer toByteBuffer(final String hexString) { + return ByteBuffer.wrap(HexDump.hexStringToByteArray(hexString)); + } + + @Test + public void testParseRtmNewAddress() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWADDR_HEX); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + assertNotNull(msg); + assertTrue(msg instanceof RtNetlinkAddressMessage); + final RtNetlinkAddressMessage addrMsg = (RtNetlinkAddressMessage) msg; + + final StructNlMsgHdr hdr = addrMsg.getHeader(); + assertNotNull(hdr); + assertEquals(72, hdr.nlmsg_len); + assertEquals(NetlinkConstants.RTM_NEWADDR, hdr.nlmsg_type); + assertEquals(0, hdr.nlmsg_flags); + assertEquals(0, hdr.nlmsg_seq); + assertEquals(0, hdr.nlmsg_pid); + + final StructIfaddrMsg ifaddrMsgHdr = addrMsg.getIfaddrHeader(); + assertNotNull(ifaddrMsgHdr); + assertEquals((byte) OsConstants.AF_INET6, ifaddrMsgHdr.family); + assertEquals(64, ifaddrMsgHdr.prefixLen); + assertEquals(0x80, ifaddrMsgHdr.flags); + assertEquals(0xFD, ifaddrMsgHdr.scope); + assertEquals(30, ifaddrMsgHdr.index); + + assertEquals((Inet6Address) addrMsg.getIpAddress(), TEST_LINK_LOCAL); + assertEquals(3600L, addrMsg.getIfacacheInfo().preferred); + assertEquals(7200L, addrMsg.getIfacacheInfo().valid); + assertEquals(28714, addrMsg.getIfacacheInfo().cstamp); + assertEquals(28741, addrMsg.getIfacacheInfo().tstamp); + assertEquals(0x80, addrMsg.getFlags()); + } + + private static final String RTM_NEWADDR_PACK_HEX = + "48000000140000000000000000000000" // struct nlmsghr + + "0A4080FD1E000000" // struct ifaddrmsg + + "14000100FE800000000000002C415CFFFE096665" // IFA_ADDRESS + + "14000600FFFFFFFFFFFFFFFF2A7000002A700000" // IFA_CACHEINFO + + "0800080081000000"; // IFA_FLAGS(override ifa_flags) + + @Test + public void testPackRtmNewAddr() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWADDR_PACK_HEX); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + assertNotNull(msg); + assertTrue(msg instanceof RtNetlinkAddressMessage); + final RtNetlinkAddressMessage addrMsg = (RtNetlinkAddressMessage) msg; + + final ByteBuffer packBuffer = ByteBuffer.allocate(72); + packBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + addrMsg.pack(packBuffer); + assertEquals(RTM_NEWADDR_PACK_HEX, HexDump.toHexString(packBuffer.array())); + } + + private static final String RTM_NEWADDR_TRUNCATED_HEX = + "44000000140000000000000000000000" // struct nlmsghr + + "0A4080FD1E000000" // struct ifaddrmsg + + "10000100FE800000000000002C415CFF" // IFA_ADDRESS(truncated) + + "14000600FFFFFFFFFFFFFFFF2A7000002A700000" // IFA_CACHEINFO + + "0800080080000000"; // IFA_FLAGS + + @Test + public void testTruncatedRtmNewAddr() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWADDR_TRUNCATED_HEX); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + // Parsing RTM_NEWADDR with truncated IFA_ADDRESS attribute returns null. + assertNull(msg); + } + + @Test + public void testCreateRtmNewAddressMessage() { + // Hexadecimal representation of our created packet. + final String expectedNewAddressHex = + // struct nlmsghdr + "48000000" + // length = 72 + "1400" + // type = 20 (RTM_NEWADDR) + "0501" + // flags = NLM_F_ACK | NLM_F_REQUEST | NLM_F_REPLACE + "01000000" + // seqno = 1 + "00000000" + // pid = 0 (send to kernel) + // struct IfaddrMsg + "0A" + // family = inet6 + "40" + // prefix len = 64 + "00" + // flags = 0 + "FD" + // scope = RT_SCOPE_LINK + "17000000" + // ifindex = 23 + // struct nlattr: IFA_ADDRESS + "1400" + // len + "0100" + // type + "FE800000000000002C415CFFFE096665" + // IP address = fe80::2C41:5cff:fe09:6665 + // struct nlattr: IFA_CACHEINFO + "1400" + // len + "0600" + // type + "FFFFFFFF" + // preferred = infinite + "FFFFFFFF" + // valid = infinite + "00000000" + // cstamp + "00000000" + // tstamp + // struct nlattr: IFA_FLAGS + "0800" + // len + "0800" + // type + "80000000"; // flags = IFA_F_PERMANENT + final byte[] expectedNewAddress = + HexEncoding.decode(expectedNewAddressHex.toCharArray(), false); + + final byte[] bytes = RtNetlinkAddressMessage.newRtmNewAddressMessage(1 /* seqno */, + TEST_LINK_LOCAL, (short) 64 /* prefix len */, IFA_F_PERMANENT /* flags */, + (byte) RT_SCOPE_LINK /* scope */, 23 /* ifindex */, + (long) 0xFFFFFFFF /* preferred */, (long) 0xFFFFFFFF /* valid */); + assertArrayEquals(expectedNewAddress, bytes); + } + + @Test + public void testCreateRtmDelAddressMessage() { + // Hexadecimal representation of our created packet. + final String expectedDelAddressHex = + // struct nlmsghdr + "2C000000" + // length = 44 + "1500" + // type = 21 (RTM_DELADDR) + "0500" + // flags = NLM_F_ACK | NLM_F_REQUEST + "01000000" + // seqno = 1 + "00000000" + // pid = 0 (send to kernel) + // struct IfaddrMsg + "0A" + // family = inet6 + "40" + // prefix len = 64 + "00" + // flags = 0 + "00" + // scope = RT_SCOPE_UNIVERSE + "3B000000" + // ifindex = 59 + // struct nlattr: IFA_ADDRESS + "1400" + // len + "0100" + // type + "20010DB8000100000000000000000100"; // IP address = 2001:db8:1::100 + final byte[] expectedDelAddress = + HexEncoding.decode(expectedDelAddressHex.toCharArray(), false); + + final byte[] bytes = RtNetlinkAddressMessage.newRtmDelAddressMessage(1 /* seqno */, + TEST_GLOBAL_ADDRESS, (short) 64 /* prefix len */, 59 /* ifindex */); + assertArrayEquals(expectedDelAddress, bytes); + } + + @Test + public void testCreateRtmNewAddressMessage_nullIpAddress() { + assertThrows(NullPointerException.class, + () -> RtNetlinkAddressMessage.newRtmNewAddressMessage(1 /* seqno */, + null /* IP address */, (short) 0 /* prefix len */, + IFA_F_PERMANENT /* flags */, (byte) RT_SCOPE_LINK /* scope */, + 23 /* ifindex */, (long) 0xFFFFFFFF /* preferred */, + (long) 0xFFFFFFFF /* valid */)); + } + + @Test + public void testCreateRtmDelAddressMessage_nullIpAddress() { + assertThrows(NullPointerException.class, + () -> RtNetlinkAddressMessage.newRtmDelAddressMessage(1 /* seqno */, + null /* IP address */, (short) 0 /* prefix len */, 59 /* ifindex */)); + } + + @Test + public void testCreateRtmNewAddressMessage_u32Flags() { + // Hexadecimal representation of our created packet. + final String expectedNewAddressHex = + // struct nlmsghdr + "48000000" + // length = 72 + "1400" + // type = 20 (RTM_NEWADDR) + "0501" + // flags = NLM_F_ACK | NLM_F_REQUEST | NLM_F_REPLACE + "01000000" + // seqno = 1 + "00000000" + // pid = 0 (send to kernel) + // struct IfaddrMsg + "0A" + // family = inet6 + "80" + // prefix len = 128 + "00" + // flags = 0 + "00" + // scope = RT_SCOPE_UNIVERSE + "17000000" + // ifindex = 23 + // struct nlattr: IFA_ADDRESS + "1400" + // len + "0100" + // type + "20010DB8000100000000000000000100" + // IP address = 2001:db8:1::100 + // struct nlattr: IFA_CACHEINFO + "1400" + // len + "0600" + // type + "FFFFFFFF" + // preferred = infinite + "FFFFFFFF" + // valid = infinite + "00000000" + // cstamp + "00000000" + // tstamp + // struct nlattr: IFA_FLAGS + "0800" + // len + "0800" + // type + "00030000"; // flags = IFA_F_MANAGETEMPADDR | IFA_F_NOPREFIXROUTE + final byte[] expectedNewAddress = + HexEncoding.decode(expectedNewAddressHex.toCharArray(), false); + + final byte[] bytes = RtNetlinkAddressMessage.newRtmNewAddressMessage(1 /* seqno */, + TEST_GLOBAL_ADDRESS, (short) 128 /* prefix len */, + (int) 0x300 /* flags: IFA_F_MANAGETEMPADDR | IFA_F_NOPREFIXROUTE */, + (byte) RT_SCOPE_UNIVERSE /* scope */, 23 /* ifindex */, + (long) 0xFFFFFFFF /* preferred */, (long) 0xFFFFFFFF /* valid */); + assertArrayEquals(expectedNewAddress, bytes); + } + + @Test + public void testToString() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWADDR_HEX); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + assertNotNull(msg); + assertTrue(msg instanceof RtNetlinkAddressMessage); + final RtNetlinkAddressMessage addrMsg = (RtNetlinkAddressMessage) msg; + final String expected = "RtNetlinkAddressMessage{ " + + "nlmsghdr{" + + "StructNlMsgHdr{ nlmsg_len{72}, nlmsg_type{20(RTM_NEWADDR)}, nlmsg_flags{0()}, " + + "nlmsg_seq{0}, nlmsg_pid{0} }}, " + + "Ifaddrmsg{" + + "family: 10, prefixLen: 64, flags: 128, scope: 253, index: 30}, " + + "IP Address{fe80::2c41:5cff:fe09:6665}, " + + "IfacacheInfo{" + + "preferred: 3600, valid: 7200, cstamp: 28714, tstamp: 28741}, " + + "Address Flags{00000080} " + + "}"; + assertEquals(expected, addrMsg.toString()); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java new file mode 100644 index 0000000000000000000000000000000000000000..9db63db1b40e2cdd2d93fc60a509d942b6b78a9d --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java @@ -0,0 +1,192 @@ +/* + * 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. + */ + +package com.android.net.module.util.netlink; + +import static android.system.OsConstants.NETLINK_ROUTE; + +import static com.android.net.module.util.NetworkStackConstants.ETHER_MTU; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.net.MacAddress; +import android.system.OsConstants; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.net.module.util.HexDump; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class RtNetlinkLinkMessageTest { + + // An example of the full RTM_NEWLINK message. + private static final String RTM_NEWLINK_HEX = + "64000000100000000000000000000000" // struct nlmsghr + + "000001001E0000000210000000000000" // struct ifinfo + + "0A000300776C616E30000000" // IFLA_IFNAME(wlan0) + + "08000D00B80B0000" // IFLA_PROTINFO + + "0500100002000000" // IFLA_OPERSTATE + + "0500110001000000" // IFLA_LINKMODE + + "08000400DC050000" // IFLA_MTU + + "0A00010092C3E3C9374E0000" // IFLA_ADDRESS + + "0A000200FFFFFFFFFFFF0000"; // IFLA_BROADCAST + + private ByteBuffer toByteBuffer(final String hexString) { + return ByteBuffer.wrap(HexDump.hexStringToByteArray(hexString)); + } + + @Test + public void testParseRtmNewLink() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWLINK_HEX); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + assertNotNull(msg); + assertTrue(msg instanceof RtNetlinkLinkMessage); + final RtNetlinkLinkMessage linkMsg = (RtNetlinkLinkMessage) msg; + + final StructNlMsgHdr hdr = linkMsg.getHeader(); + assertNotNull(hdr); + assertEquals(100, hdr.nlmsg_len); + assertEquals(NetlinkConstants.RTM_NEWLINK, hdr.nlmsg_type); + assertEquals(0, hdr.nlmsg_flags); + assertEquals(0, hdr.nlmsg_seq); + assertEquals(0, hdr.nlmsg_pid); + + final StructIfinfoMsg ifinfomsgHdr = linkMsg.getIfinfoHeader(); + assertNotNull(ifinfomsgHdr); + assertEquals((byte) OsConstants.AF_UNSPEC, ifinfomsgHdr.family); + assertEquals(OsConstants.ARPHRD_ETHER, ifinfomsgHdr.type); + assertEquals(30, ifinfomsgHdr.index); + assertEquals(0, ifinfomsgHdr.change); + + assertEquals(ETHER_MTU, linkMsg.getMtu()); + assertEquals(MacAddress.fromString("92:C3:E3:C9:37:4E"), linkMsg.getHardwareAddress()); + assertTrue(linkMsg.getInterfaceName().equals("wlan0")); + } + + /** + * Example: + * # adb shell ip tunnel add トン0 mode sit local any remote 8.8.8.8 + * # adb shell ip link show | grep トン + * 33: トン0@NONE: <POINTOPOINT,NOARP> mtu 1480 qdisc noop state DOWN mode DEFAULT group + * default qlen 1000 + * + * IFLA_IFNAME attribute: \x0c\x00\x03\x00\xe3\x83\x88\xe3\x83\xb3\x30\x00 + * length: 0x000c + * type: 0x0003 + * value: \xe3\x83\x88\xe3\x83\xb3\x30\x00 + * ト (\xe3\x83\x88) + * ン (\xe3\x83\xb3) + * 0 (\x30) + * null terminated (\x00) + */ + private static final String RTM_NEWLINK_UTF8_HEX = + "34000000100000000000000000000000" // struct nlmsghr + + "000001001E0000000210000000000000" // struct ifinfo + + "08000400DC050000" // IFLA_MTU + + "0A00010092C3E3C9374E0000" // IFLA_ADDRESS + + "0C000300E38388E383B33000"; // IFLA_IFNAME(トン0) + + @Test + public void testParseRtmNewLink_utf8Ifname() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWLINK_UTF8_HEX); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + assertNotNull(msg); + assertTrue(msg instanceof RtNetlinkLinkMessage); + final RtNetlinkLinkMessage linkMsg = (RtNetlinkLinkMessage) msg; + + assertTrue(linkMsg.getInterfaceName().equals("トン0")); + } + + private static final String RTM_NEWLINK_PACK_HEX = + "34000000100000000000000000000000" // struct nlmsghr + + "000001001E0000000210000000000000" // struct ifinfo + + "08000400DC050000" // IFLA_MTU + + "0A00010092C3E3C9374E0000" // IFLA_ADDRESS + + "0A000300776C616E30000000"; // IFLA_IFNAME(wlan0) + + @Test + public void testPackRtmNewLink() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWLINK_PACK_HEX); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + assertNotNull(msg); + assertTrue(msg instanceof RtNetlinkLinkMessage); + final RtNetlinkLinkMessage linkMsg = (RtNetlinkLinkMessage) msg; + + final ByteBuffer packBuffer = ByteBuffer.allocate(64); + packBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + linkMsg.pack(packBuffer); + assertEquals(RTM_NEWLINK_PACK_HEX, HexDump.toHexString(packBuffer.array())); + } + + private static final String RTM_NEWLINK_TRUNCATED_HEX = + "54000000100000000000000000000000" // struct nlmsghr + + "000001001E0000000210000000000000" // struct ifinfo + + "08000D00B80B0000" // IFLA_PROTINFO + + "0500100002000000" // IFLA_OPERSTATE + + "0800010092C3E3C9" // IFLA_ADDRESS(truncated) + + "0500110001000000" // IFLA_LINKMODE + + "0A000300776C616E30000000" // IFLA_IFNAME(wlan0) + + "08000400DC050000"; // IFLA_MTU + + @Test + public void testTruncatedRtmNewLink() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWLINK_TRUNCATED_HEX); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + assertNotNull(msg); + assertTrue(msg instanceof RtNetlinkLinkMessage); + final RtNetlinkLinkMessage linkMsg = (RtNetlinkLinkMessage) msg; + + // Truncated IFLA_ADDRESS attribute doesn't affect parsing other attrs. + assertNull(linkMsg.getHardwareAddress()); + assertEquals(ETHER_MTU, linkMsg.getMtu()); + assertTrue(linkMsg.getInterfaceName().equals("wlan0")); + } + + @Test + public void testToString() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWLINK_HEX); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + assertNotNull(msg); + assertTrue(msg instanceof RtNetlinkLinkMessage); + final RtNetlinkLinkMessage linkMsg = (RtNetlinkLinkMessage) msg; + final String expected = "RtNetlinkLinkMessage{ " + + "nlmsghdr{" + + "StructNlMsgHdr{ nlmsg_len{100}, nlmsg_type{16(RTM_NEWLINK)}, nlmsg_flags{0()}, " + + "nlmsg_seq{0}, nlmsg_pid{0} }}, " + + "Ifinfomsg{" + + "family: 0, type: 1, index: 30, flags: 4098, change: 0}, " + + "Hardware Address{92:c3:e3:c9:37:4e}, " + "MTU{1500}, " + + "Ifname{wlan0} " + + "}"; + assertEquals(expected, linkMsg.toString()); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkNeighborMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkNeighborMessageTest.java new file mode 100644 index 0000000000000000000000000000000000000000..4d8900cdbd96374d0ab927c788b9b443cd4b84b6 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkNeighborMessageTest.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import static android.system.OsConstants.NETLINK_ROUTE; + +import static com.android.net.module.util.netlink.StructNdMsg.NUD_STALE; +import static com.android.testutils.NetlinkTestUtils.makeDelNeighMessage; +import static com.android.testutils.NetlinkTestUtils.makeNewNeighMessage; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import android.net.InetAddresses; +import android.system.OsConstants; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import libcore.util.HexEncoding; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class RtNetlinkNeighborMessageTest { + private static final String TAG = "RtNetlinkNeighborMessageTest"; + + public static final byte[] RTM_DELNEIGH = makeDelNeighMessage( + InetAddresses.parseNumericAddress("192.168.159.254"), NUD_STALE); + + public static final byte[] RTM_NEWNEIGH = makeNewNeighMessage( + InetAddresses.parseNumericAddress("fe80::86c9:b2ff:fe6a:ed4b"), NUD_STALE); + + // An example of the full response from an RTM_GETNEIGH query. + private static final String RTM_GETNEIGH_RESPONSE_HEX = + // <-- struct nlmsghr -->|<-- struct ndmsg -->|<-- struct nlattr: NDA_DST -->|<-- NDA_LLADDR -->|<-- NDA_PROBES -->|<-- NDA_CACHEINFO -->| + "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff020000000000000000000000000001 0a00 0200 333300000001 0000 0800 0400 00000000 1400 0300 a2280000 32110000 32110000 01000000" + + "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff0200000000000000000001ff000001 0a00 0200 3333ff000001 0000 0800 0400 00000000 1400 0300 0d280000 9d100000 9d100000 00000000" + + "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 0400 80 01 1400 0100 20010db800040ca00000000000000001 0a00 0200 84c9b26aed4b 0000 0800 0400 04000000 1400 0300 90100000 90100000 90080000 01000000" + + "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff0200000000000000000001ff47da19 0a00 0200 3333ff47da19 0000 0800 0400 00000000 1400 0300 a1280000 31110000 31110000 01000000" + + "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 14000000 4000 00 05 1400 0100 ff020000000000000000000000000016 0a00 0200 333300000016 0000 0800 0400 00000000 1400 0300 912a0000 21130000 21130000 00000000" + + "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 14000000 4000 00 05 1400 0100 ff0200000000000000000001ffeace3b 0a00 0200 3333ffeace3b 0000 0800 0400 00000000 1400 0300 922a0000 22130000 22130000 00000000" + + "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff0200000000000000000001ff5c2a83 0a00 0200 3333ff5c2a83 0000 0800 0400 00000000 1400 0300 391c0000 c9040000 c9040000 01000000" + + "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 01000000 4000 00 02 1400 0100 00000000000000000000000000000000 0a00 0200 000000000000 0000 0800 0400 00000000 1400 0300 cd180200 5d010200 5d010200 08000000" + + "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff020000000000000000000000000002 0a00 0200 333300000002 0000 0800 0400 00000000 1400 0300 352a0000 c5120000 c5120000 00000000" + + "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff020000000000000000000000000016 0a00 0200 333300000016 0000 0800 0400 00000000 1400 0300 982a0000 28130000 28130000 00000000" + + "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 0800 80 01 1400 0100 fe8000000000000086c9b2fffe6aed4b 0a00 0200 84c9b26aed4b 0000 0800 0400 00000000 1400 0300 23000000 24000000 57000000 13000000" + + "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff0200000000000000000001ffeace3b 0a00 0200 3333ffeace3b 0000 0800 0400 00000000 1400 0300 992a0000 29130000 29130000 01000000" + + "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 14000000 4000 00 05 1400 0100 ff020000000000000000000000000002 0a00 0200 333300000002 0000 0800 0400 00000000 1400 0300 2e2a0000 be120000 be120000 00000000" + + "44000000 1c00 0200 00000000 3e2b0000 02 00 0000 18000000 4000 00 03 0800 0100 00000000 0400 0200 0800 0400 00000000 1400 0300 75280000 05110000 05110000 22000000"; + public static final byte[] RTM_GETNEIGH_RESPONSE = + HexEncoding.decode(RTM_GETNEIGH_RESPONSE_HEX.replaceAll(" ", "").toCharArray(), false); + + @Test + public void testParseRtmDelNeigh() { + final ByteBuffer byteBuffer = ByteBuffer.wrap(RTM_DELNEIGH); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + assertNotNull(msg); + assertTrue(msg instanceof RtNetlinkNeighborMessage); + final RtNetlinkNeighborMessage neighMsg = (RtNetlinkNeighborMessage) msg; + + final StructNlMsgHdr hdr = neighMsg.getHeader(); + assertNotNull(hdr); + assertEquals(76, hdr.nlmsg_len); + assertEquals(NetlinkConstants.RTM_DELNEIGH, hdr.nlmsg_type); + assertEquals(0, hdr.nlmsg_flags); + assertEquals(0, hdr.nlmsg_seq); + assertEquals(0, hdr.nlmsg_pid); + + final StructNdMsg ndmsgHdr = neighMsg.getNdHeader(); + assertNotNull(ndmsgHdr); + assertEquals((byte) OsConstants.AF_INET, ndmsgHdr.ndm_family); + assertEquals(21, ndmsgHdr.ndm_ifindex); + assertEquals(NUD_STALE, ndmsgHdr.ndm_state); + final InetAddress destination = neighMsg.getDestination(); + assertNotNull(destination); + assertEquals(InetAddress.parseNumericAddress("192.168.159.254"), destination); + } + + @Test + public void testParseRtmNewNeigh() { + final ByteBuffer byteBuffer = ByteBuffer.wrap(RTM_NEWNEIGH); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + assertNotNull(msg); + assertTrue(msg instanceof RtNetlinkNeighborMessage); + final RtNetlinkNeighborMessage neighMsg = (RtNetlinkNeighborMessage) msg; + + final StructNlMsgHdr hdr = neighMsg.getHeader(); + assertNotNull(hdr); + assertEquals(88, hdr.nlmsg_len); + assertEquals(NetlinkConstants.RTM_NEWNEIGH, hdr.nlmsg_type); + assertEquals(0, hdr.nlmsg_flags); + assertEquals(0, hdr.nlmsg_seq); + assertEquals(0, hdr.nlmsg_pid); + + final StructNdMsg ndmsgHdr = neighMsg.getNdHeader(); + assertNotNull(ndmsgHdr); + assertEquals((byte) OsConstants.AF_INET6, ndmsgHdr.ndm_family); + assertEquals(21, ndmsgHdr.ndm_ifindex); + assertEquals(NUD_STALE, ndmsgHdr.ndm_state); + final InetAddress destination = neighMsg.getDestination(); + assertNotNull(destination); + assertEquals(InetAddress.parseNumericAddress("fe80::86c9:b2ff:fe6a:ed4b"), destination); + } + + @Test + public void testParseRtmGetNeighResponse() { + final ByteBuffer byteBuffer = ByteBuffer.wrap(RTM_GETNEIGH_RESPONSE); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + + int messageCount = 0; + while (byteBuffer.remaining() > 0) { + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + assertNotNull(msg); + assertTrue(msg instanceof RtNetlinkNeighborMessage); + final RtNetlinkNeighborMessage neighMsg = (RtNetlinkNeighborMessage) msg; + + final StructNlMsgHdr hdr = neighMsg.getHeader(); + assertNotNull(hdr); + assertEquals(NetlinkConstants.RTM_NEWNEIGH, hdr.nlmsg_type); + assertEquals(StructNlMsgHdr.NLM_F_MULTI, hdr.nlmsg_flags); + assertEquals(0, hdr.nlmsg_seq); + assertEquals(11070, hdr.nlmsg_pid); + + final int probes = neighMsg.getProbes(); + assertTrue("Unexpected number of probes. Got " + probes + ", max=5", + probes < 5); + final int ndm_refcnt = neighMsg.getCacheInfo().ndm_refcnt; + assertTrue("nda_cacheinfo has unexpectedly high ndm_refcnt: " + ndm_refcnt, + ndm_refcnt < 0x100); + + messageCount++; + } + // TODO: add more detailed spot checks. + assertEquals(14, messageCount); + } + + @Test + public void testCreateRtmNewNeighMessage() { + final int seqNo = 2635; + final int ifIndex = 14; + final byte[] llAddr = + new byte[] { (byte) 1, (byte) 2, (byte) 3, (byte) 4, (byte) 5, (byte) 6 }; + + // Hexadecimal representation of our created packet. + final String expectedNewNeighHex = + // struct nlmsghdr + "30000000" + // length = 48 + "1c00" + // type = 28 (RTM_NEWNEIGH) + "0501" + // flags (NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE) + "4b0a0000" + // seqno + "00000000" + // pid (0 == kernel) + // struct ndmsg + "02" + // family + "00" + // pad1 + "0000" + // pad2 + "0e000000" + // interface index (14) + "0800" + // NUD state (0x08 == NUD_DELAY) + "00" + // flags + "00" + // type + // struct nlattr: NDA_DST + "0800" + // length = 8 + "0100" + // type (1 == NDA_DST, for neighbor messages) + "7f000001" + // IPv4 address (== 127.0.0.1) + // struct nlattr: NDA_LLADDR + "0a00" + // length = 10 + "0200" + // type (2 == NDA_LLADDR, for neighbor messages) + "010203040506" + // MAC Address (== 01:02:03:04:05:06) + "0000"; // padding, for 4 byte alignment + final byte[] expectedNewNeigh = + HexEncoding.decode(expectedNewNeighHex.toCharArray(), false); + + final byte[] bytes = RtNetlinkNeighborMessage.newNewNeighborMessage( + seqNo, Inet4Address.LOOPBACK, StructNdMsg.NUD_DELAY, ifIndex, llAddr); + if (!Arrays.equals(expectedNewNeigh, bytes)) { + assertEquals(expectedNewNeigh.length, bytes.length); + for (int i = 0; i < Math.min(expectedNewNeigh.length, bytes.length); i++) { + assertEquals(expectedNewNeigh[i], bytes[i]); + } + } + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkRouteMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkRouteMessageTest.java new file mode 100644 index 0000000000000000000000000000000000000000..9881653a5e1731bc418b65b0bba373a68caa7438 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkRouteMessageTest.java @@ -0,0 +1,229 @@ +/* + * 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. + */ + +package com.android.net.module.util.netlink; + +import static android.system.OsConstants.NETLINK_ROUTE; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.net.InetAddresses; +import android.net.IpPrefix; +import android.system.OsConstants; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.net.module.util.HexDump; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet6Address; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class RtNetlinkRouteMessageTest { + private static final IpPrefix TEST_IPV6_GLOBAL_PREFIX = new IpPrefix("2001:db8:1::/64"); + private static final Inet6Address TEST_IPV6_LINK_LOCAL_GATEWAY = + (Inet6Address) InetAddresses.parseNumericAddress("fe80::1"); + + // An example of the full RTM_NEWROUTE message. + private static final String RTM_NEWROUTE_HEX = + "88000000180000060000000000000000" // struct nlmsghr + + "0A400000FC02000100000000" // struct rtmsg + + "08000F00C7060000" // RTA_TABLE + + "1400010020010DB8000100000000000000000000" // RTA_DST + + "08000400DF020000" // RTA_OIF + + "0800060000010000" // RTA_PRIORITY + + "24000C0000000000000000005EEA000000000000" // RTA_CACHEINFO + + "00000000000000000000000000000000" + + "14000500FE800000000000000000000000000001" // RTA_GATEWAY + + "0500140000000000"; // RTA_PREF + + private ByteBuffer toByteBuffer(final String hexString) { + return ByteBuffer.wrap(HexDump.hexStringToByteArray(hexString)); + } + + private void assertRtmRouteMessage(final RtNetlinkRouteMessage routeMsg) { + final StructNlMsgHdr hdr = routeMsg.getHeader(); + assertNotNull(hdr); + assertEquals(136, hdr.nlmsg_len); + assertEquals(NetlinkConstants.RTM_NEWROUTE, hdr.nlmsg_type); + assertEquals(0x600, hdr.nlmsg_flags); + assertEquals(0, hdr.nlmsg_seq); + assertEquals(0, hdr.nlmsg_pid); + + final StructRtMsg rtmsg = routeMsg.getRtMsgHeader(); + assertNotNull(rtmsg); + assertEquals((byte) OsConstants.AF_INET6, rtmsg.family); + assertEquals(64, rtmsg.dstLen); + assertEquals(0, rtmsg.srcLen); + assertEquals(0, rtmsg.tos); + assertEquals(0xFC, rtmsg.table); + assertEquals(NetlinkConstants.RTPROT_KERNEL, rtmsg.protocol); + assertEquals(NetlinkConstants.RT_SCOPE_UNIVERSE, rtmsg.scope); + assertEquals(NetlinkConstants.RTN_UNICAST, rtmsg.type); + assertEquals(0, rtmsg.flags); + + assertEquals(routeMsg.getDestination(), TEST_IPV6_GLOBAL_PREFIX); + assertEquals(735, routeMsg.getInterfaceIndex()); + assertEquals((Inet6Address) routeMsg.getGateway(), TEST_IPV6_LINK_LOCAL_GATEWAY); + + assertNotNull(routeMsg.getRtaCacheInfo()); + } + + @Test + public void testParseRtmRouteMessage() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWROUTE_HEX); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + assertNotNull(msg); + assertTrue(msg instanceof RtNetlinkRouteMessage); + final RtNetlinkRouteMessage routeMsg = (RtNetlinkRouteMessage) msg; + assertRtmRouteMessage(routeMsg); + } + + private static final String RTM_NEWROUTE_PACK_HEX = + "4C000000180000060000000000000000" // struct nlmsghr + + "0A400000FC02000100000000" // struct rtmsg + + "1400010020010DB8000100000000000000000000" // RTA_DST + + "14000500FE800000000000000000000000000001" // RTA_GATEWAY + + "08000400DF020000" // RTA_OIF + + "24000C0000000000000000005EEA000000000000" // RTA_CACHEINFO + + "00000000000000000000000000000000"; + + @Test + public void testPackRtmNewRoute() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWROUTE_PACK_HEX); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + assertNotNull(msg); + assertTrue(msg instanceof RtNetlinkRouteMessage); + final RtNetlinkRouteMessage routeMsg = (RtNetlinkRouteMessage) msg; + + final ByteBuffer packBuffer = ByteBuffer.allocate(112); + packBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + routeMsg.pack(packBuffer); + assertEquals(RTM_NEWROUTE_PACK_HEX, HexDump.toHexString(packBuffer.array())); + } + + private static final String RTM_NEWROUTE_TRUNCATED_HEX = + "48000000180000060000000000000000" // struct nlmsghr + + "0A400000FC02000100000000" // struct rtmsg + + "1400010020010DB8000100000000000000000000" // RTA_DST + + "10000500FE8000000000000000000000" // RTA_GATEWAY(truncated) + + "08000400DF020000"; // RTA_OIF + + @Test + public void testTruncatedRtmNewRoute() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWROUTE_TRUNCATED_HEX); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + // Parsing RTM_NEWROUTE with truncated RTA_GATEWAY attribute returns null. + assertNull(msg); + } + + private static final String RTM_NEWROUTE_IPV4_MAPPED_IPV6_GATEWAY_HEX = + "4C000000180000060000000000000000" // struct nlmsghr + + "0A400000FC02000100000000" // struct rtmsg + + "1400010020010DB8000100000000000000000000" // RTA_DST(2001:db8:1::/64) + + "1400050000000000000000000000FFFF0A010203" // RTA_GATEWAY(::ffff:10.1.2.3) + + "08000400DF020000"; // RTA_OIF + + @Test + public void testParseRtmRouteMessage_IPv4MappedIPv6Gateway() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWROUTE_IPV4_MAPPED_IPV6_GATEWAY_HEX); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + // Parsing RTM_NEWROUTE with IPv4-mapped IPv6 gateway address, which doesn't match + // rtm_family after address parsing. + assertNull(msg); + } + + private static final String RTM_NEWROUTE_IPV4_MAPPED_IPV6_DST_HEX = + "4C000000180000060000000000000000" // struct nlmsghr + + "0A780000FC02000100000000" // struct rtmsg + + "1400010000000000000000000000FFFF0A000000" // RTA_DST(::ffff:10.0.0.0/120) + + "14000500FE800000000000000000000000000001" // RTA_GATEWAY(fe80::1) + + "08000400DF020000"; // RTA_OIF + + @Test + public void testParseRtmRouteMessage_IPv4MappedIPv6Destination() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWROUTE_IPV4_MAPPED_IPV6_DST_HEX); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + // Parsing RTM_NEWROUTE with IPv4-mapped IPv6 destination prefix, which doesn't match + // rtm_family after address parsing. + assertNull(msg); + } + + // An example of the full RTM_NEWADDR message. + private static final String RTM_NEWADDR_HEX = + "48000000140000000000000000000000" // struct nlmsghr + + "0A4080FD1E000000" // struct ifaddrmsg + + "14000100FE800000000000002C415CFFFE096665" // IFA_ADDRESS + + "14000600100E0000201C00002A70000045700000" // IFA_CACHEINFO + + "0800080080000000"; // IFA_FLAGS + + @Test + public void testParseMultipleRtmMessagesInOneByteBuffer() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWROUTE_HEX + RTM_NEWADDR_HEX); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + + // Try to parse the RTM_NEWROUTE message. + NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + assertNotNull(msg); + assertTrue(msg instanceof RtNetlinkRouteMessage); + final RtNetlinkRouteMessage routeMsg = (RtNetlinkRouteMessage) msg; + assertRtmRouteMessage(routeMsg); + + // Try to parse the RTM_NEWADDR message. + msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + assertNotNull(msg); + assertTrue(msg instanceof RtNetlinkAddressMessage); + } + + @Test + public void testToString() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWROUTE_HEX); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + assertNotNull(msg); + assertTrue(msg instanceof RtNetlinkRouteMessage); + final RtNetlinkRouteMessage routeMsg = (RtNetlinkRouteMessage) msg; + final String expected = "RtNetlinkRouteMessage{ " + + "nlmsghdr{" + + "StructNlMsgHdr{ nlmsg_len{136}, nlmsg_type{24(RTM_NEWROUTE)}, " + + "nlmsg_flags{1536(NLM_F_MATCH)}, nlmsg_seq{0}, nlmsg_pid{0} }}, " + + "Rtmsg{" + + "family: 10, dstLen: 64, srcLen: 0, tos: 0, table: 252, protocol: 2, " + + "scope: 0, type: 1, flags: 0}, " + + "destination{2001:db8:1::}, " + + "gateway{fe80::1}, " + + "ifindex{735}, " + + "rta_cacheinfo{clntref: 0, lastuse: 0, expires: 59998, error: 0, used: 0, " + + "id: 0, ts: 0, tsage: 0} " + + "}"; + assertEquals(expected, routeMsg.toString()); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructInetDiagSockIdTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructInetDiagSockIdTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ce190f2c97fcee3e0156a77059661dd51c66cc6f --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructInetDiagSockIdTest.java @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import android.net.InetAddresses; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class StructInetDiagSockIdTest { + private static final Inet4Address IPV4_SRC_ADDR = + (Inet4Address) InetAddresses.parseNumericAddress("192.0.2.1"); + private static final Inet4Address IPV4_DST_ADDR = + (Inet4Address) InetAddresses.parseNumericAddress("198.51.100.1"); + private static final Inet6Address IPV6_SRC_ADDR = + (Inet6Address) InetAddresses.parseNumericAddress("2001:db8::1"); + private static final Inet6Address IPV6_DST_ADDR = + (Inet6Address) InetAddresses.parseNumericAddress("2001:db8::2"); + private static final int SRC_PORT = 65297; + private static final int DST_PORT = 443; + private static final int IF_INDEX = 7; + private static final long COOKIE = 561; + + private static final byte[] INET_DIAG_SOCKET_ID_IPV4 = + new byte[] { + // src port, dst port + (byte) 0xff, (byte) 0x11, (byte) 0x01, (byte) 0xbb, + // src address + (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // dst address + (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // if index + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // cookie + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff + }; + + private static final byte[] INET_DIAG_SOCKET_ID_IPV4_IF_COOKIE = + new byte[] { + // src port, dst port + (byte) 0xff, (byte) 0x11, (byte) 0x01, (byte) 0xbb, + // src address + (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // dst address + (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // if index + (byte) 0x07, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // cookie + (byte) 0x31, (byte) 0x02, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + }; + + private static final byte[] INET_DIAG_SOCKET_ID_IPV6 = + new byte[] { + // src port, dst port + (byte) 0xff, (byte) 0x11, (byte) 0x01, (byte) 0xbb, + // src address + (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, + // dst address + (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02, + // if index + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // cookie + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff + }; + + private static final byte[] INET_DIAG_SOCKET_ID_IPV6_IF_COOKIE = + new byte[] { + // src port, dst port + (byte) 0xff, (byte) 0x11, (byte) 0x01, (byte) 0xbb, + // src address + (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, + // dst address + (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02, + // if index + (byte) 0x07, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // cookie + (byte) 0x31, (byte) 0x02, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + }; + + @Test + public void testPackStructInetDiagSockIdWithIpv4() { + final InetSocketAddress srcAddr = new InetSocketAddress(IPV4_SRC_ADDR, SRC_PORT); + final InetSocketAddress dstAddr = new InetSocketAddress(IPV4_DST_ADDR, DST_PORT); + final StructInetDiagSockId sockId = new StructInetDiagSockId(srcAddr, dstAddr); + final ByteBuffer buffer = ByteBuffer.allocate(StructInetDiagSockId.STRUCT_SIZE); + sockId.pack(buffer); + assertArrayEquals(INET_DIAG_SOCKET_ID_IPV4, buffer.array()); + } + + @Test + public void testPackStructInetDiagSockIdWithIpv6() { + final InetSocketAddress srcAddr = new InetSocketAddress(IPV6_SRC_ADDR, SRC_PORT); + final InetSocketAddress dstAddr = new InetSocketAddress(IPV6_DST_ADDR, DST_PORT); + final StructInetDiagSockId sockId = new StructInetDiagSockId(srcAddr, dstAddr); + final ByteBuffer buffer = ByteBuffer.allocate(StructInetDiagSockId.STRUCT_SIZE); + sockId.pack(buffer); + assertArrayEquals(INET_DIAG_SOCKET_ID_IPV6, buffer.array()); + } + + @Test + public void testPackStructInetDiagSockIdWithIpv4IfIndexCookie() { + final InetSocketAddress srcAddr = new InetSocketAddress(IPV4_SRC_ADDR, SRC_PORT); + final InetSocketAddress dstAddr = new InetSocketAddress(IPV4_DST_ADDR, DST_PORT); + final StructInetDiagSockId sockId = + new StructInetDiagSockId(srcAddr, dstAddr, IF_INDEX, COOKIE); + final ByteBuffer buffer = ByteBuffer.allocate(StructInetDiagSockId.STRUCT_SIZE); + sockId.pack(buffer); + assertArrayEquals(INET_DIAG_SOCKET_ID_IPV4_IF_COOKIE, buffer.array()); + } + + @Test + public void testPackStructInetDiagSockIdWithIpv6IfIndexCookie() { + final InetSocketAddress srcAddr = new InetSocketAddress(IPV6_SRC_ADDR, SRC_PORT); + final InetSocketAddress dstAddr = new InetSocketAddress(IPV6_DST_ADDR, DST_PORT); + final StructInetDiagSockId sockId = + new StructInetDiagSockId(srcAddr, dstAddr, IF_INDEX, COOKIE); + final ByteBuffer buffer = ByteBuffer.allocate(StructInetDiagSockId.STRUCT_SIZE); + sockId.pack(buffer); + assertArrayEquals(INET_DIAG_SOCKET_ID_IPV6_IF_COOKIE, buffer.array()); + } + + @Test + public void testParseStructInetDiagSockIdWithIpv4() { + final ByteBuffer buffer = ByteBuffer.wrap(INET_DIAG_SOCKET_ID_IPV4_IF_COOKIE); + final StructInetDiagSockId sockId = StructInetDiagSockId.parse(buffer, (byte) AF_INET); + + assertEquals(SRC_PORT, sockId.locSocketAddress.getPort()); + assertEquals(IPV4_SRC_ADDR, sockId.locSocketAddress.getAddress()); + assertEquals(DST_PORT, sockId.remSocketAddress.getPort()); + assertEquals(IPV4_DST_ADDR, sockId.remSocketAddress.getAddress()); + assertEquals(IF_INDEX, sockId.ifIndex); + assertEquals(COOKIE, sockId.cookie); + } + + @Test + public void testParseStructInetDiagSockIdWithIpv6() { + final ByteBuffer buffer = ByteBuffer.wrap(INET_DIAG_SOCKET_ID_IPV6_IF_COOKIE); + final StructInetDiagSockId sockId = StructInetDiagSockId.parse(buffer, (byte) AF_INET6); + + assertEquals(SRC_PORT, sockId.locSocketAddress.getPort()); + assertEquals(IPV6_SRC_ADDR, sockId.locSocketAddress.getAddress()); + assertEquals(DST_PORT, sockId.remSocketAddress.getPort()); + assertEquals(IPV6_DST_ADDR, sockId.remSocketAddress.getAddress()); + assertEquals(IF_INDEX, sockId.ifIndex); + assertEquals(COOKIE, sockId.cookie); + } + + @Test + public void testToStringStructInetDiagSockIdWithIpv4() { + final InetSocketAddress srcAddr = new InetSocketAddress(IPV4_SRC_ADDR, SRC_PORT); + final InetSocketAddress dstAddr = new InetSocketAddress(IPV4_DST_ADDR, DST_PORT); + final StructInetDiagSockId sockId = new StructInetDiagSockId(srcAddr, dstAddr); + assertEquals("StructInetDiagSockId{ idiag_sport{65297}, idiag_dport{443}," + + " idiag_src{192.0.2.1}, idiag_dst{198.51.100.1}, idiag_if{0}," + + " idiag_cookie{INET_DIAG_NOCOOKIE}}", sockId.toString()); + } + + @Test + public void testToStringStructInetDiagSockIdWithIpv6() { + final InetSocketAddress srcAddr = new InetSocketAddress(IPV6_SRC_ADDR, SRC_PORT); + final InetSocketAddress dstAddr = new InetSocketAddress(IPV6_DST_ADDR, DST_PORT); + final StructInetDiagSockId sockId = new StructInetDiagSockId(srcAddr, dstAddr); + assertEquals("StructInetDiagSockId{ idiag_sport{65297}, idiag_dport{443}," + + " idiag_src{2001:db8::1}, idiag_dst{2001:db8::2}, idiag_if{0}," + + " idiag_cookie{INET_DIAG_NOCOOKIE}}", sockId.toString()); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNdOptPref64Test.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNdOptPref64Test.java new file mode 100644 index 0000000000000000000000000000000000000000..beed838356c08a55353cec28b696a853433d8936 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNdOptPref64Test.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import static com.android.net.module.util.netlink.StructNdOptPref64.getScaledLifetimePlc; +import static com.android.net.module.util.netlink.StructNdOptPref64.plcToPrefixLength; +import static com.android.net.module.util.netlink.StructNdOptPref64.prefixLengthToPlc; +import static com.android.testutils.MiscAsserts.assertThrows; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import android.annotation.SuppressLint; +import android.net.IpPrefix; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import libcore.util.HexEncoding; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.InetAddress; +import java.nio.ByteBuffer; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class StructNdOptPref64Test { + + private static final String PREFIX1 = "64:ff9b::"; + private static final String PREFIX2 = "2001:db8:1:2:3:64::"; + + private static byte[] prefixBytes(String addrString) throws Exception { + InetAddress addr = InetAddress.getByName(addrString); + byte[] prefixBytes = new byte[12]; + System.arraycopy(addr.getAddress(), 0, prefixBytes, 0, 12); + return prefixBytes; + } + + @SuppressLint("NewApi") + private static IpPrefix prefix(String addrString, int prefixLength) throws Exception { + return new IpPrefix(InetAddress.getByName(addrString), prefixLength); + } + + private void assertPref64OptMatches(int lifetime, IpPrefix prefix, StructNdOptPref64 opt) { + assertEquals(StructNdOptPref64.TYPE, opt.type); + assertEquals(2, opt.length); + assertEquals(lifetime, opt.lifetime); + assertEquals(prefix, opt.prefix); + } + + private void assertToByteBufferMatches(StructNdOptPref64 opt, String expected) { + String actual = HexEncoding.encodeToString(opt.toByteBuffer().array()); + assertEquals(expected, actual); + } + + private ByteBuffer makeNdOptPref64(int lifetime, byte[] prefix, int prefixLengthCode) { + if (prefix.length != 12) throw new IllegalArgumentException("Prefix must be 12 bytes"); + + ByteBuffer buf = ByteBuffer.allocate(16) + .put((byte) StructNdOptPref64.TYPE) + .put((byte) StructNdOptPref64.LENGTH) + .putShort(getScaledLifetimePlc(lifetime, prefixLengthCode)) + .put(prefix, 0, 12); + + buf.flip(); + return buf; + } + + @Test + public void testParseCannedOption() throws Exception { + String hexBytes = "2602" // type=38, len=2 (16 bytes) + + "0088" // lifetime=136, PLC=0 (/96) + + "20010DB80003000400050006"; // 2001:db8:3:4:5:6/96 + byte[] rawBytes = HexEncoding.decode(hexBytes); + StructNdOptPref64 opt = StructNdOptPref64.parse(ByteBuffer.wrap(rawBytes)); + assertPref64OptMatches(136, prefix("2001:DB8:3:4:5:6::", 96), opt); + assertToByteBufferMatches(opt, hexBytes); + + hexBytes = "2602" // type=38, len=2 (16 bytes) + + "2752" // lifetime=10064, PLC=2 (/56) + + "0064FF9B0000000000000000"; // 64:ff9b::/56 + rawBytes = HexEncoding.decode(hexBytes); + opt = StructNdOptPref64.parse(ByteBuffer.wrap(rawBytes)); + assertPref64OptMatches(10064, prefix("64:FF9B::", 56), opt); + assertToByteBufferMatches(opt, hexBytes); + } + + @Test + public void testParsing() throws Exception { + // Valid. + ByteBuffer buf = makeNdOptPref64(600, prefixBytes(PREFIX1), 0); + StructNdOptPref64 opt = StructNdOptPref64.parse(buf); + assertPref64OptMatches(600, prefix(PREFIX1, 96), opt); + + // Valid, zero lifetime, /64. + buf = makeNdOptPref64(0, prefixBytes(PREFIX1), 1); + opt = StructNdOptPref64.parse(buf); + assertPref64OptMatches(0, prefix(PREFIX1, 64), opt); + + // Valid, low lifetime, /56. + buf = makeNdOptPref64(8, prefixBytes(PREFIX2), 2); + opt = StructNdOptPref64.parse(buf); + assertPref64OptMatches(8, prefix(PREFIX2, 56), opt); + assertEquals(new IpPrefix("2001:db8:1::/56"), opt.prefix); // Prefix is truncated. + + // Valid, maximum lifetime, /32. + buf = makeNdOptPref64(65528, prefixBytes(PREFIX2), 5); + opt = StructNdOptPref64.parse(buf); + assertPref64OptMatches(65528, prefix(PREFIX2, 32), opt); + assertEquals(new IpPrefix("2001:db8::/32"), opt.prefix); // Prefix is truncated. + + // Lifetime not divisible by 8. + buf = makeNdOptPref64(300, prefixBytes(PREFIX2), 0); + opt = StructNdOptPref64.parse(buf); + assertPref64OptMatches(296, prefix(PREFIX2, 96), opt); + + // Invalid prefix length codes. + buf = makeNdOptPref64(600, prefixBytes(PREFIX1), 6); + assertNull(StructNdOptPref64.parse(buf)); + buf = makeNdOptPref64(600, prefixBytes(PREFIX1), 7); + assertNull(StructNdOptPref64.parse(buf)); + + // Truncated to varying lengths... + buf = makeNdOptPref64(600, prefixBytes(PREFIX1), 3); + final int len = buf.limit(); + for (int i = 0; i < buf.limit() - 1; i++) { + buf.flip(); + buf.limit(i); + assertNull("Option truncated to " + i + " bytes, should have returned null", + StructNdOptPref64.parse(buf)); + } + buf.flip(); + buf.limit(len); + // ... but otherwise OK. + opt = StructNdOptPref64.parse(buf); + assertPref64OptMatches(600, prefix(PREFIX1, 48), opt); + } + + @Test + public void testToString() throws Exception { + ByteBuffer buf = makeNdOptPref64(600, prefixBytes(PREFIX1), 4); + StructNdOptPref64 opt = StructNdOptPref64.parse(buf); + assertPref64OptMatches(600, prefix(PREFIX1, 40), opt); + assertEquals("NdOptPref64(64:ff9b::/40, 600)", opt.toString()); + } + + private void assertInvalidPlc(int plc) { + assertThrows(IllegalArgumentException.class, () -> plcToPrefixLength(plc)); + } + + @Test + public void testPrefixLengthToPlc() { + for (int i = 0; i < 6; i++) { + assertEquals(i, prefixLengthToPlc(plcToPrefixLength(i))); + } + assertInvalidPlc(-1); + assertInvalidPlc(6); + assertInvalidPlc(7); + assertEquals(0, prefixLengthToPlc(96)); + } + + + private void assertInvalidParameters(IpPrefix prefix, int lifetime) { + assertThrows(IllegalArgumentException.class, () -> new StructNdOptPref64(prefix, lifetime)); + } + + @Test + public void testToByteBuffer() throws Exception { + final IpPrefix prefix1 = prefix(PREFIX1, 56); + final IpPrefix prefix2 = prefix(PREFIX2, 96); + + StructNdOptPref64 opt = new StructNdOptPref64(prefix1, 600); + assertToByteBufferMatches(opt, "2602025A0064FF9B0000000000000000"); + assertEquals(new IpPrefix("64:ff9b::/56"), opt.prefix); + assertEquals(600, opt.lifetime); + + opt = new StructNdOptPref64(prefix2, 65519); + assertToByteBufferMatches(opt, "2602FFE820010DB80001000200030064"); + assertEquals(new IpPrefix("2001:db8:1:2:3:64::/96"), opt.prefix); + assertEquals(65512, opt.lifetime); + + assertInvalidParameters(prefix1, 65535); + assertInvalidParameters(prefix2, -1); + assertInvalidParameters(prefix("1.2.3.4", 32), 600); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNdOptRdnssTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNdOptRdnssTest.java new file mode 100644 index 0000000000000000000000000000000000000000..1dcb9b59fcd053be57c63cfa3a3f13ff637a2ddb --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNdOptRdnssTest.java @@ -0,0 +1,195 @@ +/* + * 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. + */ + +package com.android.net.module.util.netlink; + +import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_RDNSS; +import static com.android.testutils.MiscAsserts.assertThrows; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import android.net.InetAddresses; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.net.module.util.structs.RdnssOption; + +import libcore.util.HexEncoding; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet6Address; +import java.nio.ByteBuffer; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class StructNdOptRdnssTest { + private static final String DNS_SERVER1 = "2001:4860:4860::64"; + private static final String DNS_SERVER2 = "2001:4860:4860::6464"; + + private static final Inet6Address[] DNS_SERVER_ADDRESSES = new Inet6Address[] { + (Inet6Address) InetAddresses.parseNumericAddress(DNS_SERVER1), + (Inet6Address) InetAddresses.parseNumericAddress(DNS_SERVER2), + }; + + private static final String RDNSS_OPTION_BYTES = + "1905" // type=25, len=5 (40 bytes) + + "0000" // reserved + + "00000E10" // lifetime=3600 + + "20014860486000000000000000000064" // 2001:4860:4860::64 + + "20014860486000000000000000006464"; // 2001:4860:4860::6464 + + private static final String RDNSS_INFINITY_LIFETIME_OPTION_BYTES = + "1905" // type=25, len=3 (24 bytes) + + "0000" // reserved + + "FFFFFFFF" // lifetime=0xffffffff + + "20014860486000000000000000000064" // 2001:4860:4860::64 + + "20014860486000000000000000006464"; // 2001:4860:4860::6464 + + private void assertRdnssOptMatches(final StructNdOptRdnss opt, int length, long lifetime, + final Inet6Address[] servers) { + assertEquals(StructNdOptRdnss.TYPE, opt.type); + assertEquals(length, opt.length); + assertEquals(lifetime, opt.header.lifetime); + assertEquals(servers, opt.servers); + } + + private ByteBuffer makeRdnssOption(byte type, byte length, long lifetime, String... servers) + throws Exception { + final ByteBuffer buf = ByteBuffer.allocate(8 + servers.length * 16) + .put(type) + .put(length) + .putShort((short) 0) // Reserved + .putInt((int) (lifetime & 0xFFFFFFFFL)); + for (int i = 0; i < servers.length; i++) { + final byte[] rawBytes = + ((Inet6Address) InetAddresses.parseNumericAddress(servers[i])).getAddress(); + buf.put(rawBytes); + } + buf.flip(); + return buf; + } + + private void assertToByteBufferMatches(StructNdOptRdnss opt, String expected) { + String actual = HexEncoding.encodeToString(opt.toByteBuffer().array()); + assertEquals(expected, actual); + } + + private void doRdnssOptionParsing(final String optionHexString, int length, long lifetime, + final Inet6Address[] servers) { + final byte[] rawBytes = HexEncoding.decode(optionHexString); + final StructNdOptRdnss opt = StructNdOptRdnss.parse(ByteBuffer.wrap(rawBytes)); + assertRdnssOptMatches(opt, length, lifetime, servers); + assertToByteBufferMatches(opt, optionHexString); + } + + @Test + public void testParsing() throws Exception { + doRdnssOptionParsing(RDNSS_OPTION_BYTES, 5 /* length */, 3600 /* lifetime */, + DNS_SERVER_ADDRESSES); + } + + @Test + public void testParsing_infinityLifetime() throws Exception { + doRdnssOptionParsing(RDNSS_INFINITY_LIFETIME_OPTION_BYTES, 5 /* length */, + 0xffffffffL /* lifetime */, DNS_SERVER_ADDRESSES); + } + + @Test + public void testToByteBuffer() { + final StructNdOptRdnss rdnss = new StructNdOptRdnss(DNS_SERVER_ADDRESSES, 3600); + assertToByteBufferMatches(rdnss, RDNSS_OPTION_BYTES); + } + + @Test + public void testToByteBuffer_infinityLifetime() { + final StructNdOptRdnss rdnss = new StructNdOptRdnss(DNS_SERVER_ADDRESSES, 0xffffffffL); + assertToByteBufferMatches(rdnss, RDNSS_INFINITY_LIFETIME_OPTION_BYTES); + } + + @Test + public void testParsing_invalidType() throws Exception { + final ByteBuffer buf = makeRdnssOption((byte) 38, (byte) 5 /* length */, + 3600 /* lifetime */, DNS_SERVER1, DNS_SERVER2); + assertNull(StructNdOptRdnss.parse(buf)); + } + + @Test + public void testParsing_smallOptionLength() throws Exception { + final ByteBuffer buf = makeRdnssOption((byte) ICMPV6_ND_OPTION_RDNSS, + (byte) 2 /* length */, 3600 /* lifetime */, DNS_SERVER1, DNS_SERVER2); + assertNull(StructNdOptRdnss.parse(buf)); + } + + @Test + public void testParsing_oddOptionLength() throws Exception { + final ByteBuffer buf = makeRdnssOption((byte) ICMPV6_ND_OPTION_RDNSS, + (byte) 6 /* length */, 3600 /* lifetime */, DNS_SERVER1, DNS_SERVER2); + assertNull(StructNdOptRdnss.parse(buf)); + } + + @Test + public void testParsing_truncatedByteBuffer() throws Exception { + ByteBuffer buf = makeRdnssOption((byte) ICMPV6_ND_OPTION_RDNSS, + (byte) 5 /* length */, 3600 /* lifetime */, DNS_SERVER1, DNS_SERVER2); + final int len = buf.limit(); + for (int i = 0; i < buf.limit() - 1; i++) { + buf.flip(); + buf.limit(i); + assertNull("Option truncated to " + i + " bytes, should have returned null", + StructNdOptRdnss.parse(buf)); + } + buf.flip(); + buf.limit(len); + + final StructNdOptRdnss opt = StructNdOptRdnss.parse(buf); + assertRdnssOptMatches(opt, 5 /* length */, 3600 /* lifetime */, DNS_SERVER_ADDRESSES); + } + + @Test + public void testParsing_invalidByteBufferLength() throws Exception { + final ByteBuffer buf = makeRdnssOption((byte) ICMPV6_ND_OPTION_RDNSS, + (byte) 5 /* length */, 3600 /* lifetime */, DNS_SERVER1, DNS_SERVER2); + buf.limit(20); // less than MIN_OPT_LEN * 8 + assertNull(StructNdOptRdnss.parse(buf)); + } + + @Test + public void testConstructor_nullDnsServerAddressArray() { + assertThrows(NullPointerException.class, + () -> new StructNdOptRdnss(null /* servers */, 3600 /* lifetime */)); + } + + @Test + public void testConstructor_emptyDnsServerAddressArray() { + assertThrows(IllegalArgumentException.class, + () -> new StructNdOptRdnss(new Inet6Address[0] /* empty server array */, + 3600 /* lifetime*/)); + } + + @Test + public void testToString() { + final ByteBuffer buf = RdnssOption.build(3600 /* lifetime */, DNS_SERVER1, DNS_SERVER2); + final StructNdOptRdnss opt = StructNdOptRdnss.parse(buf); + final String expected = "NdOptRdnss(type: 25, length: 5, reserved: 0, lifetime: 3600," + + "servers:[2001:4860:4860::64,2001:4860:4860::6464])"; + assertRdnssOptMatches(opt, 5 /* length */, 3600 /* lifetime */, DNS_SERVER_ADDRESSES); + assertEquals(expected, opt.toString()); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java new file mode 100644 index 0000000000000000000000000000000000000000..af3fac2e7dc0418025c69eca2a64d3c1a1273670 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java @@ -0,0 +1,95 @@ +/* + * 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. + */ + +package com.android.net.module.util.netlink; + +import static com.android.net.module.util.netlink.RtNetlinkAddressMessage.IFA_FLAGS; +import static com.android.net.module.util.netlink.RtNetlinkLinkMessage.IFLA_ADDRESS; +import static com.android.net.module.util.netlink.RtNetlinkLinkMessage.IFLA_IFNAME; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import android.net.MacAddress; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class StructNlAttrTest { + private static final MacAddress TEST_MAC_ADDRESS = MacAddress.fromString("00:11:22:33:44:55"); + private static final String TEST_INTERFACE_NAME = "wlan0"; + private static final int TEST_ADDR_FLAGS = 0x80; + + @Test + public void testGetValueAsMacAddress() { + final StructNlAttr attr1 = new StructNlAttr(IFLA_ADDRESS, TEST_MAC_ADDRESS); + final MacAddress address1 = attr1.getValueAsMacAddress(); + assertEquals(address1, TEST_MAC_ADDRESS); + + // Invalid mac address byte array. + final byte[] array = new byte[] { + (byte) 0x00, (byte) 0x11, (byte) 0x22, (byte) 0x33, + (byte) 0x44, (byte) 0x55, (byte) 0x66, + }; + final StructNlAttr attr2 = new StructNlAttr(IFLA_ADDRESS, array); + final MacAddress address2 = attr2.getValueAsMacAddress(); + assertNull(address2); + } + + @Test + public void testGetValueAsString() { + final StructNlAttr attr1 = new StructNlAttr(IFLA_IFNAME, TEST_INTERFACE_NAME); + final String str1 = attr1.getValueAsString(); + assertEquals(str1, TEST_INTERFACE_NAME); + + final byte[] array = new byte[] { + (byte) 0x77, (byte) 0x6c, (byte) 0x61, (byte) 0x6E, (byte) 0x30, (byte) 0x00, + }; + final StructNlAttr attr2 = new StructNlAttr(IFLA_IFNAME, array); + final String str2 = attr2.getValueAsString(); + assertEquals(str2, TEST_INTERFACE_NAME); + } + + @Test + public void testGetValueAsIntger() { + final StructNlAttr attr1 = new StructNlAttr(IFA_FLAGS, TEST_ADDR_FLAGS); + final Integer integer1 = attr1.getValueAsInteger(); + final int int1 = attr1.getValueAsInt(0x08 /* default value */); + assertEquals(integer1, new Integer(TEST_ADDR_FLAGS)); + assertEquals(int1, TEST_ADDR_FLAGS); + + // Malformed attribute. + final byte[] malformed_int = new byte[] { (byte) 0x0, (byte) 0x0, (byte) 0x80, }; + final StructNlAttr attr2 = new StructNlAttr(IFA_FLAGS, malformed_int); + final Integer integer2 = attr2.getValueAsInteger(); + final int int2 = attr2.getValueAsInt(0x08 /* default value */); + assertNull(integer2); + assertEquals(int2, 0x08 /* default value */); + + // Null attribute value. + final byte[] null_int = null; + final StructNlAttr attr3 = new StructNlAttr(IFA_FLAGS, null_int); + final Integer integer3 = attr3.getValueAsInteger(); + final int int3 = attr3.getValueAsInt(0x08 /* default value */); + assertNull(integer3); + assertEquals(int3, 0x08 /* default value */); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlMsgHdrTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlMsgHdrTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b7f68c6236d0c8c8425764a62aed89d12c1f283b --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlMsgHdrTest.java @@ -0,0 +1,102 @@ +/* + * 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. + */ + +package com.android.net.module.util.netlink; + +import static org.junit.Assert.fail; + +import android.system.OsConstants; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class StructNlMsgHdrTest { + + public static final short TEST_NLMSG_LEN = 16; + public static final short TEST_NLMSG_FLAGS = StructNlMsgHdr.NLM_F_REQUEST + | StructNlMsgHdr.NLM_F_MULTI | StructNlMsgHdr.NLM_F_ACK | StructNlMsgHdr.NLM_F_ECHO; + public static final short TEST_NLMSG_SEQ = 1234; + public static final short TEST_NLMSG_PID = 5678; + + // Checking the header string nlmsg_{len, ..} of the number can make sure that the checking + // number comes from the expected element. + // TODO: Verify more flags once StructNlMsgHdr can distinguish the flags which have the same + // value. For example, NLM_F_MATCH (0x200) and NLM_F_EXCL (0x200) can't be distinguished. + // See StructNlMsgHdrTest#stringForNlMsgFlags. + public static final String TEST_NLMSG_LEN_STR = "nlmsg_len{16}"; + public static final String TEST_NLMSG_FLAGS_STR = + "NLM_F_REQUEST|NLM_F_MULTI|NLM_F_ACK|NLM_F_ECHO"; + public static final String TEST_NLMSG_SEQ_STR = "nlmsg_seq{1234}"; + public static final String TEST_NLMSG_PID_STR = "nlmsg_pid{5678}"; + + private StructNlMsgHdr makeStructNlMsgHdr(short type) { + final StructNlMsgHdr struct = new StructNlMsgHdr(); + struct.nlmsg_len = TEST_NLMSG_LEN; + struct.nlmsg_type = type; + struct.nlmsg_flags = TEST_NLMSG_FLAGS; + struct.nlmsg_seq = TEST_NLMSG_SEQ; + struct.nlmsg_pid = TEST_NLMSG_PID; + return struct; + } + + private static void assertContains(String actualValue, String expectedSubstring) { + if (actualValue.contains(expectedSubstring)) return; + fail("\"" + actualValue + "\" does not contain \"" + expectedSubstring + "\""); + } + + @Test + public void testToString() { + StructNlMsgHdr struct = makeStructNlMsgHdr(NetlinkConstants.RTM_NEWADDR); + String s = struct.toString(); + assertContains(s, TEST_NLMSG_LEN_STR); + assertContains(s, TEST_NLMSG_FLAGS_STR); + assertContains(s, TEST_NLMSG_SEQ_STR); + assertContains(s, TEST_NLMSG_PID_STR); + assertContains(s, "nlmsg_type{20()}"); + + struct = makeStructNlMsgHdr(NetlinkConstants.SOCK_DIAG_BY_FAMILY); + s = struct.toString(); + assertContains(s, TEST_NLMSG_LEN_STR); + assertContains(s, TEST_NLMSG_FLAGS_STR); + assertContains(s, TEST_NLMSG_SEQ_STR); + assertContains(s, TEST_NLMSG_PID_STR); + assertContains(s, "nlmsg_type{20()}"); + } + + @Test + public void testToStringWithNetlinkFamily() { + StructNlMsgHdr struct = makeStructNlMsgHdr(NetlinkConstants.RTM_NEWADDR); + String s = struct.toString(OsConstants.NETLINK_ROUTE); + assertContains(s, TEST_NLMSG_LEN_STR); + assertContains(s, TEST_NLMSG_FLAGS_STR); + assertContains(s, TEST_NLMSG_SEQ_STR); + assertContains(s, TEST_NLMSG_PID_STR); + assertContains(s, "nlmsg_type{20(RTM_NEWADDR)}"); + + struct = makeStructNlMsgHdr(NetlinkConstants.SOCK_DIAG_BY_FAMILY); + s = struct.toString(OsConstants.NETLINK_INET_DIAG); + assertContains(s, TEST_NLMSG_LEN_STR); + assertContains(s, TEST_NLMSG_FLAGS_STR); + assertContains(s, TEST_NLMSG_SEQ_STR); + assertContains(s, TEST_NLMSG_PID_STR); + assertContains(s, "nlmsg_type{20(SOCK_DIAG_BY_FAMILY)}"); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/wear/NetPacketHelpersTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/wear/NetPacketHelpersTest.java new file mode 100644 index 0000000000000000000000000000000000000000..23e7b15e03219751c719201aaf7998d0cec7dc1c --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/wear/NetPacketHelpersTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.wear; + +import static org.junit.Assert.assertEquals; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import com.android.net.module.util.async.CircularByteBuffer; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NetPacketHelpersTest { + @Test + public void decodeNetworkUnsignedInt16() { + final byte[] data = new byte[4]; + data[0] = (byte) 0xFF; + data[1] = (byte) 1; + data[2] = (byte) 2; + data[3] = (byte) 0xFF; + + assertEquals(0x0102, NetPacketHelpers.decodeNetworkUnsignedInt16(data, 1)); + + CircularByteBuffer buffer = new CircularByteBuffer(100); + buffer.writeBytes(data, 0, data.length); + + assertEquals(0x0102, NetPacketHelpers.decodeNetworkUnsignedInt16(buffer, 1)); + } + + @Test + public void encodeNetworkUnsignedInt16() { + final byte[] data = new byte[4]; + data[0] = (byte) 0xFF; + data[3] = (byte) 0xFF; + NetPacketHelpers.encodeNetworkUnsignedInt16(0x0102, data, 1); + + assertEquals((byte) 0xFF, data[0]); + assertEquals((byte) 1, data[1]); + assertEquals((byte) 2, data[2]); + assertEquals((byte) 0xFF, data[3]); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/wear/StreamingPacketFileTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/wear/StreamingPacketFileTest.java new file mode 100644 index 0000000000000000000000000000000000000000..1fcca70445ae3f79560de635bad18913e7b062d5 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/wear/StreamingPacketFileTest.java @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.wear; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.ignoreStubs; +import static org.mockito.Mockito.reset; +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.os.ParcelFileDescriptor; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.net.module.util.async.AsyncFile; +import com.android.net.module.util.async.BufferedFile; +import com.android.net.module.util.async.EventManager; +import com.android.net.module.util.async.FileHandle; +import com.android.net.module.util.async.ReadableByteBuffer; +import com.android.testutils.async.ReadableDataAnswer; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class StreamingPacketFileTest { + private static final int MAX_PACKET_SIZE = 100; + + @Mock EventManager mockEventManager; + @Mock PacketFile.Listener mockFileListener; + @Mock AsyncFile mockAsyncFile; + @Mock ParcelFileDescriptor mockParcelFileDescriptor; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + } + + @After + public void tearDown() throws Exception { + verifyNoMoreInteractions(ignoreStubs(mockFileListener, mockAsyncFile, mockEventManager)); + } + + @Test + public void continueReadingAndClose() throws Exception { + final int maxBufferedInboundPackets = 3; + final int maxBufferedOutboundPackets = 5; + + final StreamingPacketFile file = + createFile(maxBufferedInboundPackets, maxBufferedOutboundPackets); + final BufferedFile bufferedFile = file.getUnderlyingFileForTest(); + + assertEquals(maxBufferedInboundPackets * (MAX_PACKET_SIZE + 2), + bufferedFile.getInboundBufferFreeSizeForTest()); + assertEquals(maxBufferedOutboundPackets * (MAX_PACKET_SIZE + 2), + bufferedFile.getOutboundBufferFreeSize()); + assertEquals(bufferedFile.getOutboundBufferFreeSize() - 2, + file.getOutboundFreeSize()); + + file.continueReading(); + verify(mockAsyncFile).enableReadEvents(true); + + file.close(); + verify(mockAsyncFile).close(); + } + + @Test + public void enqueueOutboundPacket() throws Exception { + final int maxBufferedInboundPackets = 10; + final int maxBufferedOutboundPackets = 20; + + final StreamingPacketFile file = + createFile(maxBufferedInboundPackets, maxBufferedOutboundPackets); + final BufferedFile bufferedFile = file.getUnderlyingFileForTest(); + + final byte[] packet1 = new byte[11]; + final byte[] packet2 = new byte[12]; + packet1[0] = (byte) 1; + packet2[0] = (byte) 2; + + assertEquals(0, bufferedFile.getOutboundBufferSize()); + + when(mockAsyncFile.write(any(), anyInt(), anyInt())).thenReturn(0); + assertTrue(file.enqueueOutboundPacket(packet1, 0, packet1.length)); + verify(mockAsyncFile).enableWriteEvents(true); + + assertEquals(packet1.length + 2, bufferedFile.getOutboundBufferSize()); + + checkAndResetMocks(); + + final int totalLen = packet1.length + packet2.length + 4; + + final ArgumentCaptor<byte[]> arrayCaptor = ArgumentCaptor.forClass(byte[].class); + final ArgumentCaptor<Integer> posCaptor = ArgumentCaptor.forClass(Integer.class); + final ArgumentCaptor<Integer> lenCaptor = ArgumentCaptor.forClass(Integer.class); + when(mockAsyncFile.write( + arrayCaptor.capture(), posCaptor.capture(), lenCaptor.capture())).thenReturn(totalLen); + + assertTrue(file.enqueueOutboundPacket(packet2, 0, packet2.length)); + + assertEquals(0, bufferedFile.getInboundBuffer().size()); + assertEquals(0, bufferedFile.getOutboundBufferSize()); + + assertEquals(0, posCaptor.getValue().intValue()); + assertEquals(totalLen, lenCaptor.getValue().intValue()); + + final byte[] capturedData = arrayCaptor.getValue(); + assertEquals(packet1.length, NetPacketHelpers.decodeNetworkUnsignedInt16(capturedData, 0)); + assertEquals(packet2.length, + NetPacketHelpers.decodeNetworkUnsignedInt16(capturedData, packet1.length + 2)); + assertEquals(packet1[0], capturedData[2]); + assertEquals(packet2[0], capturedData[packet1.length + 4]); + } + + @Test + public void onInboundPacket() throws Exception { + final int maxBufferedInboundPackets = 10; + final int maxBufferedOutboundPackets = 20; + + final StreamingPacketFile file = + createFile(maxBufferedInboundPackets, maxBufferedOutboundPackets); + final BufferedFile bufferedFile = file.getUnderlyingFileForTest(); + final ReadableByteBuffer inboundBuffer = bufferedFile.getInboundBuffer(); + + final int len1 = 11; + final int len2 = 12; + final byte[] data = new byte[len1 + len2 + 4]; + NetPacketHelpers.encodeNetworkUnsignedInt16(len1, data, 0); + NetPacketHelpers.encodeNetworkUnsignedInt16(len2, data, 11 + 2); + data[2] = (byte) 1; + data[len1 + 4] = (byte) 2; + + final ReadableDataAnswer dataAnswer = new ReadableDataAnswer(data); + + final ArgumentCaptor<byte[]> arrayCaptor = ArgumentCaptor.forClass(byte[].class); + final ArgumentCaptor<Integer> posCaptor = ArgumentCaptor.forClass(Integer.class); + final ArgumentCaptor<Integer> lenCaptor = ArgumentCaptor.forClass(Integer.class); + + when(mockAsyncFile.read(any(), anyInt(), anyInt())).thenAnswer(dataAnswer); + when(mockFileListener.onPreambleData(any(), eq(0), eq(data.length))).thenReturn(0); + bufferedFile.onReadReady(mockAsyncFile); + verify(mockAsyncFile).enableReadEvents(true); + verify(mockFileListener).onInboundBuffered(data.length, data.length); + verify(mockFileListener).onInboundPacket( + arrayCaptor.capture(), posCaptor.capture(), lenCaptor.capture()); + verify(mockEventManager).execute(any()); + + byte[] capturedData = arrayCaptor.getValue(); + assertEquals(2, posCaptor.getValue().intValue()); + assertEquals(len1, lenCaptor.getValue().intValue()); + assertEquals((byte) 1, capturedData[2]); + + checkAndResetMocks(); + + when(mockAsyncFile.read(any(), anyInt(), anyInt())).thenAnswer(dataAnswer); + file.onBufferedFileInboundData(0); + verify(mockFileListener).onInboundPacket( + arrayCaptor.capture(), posCaptor.capture(), lenCaptor.capture()); + verify(mockEventManager).execute(any()); + + capturedData = arrayCaptor.getValue(); + assertEquals(2, posCaptor.getValue().intValue()); + assertEquals(len2, lenCaptor.getValue().intValue()); + assertEquals((byte) 2, capturedData[2]); + + assertEquals(0, bufferedFile.getOutboundBufferSize()); + assertEquals(0, inboundBuffer.size()); + } + + @Test + public void onReadReady_preambleData() throws Exception { + final int maxBufferedInboundPackets = 10; + final int maxBufferedOutboundPackets = 20; + + final StreamingPacketFile file = + createFile(maxBufferedInboundPackets, maxBufferedOutboundPackets); + final BufferedFile bufferedFile = file.getUnderlyingFileForTest(); + final ReadableByteBuffer inboundBuffer = bufferedFile.getInboundBuffer(); + + final int preambleLen = 23; + final int len1 = 11; + final byte[] data = new byte[preambleLen + 2 + len1]; + NetPacketHelpers.encodeNetworkUnsignedInt16(len1, data, preambleLen); + data[preambleLen + 2] = (byte) 1; + + final ReadableDataAnswer dataAnswer = new ReadableDataAnswer(data); + + when(mockAsyncFile.read(any(), anyInt(), anyInt())).thenAnswer(dataAnswer); + when(mockFileListener.onPreambleData(any(), eq(0), eq(data.length))).thenReturn(5); + when(mockFileListener.onPreambleData( + any(), eq(0), eq(data.length - 5))).thenReturn(preambleLen - 5); + when(mockFileListener.onPreambleData( + any(), eq(0), eq(data.length - preambleLen))).thenReturn(0); + + bufferedFile.onReadReady(mockAsyncFile); + + final ArgumentCaptor<byte[]> arrayCaptor = ArgumentCaptor.forClass(byte[].class); + final ArgumentCaptor<Integer> posCaptor = ArgumentCaptor.forClass(Integer.class); + final ArgumentCaptor<Integer> lenCaptor = ArgumentCaptor.forClass(Integer.class); + + verify(mockFileListener).onInboundBuffered(data.length, data.length); + verify(mockFileListener).onInboundPacket( + arrayCaptor.capture(), posCaptor.capture(), lenCaptor.capture()); + verify(mockEventManager).execute(any()); + verify(mockAsyncFile).enableReadEvents(true); + + final byte[] capturedData = arrayCaptor.getValue(); + assertEquals(2, posCaptor.getValue().intValue()); + assertEquals(len1, lenCaptor.getValue().intValue()); + assertEquals((byte) 1, capturedData[2]); + + assertEquals(0, bufferedFile.getOutboundBufferSize()); + assertEquals(0, inboundBuffer.size()); + } + + @Test + public void shutdownReading() throws Exception { + final int maxBufferedInboundPackets = 10; + final int maxBufferedOutboundPackets = 20; + + final StreamingPacketFile file = + createFile(maxBufferedInboundPackets, maxBufferedOutboundPackets); + final BufferedFile bufferedFile = file.getUnderlyingFileForTest(); + + final byte[] data = new byte[100]; + final ReadableDataAnswer dataAnswer = new ReadableDataAnswer(data); + when(mockAsyncFile.read(any(), anyInt(), anyInt())).thenAnswer(dataAnswer); + + doAnswer(new Answer() { + @Override public Object answer(InvocationOnMock invocation) { + file.shutdownReading(); + return Integer.valueOf(-1); + }}).when(mockFileListener).onPreambleData(any(), anyInt(), anyInt()); + + bufferedFile.onReadReady(mockAsyncFile); + + verify(mockFileListener).onInboundBuffered(data.length, data.length); + verify(mockAsyncFile).enableReadEvents(false); + + assertEquals(0, bufferedFile.getInboundBuffer().size()); + } + + private void checkAndResetMocks() { + verifyNoMoreInteractions(ignoreStubs(mockFileListener, mockAsyncFile, mockEventManager, + mockParcelFileDescriptor)); + reset(mockFileListener, mockAsyncFile, mockEventManager); + } + + private StreamingPacketFile createFile( + int maxBufferedInboundPackets, int maxBufferedOutboundPackets) throws Exception { + when(mockEventManager.registerFile(any(), any())).thenReturn(mockAsyncFile); + return new StreamingPacketFile( + mockEventManager, + FileHandle.fromFileDescriptor(mockParcelFileDescriptor), + mockFileListener, + MAX_PACKET_SIZE, + maxBufferedInboundPackets, + maxBufferedOutboundPackets); + } +} diff --git a/staticlibs/tests/unit/src/com/android/testutils/DeviceInfoUtilsTest.java b/staticlibs/tests/unit/src/com/android/testutils/DeviceInfoUtilsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..e46dd5997cde4fffea96fe8fbb491b484f950b7b --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/testutils/DeviceInfoUtilsTest.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.testutils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; + +@SmallTest +public final class DeviceInfoUtilsTest { + /** + * Verifies that version string compare logic returns expected result for various cases. + * Note that only major and minor number are compared. + */ + @Test + public void testMajorMinorVersionCompare() { + assertEquals(0, DeviceInfoUtils.compareMajorMinorVersion("4.8.1", "4.8")); + assertEquals(1, DeviceInfoUtils.compareMajorMinorVersion("4.9", "4.8.1")); + assertEquals(1, DeviceInfoUtils.compareMajorMinorVersion("5.0", "4.8")); + assertEquals(1, DeviceInfoUtils.compareMajorMinorVersion("5", "4.8")); + assertEquals(0, DeviceInfoUtils.compareMajorMinorVersion("5", "5.0")); + assertEquals(1, DeviceInfoUtils.compareMajorMinorVersion("5-beta1", "4.8")); + assertEquals(0, DeviceInfoUtils.compareMajorMinorVersion("4.8.0.0", "4.8")); + assertEquals(0, DeviceInfoUtils.compareMajorMinorVersion("4.8-RC1", "4.8")); + assertEquals(0, DeviceInfoUtils.compareMajorMinorVersion("4.8", "4.8")); + assertEquals(-1, DeviceInfoUtils.compareMajorMinorVersion("3.10", "4.8.0")); + assertEquals(-1, DeviceInfoUtils.compareMajorMinorVersion("4.7.10.10", "4.8")); + } + + @Test + public void testGetMajorMinorSubminorVersion() throws Exception { + final DeviceInfoUtils.KVersion expected = new DeviceInfoUtils.KVersion(4, 19, 220); + assertEquals(expected, DeviceInfoUtils.getMajorMinorSubminorVersion("4.19.220")); + assertEquals(expected, DeviceInfoUtils.getMajorMinorSubminorVersion("4.19.220.50")); + assertEquals(expected, DeviceInfoUtils.getMajorMinorSubminorVersion( + "4.19.220-g500ede0aed22-ab8272303")); + + final DeviceInfoUtils.KVersion expected2 = new DeviceInfoUtils.KVersion(5, 17, 0); + assertEquals(expected2, DeviceInfoUtils.getMajorMinorSubminorVersion("5.17")); + assertEquals(expected2, DeviceInfoUtils.getMajorMinorSubminorVersion("5.17.")); + assertEquals(expected2, DeviceInfoUtils.getMajorMinorSubminorVersion("5.17.beta")); + assertEquals(expected2, DeviceInfoUtils.getMajorMinorSubminorVersion( + "5.17-rc6-g52099515ca00-ab8032400")); + + final DeviceInfoUtils.KVersion invalid = new DeviceInfoUtils.KVersion(0, 0, 0); + assertEquals(invalid, DeviceInfoUtils.getMajorMinorSubminorVersion("")); + assertEquals(invalid, DeviceInfoUtils.getMajorMinorSubminorVersion("4")); + assertEquals(invalid, DeviceInfoUtils.getMajorMinorSubminorVersion("4.")); + assertEquals(invalid, DeviceInfoUtils.getMajorMinorSubminorVersion("4-beta")); + assertEquals(invalid, DeviceInfoUtils.getMajorMinorSubminorVersion("1.x.1")); + assertEquals(invalid, DeviceInfoUtils.getMajorMinorSubminorVersion("x.1.1")); + } + + @Test + public void testVersion() throws Exception { + final DeviceInfoUtils.KVersion v1 = new DeviceInfoUtils.KVersion(4, 8, 1); + final DeviceInfoUtils.KVersion v2 = new DeviceInfoUtils.KVersion(4, 8, 1); + final DeviceInfoUtils.KVersion v3 = new DeviceInfoUtils.KVersion(4, 8, 2); + final DeviceInfoUtils.KVersion v4 = new DeviceInfoUtils.KVersion(4, 9, 1); + final DeviceInfoUtils.KVersion v5 = new DeviceInfoUtils.KVersion(5, 8, 1); + + assertEquals(v1, v2); + assertNotEquals(v1, v3); + assertNotEquals(v1, v4); + assertNotEquals(v1, v5); + + assertEquals(0, v1.compareTo(v2)); + assertEquals(-1, v1.compareTo(v3)); + assertEquals(1, v3.compareTo(v1)); + assertEquals(-1, v1.compareTo(v4)); + assertEquals(1, v4.compareTo(v1)); + assertEquals(-1, v1.compareTo(v5)); + assertEquals(1, v5.compareTo(v1)); + + assertTrue(v2.isInRange(v1, v5)); + assertTrue(v3.isInRange(v1, v5)); + assertTrue(v4.isInRange(v1, v5)); + assertFalse(v5.isInRange(v1, v5)); + assertFalse(v1.isInRange(v3, v5)); + assertFalse(v5.isInRange(v2, v4)); + + assertTrue(v2.isAtLeast(v1)); + assertTrue(v3.isAtLeast(v1)); + assertTrue(v4.isAtLeast(v1)); + assertTrue(v5.isAtLeast(v1)); + assertFalse(v1.isAtLeast(v3)); + assertFalse(v1.isAtLeast(v4)); + assertFalse(v1.isAtLeast(v5)); + } + + @Test + public void testKernelVersionIsAtLeast() { + // Pick a lower kernel version 4.0 which was released at April 2015, the kernel + // version running on all test devices nowadays should be higher than it. + assertTrue(DeviceInfoUtils.isKernelVersionAtLeast("4.0")); + + // Invalid kernel version. + assertTrue(DeviceInfoUtils.isKernelVersionAtLeast("0.0.0")); + + // Pick a higher kernel version which isn't released yet, comparison should return false. + // Need to update the target version in the future to make sure the test still passes. + assertFalse(DeviceInfoUtils.isKernelVersionAtLeast("20.0.0")); + } +} diff --git a/staticlibs/tests/unit/src/com/android/testutils/HandlerUtilsTest.kt b/staticlibs/tests/unit/src/com/android/testutils/HandlerUtilsTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..0f6fa48baf63c2d5727394696629256bf47d2519 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/testutils/HandlerUtilsTest.kt @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.testutils + +import android.os.Handler +import android.os.HandlerThread +import com.android.testutils.FunctionalUtils.ThrowingSupplier +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertNull +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +private const val ATTEMPTS = 50 // Causes testWaitForIdle to take about 150ms on aosp_crosshatch-eng +private const val TIMEOUT_MS = 200 + +@RunWith(JUnit4::class) +class HandlerUtilsTest { + @Test + fun testWaitForIdle() { + val handlerThread = HandlerThread("testHandler").apply { start() } + + // Tests that waitForIdle can be called many times without ill impact if the service is + // already idle. + repeat(ATTEMPTS) { + handlerThread.waitForIdle(TIMEOUT_MS) + } + + // Tests that calling waitForIdle waits for messages to be processed. Use both an + // inline runnable that's instantiated at each loop run and a runnable that's instantiated + // once for all. + val tempRunnable = object : Runnable { + // Use StringBuilder preferentially to StringBuffer because StringBuilder is NOT + // thread-safe. It's part of the point that both runnables run on the same thread + // so if anything is wrong in that space it's better to opportunistically use a class + // where things might go wrong, even if there is no guarantee of failure. + var memory = StringBuilder() + override fun run() { + memory.append("b") + } + } + repeat(ATTEMPTS) { i -> + handlerThread.threadHandler.post { tempRunnable.memory.append("a"); } + handlerThread.threadHandler.post(tempRunnable) + handlerThread.waitForIdle(TIMEOUT_MS) + assertEquals(tempRunnable.memory.toString(), "ab".repeat(i + 1)) + } + } + + // Statistical test : even if visibleOnHandlerThread doesn't work this is likely to succeed, + // but it will be at least flaky. + @Test + fun testVisibleOnHandlerThread() { + val handlerThread = HandlerThread("testHandler").apply { start() } + val handler = Handler(handlerThread.looper) + + repeat(ATTEMPTS) { attempt -> + var x = -10 + var y = -11 + y = visibleOnHandlerThread(handler, ThrowingSupplier<Int> { x = attempt; attempt }) + assertEquals(attempt, x) + assertEquals(attempt, y) + handler.post { assertEquals(attempt, x) } + } + + assertFailsWith<IllegalArgumentException> { + visibleOnHandlerThread(handler) { throw IllegalArgumentException() } + } + + // Null values may be returned by the supplier + assertNull(visibleOnHandlerThread(handler, ThrowingSupplier<Nothing?> { null })) + } +} diff --git a/staticlibs/tests/unit/src/com/android/testutils/TestDnsServerTest.kt b/staticlibs/tests/unit/src/com/android/testutils/TestDnsServerTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..6f4587b140842ffb46f87ff2c7d8edfa53ccaf87 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/testutils/TestDnsServerTest.kt @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.testutils + +import android.net.DnsResolver.CLASS_IN +import android.net.DnsResolver.TYPE_AAAA +import android.net.Network +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.net.module.util.DnsPacket +import com.android.net.module.util.DnsPacket.DnsRecord +import libcore.net.InetAddressUtils +import org.junit.After +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito +import java.net.DatagramPacket +import java.net.DatagramSocket +import java.net.InetAddress +import java.net.InetSocketAddress +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +val TEST_V6_ADDR = InetAddressUtils.parseNumericAddress("2001:db8::3") +const val TEST_DOMAIN = "hello.example.com" + +@RunWith(AndroidJUnit4::class) +@SmallTest +class TestDnsServerTest { + private val network = Mockito.mock(Network::class.java) + private val localAddr = InetSocketAddress(InetAddress.getLocalHost(), 0 /* port */) + private val testServer: TestDnsServer = TestDnsServer(network, localAddr) + + @After + fun tearDown() { + if (testServer.isAlive) testServer.stop() + } + + @Test + fun testStartStop() { + repeat(100) { + val server = TestDnsServer(network, localAddr) + server.start() + assertTrue(server.isAlive) + server.stop() + assertFalse(server.isAlive) + } + + // Test illegal start/stop. + assertFailsWith<IllegalStateException> { testServer.stop() } + testServer.start() + assertTrue(testServer.isAlive) + assertFailsWith<IllegalStateException> { testServer.start() } + testServer.stop() + assertFalse(testServer.isAlive) + assertFailsWith<IllegalStateException> { testServer.stop() } + // TestDnsServer rejects start after stop. + assertFailsWith<IllegalStateException> { testServer.start() } + } + + @Test + fun testHandleDnsQuery() { + testServer.setAnswer(TEST_DOMAIN, listOf(TEST_V6_ADDR)) + testServer.start() + + // Mock query and send it to the test server. + val queryHeader = DnsPacket.DnsHeader(0xbeef /* id */, + 0x0 /* flag */, 1 /* qcount */, 0 /* ancount */) + val qlist = listOf(DnsRecord.makeQuestion(TEST_DOMAIN, TYPE_AAAA, CLASS_IN)) + val queryPacket = TestDnsServer.DnsQueryPacket(queryHeader, qlist, emptyList()) + val response = resolve(queryPacket, testServer.port) + + // Verify expected answer packet. Set QR bit of flag to 1 for response packet + // according to RFC 1035 section 4.1.1. + val answerHeader = DnsPacket.DnsHeader(0xbeef, + 1 shl 15 /* flag */, 1 /* qcount */, 1 /* ancount */) + val alist = listOf(DnsRecord.makeAOrAAAARecord(DnsPacket.ANSECTION, TEST_DOMAIN, + CLASS_IN, DEFAULT_TTL_S, TEST_V6_ADDR)) + val expectedAnswerPacket = TestDnsServer.DnsAnswerPacket(answerHeader, qlist, alist) + assertEquals(expectedAnswerPacket, response) + + // Clean up the server in tearDown. + } + + private fun resolve(queryDnsPacket: DnsPacket, serverPort: Int): TestDnsServer.DnsAnswerPacket { + val bytes = queryDnsPacket.bytes + // Create a new client socket, the socket will be bound to a + // random port other than the server port. + val socket = DatagramSocket(localAddr).also { it.soTimeout = 100 } + val queryPacket = DatagramPacket(bytes, bytes.size, localAddr.address, serverPort) + + // Send query and wait for the reply. + socket.send(queryPacket) + val buffer = ByteArray(MAX_BUF_SIZE) + val reply = DatagramPacket(buffer, buffer.size) + socket.receive(reply) + return TestDnsServer.DnsAnswerPacket(reply.data) + } + + // TODO: Add more tests, which includes: + // * Empty question RR packet (or more unexpected states) + // * No answer found (setAnswer empty list at L.78) + // * Test one or multi A record(s) + // * Test multi AAAA records + // * Test CNAME records +} diff --git a/staticlibs/tests/unit/src/com/android/testutils/TestableNetworkCallbackTest.kt b/staticlibs/tests/unit/src/com/android/testutils/TestableNetworkCallbackTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..e838bdc564677980e6d25f80cbfa873255e875d6 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/testutils/TestableNetworkCallbackTest.kt @@ -0,0 +1,474 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.testutils + +import android.annotation.SuppressLint +import android.net.LinkAddress +import android.net.LinkProperties +import android.net.Network +import android.net.NetworkCapabilities +import com.android.testutils.RecorderCallback.CallbackEntry +import com.android.testutils.RecorderCallback.CallbackEntry.Available +import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus +import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged +import com.android.testutils.RecorderCallback.CallbackEntry.Companion.AVAILABLE +import com.android.testutils.RecorderCallback.CallbackEntry.Companion.BLOCKED_STATUS +import com.android.testutils.RecorderCallback.CallbackEntry.Companion.LINK_PROPERTIES_CHANGED +import com.android.testutils.RecorderCallback.CallbackEntry.Companion.LOSING +import com.android.testutils.RecorderCallback.CallbackEntry.Companion.LOST +import com.android.testutils.RecorderCallback.CallbackEntry.Companion.NETWORK_CAPS_UPDATED +import com.android.testutils.RecorderCallback.CallbackEntry.Companion.RESUMED +import com.android.testutils.RecorderCallback.CallbackEntry.Companion.SUSPENDED +import com.android.testutils.RecorderCallback.CallbackEntry.Companion.UNAVAILABLE +import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged +import kotlin.reflect.KClass +import kotlin.test.assertEquals +import kotlin.test.assertFails +import kotlin.test.assertNull +import kotlin.test.assertTrue +import kotlin.test.fail +import org.junit.Assume.assumeTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +const val SHORT_TIMEOUT_MS = 20L +const val DEFAULT_LINGER_DELAY_MS = 30000 +const val NOT_METERED = NetworkCapabilities.NET_CAPABILITY_NOT_METERED +const val WIFI = NetworkCapabilities.TRANSPORT_WIFI +const val CELLULAR = NetworkCapabilities.TRANSPORT_CELLULAR +const val TEST_INTERFACE_NAME = "testInterfaceName" + +@RunWith(JUnit4::class) +@SuppressLint("NewApi") // Uses hidden APIs, which the linter would identify as missing APIs. +class TestableNetworkCallbackTest { + private lateinit var mCallback: TestableNetworkCallback + + private fun makeHasNetwork(netId: Int) = object : TestableNetworkCallback.HasNetwork { + override val network: Network = Network(netId) + } + + @Before + fun setUp() { + mCallback = TestableNetworkCallback() + } + + @Test + fun testLastAvailableNetwork() { + // Make sure there is no last available network at first, then the last available network + // is returned after onAvailable is called. + val net2097 = Network(2097) + assertNull(mCallback.lastAvailableNetwork) + mCallback.onAvailable(net2097) + assertEquals(mCallback.lastAvailableNetwork, net2097) + + // Make sure calling onCapsChanged/onLinkPropertiesChanged don't affect the last available + // network. + mCallback.onCapabilitiesChanged(net2097, NetworkCapabilities()) + mCallback.onLinkPropertiesChanged(net2097, LinkProperties()) + assertEquals(mCallback.lastAvailableNetwork, net2097) + + // Make sure onLost clears the last available network. + mCallback.onLost(net2097) + assertNull(mCallback.lastAvailableNetwork) + + // Do the same but with a different network after onLost : make sure the last available + // network is the new one, not the original one. + val net2098 = Network(2098) + mCallback.onAvailable(net2098) + mCallback.onCapabilitiesChanged(net2098, NetworkCapabilities()) + mCallback.onLinkPropertiesChanged(net2098, LinkProperties()) + assertEquals(mCallback.lastAvailableNetwork, net2098) + + // Make sure onAvailable changes the last available network even if onLost was not called. + val net2099 = Network(2099) + mCallback.onAvailable(net2099) + assertEquals(mCallback.lastAvailableNetwork, net2099) + + // For legacy reasons, lastAvailableNetwork is null as soon as any is lost, not necessarily + // the last available one. Check that behavior. + mCallback.onLost(net2098) + assertNull(mCallback.lastAvailableNetwork) + + // Make sure that losing the really last available one still results in null. + mCallback.onLost(net2099) + assertNull(mCallback.lastAvailableNetwork) + + // Make sure multiple onAvailable in a row then onLost still results in null. + mCallback.onAvailable(net2097) + mCallback.onAvailable(net2098) + mCallback.onAvailable(net2099) + mCallback.onLost(net2097) + assertNull(mCallback.lastAvailableNetwork) + } + + @Test + fun testAssertNoCallback() { + mCallback.assertNoCallback(SHORT_TIMEOUT_MS) + mCallback.onAvailable(Network(100)) + assertFails { mCallback.assertNoCallback(SHORT_TIMEOUT_MS) } + val net = Network(101) + mCallback.assertNoCallback { it is Available } + mCallback.onAvailable(net) + // Expect no blocked status change. Receive other callback does not fail the test. + mCallback.assertNoCallback { it is BlockedStatus } + mCallback.onBlockedStatusChanged(net, true) + assertFails { mCallback.assertNoCallback { it is BlockedStatus } } + mCallback.onBlockedStatusChanged(net, false) + mCallback.onCapabilitiesChanged(net, NetworkCapabilities()) + assertFails { mCallback.assertNoCallback { it is CapabilitiesChanged } } + } + + @Test + fun testCapabilitiesWithAndWithout() { + val net = Network(101) + val matcher = makeHasNetwork(101) + val meteredNc = NetworkCapabilities() + val unmeteredNc = NetworkCapabilities().addCapability(NOT_METERED) + // Check that expecting caps (with or without) fails when no callback has been received. + assertFails { + mCallback.expectCaps(matcher, SHORT_TIMEOUT_MS) { it.hasCapability(NOT_METERED) } + } + assertFails { + mCallback.expectCaps(matcher, SHORT_TIMEOUT_MS) { !it.hasCapability(NOT_METERED) } + } + + // Add NOT_METERED and check that With succeeds and Without fails. + mCallback.onCapabilitiesChanged(net, unmeteredNc) + mCallback.expectCaps(matcher) { it.hasCapability(NOT_METERED) } + mCallback.onCapabilitiesChanged(net, unmeteredNc) + assertFails { + mCallback.expectCaps(matcher, SHORT_TIMEOUT_MS) { !it.hasCapability(NOT_METERED) } + } + + // Don't add NOT_METERED and check that With fails and Without succeeds. + mCallback.onCapabilitiesChanged(net, meteredNc) + assertFails { + mCallback.expectCaps(matcher, SHORT_TIMEOUT_MS) { it.hasCapability(NOT_METERED) } + } + mCallback.onCapabilitiesChanged(net, meteredNc) + mCallback.expectCaps(matcher) { !it.hasCapability(NOT_METERED) } + } + + @Test + fun testExpectWithPredicate() { + val net = Network(193) + val netCaps = NetworkCapabilities().addTransportType(CELLULAR) + // Check that expecting callbackThat anything fails when no callback has been received. + assertFails { mCallback.expect<CallbackEntry>(timeoutMs = SHORT_TIMEOUT_MS) { true } } + + // Basic test for true and false + mCallback.onAvailable(net) + mCallback.expect<Available> { true } + mCallback.onAvailable(net) + assertFails { mCallback.expect<CallbackEntry>(timeoutMs = SHORT_TIMEOUT_MS) { false } } + + // Try a positive and a negative case + mCallback.onBlockedStatusChanged(net, true) + mCallback.expect<CallbackEntry> { cb -> cb is BlockedStatus && cb.blocked } + mCallback.onCapabilitiesChanged(net, netCaps) + assertFails { mCallback.expect<CallbackEntry>(timeoutMs = SHORT_TIMEOUT_MS) { cb -> + cb is CapabilitiesChanged && cb.caps.hasTransport(WIFI) + } } + } + + @Test + fun testExpectCaps() { + val net = Network(101) + val netCaps = NetworkCapabilities().addCapability(NOT_METERED).addTransportType(WIFI) + // Check that expecting capabilitiesThat anything fails when no callback has been received. + assertFails { mCallback.expectCaps(net, SHORT_TIMEOUT_MS) { true } } + + // Basic test for true and false + mCallback.onCapabilitiesChanged(net, netCaps) + mCallback.expectCaps(net) { true } + mCallback.onCapabilitiesChanged(net, netCaps) + assertFails { mCallback.expectCaps(net, SHORT_TIMEOUT_MS) { false } } + + // Try a positive and a negative case + mCallback.onCapabilitiesChanged(net, netCaps) + mCallback.expectCaps(net) { + it.hasCapability(NOT_METERED) && it.hasTransport(WIFI) && !it.hasTransport(CELLULAR) + } + mCallback.onCapabilitiesChanged(net, netCaps) + assertFails { mCallback.expectCaps(net, SHORT_TIMEOUT_MS) { it.hasTransport(CELLULAR) } } + + // Try a matching callback on the wrong network + mCallback.onCapabilitiesChanged(net, netCaps) + assertFails { + mCallback.expectCaps(Network(100), SHORT_TIMEOUT_MS) { true } + } + } + + @Test + fun testLinkPropertiesCallbacks() { + val net = Network(112) + val linkAddress = LinkAddress("fe80::ace:d00d/64") + val mtu = 1984 + val linkProps = LinkProperties().apply { + this.mtu = mtu + interfaceName = TEST_INTERFACE_NAME + addLinkAddress(linkAddress) + } + + // Check that expecting linkPropsThat anything fails when no callback has been received. + assertFails { mCallback.expect<LinkPropertiesChanged>(net, SHORT_TIMEOUT_MS) { true } } + + // Basic test for true and false + mCallback.onLinkPropertiesChanged(net, linkProps) + mCallback.expect<LinkPropertiesChanged>(net) { true } + mCallback.onLinkPropertiesChanged(net, linkProps) + assertFails { mCallback.expect<LinkPropertiesChanged>(net, SHORT_TIMEOUT_MS) { false } } + + // Try a positive and negative case + mCallback.onLinkPropertiesChanged(net, linkProps) + mCallback.expect<LinkPropertiesChanged>(net) { + it.lp.interfaceName == TEST_INTERFACE_NAME && + it.lp.linkAddresses.contains(linkAddress) && + it.lp.mtu == mtu + } + mCallback.onLinkPropertiesChanged(net, linkProps) + assertFails { mCallback.expect<LinkPropertiesChanged>(net, SHORT_TIMEOUT_MS) { + it.lp.interfaceName != TEST_INTERFACE_NAME + } } + + // Try a matching callback on the wrong network + mCallback.onLinkPropertiesChanged(net, linkProps) + assertFails { mCallback.expect<LinkPropertiesChanged>(Network(114), SHORT_TIMEOUT_MS) { + it.lp.interfaceName == TEST_INTERFACE_NAME + } } + } + + @Test + fun testExpect() { + val net = Network(103) + // Test expectCallback fails when nothing was sent. + assertFails { mCallback.expect<BlockedStatus>(net, SHORT_TIMEOUT_MS) } + + // Test onAvailable is seen and can be expected + mCallback.onAvailable(net) + mCallback.expect<Available>(net, SHORT_TIMEOUT_MS) + + // Test onAvailable won't return calls with a different network + mCallback.onAvailable(Network(106)) + assertFails { mCallback.expect<Available>(net, SHORT_TIMEOUT_MS) } + + // Test onAvailable won't return calls with a different callback + mCallback.onAvailable(net) + assertFails { mCallback.expect<BlockedStatus>(net, SHORT_TIMEOUT_MS) } + } + + @Test + fun testAllExpectOverloads() { + // This test should never run, it only checks that all overloads exist and build + assumeTrue(false) + val hn = object : TestableNetworkCallback.HasNetwork { override val network = ANY_NETWORK } + + // Method with all arguments (version that takes a Network) + mCallback.expect(AVAILABLE, ANY_NETWORK, 10, "error") { true } + + // Java overloads omitting one argument. One line for omitting each argument, in positional + // order. Versions that take a Network. + mCallback.expect(AVAILABLE, 10, "error") { true } + mCallback.expect(AVAILABLE, ANY_NETWORK, "error") { true } + mCallback.expect(AVAILABLE, ANY_NETWORK, 10) { true } + mCallback.expect(AVAILABLE, ANY_NETWORK, 10, "error") + + // Java overloads for omitting two arguments. One line for omitting each pair of arguments. + // Versions that take a Network. + mCallback.expect(AVAILABLE, "error") { true } + mCallback.expect(AVAILABLE, 10) { true } + mCallback.expect(AVAILABLE, 10, "error") + mCallback.expect(AVAILABLE, ANY_NETWORK) { true } + mCallback.expect(AVAILABLE, ANY_NETWORK, "error") + mCallback.expect(AVAILABLE, ANY_NETWORK, 10) + + // Java overloads for omitting three arguments. One line for each remaining argument. + // Versions that take a Network. + mCallback.expect(AVAILABLE) { true } + mCallback.expect(AVAILABLE, "error") + mCallback.expect(AVAILABLE, 10) + mCallback.expect(AVAILABLE, ANY_NETWORK) + + // Java overload for omitting all four arguments. + mCallback.expect(AVAILABLE) + + // Same orders as above, but versions that take a HasNetwork. Except overloads that + // were already tested because they omitted the Network argument + mCallback.expect(AVAILABLE, hn, 10, "error") { true } + mCallback.expect(AVAILABLE, hn, "error") { true } + mCallback.expect(AVAILABLE, hn, 10) { true } + mCallback.expect(AVAILABLE, hn, 10, "error") + + mCallback.expect(AVAILABLE, hn) { true } + mCallback.expect(AVAILABLE, hn, "error") + mCallback.expect(AVAILABLE, hn, 10) + + mCallback.expect(AVAILABLE, hn) + + // Same as above but for reified versions. + mCallback.expect<Available>(ANY_NETWORK, 10, "error") { true } + mCallback.expect<Available>(timeoutMs = 10, errorMsg = "error") { true } + mCallback.expect<Available>(network = ANY_NETWORK, errorMsg = "error") { true } + mCallback.expect<Available>(network = ANY_NETWORK, timeoutMs = 10) { true } + mCallback.expect<Available>(network = ANY_NETWORK, timeoutMs = 10, errorMsg = "error") + + mCallback.expect<Available>(errorMsg = "error") { true } + mCallback.expect<Available>(timeoutMs = 10) { true } + mCallback.expect<Available>(timeoutMs = 10, errorMsg = "error") + mCallback.expect<Available>(network = ANY_NETWORK) { true } + mCallback.expect<Available>(network = ANY_NETWORK, errorMsg = "error") + mCallback.expect<Available>(network = ANY_NETWORK, timeoutMs = 10) + + mCallback.expect<Available> { true } + mCallback.expect<Available>(errorMsg = "error") + mCallback.expect<Available>(timeoutMs = 10) + mCallback.expect<Available>(network = ANY_NETWORK) + mCallback.expect<Available>() + + mCallback.expect<Available>(hn, 10, "error") { true } + mCallback.expect<Available>(network = hn, errorMsg = "error") { true } + mCallback.expect<Available>(network = hn, timeoutMs = 10) { true } + mCallback.expect<Available>(network = hn, timeoutMs = 10, errorMsg = "error") + + mCallback.expect<Available>(network = hn) { true } + mCallback.expect<Available>(network = hn, errorMsg = "error") + mCallback.expect<Available>(network = hn, timeoutMs = 10) + + mCallback.expect<Available>(network = hn) + } + + @Test + fun testExpectClass() { + val net = Network(1) + mCallback.onAvailable(net) + assertFails { mCallback.expect(LOST, net) } + } + + @Test + fun testPoll() { + assertNull(mCallback.poll(SHORT_TIMEOUT_MS)) + TNCInterpreter.interpretTestSpec(initial = mCallback, lineShift = 1, + threadTransform = { cb -> cb.createLinkedCopy() }, spec = """ + sleep; onAvailable(133) | poll(2) = Available(133) time 1..4 + | poll(1) = null + onCapabilitiesChanged(108) | poll(1) = CapabilitiesChanged(108) time 0..3 + onBlockedStatus(199) | poll(1) = BlockedStatus(199) time 0..3 + """) + } + + @Test + fun testEventuallyExpect() { + // TODO: Current test does not verify the inline one. Also verify the behavior after + // aligning two eventuallyExpect() + val net1 = Network(100) + val net2 = Network(101) + mCallback.onAvailable(net1) + mCallback.onCapabilitiesChanged(net1, NetworkCapabilities()) + mCallback.onLinkPropertiesChanged(net1, LinkProperties()) + mCallback.eventuallyExpect(LINK_PROPERTIES_CHANGED) { + net1.equals(it.network) + } + // No further new callback. Expect no callback. + assertFails { mCallback.eventuallyExpect(LINK_PROPERTIES_CHANGED, SHORT_TIMEOUT_MS) } + + // Verify no predicate set. + mCallback.onAvailable(net2) + mCallback.onLinkPropertiesChanged(net2, LinkProperties()) + mCallback.onBlockedStatusChanged(net1, false) + mCallback.eventuallyExpect(BLOCKED_STATUS) { net1.equals(it.network) } + // Verify no callback received if the callback does not happen. + assertFails { mCallback.eventuallyExpect(LOSING, SHORT_TIMEOUT_MS) } + } + + @Test + fun testEventuallyExpectOnMultiThreads() { + TNCInterpreter.interpretTestSpec(initial = mCallback, lineShift = 1, + threadTransform = { cb -> cb.createLinkedCopy() }, spec = """ + onAvailable(100) | eventually(CapabilitiesChanged(100), 1) fails + sleep ; onCapabilitiesChanged(100) | eventually(CapabilitiesChanged(100), 3) + onAvailable(101) ; onBlockedStatus(101) | eventually(BlockedStatus(100), 2) fails + onSuspended(100) ; sleep ; onLost(100) | eventually(Lost(100), 3) + """) + } +} + +private object TNCInterpreter : ConcurrentInterpreter<TestableNetworkCallback>(interpretTable) + +val EntryList = CallbackEntry::class.sealedSubclasses.map { it.simpleName }.joinToString("|") +private fun callbackEntryFromString(name: String): KClass<out CallbackEntry> { + return CallbackEntry::class.sealedSubclasses.first { it.simpleName == name } +} + +@SuppressLint("NewApi") // Uses hidden APIs, which the linter would identify as missing APIs. +private val interpretTable = listOf<InterpretMatcher<TestableNetworkCallback>>( + // Interpret "Available(xx)" as "call to onAvailable with netId xx", and likewise for + // all callback types. This is implemented above by enumerating the subclasses of + // CallbackEntry and reading their simpleName. + Regex("""(.*)\s+=\s+($EntryList)\((\d+)\)""") to { i, cb, t -> + val record = i.interpret(t.strArg(1), cb) + assertTrue(callbackEntryFromString(t.strArg(2)).isInstance(record)) + // Strictly speaking testing for is CallbackEntry is useless as it's been tested above + // but the compiler can't figure things out from the isInstance call. It does understand + // from the assertTrue(is CallbackEntry) that this is true, which allows to access + // the 'network' member below. + assertTrue(record is CallbackEntry) + assertEquals(record.network.netId, t.intArg(3)) + }, + // Interpret "onAvailable(xx)" as calling "onAvailable" with a netId of xx, and likewise for + // all callback types. NetworkCapabilities and LinkProperties just get an empty object + // as their argument. Losing gets the default linger timer. Blocked gets false. + Regex("""on($EntryList)\((\d+)\)""") to { i, cb, t -> + val net = Network(t.intArg(2)) + when (t.strArg(1)) { + "Available" -> cb.onAvailable(net) + // PreCheck not used in tests. Add it here if it becomes useful. + "CapabilitiesChanged" -> cb.onCapabilitiesChanged(net, NetworkCapabilities()) + "LinkPropertiesChanged" -> cb.onLinkPropertiesChanged(net, LinkProperties()) + "Suspended" -> cb.onNetworkSuspended(net) + "Resumed" -> cb.onNetworkResumed(net) + "Losing" -> cb.onLosing(net, DEFAULT_LINGER_DELAY_MS) + "Lost" -> cb.onLost(net) + "Unavailable" -> cb.onUnavailable() + "BlockedStatus" -> cb.onBlockedStatusChanged(net, false) + else -> fail("Unknown callback type") + } + }, + Regex("""poll\((\d+)\)""") to { i, cb, t -> cb.poll(t.timeArg(1)) }, + // Interpret "eventually(Available(xx), timeout)" as calling eventuallyExpect that expects + // CallbackEntry.AVAILABLE with netId of xx within timeout*INTERPRET_TIME_UNIT timeout, and + // likewise for all callback types. + Regex("""eventually\(($EntryList)\((\d+)\),\s+(\d+)\)""") to { i, cb, t -> + val net = Network(t.intArg(2)) + val timeout = t.timeArg(3) + when (t.strArg(1)) { + "Available" -> cb.eventuallyExpect(AVAILABLE, timeout) { net == it.network } + "Suspended" -> cb.eventuallyExpect(SUSPENDED, timeout) { net == it.network } + "Resumed" -> cb.eventuallyExpect(RESUMED, timeout) { net == it.network } + "Losing" -> cb.eventuallyExpect(LOSING, timeout) { net == it.network } + "Lost" -> cb.eventuallyExpect(LOST, timeout) { net == it.network } + "Unavailable" -> cb.eventuallyExpect(UNAVAILABLE, timeout) { net == it.network } + "BlockedStatus" -> cb.eventuallyExpect(BLOCKED_STATUS, timeout) { net == it.network } + "CapabilitiesChanged" -> + cb.eventuallyExpect(NETWORK_CAPS_UPDATED, timeout) { net == it.network } + "LinkPropertiesChanged" -> + cb.eventuallyExpect(LINK_PROPERTIES_CHANGED, timeout) { net == it.network } + else -> fail("Unknown callback type") + } + } +) diff --git a/staticlibs/tests/unit/src/com/android/testutils/TestableNetworkCallbackTestJava.java b/staticlibs/tests/unit/src/com/android/testutils/TestableNetworkCallbackTestJava.java new file mode 100644 index 0000000000000000000000000000000000000000..4570d0aecb94e1e34d99ca9add0d9e7a2b51383f --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/testutils/TestableNetworkCallbackTestJava.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.testutils; + +import static com.android.testutils.RecorderCallback.CallbackEntry.AVAILABLE; +import static com.android.testutils.TestableNetworkCallbackKt.anyNetwork; + +import static org.junit.Assume.assumeTrue; + +import org.junit.Test; + +public class TestableNetworkCallbackTestJava { + @Test + void testAllExpectOverloads() { + // This test should never run, it only checks that all overloads exist and build + assumeTrue(false); + final TestableNetworkCallback callback = new TestableNetworkCallback(); + TestableNetworkCallback.HasNetwork hn = TestableNetworkCallbackKt::anyNetwork; + + // Method with all arguments (version that takes a Network) + callback.expect(AVAILABLE, anyNetwork(), 10, "error", cb -> true); + + // Overloads omitting one argument. One line for omitting each argument, in positional + // order. Versions that take a Network. + callback.expect(AVAILABLE, 10, "error", cb -> true); + callback.expect(AVAILABLE, anyNetwork(), "error", cb -> true); + callback.expect(AVAILABLE, anyNetwork(), 10, cb -> true); + callback.expect(AVAILABLE, anyNetwork(), 10, "error"); + + // Overloads for omitting two arguments. One line for omitting each pair of arguments. + // Versions that take a Network. + callback.expect(AVAILABLE, "error", cb -> true); + callback.expect(AVAILABLE, 10, cb -> true); + callback.expect(AVAILABLE, 10, "error"); + callback.expect(AVAILABLE, anyNetwork(), cb -> true); + callback.expect(AVAILABLE, anyNetwork(), "error"); + callback.expect(AVAILABLE, anyNetwork(), 10); + + // Overloads for omitting three arguments. One line for each remaining argument. + // Versions that take a Network. + callback.expect(AVAILABLE, cb -> true); + callback.expect(AVAILABLE, "error"); + callback.expect(AVAILABLE, 10); + callback.expect(AVAILABLE, anyNetwork()); + + // Java overload for omitting all four arguments. + callback.expect(AVAILABLE); + + // Same orders as above, but versions that take a HasNetwork. Except overloads that + // were already tested because they omitted the Network argument + callback.expect(AVAILABLE, hn, 10, "error", cb -> true); + callback.expect(AVAILABLE, hn, "error", cb -> true); + callback.expect(AVAILABLE, hn, 10, cb -> true); + callback.expect(AVAILABLE, hn, 10, "error"); + + callback.expect(AVAILABLE, hn, cb -> true); + callback.expect(AVAILABLE, hn, "error"); + callback.expect(AVAILABLE, hn, 10); + + callback.expect(AVAILABLE, hn); + } +} diff --git a/staticlibs/testutils/Android.bp b/staticlibs/testutils/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..5fe7ac3a9661037d07e7d9ca547d28251bbc0702 --- /dev/null +++ b/staticlibs/testutils/Android.bp @@ -0,0 +1,88 @@ +// 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. + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +java_library { + name: "net-tests-utils", + srcs: [ + "devicetests/**/*.java", + "devicetests/**/*.kt", + ], + defaults: [ + "framework-connectivity-test-defaults", + "lib_mockito_extended" + ], + libs: [ + "androidx.annotation_annotation", + "net-utils-device-common-bpf", // TestBpfMap extends IBpfMap. + ], + static_libs: [ + "androidx.test.ext.junit", + "kotlin-reflect", + "libnanohttpd", + "net-tests-utils-host-device-common", + "net-utils-device-common", + "net-utils-device-common-async", + "net-utils-device-common-netlink", + "net-utils-device-common-wear", + "modules-utils-build_system", + ], + lint: { strict_updatability_linting: true }, +} + +java_library { + // Consider using net-tests-utils instead if writing device code. + // That library has a lot more useful tools into it for users that + // work on Android and includes this lib. + name: "net-tests-utils-host-device-common", + srcs: [ + "hostdevice/**/*.java", + "hostdevice/**/*.kt", + ], + host_supported: true, + visibility: [ + "//packages/modules/Connectivity/staticlibs/tests:__subpackages__", + "//packages/modules/Connectivity/staticlibs/client-libs/tests:__subpackages__", + "//packages/modules/Connectivity/tests/cts/hostside", + ], + // There are downstream branches using an old version of Kotlin + // that used to reserve the right to make breaking changes to the + // Result type and disallowed returning an instance of it. + // Later versions allowed this and there was never a change, + // so no matter the version returning Result is always fine, + // but on sc-mainline-prod the compiler rejects it without + // the following flag. + kotlincflags: ["-Xallow-result-return-type"], + libs: [ + "jsr305", + ], + static_libs: [ + "kotlin-test" + ], + lint: { strict_updatability_linting: true }, +} + +java_test_host { + name: "net-tests-utils-host-common", + srcs: [ + "host/**/*.java", + "host/**/*.kt", + ], + libs: ["tradefed"], + test_suites: ["ats", "device-tests", "general-tests", "cts", "mts-networking"], + data: [":ConnectivityTestPreparer"], +} diff --git a/staticlibs/testutils/app/connectivitychecker/Android.bp b/staticlibs/testutils/app/connectivitychecker/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..f7118cfaa067edd352bb0e98cafc460e0b4becd1 --- /dev/null +++ b/staticlibs/testutils/app/connectivitychecker/Android.bp @@ -0,0 +1,34 @@ +// 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. + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_test_helper_app { + name: "ConnectivityTestPreparer", + srcs: ["src/**/*.kt"], + sdk_version: "system_current", + // Allow running the test on any device with SDK Q+, even when built from a branch that uses + // an unstable SDK, by targeting a stable SDK regardless of the build SDK. + min_sdk_version: "29", + target_sdk_version: "30", + static_libs: [ + "androidx.test.rules", + "modules-utils-build_system", + "net-tests-utils", + ], + host_required: ["net-tests-utils-host-common"], + lint: { strict_updatability_linting: true }, +} diff --git a/staticlibs/testutils/app/connectivitychecker/AndroidManifest.xml b/staticlibs/testutils/app/connectivitychecker/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..015b41f9bae9ba983e637b7699c82110345af9d1 --- /dev/null +++ b/staticlibs/testutils/app/connectivitychecker/AndroidManifest.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.testutils.connectivitypreparer"> + + <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> + <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> + <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> + <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> + <!-- For wifi scans --> + <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> + <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.testutils.connectivitypreparer" + android:label="Connectivity test target preparer" /> +</manifest> diff --git a/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt b/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..f1f0975457c5738847be29bde40580c8fa04fb84 --- /dev/null +++ b/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt @@ -0,0 +1,88 @@ +/* + * 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. + */ + +package com.android.testutils.connectivitypreparer + +import android.content.pm.PackageManager.FEATURE_TELEPHONY +import android.content.pm.PackageManager.FEATURE_WIFI +import android.net.ConnectivityManager +import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET +import android.net.NetworkCapabilities.TRANSPORT_CELLULAR +import android.net.NetworkRequest +import android.telephony.TelephonyManager +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.android.testutils.ConnectUtil +import com.android.testutils.RecorderCallback +import com.android.testutils.TestableNetworkCallback +import com.android.testutils.tryTest +import kotlin.test.assertTrue +import kotlin.test.fail +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ConnectivityCheckTest { + val context by lazy { InstrumentationRegistry.getInstrumentation().context } + val pm by lazy { context.packageManager } + + @Test + fun testCheckConnectivity() { + checkWifiSetup() + checkTelephonySetup() + } + + private fun checkWifiSetup() { + if (!pm.hasSystemFeature(FEATURE_WIFI)) return + ConnectUtil(context).ensureWifiConnected() + } + + private fun checkTelephonySetup() { + if (!pm.hasSystemFeature(FEATURE_TELEPHONY)) return + val tm = context.getSystemService(TelephonyManager::class.java) + ?: fail("Could not get telephony service") + + val commonError = "Check the test bench. To run the tests anyway for quick & dirty local " + + "testing, you can use atest X -- " + + "--test-arg com.android.testutils.ConnectivityTestTargetPreparer" + + ":ignore-connectivity-check:true" + // Do not use assertEquals: it outputs "expected X, was Y", which looks like a test failure + if (tm.simState == TelephonyManager.SIM_STATE_ABSENT) { + fail("The device has no SIM card inserted. $commonError") + } else if (tm.simState != TelephonyManager.SIM_STATE_READY) { + fail("The device is not setup with a usable SIM card. Sim state was ${tm.simState}. " + + commonError) + } + assertTrue(tm.isDataConnectivityPossible, + "The device is not setup with a SIM card that supports data connectivity. " + + commonError) + val cb = TestableNetworkCallback() + val cm = context.getSystemService(ConnectivityManager::class.java) + ?: fail("Could not get ConnectivityManager") + cm.requestNetwork( + NetworkRequest.Builder() + .addTransportType(TRANSPORT_CELLULAR) + .addCapability(NET_CAPABILITY_INTERNET).build(), cb) + tryTest { + cb.poll { it is RecorderCallback.CallbackEntry.Available } + ?: fail("The device does not have mobile data available. Check that it is " + + "setup with a SIM card that has a working data plan, and that the " + + "APN configuration is valid.") + } cleanup { + cm.unregisterNetworkCallback(cb) + } + } +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/ArpResponder.kt b/staticlibs/testutils/devicetests/com/android/testutils/ArpResponder.kt new file mode 100644 index 0000000000000000000000000000000000000000..cf0490c565982486d0e8f6693e5722209ceb47c4 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/ArpResponder.kt @@ -0,0 +1,64 @@ +/* + * 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. + */ + +package com.android.testutils + +import android.net.MacAddress +import com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN +import java.net.Inet4Address +import java.net.InetAddress +import java.nio.ByteBuffer + +private const val ARP_SENDER_MAC_OFFSET = ETHER_HEADER_LEN + 8 +private const val ARP_TARGET_IPADDR_OFFSET = ETHER_HEADER_LEN + 24 + +private val TYPE_ARP = byteArrayOf(0x08, 0x06) +// Arp reply header for IPv4 over ethernet +private val ARP_REPLY_IPV4 = byteArrayOf(0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x02) + +/** + * A class that can be used to reply to ARP packets on a [TapPacketReader]. + */ +class ArpResponder( + reader: TapPacketReader, + table: Map<Inet4Address, MacAddress>, + name: String = ArpResponder::class.java.simpleName +) : PacketResponder(reader, ArpRequestFilter(), name) { + // Copy the map if not already immutable (toMap) to make sure it is not modified + private val table = table.toMap() + + override fun replyToPacket(packet: ByteArray, reader: TapPacketReader) { + val targetIp = InetAddress.getByAddress( + packet.copyFromIndexWithLength(ARP_TARGET_IPADDR_OFFSET, 4)) + as Inet4Address + + val macAddr = table[targetIp]?.toByteArray() ?: return + val senderMac = packet.copyFromIndexWithLength(ARP_SENDER_MAC_OFFSET, 6) + reader.sendResponse(ByteBuffer.wrap( + // Ethernet header + senderMac + macAddr + TYPE_ARP + + // ARP message + ARP_REPLY_IPV4 + + macAddr /* sender MAC */ + + targetIp.address /* sender IP addr */ + + macAddr /* target mac */ + + targetIp.address /* target IP addr */ + )) + } +} + +private fun ByteArray.copyFromIndexWithLength(start: Int, len: Int) = + copyOfRange(start, start + len) diff --git a/staticlibs/testutils/devicetests/com/android/testutils/CompatUtil.kt b/staticlibs/testutils/devicetests/com/android/testutils/CompatUtil.kt new file mode 100644 index 0000000000000000000000000000000000000000..82f1d9b967a2b1f4e78e29fa3f896e0079f245a3 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/CompatUtil.kt @@ -0,0 +1,53 @@ +/* + * 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. + */ + +package com.android.testutils + +import android.net.NetworkSpecifier +import com.android.modules.utils.build.SdkLevel.isAtLeastS + +/** + * Test utility to create [NetworkSpecifier]s on different SDK versions. + */ +object CompatUtil { + @JvmStatic + fun makeTestNetworkSpecifier(ifName: String): NetworkSpecifier { + // Until R, there was no TestNetworkSpecifier, StringNetworkSpecifier was used instead + if (!isAtLeastS()) { + return makeNetworkSpecifierInternal("android.net.StringNetworkSpecifier", ifName) + } + // TestNetworkSpecifier is not part of the SDK in some branches using this utility + // TODO: replace with a direct call to the constructor + return makeNetworkSpecifierInternal("android.net.TestNetworkSpecifier", ifName) + } + + @JvmStatic + fun makeEthernetNetworkSpecifier(ifName: String): NetworkSpecifier { + // Until R, there was no EthernetNetworkSpecifier, StringNetworkSpecifier was used instead + if (!isAtLeastS()) { + return makeNetworkSpecifierInternal("android.net.StringNetworkSpecifier", ifName) + } + // EthernetNetworkSpecifier is not part of the SDK in some branches using this utility + // TODO: replace with a direct call to the constructor + return makeNetworkSpecifierInternal("android.net.EthernetNetworkSpecifier", ifName) + } + + private fun makeNetworkSpecifierInternal(clazz: String, specifier: String): NetworkSpecifier { + // StringNetworkSpecifier was removed after R (and was hidden API before that) + return Class.forName(clazz) + .getConstructor(String::class.java).newInstance(specifier) as NetworkSpecifier + } +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/ConcurrentInterpreter.kt b/staticlibs/testutils/devicetests/com/android/testutils/ConcurrentInterpreter.kt new file mode 100644 index 0000000000000000000000000000000000000000..9e72f4b4e68ddb1b9257895d548f555f70fe7914 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/ConcurrentInterpreter.kt @@ -0,0 +1,240 @@ +package com.android.testutils + +import android.os.SystemClock +import java.util.concurrent.CyclicBarrier +import kotlin.test.assertEquals +import kotlin.test.assertFails +import kotlin.test.assertNull +import kotlin.test.assertTrue + +// The table contains pairs associating a regexp with the code to run. The statement is matched +// against each matcher in sequence and when a match is found the associated code is run, passing +// it the TrackRecord under test and the result of the regexp match. +typealias InterpretMatcher<T> = Pair<Regex, (ConcurrentInterpreter<T>, T, MatchResult) -> Any?> + +// The default unit of time for interpreted tests +const val INTERPRET_TIME_UNIT = 60L // ms + +/** + * A small interpreter for testing parallel code. + * + * The interpreter will read a list of lines consisting of "|"-separated statements, e.g. : + * sleep 2 ; unblock thread2 | wait thread2 time 2..5 + * sendMessage "x" | obtainMessage = "x" time 0..1 + * + * Each column runs in a different concurrent thread and all threads wait for each other in + * between lines. Each statement is split on ";" then matched with regular expressions in the + * instructionTable constant, which contains the code associated with each statement. The + * interpreter supports an object being passed to the interpretTestSpec() method to be passed + * in each lambda (think about the object under test), and an optional transform function to be + * executed on the object at the start of every thread. + * + * The time unit is defined in milliseconds by the interpretTimeUnit member, which has a default + * value but can be passed to the constructor. Whitespace is ignored. + * + * The interpretation table has to be passed as an argument. It's a table associating a regexp + * with the code that should execute, as a function taking three arguments : the interpreter, + * the regexp match, and the object. See the individual tests for the DSL of that test. + * Implementors for new interpreting languages are encouraged to look at the defaultInterpretTable + * constant below for an example of how to write an interpreting table. + * Some expressions already exist by default and can be used by all interpreters. Refer to + * getDefaultInstructions() below for a list and documentation. + */ +open class ConcurrentInterpreter<T>(localInterpretTable: List<InterpretMatcher<T>>) { + private val interpretTable: List<InterpretMatcher<T>> = + localInterpretTable + getDefaultInstructions() + // The last time the thread became blocked, with base System.currentTimeMillis(). This should + // be set immediately before any time the thread gets blocked. + internal val lastBlockedTime = ThreadLocal<Long>() + + // Split the line into multiple statements separated by ";" and execute them. Return whatever + // the last statement returned. + fun interpretMultiple(instr: String, r: T): Any? { + return instr.split(";").map { interpret(it.trim(), r) }.last() + } + + // Match the statement to a regex and interpret it. + fun interpret(instr: String, r: T): Any? { + val (matcher, code) = + interpretTable.find { instr matches it.first } ?: throw SyntaxException(instr) + val match = matcher.matchEntire(instr) ?: throw SyntaxException(instr) + return code(this, r, match) + } + + /** + * Spins as many threads as needed by the test spec and interpret each program concurrently. + * + * All threads wait on a CyclicBarrier after each line. + * |lineShift| says how many lines after the call the spec starts. This is used for error + * reporting. Unfortunately AFAICT there is no way to get the line of an argument rather + * than the line at which the expression starts. + * + * This method is mostly meant for implementations that extend the ConcurrentInterpreter + * class to add their own directives and instructions. These may need to operate on some + * data, which can be passed in |initial|. For example, an interpreter specialized in callbacks + * may want to pass the callback there. In some cases, it's necessary that each thread + * performs a transformation *after* it starts on that value before starting ; in this case, + * the transformation can be passed to |threadTransform|. The default is to return |initial| as + * is. Look at some existing child classes of this interpreter for some examples of how this + * can be used. + * + * @param spec The test spec, as a string of lines separated by pipes. + * @param initial An initial value passed to all threads. + * @param lineShift How many lines after the call the spec starts, for error reporting. + * @param threadTransform an optional transformation that each thread will apply to |initial| + */ + fun interpretTestSpec( + spec: String, + initial: T, + lineShift: Int = 0, + threadTransform: (T) -> T = { it } + ) { + // For nice stack traces + val callSite = getCallingMethod() + val lines = spec.trim().trim('\n').split("\n").map { it.split("|") } + // |lines| contains arrays of strings that make up the statements of a thread : in other + // words, it's an array that contains a list of statements for each column in the spec. + // E.g. if the string is """ + // a | b | c + // d | e | f + // """, then lines is [ [ "a", "b", "c" ], [ "d", "e", "f" ] ]. + val threadCount = lines[0].size + assertTrue(lines.all { it.size == threadCount }) + val threadInstructions = (0 until threadCount).map { i -> lines.map { it[i].trim() } } + // |threadInstructions| is a list where each element is the list of instructions for the + // thread at the index. In other words, it's just |lines| transposed. In the example + // above, it would be [ [ "a", "d" ], [ "b", "e" ], [ "c", "f" ] ] + // mapIndexed below will pass in |instructions| the list of instructions for this thread. + val barrier = CyclicBarrier(threadCount) + var crash: InterpretException? = null + threadInstructions.mapIndexed { threadIndex, instructions -> + Thread { + val threadLocal = threadTransform(initial) + lastBlockedTime.set(System.currentTimeMillis()) + barrier.await() + var lineNum = 0 + instructions.forEach { + if (null != crash) return@Thread + lineNum += 1 + try { + interpretMultiple(it, threadLocal) + } catch (e: Throwable) { + // If fail() or some exception was called, the thread will come here ; if + // the exception isn't caught the process will crash, which is not nice for + // testing. Instead, catch the exception, cancel other threads, and report + // nicely. Catch throwable because fail() is AssertionError, which inherits + // from Error. + crash = InterpretException(threadIndex, it, + callSite.lineNumber + lineNum + lineShift, + callSite.className, callSite.methodName, callSite.fileName, e) + } + lastBlockedTime.set(System.currentTimeMillis()) + barrier.await() + } + }.also { it.start() } + }.forEach { it.join() } + // If the test failed, crash with line number + crash?.let { throw it } + } + + // Helper to get the stack trace for a calling method + private fun getCallingStackTrace(): Array<StackTraceElement> { + try { + throw RuntimeException() + } catch (e: RuntimeException) { + return e.stackTrace + } + } + + // Find the calling method. This is the first method in the stack trace that is annotated + // with @Test. + fun getCallingMethod(): StackTraceElement { + val stackTrace = getCallingStackTrace() + return stackTrace.find { element -> + val clazz = Class.forName(element.className) + // Because the stack trace doesn't list the formal arguments, find all methods with + // this name and return this name if any of them is annotated with @Test. + clazz.declaredMethods + .filter { method -> method.name == element.methodName } + .any { method -> method.getAnnotation(org.junit.Test::class.java) != null } + } ?: stackTrace[3] + // If no method is annotated return the 4th one, because that's what it usually is : + // 0 is getCallingStackTrace, 1 is this method, 2 is ConcurrentInterpreter#interpretTestSpec + } +} + +/** + * Default instructions available to all interpreters. + * sleep(x) : sleeps for x time units and returns Unit ; sleep alone means sleep(1) + * EXPR = VALUE : asserts that EXPR equals VALUE. EXPR is interpreted. VALUE can either be the + * string "null" or an int. Returns Unit. + * EXPR time x..y : measures the time taken by EXPR and asserts it took at least x and at most + * y time units. + * EXPR // any text : comments are ignored. + * EXPR fails : checks that EXPR throws some exception. + */ +private fun <T> getDefaultInstructions() = listOf<InterpretMatcher<T>>( + // Interpret an empty line as doing nothing. + Regex("") to { _, _, _ -> null }, + // Ignore comments. + Regex("(.*)//.*") to { i, t, r -> i.interpret(r.strArg(1), t) }, + // Interpret "XXX time x..y" : run XXX and check it took at least x and not more than y + Regex("""(.*)\s*time\s*(\d+)\.\.(\d+)""") to { i, t, r -> + val lateStart = System.currentTimeMillis() + i.interpret(r.strArg(1), t) + val end = System.currentTimeMillis() + // There is uncertainty in measuring time. + // It takes some (small) time for the thread to even measure the time at which it + // starts interpreting the instruction. It is therefore possible that thread A sleeps for + // n milliseconds, and B expects to have waited for at least n milliseconds, but because + // B started measuring after 1ms or so, B thinks it didn't wait long enough. + // To avoid this, when the `time` instruction tests the instruction took at least X and + // at most Y, it tests X against a time measured since *before* the thread blocked but + // Y against a time measured as late as possible. This ensures that the timer is + // sufficiently lenient in both directions that there are no flaky measures. + val minTime = end - lateStart + val maxTime = end - i.lastBlockedTime.get()!! + + assertTrue(maxTime >= r.timeArg(2), + "Should have taken at least ${r.timeArg(2)} but took less than $maxTime") + assertTrue(minTime <= r.timeArg(3), + "Should have taken at most ${r.timeArg(3)} but took more than $minTime") + }, + // Interpret "XXX = YYY" : run XXX and assert its return value is equal to YYY. "null" supported + Regex("""(.*)\s*=\s*(null|\d+)""") to { i, t, r -> + i.interpret(r.strArg(1), t).also { + if ("null" == r.strArg(2)) assertNull(it) else assertEquals(r.intArg(2), it) + } + }, + // Interpret sleep. Optional argument for the count, in INTERPRET_TIME_UNIT units. + Regex("""sleep(\((\d+)\))?""") to { i, t, r -> + SystemClock.sleep(if (r.strArg(2).isEmpty()) INTERPRET_TIME_UNIT else r.timeArg(2)) + }, + Regex("""(.*)\s*fails""") to { i, t, r -> + assertFails { i.interpret(r.strArg(1), t) } + } +) + +class SyntaxException(msg: String, cause: Throwable? = null) : RuntimeException(msg, cause) +class InterpretException( + threadIndex: Int, + instr: String, + lineNum: Int, + className: String, + methodName: String, + fileName: String, + cause: Throwable +) : RuntimeException("Failure: $instr", cause) { + init { + stackTrace = arrayOf(StackTraceElement( + className, + "$methodName:thread$threadIndex", + fileName, + lineNum)) + super.getStackTrace() + } +} + +// Some small helpers to avoid to say the large ".groupValues[index].trim()" every time +fun MatchResult.strArg(index: Int) = this.groupValues[index].trim() +fun MatchResult.intArg(index: Int) = strArg(index).toInt() +fun MatchResult.timeArg(index: Int) = INTERPRET_TIME_UNIT * intArg(index) diff --git a/staticlibs/testutils/devicetests/com/android/testutils/ConnectUtil.kt b/staticlibs/testutils/devicetests/com/android/testutils/ConnectUtil.kt new file mode 100644 index 0000000000000000000000000000000000000000..71f7877e1a32675c6b5fc4970df3b2d4f64e0ba8 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/ConnectUtil.kt @@ -0,0 +1,203 @@ +/* + * 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. + */ + +package com.android.testutils + +import android.Manifest.permission +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities.TRANSPORT_WIFI +import android.net.NetworkRequest +import android.net.wifi.ScanResult +import android.net.wifi.WifiConfiguration +import android.net.wifi.WifiManager +import android.os.ParcelFileDescriptor +import android.os.SystemClock +import android.util.Log +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import com.android.testutils.RecorderCallback.CallbackEntry +import java.util.concurrent.CompletableFuture +import java.util.concurrent.TimeUnit +import kotlin.test.assertNotNull +import kotlin.test.assertTrue +import kotlin.test.fail + +private const val MAX_WIFI_CONNECT_RETRIES = 10 +private const val WIFI_CONNECT_INTERVAL_MS = 500L +private const val WIFI_CONNECT_TIMEOUT_MS = 30_000L + +// Constants used by WifiManager.ActionListener#onFailure. Although onFailure is SystemApi, +// the error code constants are not (b/204277752) +private const val WIFI_ERROR_IN_PROGRESS = 1 +private const val WIFI_ERROR_BUSY = 2 + +class ConnectUtil(private val context: Context) { + private val TAG = ConnectUtil::class.java.simpleName + + private val cm = context.getSystemService(ConnectivityManager::class.java) + ?: fail("Could not find ConnectivityManager") + private val wifiManager = context.getSystemService(WifiManager::class.java) + ?: fail("Could not find WifiManager") + + fun ensureWifiConnected(): Network { + val callback = TestableNetworkCallback() + cm.registerNetworkCallback(NetworkRequest.Builder() + .addTransportType(TRANSPORT_WIFI) + .build(), callback) + + try { + val connInfo = wifiManager.connectionInfo + Log.d(TAG, "connInfo=" + connInfo) + if (connInfo == null || connInfo.networkId == -1) { + clearWifiBlocklist() + val pfd = getInstrumentation().uiAutomation.executeShellCommand("svc wifi enable") + // Read the output stream to ensure the command has completed + ParcelFileDescriptor.AutoCloseInputStream(pfd).use { it.readBytes() } + val config = getOrCreateWifiConfiguration() + connectToWifiConfig(config) + } + val cb = callback.poll(WIFI_CONNECT_TIMEOUT_MS) { it is CallbackEntry.Available } + assertNotNull(cb, "Could not connect to a wifi access point within " + + "$WIFI_CONNECT_TIMEOUT_MS ms. Check that the test device has a wifi network " + + "configured, and that the test access point is functioning properly.") + return cb.network + } finally { + cm.unregisterNetworkCallback(callback) + } + } + + private fun connectToWifiConfig(config: WifiConfiguration) { + repeat(MAX_WIFI_CONNECT_RETRIES) { + val error = runAsShell(permission.NETWORK_SETTINGS) { + val listener = ConnectWifiListener() + wifiManager.connect(config, listener) + listener.connectFuture.get(WIFI_CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS) + } ?: return // Connect succeeded + + // Only retry for IN_PROGRESS and BUSY + if (error != WIFI_ERROR_IN_PROGRESS && error != WIFI_ERROR_BUSY) { + fail("Failed to connect to " + config.SSID + ": " + error) + } + Log.w(TAG, "connect failed with $error; waiting before retry") + SystemClock.sleep(WIFI_CONNECT_INTERVAL_MS) + } + fail("Failed to connect to ${config.SSID} after $MAX_WIFI_CONNECT_RETRIES retries") + } + + private class ConnectWifiListener : WifiManager.ActionListener { + /** + * Future completed when the connect process ends. Provides the error code or null if none. + */ + val connectFuture = CompletableFuture<Int?>() + override fun onSuccess() { + connectFuture.complete(null) + } + + override fun onFailure(reason: Int) { + connectFuture.complete(reason) + } + } + + private fun getOrCreateWifiConfiguration(): WifiConfiguration { + val configs = runAsShell(permission.NETWORK_SETTINGS) { + wifiManager.getConfiguredNetworks() + } + // If no network is configured, add a config for virtual access points if applicable + if (configs.size == 0) { + val scanResults = getWifiScanResults() + val virtualConfig = maybeConfigureVirtualNetwork(scanResults) + assertNotNull(virtualConfig, "The device has no configured wifi network") + return virtualConfig + } + // No need to add a configuration: there is already one. + if (configs.size > 1) { + // For convenience in case of local testing on devices with multiple saved configs, + // prefer the first configuration that is in range. + // In actual tests, there should only be one configuration, and it should be usable as + // assumed by WifiManagerTest.testConnect. + Log.w(TAG, "Multiple wifi configurations found: " + + configs.joinToString(", ") { it.SSID }) + val scanResultsList = getWifiScanResults() + Log.i(TAG, "Scan results: " + scanResultsList.joinToString(", ") { + "${it.SSID} (${it.level})" + }) + + val scanResults = scanResultsList.map { "\"${it.SSID}\"" }.toSet() + return configs.firstOrNull { scanResults.contains(it.SSID) } ?: configs[0] + } + return configs[0] + } + + private fun getWifiScanResults(): List<ScanResult> { + val scanResultsFuture = CompletableFuture<List<ScanResult>>() + runAsShell(permission.NETWORK_SETTINGS) { + val receiver: BroadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + scanResultsFuture.complete(wifiManager.scanResults) + } + } + context.registerReceiver(receiver, + IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) + wifiManager.startScan() + } + return try { + scanResultsFuture.get(WIFI_CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS) + } catch (e: Exception) { + throw AssertionError("Wifi scan results not received within timeout", e) + } + } + + /** + * If a virtual wifi network is detected, add a configuration for that network. + * TODO(b/158150376): have the test infrastructure add virtual wifi networks when appropriate. + */ + private fun maybeConfigureVirtualNetwork(scanResults: List<ScanResult>): WifiConfiguration? { + // Virtual wifi networks used on the emulator and cloud testing infrastructure + val virtualSsids = listOf("VirtWifi", "AndroidWifi") + Log.d(TAG, "Wifi scan results: $scanResults") + val virtualScanResult = scanResults.firstOrNull { virtualSsids.contains(it.SSID) } + ?: return null + + // Only add the virtual configuration if the virtual AP is detected in scans + val virtualConfig = WifiConfiguration() + // ASCII SSIDs need to be surrounded by double quotes + virtualConfig.SSID = "\"${virtualScanResult.SSID}\"" + virtualConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE) + runAsShell(permission.NETWORK_SETTINGS) { + val networkId = wifiManager.addNetwork(virtualConfig) + assertTrue(networkId >= 0) + assertTrue(wifiManager.enableNetwork(networkId, false /* attemptConnect */)) + } + return virtualConfig + } + + /** + * Re-enable wifi networks that were blocked, typically because no internet connection was + * detected the last time they were connected. This is necessary to make sure wifi can reconnect + * to them. + */ + private fun clearWifiBlocklist() { + runAsShell(permission.NETWORK_SETTINGS, permission.ACCESS_WIFI_STATE) { + for (cfg in wifiManager.configuredNetworks) { + assertTrue(wifiManager.enableNetwork(cfg.networkId, false /* attemptConnect */)) + } + } + } +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/ContextUtils.kt b/staticlibs/testutils/devicetests/com/android/testutils/ContextUtils.kt new file mode 100644 index 0000000000000000000000000000000000000000..936b5682923ec37cbd9bb25da7f132454a99ad5a --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/ContextUtils.kt @@ -0,0 +1,67 @@ +/* + * 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. + */ + +@file:JvmName("ContextUtils") + +package com.android.testutils + +import android.content.Context +import android.os.UserHandle +import org.mockito.AdditionalAnswers.delegatesTo +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mockito.doAnswer +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.mock +import java.util.function.BiConsumer + +// Helper function so that Java doesn't have to pass a method that returns Unit +fun mockContextAsUser(context: Context, functor: BiConsumer<Context, UserHandle>? = null) = + mockContextAsUser(context) { c, h -> functor?.accept(c, h) } + +/** + * Return a context with assigned user and delegate to original context. + * + * @param context the mock context to set up createContextAsUser on. After this function + * is called, client code can call createContextAsUser and expect a context that + * will return the correct user and userId. + * + * @param functor additional code to run on the created context-as-user instances, for example to + * set up further mocks on these contexts. + */ +fun mockContextAsUser(context: Context, functor: ((Context, UserHandle) -> Unit)? = null) { + doAnswer { invocation -> + val asUserContext = mock(Context::class.java, delegatesTo<Context>(context)) + val user = invocation.arguments[0] as UserHandle + val userId = user.identifier + doReturn(user).`when`(asUserContext).user + doReturn(userId).`when`(asUserContext).userId + functor?.let { it(asUserContext, user) } + asUserContext + }.`when`(context).createContextAsUser(any(UserHandle::class.java), anyInt() /* flags */) +} + +/** + * Helper function to mock the desired system service. + * + * @param context the mock context to set up the getSystemService and getSystemServiceName. + * @param clazz the system service class that intents to mock. + * @param service the system service name that intents to mock. + */ +fun <T> mockService(context: Context, clazz: Class<T>, name: String, service: T) { + doReturn(service).`when`(context).getSystemService(name) + doReturn(name).`when`(context).getSystemServiceName(clazz) +} \ No newline at end of file diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt new file mode 100644 index 0000000000000000000000000000000000000000..35f22b9e587d45771b05382d7a3ceeeb2050d7e6 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt @@ -0,0 +1,144 @@ +/* + * 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. + */ + +package com.android.testutils + +import android.os.Build +import androidx.test.InstrumentationRegistry +import com.android.modules.utils.build.UnboundedSdkLevel +import java.util.regex.Pattern +import org.junit.Assume.assumeTrue +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +@Deprecated("Use Build.VERSION_CODES", ReplaceWith("Build.VERSION_CODES.S_V2")) +const val SC_V2 = Build.VERSION_CODES.S_V2 + +private val MAX_TARGET_SDK_ANNOTATION_RE = Pattern.compile("MaxTargetSdk([0-9]+)$") +private val targetSdk = InstrumentationRegistry.getContext().applicationInfo.targetSdkVersion + +private fun isDevSdkInRange(minExclusive: String?, maxInclusive: String?): Boolean { + return (minExclusive == null || !isAtMost(minExclusive)) && + (maxInclusive == null || isAtMost(maxInclusive)) +} + +private fun isAtMost(sdkVersionOrCodename: String): Boolean { + // UnboundedSdkLevel does not support builds < Q, and may stop supporting Q as well since it + // is intended for mainline modules that are now R+. + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) { + // Assume that any codename passed as argument from current code is a more recent build than + // Q: this util did not exist before Q, and codenames are only used before the corresponding + // build is finalized. This util could list 28 older codenames to check against (as per + // ro.build.version.known_codenames in more recent builds), but this does not seem valuable. + val intVersion = sdkVersionOrCodename.toIntOrNull() ?: return true + return Build.VERSION.SDK_INT <= intVersion + } + return UnboundedSdkLevel.isAtMost(sdkVersionOrCodename) +} + +/** + * Returns true if the development SDK version of the device is in the provided annotation range. + * + * If the device is not using a release SDK, the development SDK differs from + * [Build.VERSION.SDK_INT], and is indicated by the device codenames; see [UnboundedSdkLevel]. + */ +fun isDevSdkInRange( + ignoreUpTo: DevSdkIgnoreRule.IgnoreUpTo?, + ignoreAfter: DevSdkIgnoreRule.IgnoreAfter? +): Boolean { + val minExclusive = + if (ignoreUpTo?.value == 0) ignoreUpTo.codename + else ignoreUpTo?.value?.toString() + val maxInclusive = + if (ignoreAfter?.value == 0) ignoreAfter.codename + else ignoreAfter?.value?.toString() + return isDevSdkInRange(minExclusive, maxInclusive) +} + +private fun getMaxTargetSdk(description: Description): Int? { + return description.annotations.firstNotNullOfOrNull { + MAX_TARGET_SDK_ANNOTATION_RE.matcher(it.annotationClass.simpleName).let { m -> + if (m.find()) m.group(1).toIntOrNull() else null + } + } +} + +/** + * A test rule to ignore tests based on the development SDK level. + * + * If the device is not using a release SDK, the development SDK is considered to be higher than + * [Build.VERSION.SDK_INT]. + * + * @param ignoreClassUpTo Skip all tests in the class if the device dev SDK is <= this codename or + * SDK level. + * @param ignoreClassAfter Skip all tests in the class if the device dev SDK is > this codename or + * SDK level. + */ +class DevSdkIgnoreRule @JvmOverloads constructor( + private val ignoreClassUpTo: String? = null, + private val ignoreClassAfter: String? = null +) : TestRule { + /** + * @param ignoreClassUpTo Skip all tests in the class if the device dev SDK is <= this value. + * @param ignoreClassAfter Skip all tests in the class if the device dev SDK is > this value. + */ + @JvmOverloads + constructor(ignoreClassUpTo: Int?, ignoreClassAfter: Int? = null) : this( + ignoreClassUpTo?.toString(), ignoreClassAfter?.toString()) + + override fun apply(base: Statement, description: Description): Statement { + return IgnoreBySdkStatement(base, description) + } + + /** + * Ignore the test for any development SDK that is strictly after [value]. + * + * If the device is not using a release SDK, the development SDK is considered to be higher + * than [Build.VERSION.SDK_INT]. + */ + annotation class IgnoreAfter(val value: Int = 0, val codename: String = "") + + /** + * Ignore the test for any development SDK that lower than or equal to [value]. + * + * If the device is not using a release SDK, the development SDK is considered to be higher + * than [Build.VERSION.SDK_INT]. + */ + annotation class IgnoreUpTo(val value: Int = 0, val codename: String = "") + + private inner class IgnoreBySdkStatement( + private val base: Statement, + private val description: Description + ) : Statement() { + override fun evaluate() { + val ignoreAfter = description.getAnnotation(IgnoreAfter::class.java) + val ignoreUpTo = description.getAnnotation(IgnoreUpTo::class.java) + + val devSdkMessage = "Skipping test for build ${Build.VERSION.CODENAME} " + + "with SDK ${Build.VERSION.SDK_INT}" + assumeTrue(devSdkMessage, isDevSdkInRange(ignoreClassUpTo, ignoreClassAfter)) + assumeTrue(devSdkMessage, isDevSdkInRange(ignoreUpTo, ignoreAfter)) + + val maxTargetSdk = getMaxTargetSdk(description) + if (maxTargetSdk != null) { + assumeTrue("Skipping test, target SDK $targetSdk greater than $maxTargetSdk", + targetSdk <= maxTargetSdk) + } + base.evaluate() + } + } +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt new file mode 100644 index 0000000000000000000000000000000000000000..2e73666bf4cf2f8d8e275d1b42ac1f25a18bf363 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt @@ -0,0 +1,91 @@ +/* + * 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. + */ + +package com.android.testutils + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo +import org.junit.runner.Description +import org.junit.runner.Runner +import org.junit.runner.manipulation.Filter +import org.junit.runner.manipulation.Filterable +import org.junit.runner.manipulation.NoTestsRemainException +import org.junit.runner.manipulation.Sortable +import org.junit.runner.manipulation.Sorter +import org.junit.runner.notification.RunNotifier +import kotlin.jvm.Throws + +/** + * A runner that can skip tests based on the development SDK as defined in [DevSdkIgnoreRule]. + * + * Generally [DevSdkIgnoreRule] should be used for that purpose (using rules is preferable over + * replacing the test runner), however JUnit runners inspect all methods in the test class before + * processing test rules. This may cause issues if the test methods are referencing classes that do + * not exist on the SDK of the device the test is run on. + * + * This runner inspects [IgnoreAfter] and [IgnoreUpTo] annotations on the test class, and will skip + * the whole class if they do not match the development SDK as defined in [DevSdkIgnoreRule]. + * Otherwise, it will delegate to [AndroidJUnit4] to run the test as usual. + * + * Example usage: + * + * @RunWith(DevSdkIgnoreRunner::class) + * @IgnoreUpTo(Build.VERSION_CODES.Q) + * class MyTestClass { ... } + */ +class DevSdkIgnoreRunner(private val klass: Class<*>) : Runner(), Filterable, Sortable { + private val baseRunner = klass.let { + val ignoreAfter = it.getAnnotation(IgnoreAfter::class.java) + val ignoreUpTo = it.getAnnotation(IgnoreUpTo::class.java) + + if (isDevSdkInRange(ignoreUpTo, ignoreAfter)) AndroidJUnit4(klass) else null + } + + override fun run(notifier: RunNotifier) { + if (baseRunner != null) { + baseRunner.run(notifier) + return + } + + // Report a single, skipped placeholder test for this class, as the class is expected to + // report results when run. In practice runners that apply the Filterable implementation + // would see a NoTestsRemainException and not call the run method. + notifier.fireTestIgnored( + Description.createTestDescription(klass, "skippedClassForDevSdkMismatch")) + } + + override fun getDescription(): Description { + return baseRunner?.description ?: Description.createSuiteDescription(klass) + } + + /** + * Get the test count before applying the [Filterable] implementation. + */ + override fun testCount(): Int { + // When ignoring the tests, a skipped placeholder test is reported, so test count is 1. + return baseRunner?.testCount() ?: 1 + } + + @Throws(NoTestsRemainException::class) + override fun filter(filter: Filter?) { + baseRunner?.filter(filter) ?: throw NoTestsRemainException() + } + + override fun sort(sorter: Sorter?) { + baseRunner?.sort(sorter) + } +} \ No newline at end of file diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DeviceConfigRule.kt b/staticlibs/testutils/devicetests/com/android/testutils/DeviceConfigRule.kt new file mode 100644 index 0000000000000000000000000000000000000000..3d98cc381b60a2cd2ac135a10d13ea437125e979 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/DeviceConfigRule.kt @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.testutils + +import android.Manifest.permission.READ_DEVICE_CONFIG +import android.Manifest.permission.WRITE_DEVICE_CONFIG +import android.provider.DeviceConfig +import android.util.Log +import com.android.modules.utils.build.SdkLevel +import com.android.testutils.FunctionalUtils.ThrowingRunnable +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement +import java.util.concurrent.CompletableFuture +import java.util.concurrent.Executor +import java.util.concurrent.TimeUnit + +private val TAG = DeviceConfigRule::class.simpleName + +private const val TIMEOUT_MS = 20_000L + +/** + * A [TestRule] that helps set [DeviceConfig] for tests and clean up the test configuration + * automatically on teardown. + * + * The rule can also optionally retry tests when they fail following an external change of + * DeviceConfig before S; this typically happens because device config flags are synced while the + * test is running, and DisableConfigSyncTargetPreparer is only usable starting from S. + * + * @param retryCountBeforeSIfConfigChanged if > 0, when the test fails before S, check if + * the configs that were set through this rule were changed, and retry the test + * up to the specified number of times if yes. + */ +class DeviceConfigRule @JvmOverloads constructor( + val retryCountBeforeSIfConfigChanged: Int = 0 +) : TestRule { + // Maps (namespace, key) -> value + private val originalConfig = mutableMapOf<Pair<String, String>, String?>() + private val usedConfig = mutableMapOf<Pair<String, String>, String?>() + + /** + * Actions to be run after cleanup of the config, for the current test only. + */ + private val currentTestCleanupActions = mutableListOf<ThrowingRunnable>() + + override fun apply(base: Statement, description: Description): Statement { + return TestValidationUrlStatement(base, description) + } + + private inner class TestValidationUrlStatement( + private val base: Statement, + private val description: Description + ) : Statement() { + override fun evaluate() { + var retryCount = if (SdkLevel.isAtLeastS()) 1 else retryCountBeforeSIfConfigChanged + 1 + while (retryCount > 0) { + retryCount-- + tryTest { + base.evaluate() + // Can't use break/return out of a loop here because this is a tryTest lambda, + // so set retryCount to exit instead + retryCount = 0 + }.catch<Throwable> { e -> // junit AssertionFailedError does not extend Exception + if (retryCount == 0) throw e + usedConfig.forEach { (key, value) -> + val currentValue = runAsShell(READ_DEVICE_CONFIG) { + DeviceConfig.getProperty(key.first, key.second) + } + if (currentValue != value) { + Log.w(TAG, "Test failed with unexpected device config change, retrying") + return@catch + } + } + throw e + } cleanupStep { + runAsShell(WRITE_DEVICE_CONFIG) { + originalConfig.forEach { (key, value) -> + DeviceConfig.setProperty( + key.first, key.second, value, false /* makeDefault */) + } + } + } cleanupStep { + originalConfig.clear() + usedConfig.clear() + } cleanup { + // Fold all cleanup actions into cleanup steps of an empty tryTest, so they are + // all run even if exceptions are thrown, and exceptions are reported properly. + currentTestCleanupActions.fold(tryTest { }) { + tryBlock, action -> tryBlock.cleanupStep { action.run() } + }.cleanup { + currentTestCleanupActions.clear() + } + } + } + } + } + + /** + * Set a configuration key/value. After the test case ends, it will be restored to the value it + * had when this method was first called. + */ + fun setConfig(namespace: String, key: String, value: String?): String? { + Log.i(TAG, "Setting config \"$key\" to \"$value\"") + val readWritePermissions = arrayOf(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG) + + val keyPair = Pair(namespace, key) + val existingValue = runAsShell(*readWritePermissions) { + DeviceConfig.getProperty(namespace, key) + } + if (!originalConfig.containsKey(keyPair)) { + originalConfig[keyPair] = existingValue + } + usedConfig[keyPair] = value + if (existingValue == value) { + // Already the correct value. There may be a race if a change is already in flight, + // but if multiple threads update the config there is no way to fix that anyway. + Log.i(TAG, "\"$key\" already had value \"$value\"") + return value + } + + val future = CompletableFuture<String>() + val listener = DeviceConfig.OnPropertiesChangedListener { + // The listener receives updates for any change to any key, so don't react to + // changes that do not affect the relevant key + if (!it.keyset.contains(key)) return@OnPropertiesChangedListener + // "null" means absent in DeviceConfig : there is no such thing as a present but + // null value, so the following works even if |value| is null. + if (it.getString(key, null) == value) { + future.complete(value) + } + } + + return tryTest { + runAsShell(*readWritePermissions) { + DeviceConfig.addOnPropertiesChangedListener( + DeviceConfig.NAMESPACE_CONNECTIVITY, + inlineExecutor, + listener) + DeviceConfig.setProperty( + DeviceConfig.NAMESPACE_CONNECTIVITY, + key, + value, + false /* makeDefault */) + // Don't drop the permission until the config is applied, just in case + future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS) + }.also { + Log.i(TAG, "Config \"$key\" successfully set to \"$value\"") + } + } cleanup { + DeviceConfig.removeOnPropertiesChangedListener(listener) + } + } + + private val inlineExecutor get() = Executor { r -> r.run() } + + /** + * Add an action to be run after config cleanup when the current test case ends. + */ + fun runAfterNextCleanup(action: ThrowingRunnable) { + currentTestCleanupActions.add(action) + } +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DeviceInfoUtils.java b/staticlibs/testutils/devicetests/com/android/testutils/DeviceInfoUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..ce55fdc1d35a6bf53229ed054f7813e9987b1eda --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/DeviceInfoUtils.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.testutils; + +import android.os.VintfRuntimeInfo; +import android.text.TextUtils; +import android.util.Pair; + +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Utilities for device information. + */ +public class DeviceInfoUtils { + /** + * Class for a three-part kernel version number. + */ + public static class KVersion { + public final int major; + public final int minor; + public final int sub; + + public KVersion(int major, int minor, int sub) { + this.major = major; + this.minor = minor; + this.sub = sub; + } + + /** + * Compares with other version numerically. + * + * @param other the other version to compare + * @return the value 0 if this == other; + * a value less than 0 if this < other and + * a value greater than 0 if this > other. + */ + public int compareTo(final KVersion other) { + int res = Integer.compare(this.major, other.major); + if (res == 0) { + res = Integer.compare(this.minor, other.minor); + } + if (res == 0) { + res = Integer.compare(this.sub, other.sub); + } + return res; + } + + /** + * At least satisfied with the given version. + * + * @param from the start version to compare + * @return return true if this version is at least satisfied with the given version. + * otherwise, return false. + */ + public boolean isAtLeast(final KVersion from) { + return compareTo(from) >= 0; + } + + /** + * Falls within the given range [from, to). + * + * @param from the start version to compare + * @param to the end version to compare + * @return return true if this version falls within the given range. + * otherwise, return false. + */ + public boolean isInRange(final KVersion from, final KVersion to) { + return isAtLeast(from) && !isAtLeast(to); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof KVersion)) return false; + KVersion that = (KVersion) o; + return this.major == that.major + && this.minor == that.minor + && this.sub == that.sub; + } + }; + + /** + * Get a two-part kernel version number (major and minor) from a given string. + * + * TODO: use class KVersion. + */ + private static Pair<Integer, Integer> getMajorMinorVersion(String version) { + // Only gets major and minor number of the version string. + final Pattern versionPattern = Pattern.compile("^(\\d+)(\\.(\\d+))?.*"); + final Matcher m = versionPattern.matcher(version); + if (m.matches()) { + final int major = Integer.parseInt(m.group(1)); + final int minor = TextUtils.isEmpty(m.group(3)) ? 0 : Integer.parseInt(m.group(3)); + return new Pair<>(major, minor); + } else { + return new Pair<>(0, 0); + } + } + + /** + * Compares two version strings numerically. Compare only major and minor number of the + * version string. The version comparison uses #Integer.compare. Possible version + * 5, 5.10, 5-beta1, 4.8-RC1, 4.7.10.10 and so on. + * + * @param s1 the first version string to compare + * @param s2 the second version string to compare + * @return the value 0 if s1 == s2; + * a value less than 0 if s1 < s2 and + * a value greater than 0 if s1 > s2. + * + * TODO: use class KVersion. + */ + public static int compareMajorMinorVersion(final String s1, final String s2) { + final Pair<Integer, Integer> v1 = getMajorMinorVersion(s1); + final Pair<Integer, Integer> v2 = getMajorMinorVersion(s2); + + if (Objects.equals(v1.first, v2.first)) { + return Integer.compare(v1.second, v2.second); + } else { + return Integer.compare(v1.first, v2.first); + } + } + + /** + * Get a three-part kernel version number (major, minor and subminor) from a given string. + * Any version string must at least have major and minor number. If the subminor number can't + * be parsed from string. Assign zero as subminor number. Invalid version is treated as + * version 0.0.0. + */ + public static KVersion getMajorMinorSubminorVersion(final String version) { + // The kernel version is a three-part version number (major, minor and subminor). Get + // the three-part version numbers and discard the remaining stuff if any. + // For example: + // 4.19.220-g500ede0aed22-ab8272303 --> 4.19.220 + // 5.17-rc6-g52099515ca00-ab8032400 --> 5.17.0 + final Pattern versionPattern = Pattern.compile("^(\\d+)\\.(\\d+)(\\.(\\d+))?.*"); + final Matcher m = versionPattern.matcher(version); + if (m.matches()) { + final int major = Integer.parseInt(m.group(1)); + final int minor = Integer.parseInt(m.group(2)); + final int sub = TextUtils.isEmpty(m.group(4)) ? 0 : Integer.parseInt(m.group(4)); + return new KVersion(major, minor, sub); + } else { + return new KVersion(0, 0, 0); + } + } + + /** + * Check if the current kernel version is at least satisfied with the given version. + * + * @param version the start version to compare + * @return return true if the current version is at least satisfied with the given version. + * otherwise, return false. + */ + public static boolean isKernelVersionAtLeast(final String version) { + final String kernelVersion = VintfRuntimeInfo.getKernelVersion(); + final KVersion current = DeviceInfoUtils.getMajorMinorSubminorVersion(kernelVersion); + final KVersion from = DeviceInfoUtils.getMajorMinorSubminorVersion(version); + return current.isAtLeast(from); + } +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DnsAnswerProvider.kt b/staticlibs/testutils/devicetests/com/android/testutils/DnsAnswerProvider.kt new file mode 100644 index 0000000000000000000000000000000000000000..6a804bf9374caa47f2916b0c3f072349ed879572 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/DnsAnswerProvider.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.testutils + +import android.net.DnsResolver.CLASS_IN +import com.android.net.module.util.DnsPacket +import com.android.net.module.util.DnsPacket.ANSECTION +import java.net.InetAddress +import java.util.concurrent.ConcurrentHashMap + +const val DEFAULT_TTL_S = 5L + +/** + * Helper class to store the mapping of DNS queries. + * + * DnsAnswerProvider is built atop a ConcurrentHashMap and as such it provides the same + * guarantees as ConcurrentHashMap between writing and reading elements. Specifically : + * - Setting an answer happens-before reading the same answer. + * - Callers can read and write concurrently from DnsAnswerProvider and expect no + * ConcurrentModificationException. + * Freshness of the answers depends on ordering of the threads ; if callers need a + * freshness guarantee, they need to provide the happens-before relationship from a + * write that they want to observe to the read that they need to be observed. + */ +class DnsAnswerProvider { + private val mDnsKeyToRecords = ConcurrentHashMap<String, List<DnsPacket.DnsRecord>>() + + /** + * Get answer for the specified hostname. + * + * @param query the target hostname. + * @param type type of record, could be A or AAAA. + * + * @return list of [DnsPacket.DnsRecord] associated to the query. Empty if no record matches. + */ + fun getAnswer(query: String, type: Int) = mDnsKeyToRecords[query] + .orEmpty().filter { it.nsType == type } + + /** Set answer for the specified {@code query}. + * + * @param query the target hostname + * @param addresses [List<InetAddress>] which could be used to generate multiple A or AAAA + * RRs with the corresponding addresses. + */ + fun setAnswer(query: String, hosts: List<InetAddress>) = mDnsKeyToRecords.put(query, hosts.map { + DnsPacket.DnsRecord.makeAOrAAAARecord(ANSECTION, query, CLASS_IN, DEFAULT_TTL_S, it) + }) + + fun clearAnswer(query: String) = mDnsKeyToRecords.remove(query) + fun clearAll() = mDnsKeyToRecords.clear() +} \ No newline at end of file diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DumpTestUtils.java b/staticlibs/testutils/devicetests/com/android/testutils/DumpTestUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..d10374885030113fe8b007a5ade18c3252b52d65 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/DumpTestUtils.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.testutils; + +import static com.android.testutils.TestPermissionUtil.runAsShell; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.system.ErrnoException; +import android.system.Os; + +import libcore.io.IoUtils; +import libcore.io.Streams; + +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Utilities for testing output of service dumps. + */ +public class DumpTestUtils { + + private static String dumpService(String serviceName, boolean adoptPermission, String... args) + throws RemoteException, InterruptedException, ErrnoException { + final IBinder ib = ServiceManager.getService(serviceName); + FileDescriptor[] pipe = Os.pipe(); + + // Start a thread to read the dump output, or dump might block if it fills the pipe. + final CountDownLatch latch = new CountDownLatch(1); + AtomicReference<String> output = new AtomicReference<>(); + // Used to send exceptions back to the main thread to ensure that the test fails cleanly. + AtomicReference<Exception> exception = new AtomicReference<>(); + new Thread(() -> { + try { + output.set(Streams.readFully( + new InputStreamReader(new FileInputStream(pipe[0]), + StandardCharsets.UTF_8))); + latch.countDown(); + } catch (Exception e) { + exception.set(e); + latch.countDown(); + } + }).start(); + + final int timeoutMs = 5_000; + final String what = "service '" + serviceName + "' with args: " + Arrays.toString(args); + try { + if (adoptPermission) { + runAsShell(android.Manifest.permission.DUMP, () -> ib.dump(pipe[1], args)); + } else { + ib.dump(pipe[1], args); + } + IoUtils.closeQuietly(pipe[1]); + assertTrue("Dump of " + what + " timed out after " + timeoutMs + "ms", + latch.await(timeoutMs, TimeUnit.MILLISECONDS)); + } finally { + // Closing the fds will terminate the thread if it's blocked on read. + IoUtils.closeQuietly(pipe[0]); + if (pipe[1].valid()) IoUtils.closeQuietly(pipe[1]); + } + if (exception.get() != null) { + fail("Exception dumping " + what + ": " + exception.get()); + } + return output.get(); + } + + /** + * Dumps the specified service and returns a string. Sends a dump IPC to the given service + * with the specified args and a pipe, then reads from the pipe in a separate thread. + * The current process must already have the DUMP permission. + * + * @param serviceName the service to dump. + * @param args the arguments to pass to the dump function. + * @return The dump text. + * @throws RemoteException dumping the service failed. + * @throws InterruptedException the dump timed out. + * @throws ErrnoException opening or closing the pipe for the dump failed. + */ + public static String dumpService(String serviceName, String... args) + throws RemoteException, InterruptedException, ErrnoException { + return dumpService(serviceName, false, args); + } + + /** + * Dumps the specified service and returns a string. Sends a dump IPC to the given service + * with the specified args and a pipe, then reads from the pipe in a separate thread. + * Adopts the {@code DUMP} permission via {@code adoptShellPermissionIdentity} and then releases + * it. This method should not be used if the caller already has the shell permission identity. + * TODO: when Q and R are no longer supported, use + * {@link android.app.UiAutomation#getAdoptedShellPermissions} to automatically acquire the + * shell permission if the caller does not already have it. + * + * @param serviceName the service to dump. + * @param args the arguments to pass to the dump function. + * @return The dump text. + * @throws RemoteException dumping the service failed. + * @throws InterruptedException the dump timed out. + * @throws ErrnoException opening or closing the pipe for the dump failed. + */ + public static String dumpServiceWithShellPermission(String serviceName, String... args) + throws RemoteException, InterruptedException, ErrnoException { + return dumpService(serviceName, true, args); + } +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/FakeDns.kt b/staticlibs/testutils/devicetests/com/android/testutils/FakeDns.kt new file mode 100644 index 0000000000000000000000000000000000000000..1f82a35dc5d63ce21f8d3cf18e727dad9c8e6a9f --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/FakeDns.kt @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.testutils + +import android.net.DnsResolver +import android.net.InetAddresses +import android.os.Looper +import android.os.Handler +import com.android.internal.annotations.GuardedBy +import java.net.InetAddress +import java.util.concurrent.Executor +import org.mockito.invocation.InvocationOnMock +import org.mockito.Mockito.any +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.doAnswer + +const val TYPE_UNSPECIFIED = -1 +// TODO: Integrate with NetworkMonitorTest. +class FakeDns(val mockResolver: DnsResolver) { + class DnsEntry(val hostname: String, val type: Int, val addresses: List<InetAddress>) { + fun match(host: String, type: Int) = hostname.equals(host) && type == type + } + + @GuardedBy("answers") + val answers = ArrayList<DnsEntry>() + + fun getAnswer(hostname: String, type: Int): DnsEntry? = synchronized(answers) { + return answers.firstOrNull { it.match(hostname, type) } + } + + fun setAnswer(hostname: String, answer: Array<String>, type: Int) = synchronized(answers) { + val ans = DnsEntry(hostname, type, generateAnswer(answer)) + // Replace or remove the existing one. + when (val index = answers.indexOfFirst { it.match(hostname, type) }) { + -1 -> answers.add(ans) + else -> answers[index] = ans + } + } + + private fun generateAnswer(answer: Array<String>) = + answer.filterNotNull().map { InetAddresses.parseNumericAddress(it) } + + fun startMocking() { + // Mock DnsResolver.query() w/o type + doAnswer { + mockAnswer(it, 1, -1, 3, 5) + }.`when`(mockResolver).query(any() /* network */, any() /* domain */, anyInt() /* flags */, + any() /* executor */, any() /* cancellationSignal */, any() /*callback*/) + // Mock DnsResolver.query() w/ type + doAnswer { + mockAnswer(it, 1, 2, 4, 6) + }.`when`(mockResolver).query(any() /* network */, any() /* domain */, anyInt() /* nsType */, + anyInt() /* flags */, any() /* executor */, any() /* cancellationSignal */, + any() /*callback*/) + } + + private fun mockAnswer( + it: InvocationOnMock, + posHos: Int, + posType: Int, + posExecutor: Int, + posCallback: Int + ) { + val hostname = it.arguments[posHos] as String + val executor = it.arguments[posExecutor] as Executor + val callback = it.arguments[posCallback] as DnsResolver.Callback<List<InetAddress>> + var type = if (posType != -1) it.arguments[posType] as Int else TYPE_UNSPECIFIED + val answer = getAnswer(hostname, type) + + if (answer != null && !answer.addresses.isNullOrEmpty()) { + Handler(Looper.getMainLooper()).post({ executor.execute({ + callback.onAnswer(answer.addresses, 0); }) }) + } + } + + /** Clears all entries. */ + fun clearAll() = synchronized(answers) { + answers.clear() + } +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/HandlerUtils.kt b/staticlibs/testutils/devicetests/com/android/testutils/HandlerUtils.kt new file mode 100644 index 0000000000000000000000000000000000000000..f00ca116c78c00ef3fa1d203becfac742567d35a --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/HandlerUtils.kt @@ -0,0 +1,90 @@ +/* + * 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. + */ + +@file:JvmName("HandlerUtils") + +package com.android.testutils + +import android.os.ConditionVariable +import android.os.Handler +import android.os.HandlerThread +import android.util.Log +import com.android.testutils.FunctionalUtils.ThrowingRunnable +import com.android.testutils.FunctionalUtils.ThrowingSupplier +import java.lang.Exception +import java.util.concurrent.Executor +import kotlin.test.fail + +private const val TAG = "HandlerUtils" + +/** + * Block until the specified Handler or HandlerThread becomes idle, or until timeoutMs has passed. + */ +fun HandlerThread.waitForIdle(timeoutMs: Int) = threadHandler.waitForIdle(timeoutMs.toLong()) +fun HandlerThread.waitForIdle(timeoutMs: Long) = threadHandler.waitForIdle(timeoutMs) +fun Handler.waitForIdle(timeoutMs: Int) = waitForIdle(timeoutMs.toLong()) +fun Handler.waitForIdle(timeoutMs: Long) { + val cv = ConditionVariable(false) + post(cv::open) + if (!cv.block(timeoutMs)) { + fail("Handler did not become idle after ${timeoutMs}ms") + } +} + +/** + * Block until the given Serial Executor becomes idle, or until timeoutMs has passed. + */ +fun waitForIdleSerialExecutor(executor: Executor, timeoutMs: Long) { + val cv = ConditionVariable() + executor.execute(cv::open) + if (!cv.block(timeoutMs)) { + fail("Executor did not become idle after ${timeoutMs}ms") + } +} + +/** + * Executes a block of code that returns a value, making its side effects visible on the caller and + * the handler thread. + * + * After this function returns, the side effects of the passed block of code are guaranteed to be + * observed both on the thread running the handler and on the thread running this method. + * To achieve this, this method runs the passed block on the handler and blocks this thread + * until it's executed, so keep in mind this method will block, (including, if the handler isn't + * running, blocking forever). + */ +fun <T> visibleOnHandlerThread(handler: Handler, supplier: ThrowingSupplier<T>): T { + val cv = ConditionVariable() + var rv: Result<T> = Result.failure(RuntimeException("Not run")) + handler.post { + try { + rv = Result.success(supplier.get()) + } catch (exception: Exception) { + Log.e(TAG, "visibleOnHandlerThread caught exception", exception) + rv = Result.failure(exception) + } + cv.open() + } + // After block() returns, the handler thread has seen the change (since it ran it) + // and this thread also has seen the change (since cv.open() happens-before cv.block() + // returns). + cv.block() + return rv.getOrThrow() +} + +/** Overload of visibleOnHandlerThread but executes a block of code that does not return a value. */ +inline fun visibleOnHandlerThread(handler: Handler, r: ThrowingRunnable){ + visibleOnHandlerThread(handler, ThrowingSupplier<Unit> { r.run() }) +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/NatExternalPacketForwarder.kt b/staticlibs/testutils/devicetests/com/android/testutils/NatExternalPacketForwarder.kt new file mode 100644 index 0000000000000000000000000000000000000000..d7961a08f5d848f5e3e13674c100c438403a1e1f --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/NatExternalPacketForwarder.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.testutils + +import java.io.FileDescriptor +import java.net.InetAddress + +/** + * A class that forwards packets from the external {@link TestNetworkInterface} to the internal + * {@link TestNetworkInterface} with NAT. See {@link NatPacketForwarderBase} for detail. + */ +class NatExternalPacketForwarder( + srcFd: FileDescriptor, + mtu: Int, + dstFd: FileDescriptor, + extAddr: InetAddress, + natMap: PacketBridge.NatMap +) : NatPacketForwarderBase(srcFd, mtu, dstFd, extAddr, natMap) { + + /** + * Rewrite addresses, ports and fix up checksums for packets received on the external + * interface. + * + * Incoming response from external interface which is being forwarded to the internal + * interface with translated address, e.g. 1.2.3.4:80 -> 8.8.8.8:1234 + * will be translated into 8.8.8.8:80 -> 192.168.1.1:5678. + * + * For packets that are not an incoming response, do not forward them to the + * internal interface. + */ + override fun preparePacketForForwarding(buf: ByteArray, len: Int, version: Int, proto: Int) { + val (addrPos, addrLen) = getAddressPositionAndLength(version) + + // TODO: support one external address per ip version. + val extAddrBuf = mExtAddr.address + if (addrLen != extAddrBuf.size) throw IllegalStateException("Packet IP version mismatch") + + // Get internal address by port. + val transportOffset = + if (version == 4) PacketReflector.IPV4_HEADER_LENGTH + else PacketReflector.IPV6_HEADER_LENGTH + val dstPort = getPortAt(buf, transportOffset + DESTINATION_PORT_OFFSET) + val intAddrInfo = synchronized(mNatMap) { mNatMap.fromExternalPort(dstPort) } + // No mapping, skip. This usually happens if the connection is initiated directly on + // the external interface, e.g. DNS64 resolution, network validation, etc. + if (intAddrInfo == null) return + + val intAddrBuf = intAddrInfo.address.address + val intPort = intAddrInfo.port + + // Copy the original destination to into the source address. + for (i in 0 until addrLen) { + buf[addrPos + i] = buf[addrPos + addrLen + i] + } + + // Copy the internal address into the destination address. + for (i in 0 until addrLen) { + buf[addrPos + addrLen + i] = intAddrBuf[i] + } + + // Copy the internal port into the destination port. + setPortAt(intPort, buf, transportOffset + DESTINATION_PORT_OFFSET) + + // Fix IP and Transport layer checksum. + fixPacketChecksum(buf, len, version, proto.toByte()) + } +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/NatInternalPacketForwarder.kt b/staticlibs/testutils/devicetests/com/android/testutils/NatInternalPacketForwarder.kt new file mode 100644 index 0000000000000000000000000000000000000000..fa39d19e1c27d9bca4a535af45e9c4a34e3c2ed3 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/NatInternalPacketForwarder.kt @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.testutils + +import java.io.FileDescriptor +import java.net.InetAddress + +/** + * A class that forwards packets from the internal {@link TestNetworkInterface} to the external + * {@link TestNetworkInterface} with NAT. See {@link NatPacketForwarderBase} for detail. + */ +class NatInternalPacketForwarder( + srcFd: FileDescriptor, + mtu: Int, + dstFd: FileDescriptor, + extAddr: InetAddress, + natMap: PacketBridge.NatMap +) : NatPacketForwarderBase(srcFd, mtu, dstFd, extAddr, natMap) { + + /** + * Rewrite addresses, ports and fix up checksums for packets received on the internal + * interface. + * + * Outgoing packet from the internal interface which is being forwarded to the + * external interface with translated address, e.g. 192.168.1.1:5678 -> 8.8.8.8:80 + * will be translated into 8.8.8.8:1234 -> 1.2.3.4:80. + * + * The external port, e.g. 1234 in the above example, is the port number assigned by + * the forwarder when creating the mapping to identify the source address and port when + * the response is coming from the external interface. See {@link PacketBridge.NatMap} + * for detail. + */ + override fun preparePacketForForwarding(buf: ByteArray, len: Int, version: Int, proto: Int) { + val (addrPos, addrLen) = getAddressPositionAndLength(version) + + // TODO: support one external address per ip version. + val extAddrBuf = mExtAddr.address + if (addrLen != extAddrBuf.size) throw IllegalStateException("Packet IP version mismatch") + + val srcAddr = getInetAddressAt(buf, addrPos, addrLen) + + // Copy the original destination to into the source address. + for (i in 0 until addrLen) { + buf[addrPos + i] = buf[addrPos + addrLen + i] + } + + // Copy the external address into the destination address. + for (i in 0 until addrLen) { + buf[addrPos + addrLen + i] = extAddrBuf[i] + } + + // Add an entry to NAT mapping table. + val transportOffset = + if (version == 4) PacketReflector.IPV4_HEADER_LENGTH + else PacketReflector.IPV6_HEADER_LENGTH + val srcPort = getPortAt(buf, transportOffset) + val extPort = synchronized(mNatMap) { mNatMap.toExternalPort(srcAddr, srcPort, proto) } + // Copy the external port to into the source port. + setPortAt(extPort, buf, transportOffset) + + // Fix IP and Transport layer checksum. + fixPacketChecksum(buf, len, version, proto.toByte()) + } +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/NatPacketForwarderBase.java b/staticlibs/testutils/devicetests/com/android/testutils/NatPacketForwarderBase.java new file mode 100644 index 0000000000000000000000000000000000000000..0a2b5d418c5ab983be7803b7ea0374f6ba335bb7 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/NatPacketForwarderBase.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.testutils; + +import static com.android.testutils.PacketReflector.IPPROTO_TCP; +import static com.android.testutils.PacketReflector.IPPROTO_UDP; +import static com.android.testutils.PacketReflector.IPV4_HEADER_LENGTH; +import static com.android.testutils.PacketReflector.IPV6_HEADER_LENGTH; +import static com.android.testutils.PacketReflector.IPV6_PROTO_OFFSET; +import static com.android.testutils.PacketReflector.TCP_HEADER_LENGTH; +import static com.android.testutils.PacketReflector.UDP_HEADER_LENGTH; + +import android.annotation.NonNull; +import android.net.TestNetworkInterface; +import android.system.ErrnoException; +import android.system.Os; +import android.util.Log; + +import androidx.annotation.GuardedBy; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.net.InetAddress; +import java.util.Objects; + +/** + * A class that forwards packets from a {@link TestNetworkInterface} to another + * {@link TestNetworkInterface} with NAT. + * + * For testing purposes, a {@link TestNetworkInterface} provides a {@link FileDescriptor} + * which allows content injection on the test network. However, this could be hard to use + * because the callers need to compose IP packets in order to inject content to the + * test network. + * + * In order to remove the need of composing the IP packets, this class forwards IP packets to + * the {@link FileDescriptor} of another {@link TestNetworkInterface} instance. Thus, + * the TCP/IP headers could be parsed/composed automatically by the protocol stack of this + * additional {@link TestNetworkInterface}, while the payload is supplied by the + * servers run on the interface. + * + * To make it work, an internal interface and an external interface are defined, where + * the client might send packets from the internal interface which are originated from + * multiple addresses to a server that listens on the external address. + * + * When forwarding the outgoing packet on the internal interface, a simple NAT mechanism + * is implemented during forwarding, which will swap the source and destination, + * but replacing the source address with the external address, + * e.g. 192.168.1.1:1234 -> 8.8.8.8:80 will be translated into 8.8.8.8:1234 -> 1.2.3.4:80. + * + * For the above example, a client who sends http request will have a hallucination that + * it is talking to a remote server at 8.8.8.8. Also, the server listens on 1.2.3.4 will + * have a different hallucination that the request is sent from a remote client at 8.8.8.8, + * to a local address 1.2.3.4. + * + * And a NAT mapping is created at the time when the outgoing packet is forwarded. + * With a different internal source port, the instance learned that when a response with the + * destination port 1234, it should forward the packet to the internal address 192.168.1.1. + * + * For the incoming packet received from external interface, for example a http response sent + * from the http server, the same mechanism is applied but in a different direction, + * where the source and destination will be swapped, and the source address will be replaced + * with the internal address, which is obtained from the NAT mapping described above. + */ +public abstract class NatPacketForwarderBase extends Thread { + private static final String TAG = "NatPacketForwarder"; + static final int DESTINATION_PORT_OFFSET = 2; + + // The source fd to read packets from. + @NonNull + final FileDescriptor mSrcFd; + // The buffer to temporarily hold the entire packet after receiving. + @NonNull + final byte[] mBuf; + // The destination fd to write packets to. + @NonNull + final FileDescriptor mDstFd; + // The NAT mapping table shared between two NatPacketForwarder instances to map from + // the source port to the associated internal address. The map can be read/write from two + // different threads on any given time whenever receiving packets on the + // {@link TestNetworkInterface}. Thus, synchronize on the object when reading/writing is needed. + @GuardedBy("mNatMap") + @NonNull + final PacketBridge.NatMap mNatMap; + // The address of the external interface. See {@link NatPacketForwarder}. + @NonNull + final InetAddress mExtAddr; + + /** + * Construct a {@link NatPacketForwarderBase}. + * + * This class reads packets from {@code srcFd} of a {@link TestNetworkInterface}, and + * forwards them to the {@code dstFd} of another {@link TestNetworkInterface} with + * NAT applied. See {@link NatPacketForwarderBase}. + * + * To apply NAT, the address of the external interface needs to be supplied through + * {@code extAddr} to identify the external interface. And a shared NAT mapping table, + * {@code natMap} is needed to be shared between these two instances. + * + * Note that this class is not useful if the instance is not managed by a + * {@link PacketBridge} to set up a two-way communication. + * + * @param srcFd {@link FileDescriptor} to read packets from. + * @param mtu MTU of the test network. + * @param dstFd {@link FileDescriptor} to write packets to. + * @param extAddr the external address, which is the address of the external interface. + * See {@link NatPacketForwarderBase}. + * @param natMap the NAT mapping table shared between two {@link NatPacketForwarderBase} + * instance. + */ + public NatPacketForwarderBase(@NonNull FileDescriptor srcFd, int mtu, + @NonNull FileDescriptor dstFd, @NonNull InetAddress extAddr, + @NonNull PacketBridge.NatMap natMap) { + super(TAG); + mSrcFd = Objects.requireNonNull(srcFd); + mBuf = new byte[mtu]; + mDstFd = Objects.requireNonNull(dstFd); + mExtAddr = Objects.requireNonNull(extAddr); + mNatMap = Objects.requireNonNull(natMap); + } + + /** + * A method to prepare forwarding packets between two instances of {@link TestNetworkInterface}, + * which includes re-write addresses, ports and fix up checksums. + * Subclasses should override this method to implement a simple NAT. + */ + abstract void preparePacketForForwarding(@NonNull byte[] buf, int len, int version, int proto); + + private void forwardPacket(@NonNull byte[] buf, int len) { + try { + Os.write(mDstFd, buf, 0, len); + } catch (ErrnoException | IOException e) { + Log.e(TAG, "Error writing packet: " + e.getMessage()); + } + } + + // Reads one packet from mSrcFd, and writes the packet to the mDstFd for supported protocols. + private void processPacket() { + final int len = PacketReflectorUtil.readPacket(mSrcFd, mBuf); + if (len < 1) { + // Usually happens when socket read is being interrupted, e.g. stopping PacketForwarder. + return; + } + + final int version = mBuf[0] >>> 4; + final int protoPos, ipHdrLen; + switch (version) { + case 4: + ipHdrLen = IPV4_HEADER_LENGTH; + protoPos = PacketReflector.IPV4_PROTO_OFFSET; + break; + case 6: + ipHdrLen = IPV6_HEADER_LENGTH; + protoPos = IPV6_PROTO_OFFSET; + break; + default: + throw new IllegalStateException("Unexpected version: " + version); + } + if (len < ipHdrLen) { + throw new IllegalStateException("Unexpected buffer length: " + len); + } + + final byte proto = mBuf[protoPos]; + final int transportHdrLen; + switch (proto) { + case IPPROTO_TCP: + transportHdrLen = TCP_HEADER_LENGTH; + break; + case IPPROTO_UDP: + transportHdrLen = UDP_HEADER_LENGTH; + break; + // TODO: Support ICMP. + default: + return; // Unknown protocol, ignored. + } + + if (len < ipHdrLen + transportHdrLen) { + throw new IllegalStateException("Unexpected buffer length: " + len); + } + // Re-write addresses, ports and fix up checksums. + preparePacketForForwarding(mBuf, len, version, proto); + // Send the packet to the destination fd. + forwardPacket(mBuf, len); + } + + @Override + public void run() { + Log.i(TAG, "starting fd=" + mSrcFd + " valid=" + mSrcFd.valid()); + while (!interrupted() && mSrcFd.valid()) { + processPacket(); + } + Log.i(TAG, "exiting fd=" + mSrcFd + " valid=" + mSrcFd.valid()); + } +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/NetlinkTestUtils.kt b/staticlibs/testutils/devicetests/com/android/testutils/NetlinkTestUtils.kt new file mode 100644 index 0000000000000000000000000000000000000000..3f5460bf9727f179b34959b2c04c0cd4503aae60 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/NetlinkTestUtils.kt @@ -0,0 +1,108 @@ +/* + * 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. + */ + +@file:JvmName("NetlinkTestUtils") + +package com.android.testutils + +import com.android.net.module.util.netlink.NetlinkConstants.RTM_DELNEIGH +import com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWNEIGH +import libcore.util.HexEncoding +import libcore.util.HexEncoding.encodeToString +import java.net.Inet6Address +import java.net.InetAddress + +private const val VRRP_MAC_ADDR = "00005e000164" + +/** + * Make a RTM_NEWNEIGH netlink message. + */ +@JvmOverloads +fun makeNewNeighMessage( + neighAddr: InetAddress, + nudState: Short, + linkLayerAddr: String = VRRP_MAC_ADDR +) = makeNeighborMessage( + neighAddr = neighAddr, + type = RTM_NEWNEIGH, + nudState = nudState, + linkLayerAddr = linkLayerAddr +) + +/** + * Make a RTM_DELNEIGH netlink message. + */ +fun makeDelNeighMessage( + neighAddr: InetAddress, + nudState: Short +) = makeNeighborMessage( + neighAddr = neighAddr, + type = RTM_DELNEIGH, + nudState = nudState +) + +private fun makeNeighborMessage( + neighAddr: InetAddress, + type: Short, + nudState: Short, + linkLayerAddr: String = VRRP_MAC_ADDR +) = HexEncoding.decode( + /* ktlint-disable indent */ + // -- struct nlmsghdr -- + // length = 88 or 76: + (if (neighAddr is Inet6Address) "58000000" else "4c000000") + + type.toLEHex() + // type + "0000" + // flags + "00000000" + // seqno + "00000000" + // pid (0 == kernel) + // struct ndmsg + // family (AF_INET6 or AF_INET) + (if (neighAddr is Inet6Address) "0a" else "02") + + "00" + // pad1 + "0000" + // pad2 + "15000000" + // interface index (21 == wlan0, on test device) + nudState.toLEHex() + // NUD state + "00" + // flags + "01" + // type + // -- struct nlattr: NDA_DST -- + // length = 20 or 8: + (if (neighAddr is Inet6Address) "1400" else "0800") + + "0100" + // type (1 == NDA_DST, for neighbor messages) + // IP address: + encodeToString(neighAddr.address) + + // -- struct nlattr: NDA_LLADDR -- + "0a00" + // length = 10 + "0200" + // type (2 == NDA_LLADDR, for neighbor messages) + linkLayerAddr + // MAC Address(default == 00:00:5e:00:01:64) + "0000" + // padding, for 4 byte alignment + // -- struct nlattr: NDA_PROBES -- + "0800" + // length = 8 + "0400" + // type (4 == NDA_PROBES, for neighbor messages) + "01000000" + // number of probes + // -- struct nlattr: NDA_CACHEINFO -- + "1400" + // length = 20 + "0300" + // type (3 == NDA_CACHEINFO, for neighbor messages) + "05190000" + // ndm_used, as "clock ticks ago" + "05190000" + // ndm_confirmed, as "clock ticks ago" + "190d0000" + // ndm_updated, as "clock ticks ago" + "00000000", // ndm_refcnt + false /* allowSingleChar */) + /* ktlint-enable indent */ + +/** + * Convert a [Short] to a little-endian hex string. + */ +private fun Short.toLEHex() = String.format("%04x", java.lang.Short.reverseBytes(this)) diff --git a/staticlibs/testutils/devicetests/com/android/testutils/NetworkStatsProviderCbStubCompat.java b/staticlibs/testutils/devicetests/com/android/testutils/NetworkStatsProviderCbStubCompat.java new file mode 100644 index 0000000000000000000000000000000000000000..642da7ac9ad0118ec386844a3e8d9f6e83270801 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/NetworkStatsProviderCbStubCompat.java @@ -0,0 +1,46 @@ +/* + * 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. + */ + +package com.android.testutils; + +import android.net.NetworkStats; +import android.net.netstats.provider.INetworkStatsProviderCallback; +import android.os.RemoteException; + +/** + * A shim class that allows {@link TestableNetworkStatsProviderCbBinder} to be built against + * different SDK versions. + */ +public class NetworkStatsProviderCbStubCompat extends INetworkStatsProviderCallback.Stub { + @Override + public void notifyStatsUpdated(int token, NetworkStats ifaceStats, NetworkStats uidStats) + throws RemoteException {} + + @Override + public void notifyAlertReached() throws RemoteException {} + + /** Added in T. */ + public void notifyLimitReached() throws RemoteException {} + + /** Added in T. */ + public void notifyWarningReached() throws RemoteException {} + + /** Added in S, removed in T. */ + public void notifyWarningOrLimitReached() throws RemoteException {} + + @Override + public void unregister() throws RemoteException {} +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/NetworkStatsProviderStubCompat.java b/staticlibs/testutils/devicetests/com/android/testutils/NetworkStatsProviderStubCompat.java new file mode 100644 index 0000000000000000000000000000000000000000..a77aa025acb57fd8123d93f9f3ed1859bf85e9b1 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/NetworkStatsProviderStubCompat.java @@ -0,0 +1,37 @@ +/* + * 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. + */ + +package com.android.testutils; + +import android.net.netstats.provider.INetworkStatsProvider; + +/** + * A shim class that allows {@link TestableNetworkStatsProviderBinder} to be built against + * different SDK versions. + */ +public class NetworkStatsProviderStubCompat extends INetworkStatsProvider.Stub { + @Override + public void onRequestStatsUpdate(int token) {} + + // Removed and won't be called in S+. + public void onSetLimit(String iface, long quotaBytes) {} + + @Override + public void onSetAlert(long bytes) {} + + // Added in S. + public void onSetWarningAndLimit(String iface, long warningBytes, long limitBytes) {} +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/NetworkStatsUtils.kt b/staticlibs/testutils/devicetests/com/android/testutils/NetworkStatsUtils.kt new file mode 100644 index 0000000000000000000000000000000000000000..8324b25ee3933bef1ee0f3099eb3626ec84fbe74 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/NetworkStatsUtils.kt @@ -0,0 +1,78 @@ +/* + * 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. + */ + +package com.android.testutils + +import android.net.NetworkStats +import kotlin.test.assertTrue + +@JvmOverloads +fun orderInsensitiveEquals( + leftStats: NetworkStats, + rightStats: NetworkStats, + compareTime: Boolean = false +): Boolean { + if (leftStats == rightStats) return true + if (compareTime && leftStats.getElapsedRealtime() != rightStats.getElapsedRealtime()) { + return false + } + + // While operations such as add/subtract will preserve empty entries. This will make + // the result be hard to verify during test. Remove them before comparing since they + // are not really affect correctness. + // TODO (b/152827872): Remove empty entries after addition/subtraction. + val leftTrimmedEmpty = leftStats.removeEmptyEntries() + val rightTrimmedEmpty = rightStats.removeEmptyEntries() + + if (leftTrimmedEmpty.size() != rightTrimmedEmpty.size()) return false + val left = NetworkStats.Entry() + val right = NetworkStats.Entry() + // Order insensitive compare. + for (i in 0 until leftTrimmedEmpty.size()) { + leftTrimmedEmpty.getValues(i, left) + val j: Int = rightTrimmedEmpty.findIndexHinted(left.iface, left.uid, left.set, left.tag, + left.metered, left.roaming, left.defaultNetwork, i) + if (j == -1) return false + rightTrimmedEmpty.getValues(j, right) + if (left != right) return false + } + return true +} + +/** + * Assert that two {@link NetworkStats} are equals, assuming the order of the records are not + * necessarily the same. + * + * @note {@code elapsedRealtime} is not compared by default, given that in test cases that is not + * usually used. + */ +@JvmOverloads +fun assertNetworkStatsEquals( + expected: NetworkStats, + actual: NetworkStats, + compareTime: Boolean = false +) { + assertTrue(orderInsensitiveEquals(expected, actual, compareTime), + "expected: " + expected + " but was: " + actual) +} + +/** + * Assert that after being parceled then unparceled, {@link NetworkStats} is equal to the original + * object. + */ +fun assertParcelingIsLossless(stats: NetworkStats) { + assertParcelingIsLossless(stats, { a, b -> orderInsensitiveEquals(a, b) }) +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/NonNullTestUtils.java b/staticlibs/testutils/devicetests/com/android/testutils/NonNullTestUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..463c4706ccccff1f93c82992f62a0f564078654a --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/NonNullTestUtils.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.testutils; + +import android.annotation.NonNull; + +/** + * Utilities to help Kotlin test to verify java @NonNull code + */ +public class NonNullTestUtils { + /** + * This method allows Kotlin to pass nullable to @NonNull java code for testing. + * For Foo(@NonNull arg) java method, Kotlin can pass nullable variable by + * Foo(NonNullTestUtils.nullUnsafe(nullableVar)). + */ + @NonNull public static <T> T nullUnsafe(T v) { + return v; + } +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/PacketBridge.kt b/staticlibs/testutils/devicetests/com/android/testutils/PacketBridge.kt new file mode 100644 index 0000000000000000000000000000000000000000..d50f78a178c20b2e9e39a7079648120c8341352b --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/PacketBridge.kt @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.testutils + +import android.content.Context +import android.net.ConnectivityManager +import android.net.LinkAddress +import android.net.LinkProperties +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkRequest +import android.net.TestNetworkInterface +import android.net.TestNetworkManager +import android.net.TestNetworkSpecifier +import android.os.Binder +import com.android.testutils.RecorderCallback.CallbackEntry.Available +import java.net.InetAddress +import libcore.io.IoUtils + +private const val MIN_PORT_NUMBER = 1025 +private const val MAX_PORT_NUMBER = 65535 + +/** + * A class that set up two {@link TestNetworkInterface} with NAT, and forward packets between them. + * + * See {@link NatPacketForwarder} for more detailed information. + */ +class PacketBridge( + context: Context, + internalAddr: LinkAddress, + externalAddr: LinkAddress, + dnsAddr: InetAddress +) { + private val natMap = NatMap() + private val binder = Binder() + + private val cm = context.getSystemService(ConnectivityManager::class.java)!! + private val tnm = context.getSystemService(TestNetworkManager::class.java)!! + + // Create test networks. + private val internalIface = tnm.createTunInterface(listOf(internalAddr)) + private val externalIface = tnm.createTunInterface(listOf(externalAddr)) + + // Register test networks to ConnectivityService. + private val internalNetworkCallback: TestableNetworkCallback + private val externalNetworkCallback: TestableNetworkCallback + val internalNetwork: Network + val externalNetwork: Network + init { + val (inCb, inNet) = createTestNetwork(internalIface, internalAddr, dnsAddr) + val (exCb, exNet) = createTestNetwork(externalIface, externalAddr, dnsAddr) + internalNetworkCallback = inCb + externalNetworkCallback = exCb + internalNetwork = inNet + externalNetwork = exNet + } + + // Setup the packet bridge. + private val internalFd = internalIface.fileDescriptor.fileDescriptor + private val externalFd = externalIface.fileDescriptor.fileDescriptor + + private val pr1 = NatInternalPacketForwarder( + internalFd, + 1500, + externalFd, + externalAddr.address, + natMap + ) + private val pr2 = NatExternalPacketForwarder( + externalFd, + 1500, + internalFd, + externalAddr.address, + natMap + ) + + fun start() { + IoUtils.setBlocking(internalFd, true /* blocking */) + IoUtils.setBlocking(externalFd, true /* blocking */) + pr1.start() + pr2.start() + } + + fun stop() { + pr1.interrupt() + pr2.interrupt() + cm.unregisterNetworkCallback(internalNetworkCallback) + cm.unregisterNetworkCallback(externalNetworkCallback) + } + + /** + * Creates a test network with given test TUN interface and addresses. + */ + private fun createTestNetwork( + testIface: TestNetworkInterface, + addr: LinkAddress, + dnsAddr: InetAddress + ): Pair<TestableNetworkCallback, Network> { + // Make a network request to hold the test network + val nr = NetworkRequest.Builder() + .clearCapabilities() + .addTransportType(NetworkCapabilities.TRANSPORT_TEST) + .setNetworkSpecifier(TestNetworkSpecifier(testIface.interfaceName)) + .build() + val testCb = TestableNetworkCallback() + cm.requestNetwork(nr, testCb) + + val lp = LinkProperties().apply { + addLinkAddress(addr) + interfaceName = testIface.interfaceName + addDnsServer(dnsAddr) + } + tnm.setupTestNetwork(lp, true /* isMetered */, binder) + + // Wait for available before return. + val network = testCb.expect<Available>().network + return testCb to network + } + + /** + * A helper class to maintain the mappings between internal addresses/ports and external + * ports. + * + * This class assigns an unused external port number if the mapping between + * srcaddress:srcport:protocol and the external port does not exist yet. + * + * Note that this class is not thread-safe. The instance of the class needs to be + * synchronized in the callers when being used in multiple threads. + */ + class NatMap { + data class AddressInfo(val address: InetAddress, val port: Int, val protocol: Int) + + private val mToExternalPort = HashMap<AddressInfo, Int>() + private val mFromExternalPort = HashMap<Int, AddressInfo>() + + // Skip well-known port 0~1024. + private var nextExternalPort = MIN_PORT_NUMBER + + fun toExternalPort(addr: InetAddress, port: Int, protocol: Int): Int { + val info = AddressInfo(addr, port, protocol) + val extPort: Int + if (!mToExternalPort.containsKey(info)) { + extPort = nextExternalPort++ + if (nextExternalPort > MAX_PORT_NUMBER) { + throw IllegalStateException("Available ports are exhausted") + } + mToExternalPort[info] = extPort + mFromExternalPort[extPort] = info + } else { + extPort = mToExternalPort[info]!! + } + return extPort + } + + fun fromExternalPort(port: Int): AddressInfo? { + return mFromExternalPort[port] + } + } +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/PacketReflector.java b/staticlibs/testutils/devicetests/com/android/testutils/PacketReflector.java new file mode 100644 index 0000000000000000000000000000000000000000..69392d444e3e861276883ffcdf3df68085c3e787 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/PacketReflector.java @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.testutils; + +import static android.system.OsConstants.ICMP6_ECHO_REPLY; +import static android.system.OsConstants.ICMP6_ECHO_REQUEST; + +import android.annotation.NonNull; +import android.net.TestNetworkInterface; +import android.system.ErrnoException; +import android.system.Os; +import android.util.Log; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.util.Objects; + +/** + * A class that echoes packets received on a {@link TestNetworkInterface} back to itself. + * + * For testing purposes, sometimes a mocked environment to simulate a simple echo from the + * server side is needed. This is particularly useful if the test, e.g. VpnTest, is + * heavily relying on the outside world. + * + * This class reads packets from the {@link FileDescriptor} of a {@link TestNetworkInterface}, and: + * 1. For TCP and UDP packets, simply swaps the source address and the destination + * address, then send it back to the {@link FileDescriptor}. + * 2. For ICMP ping packets, composes a ping reply and sends it back to the sender. + * 3. Ignore all other packets. + */ +public class PacketReflector extends Thread { + + static final int IPV4_HEADER_LENGTH = 20; + static final int IPV6_HEADER_LENGTH = 40; + + static final int IPV4_ADDR_OFFSET = 12; + static final int IPV6_ADDR_OFFSET = 8; + static final int IPV4_ADDR_LENGTH = 4; + static final int IPV6_ADDR_LENGTH = 16; + + static final int IPV4_PROTO_OFFSET = 9; + static final int IPV6_PROTO_OFFSET = 6; + + static final byte IPPROTO_ICMP = 1; + static final byte IPPROTO_TCP = 6; + static final byte IPPROTO_UDP = 17; + private static final byte IPPROTO_ICMPV6 = 58; + + private static final int ICMP_HEADER_LENGTH = 8; + static final int TCP_HEADER_LENGTH = 20; + static final int UDP_HEADER_LENGTH = 8; + + private static final byte ICMP_ECHO = 8; + private static final byte ICMP_ECHOREPLY = 0; + + private static String TAG = "PacketReflector"; + + @NonNull + private final FileDescriptor mFd; + @NonNull + private final byte[] mBuf; + + /** + * Construct a {@link PacketReflector} from the given {@code fd} of + * a {@link TestNetworkInterface}. + * + * @param fd {@link FileDescriptor} to read/write packets. + * @param mtu MTU of the test network. + */ + public PacketReflector(@NonNull FileDescriptor fd, int mtu) { + super("PacketReflector"); + mFd = Objects.requireNonNull(fd); + mBuf = new byte[mtu]; + } + + private static void swapBytes(@NonNull byte[] buf, int pos1, int pos2, int len) { + for (int i = 0; i < len; i++) { + byte b = buf[pos1 + i]; + buf[pos1 + i] = buf[pos2 + i]; + buf[pos2 + i] = b; + } + } + + private static void swapAddresses(@NonNull byte[] buf, int version) { + int addrPos, addrLen; + switch (version) { + case 4: + addrPos = IPV4_ADDR_OFFSET; + addrLen = IPV4_ADDR_LENGTH; + break; + case 6: + addrPos = IPV6_ADDR_OFFSET; + addrLen = IPV6_ADDR_LENGTH; + break; + default: + throw new IllegalArgumentException(); + } + swapBytes(buf, addrPos, addrPos + addrLen, addrLen); + } + + // Reflect TCP packets: swap the source and destination addresses, but don't change the ports. + // This is used by the test to "connect to itself" through the VPN. + private void processTcpPacket(@NonNull byte[] buf, int version, int len, int hdrLen) { + if (len < hdrLen + TCP_HEADER_LENGTH) { + return; + } + + // Swap src and dst IP addresses. + swapAddresses(buf, version); + + // Send the packet back. + writePacket(buf, len); + } + + // Echo UDP packets: swap source and destination addresses, and source and destination ports. + // This is used by the test to check that the bytes it sends are echoed back. + private void processUdpPacket(@NonNull byte[] buf, int version, int len, int hdrLen) { + if (len < hdrLen + UDP_HEADER_LENGTH) { + return; + } + + // Swap src and dst IP addresses. + swapAddresses(buf, version); + + // Swap dst and src ports. + int portOffset = hdrLen; + swapBytes(buf, portOffset, portOffset + 2, 2); + + // Send the packet back. + writePacket(buf, len); + } + + private void processIcmpPacket(@NonNull byte[] buf, int version, int len, int hdrLen) { + if (len < hdrLen + ICMP_HEADER_LENGTH) { + return; + } + + byte type = buf[hdrLen]; + if (!(version == 4 && type == ICMP_ECHO) && + !(version == 6 && type == (byte) ICMP6_ECHO_REQUEST)) { + return; + } + + // Save the ping packet we received. + byte[] request = buf.clone(); + + // Swap src and dst IP addresses, and send the packet back. + // This effectively pings the device to see if it replies. + swapAddresses(buf, version); + writePacket(buf, len); + + // The device should have replied, and buf should now contain a ping response. + int received = PacketReflectorUtil.readPacket(mFd, buf); + if (received != len) { + Log.i(TAG, "Reflecting ping did not result in ping response: " + + "read=" + received + " expected=" + len); + return; + } + + byte replyType = buf[hdrLen]; + if ((type == ICMP_ECHO && replyType != ICMP_ECHOREPLY) + || (type == (byte) ICMP6_ECHO_REQUEST && replyType != (byte) ICMP6_ECHO_REPLY)) { + Log.i(TAG, "Received unexpected ICMP reply: original " + type + + ", reply " + replyType); + return; + } + + // Compare the response we got with the original packet. + // The only thing that should have changed are addresses, type and checksum. + // Overwrite them with the received bytes and see if the packet is otherwise identical. + request[hdrLen] = buf[hdrLen]; // Type + request[hdrLen + 2] = buf[hdrLen + 2]; // Checksum byte 1. + request[hdrLen + 3] = buf[hdrLen + 3]; // Checksum byte 2. + + // Since Linux kernel 4.2, net.ipv6.auto_flowlabels is set by default, and therefore + // the request and reply may have different IPv6 flow label: ignore that as well. + if (version == 6) { + request[1] = (byte) (request[1] & 0xf0 | buf[1] & 0x0f); + request[2] = buf[2]; + request[3] = buf[3]; + } + + for (int i = 0; i < len; i++) { + if (buf[i] != request[i]) { + Log.i(TAG, "Received non-matching packet when expecting ping response."); + return; + } + } + + // Now swap the addresses again and reflect the packet. This sends a ping reply. + swapAddresses(buf, version); + writePacket(buf, len); + } + + private void writePacket(@NonNull byte[] buf, int len) { + try { + Os.write(mFd, buf, 0, len); + } catch (ErrnoException | IOException e) { + Log.e(TAG, "Error writing packet: " + e.getMessage()); + } + } + + // Reads one packet from our mFd, and possibly writes the packet back. + private void processPacket() { + int len = PacketReflectorUtil.readPacket(mFd, mBuf); + if (len < 1) { + // Usually happens when socket read is being interrupted, e.g. stopping PacketReflector. + return; + } + + int version = mBuf[0] >> 4; + int protoPos, hdrLen; + if (version == 4) { + hdrLen = IPV4_HEADER_LENGTH; + protoPos = IPV4_PROTO_OFFSET; + } else if (version == 6) { + hdrLen = IPV6_HEADER_LENGTH; + protoPos = IPV6_PROTO_OFFSET; + } else { + throw new IllegalStateException("Unexpected version: " + version); + } + + if (len < hdrLen) { + throw new IllegalStateException("Unexpected buffer length: " + len); + } + + byte proto = mBuf[protoPos]; + switch (proto) { + case IPPROTO_ICMP: + // fall through + case IPPROTO_ICMPV6: + processIcmpPacket(mBuf, version, len, hdrLen); + break; + case IPPROTO_TCP: + processTcpPacket(mBuf, version, len, hdrLen); + break; + case IPPROTO_UDP: + processUdpPacket(mBuf, version, len, hdrLen); + break; + } + } + + public void run() { + Log.i(TAG, "starting fd=" + mFd + " valid=" + mFd.valid()); + while (!interrupted() && mFd.valid()) { + processPacket(); + } + Log.i(TAG, "exiting fd=" + mFd + " valid=" + mFd.valid()); + } +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/PacketReflectorUtil.kt b/staticlibs/testutils/devicetests/com/android/testutils/PacketReflectorUtil.kt new file mode 100644 index 0000000000000000000000000000000000000000..498b1a36f037a6fb6f2f6a67b5e1c98a8161a992 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/PacketReflectorUtil.kt @@ -0,0 +1,114 @@ +/* + * 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. + */ + +@file:JvmName("PacketReflectorUtil") + +package com.android.testutils + +import android.system.ErrnoException +import android.system.Os +import android.system.OsConstants +import com.android.net.module.util.IpUtils +import com.android.testutils.PacketReflector.IPV4_HEADER_LENGTH +import com.android.testutils.PacketReflector.IPV6_HEADER_LENGTH +import java.io.FileDescriptor +import java.io.InterruptedIOException +import java.net.InetAddress +import java.nio.ByteBuffer + +fun readPacket(fd: FileDescriptor, buf: ByteArray): Int { + return try { + Os.read(fd, buf, 0, buf.size) + } catch (e: ErrnoException) { + // Ignore normal use cases such as the EAGAIN error indicates that the read operation + // cannot be completed immediately, or the EINTR error indicates that the read + // operation was interrupted by a signal. + if (e.errno == OsConstants.EAGAIN || e.errno == OsConstants.EINTR) { + -1 + } else { + throw e + } + } catch (e: InterruptedIOException) { + -1 + } +} + +fun getInetAddressAt(buf: ByteArray, pos: Int, len: Int): InetAddress = + InetAddress.getByAddress(buf.copyOfRange(pos, pos + len)) + +/** + * Reads a 16-bit unsigned int at pos in big endian, with no alignment requirements. + */ +fun getPortAt(buf: ByteArray, pos: Int): Int { + return (buf[pos].toInt() and 0xff shl 8) + (buf[pos + 1].toInt() and 0xff) +} + +fun setPortAt(port: Int, buf: ByteArray, pos: Int) { + buf[pos] = (port ushr 8).toByte() + buf[pos + 1] = (port and 0xff).toByte() +} + +fun getAddressPositionAndLength(version: Int) = when (version) { + 4 -> PacketReflector.IPV4_ADDR_OFFSET to PacketReflector.IPV4_ADDR_LENGTH + 6 -> PacketReflector.IPV6_ADDR_OFFSET to PacketReflector.IPV6_ADDR_LENGTH + else -> throw IllegalArgumentException("Unknown IP version $version") +} + +private const val IPV4_CHKSUM_OFFSET = 10 +private const val UDP_CHECKSUM_OFFSET = 6 +private const val TCP_CHECKSUM_OFFSET = 16 + +fun fixPacketChecksum(buf: ByteArray, len: Int, version: Int, protocol: Byte) { + // Fill Ip checksum for IPv4. IPv6 header doesn't have a checksum field. + if (version == 4) { + val checksum = IpUtils.ipChecksum(ByteBuffer.wrap(buf), 0) + // Place checksum in Big-endian order. + buf[IPV4_CHKSUM_OFFSET] = (checksum.toInt() ushr 8).toByte() + buf[IPV4_CHKSUM_OFFSET + 1] = (checksum.toInt() and 0xff).toByte() + } + + // Fill transport layer checksum. + val transportOffset = if (version == 4) IPV4_HEADER_LENGTH else IPV6_HEADER_LENGTH + when (protocol) { + PacketReflector.IPPROTO_UDP -> { + val checksumPos = transportOffset + UDP_CHECKSUM_OFFSET + // Clear before calculate. + buf[checksumPos + 1] = 0x00 + buf[checksumPos] = buf[checksumPos + 1] + val checksum = IpUtils.udpChecksum( + ByteBuffer.wrap(buf), 0, + transportOffset + ) + buf[checksumPos] = (checksum.toInt() ushr 8).toByte() + buf[checksumPos + 1] = (checksum.toInt() and 0xff).toByte() + } + PacketReflector.IPPROTO_TCP -> { + val checksumPos = transportOffset + TCP_CHECKSUM_OFFSET + // Clear before calculate. + buf[checksumPos + 1] = 0x00 + buf[checksumPos] = buf[checksumPos + 1] + val transportLen: Int = len - transportOffset + val checksum = IpUtils.tcpChecksum( + ByteBuffer.wrap(buf), 0, transportOffset, + transportLen + ) + buf[checksumPos] = (checksum.toInt() ushr 8).toByte() + buf[checksumPos + 1] = (checksum.toInt() and 0xff).toByte() + } + // TODO: Support ICMP. + else -> throw IllegalArgumentException("Unsupported protocol: $protocol") + } +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/PacketResponder.kt b/staticlibs/testutils/devicetests/com/android/testutils/PacketResponder.kt new file mode 100644 index 0000000000000000000000000000000000000000..964c6c60d08b7f34f6374d3efef51e58a08b1016 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/PacketResponder.kt @@ -0,0 +1,75 @@ +/* + * 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. + */ + +package com.android.testutils + +import java.util.function.Predicate + +private const val POLL_FREQUENCY_MS = 1000L + +/** + * A class that can be used to reply to packets from a [TapPacketReader]. + * + * A reply thread will be created to reply to incoming packets asynchronously. + * The receiver creates a new read head on the [TapPacketReader], to read packets, so it does not + * affect packets obtained through [TapPacketReader.popPacket]. + * + * @param reader a [TapPacketReader] to obtain incoming packets and reply to them. + * @param packetFilter A filter to apply to incoming packets. + * @param name Name to use for the internal responder thread. + */ +abstract class PacketResponder( + private val reader: TapPacketReader, + private val packetFilter: Predicate<ByteArray>, + name: String +) { + private val replyThread = ReplyThread(name) + + protected abstract fun replyToPacket(packet: ByteArray, reader: TapPacketReader) + + /** + * Start the [PacketResponder]. + */ + fun start() { + replyThread.start() + } + + /** + * Stop the [PacketResponder]. + * + * The responder cannot be used anymore after being stopped. + */ + fun stop() { + replyThread.interrupt() + replyThread.join() + } + + private inner class ReplyThread(name: String) : Thread(name) { + override fun run() { + try { + // Create a new ReadHead so other packets polled on the reader are not affected + val recvPackets = reader.receivedPackets.newReadHead() + while (!isInterrupted) { + recvPackets.poll(POLL_FREQUENCY_MS, packetFilter::test)?.let { + replyToPacket(it, reader) + } + } + } catch (e: InterruptedException) { + // Exit gracefully + } + } + } +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/ParcelUtils.kt b/staticlibs/testutils/devicetests/com/android/testutils/ParcelUtils.kt new file mode 100644 index 0000000000000000000000000000000000000000..14ed8e9e3ad66e52034f1b7049cbc1b178f59a53 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/ParcelUtils.kt @@ -0,0 +1,70 @@ +/* + * 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. + */ + +@file:JvmName("ParcelUtils") + +package com.android.testutils + +import android.os.Parcel +import android.os.Parcelable +import kotlin.test.assertTrue +import kotlin.test.fail + +/** + * Return a new instance of `T` after being parceled then unparceled. + */ +fun <T : Parcelable> parcelingRoundTrip(source: T): T { + val creator: Parcelable.Creator<T> + try { + creator = source.javaClass.getField("CREATOR").get(null) as Parcelable.Creator<T> + } catch (e: IllegalAccessException) { + fail("Missing CREATOR field: " + e.message) + } catch (e: NoSuchFieldException) { + fail("Missing CREATOR field: " + e.message) + } + + var p = Parcel.obtain() + source.writeToParcel(p, /* flags */ 0) + p.setDataPosition(0) + val marshalled = p.marshall() + p = Parcel.obtain() + p.unmarshall(marshalled, 0, marshalled.size) + p.setDataPosition(0) + return creator.createFromParcel(p) +} + +/** + * Assert that after being parceled then unparceled, `source` is equal to the original + * object. If a customized equals function is provided, uses the provided one. + */ +@JvmOverloads +fun <T : Parcelable> assertParcelingIsLossless( + source: T, + equals: (T, T) -> Boolean = { a, b -> a == b } +) { + val actual = parcelingRoundTrip(source) + assertTrue(equals(source, actual), "Expected $source, but was $actual") +} + +@JvmOverloads +fun <T : Parcelable> assertParcelSane( + obj: T, + fieldCount: Int, + equals: (T, T) -> Boolean = { a, b -> a == b } +) { + assertFieldCountEquals(fieldCount, obj::class.java) + assertParcelingIsLossless(obj, equals) +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/RouterAdvertisementResponder.java b/staticlibs/testutils/devicetests/com/android/testutils/RouterAdvertisementResponder.java new file mode 100644 index 0000000000000000000000000000000000000000..51d57bc1f0bae157094c7781b904bb652ffa0a5c --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/RouterAdvertisementResponder.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.testutils; + +import static android.system.OsConstants.IPPROTO_ICMPV6; + +import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6; +import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA; +import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_TLLA; +import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_SOLICITATION; +import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION; +import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_NODES_MULTICAST; +import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE; +import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER; +import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED; +import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_AUTONOMOUS; +import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_ON_LINK; + +import android.net.InetAddresses; +import android.net.IpPrefix; +import android.net.MacAddress; +import android.util.ArrayMap; +import android.util.Log; +import android.util.Pair; + +import com.android.net.module.util.Ipv6Utils; +import com.android.net.module.util.Struct; +import com.android.net.module.util.structs.EthernetHeader; +import com.android.net.module.util.structs.Icmpv6Header; +import com.android.net.module.util.structs.Ipv6Header; +import com.android.net.module.util.structs.LlaOption; +import com.android.net.module.util.structs.NsHeader; +import com.android.net.module.util.structs.PrefixInformationOption; +import com.android.net.module.util.structs.RdnssOption; + +import java.io.IOException; +import java.net.Inet6Address; +import java.nio.ByteBuffer; +import java.util.Map; +import java.util.Objects; +import java.util.Random; + +/** + * ND (RA & NA) responder class useful for tests that require a provisioned IPv6 interface. + * TODO: rename to NdResponder + */ +public class RouterAdvertisementResponder extends PacketResponder { + private static final String TAG = "RouterAdvertisementResponder"; + private static final Inet6Address DNS_SERVER = + (Inet6Address) InetAddresses.parseNumericAddress("2001:4860:4860::64"); + private final TapPacketReader mPacketReader; + // Maps IPv6 address to MacAddress and isRouter boolean. + private final Map<Inet6Address, Pair<MacAddress, Boolean>> mNeighborMap = new ArrayMap<>(); + private final IpPrefix mPrefix; + + public RouterAdvertisementResponder(TapPacketReader packetReader, IpPrefix prefix) { + super(packetReader, RouterAdvertisementResponder::isRsOrNs, TAG); + mPacketReader = packetReader; + mPrefix = Objects.requireNonNull(prefix); + } + + public RouterAdvertisementResponder(TapPacketReader packetReader) { + this(packetReader, makeRandomPrefix()); + } + + private static IpPrefix makeRandomPrefix() { + final byte[] prefixBytes = new IpPrefix("2001:db8::/64").getAddress().getAddress(); + final Random r = new Random(); + for (int i = 4; i < 8; i++) { + prefixBytes[i] = (byte) r.nextInt(); + } + return new IpPrefix(prefixBytes, 64); + } + + /** Returns true if the packet is a router solicitation or neighbor solicitation message. */ + private static boolean isRsOrNs(byte[] packet) { + final ByteBuffer buffer = ByteBuffer.wrap(packet); + final EthernetHeader ethHeader = Struct.parse(EthernetHeader.class, buffer); + if (ethHeader.etherType != ETHER_TYPE_IPV6) { + return false; + } + final Ipv6Header ipv6Header = Struct.parse(Ipv6Header.class, buffer); + if (ipv6Header.nextHeader != IPPROTO_ICMPV6) { + return false; + } + final Icmpv6Header icmpv6Header = Struct.parse(Icmpv6Header.class, buffer); + return icmpv6Header.type == ICMPV6_ROUTER_SOLICITATION + || icmpv6Header.type == ICMPV6_NEIGHBOR_SOLICITATION; + } + + /** + * Adds a new router to be advertised. + * @param mac the mac address of the router. + * @param ip the link-local address of the router. + */ + public void addRouterEntry(MacAddress mac, Inet6Address ip) { + mNeighborMap.put(ip, new Pair<>(mac, true)); + } + + /** + * Adds a new neighbor to be advertised. + * @param mac the mac address of the neighbor. + * @param ip the link-local address of the neighbor. + */ + public void addNeighborEntry(MacAddress mac, Inet6Address ip) { + mNeighborMap.put(ip, new Pair<>(mac, false)); + } + + /** + * @return the prefix that is announced in the Router Advertisements sent by this object. + */ + public IpPrefix getPrefix() { + return mPrefix; + } + + private ByteBuffer buildPrefixOption() { + return PrefixInformationOption.build( + mPrefix, (byte) (PIO_FLAG_ON_LINK | PIO_FLAG_AUTONOMOUS), + 3600 /* valid lifetime */, 3600 /* preferred lifetime */); + } + + private ByteBuffer buildRdnssOption() { + return RdnssOption.build(3600/*lifetime, must be at least 120*/, DNS_SERVER); + } + + private ByteBuffer buildSllaOption(MacAddress srcMac) { + return LlaOption.build((byte) ICMPV6_ND_OPTION_SLLA, srcMac); + } + + private ByteBuffer buildRaPacket(MacAddress srcMac, MacAddress dstMac, Inet6Address srcIp) { + return Ipv6Utils.buildRaPacket(srcMac, dstMac, srcIp, IPV6_ADDR_ALL_NODES_MULTICAST, + (byte) 0 /*M=0, O=0*/, 3600 /*lifetime*/, 0 /*reachableTime, unspecified*/, + 0/*retransTimer, unspecified*/, buildPrefixOption(), buildRdnssOption(), + buildSllaOption(srcMac)); + } + + private static void sendResponse(TapPacketReader reader, ByteBuffer buffer) { + try { + reader.sendResponse(buffer); + } catch (IOException e) { + // Throwing an exception here will crash the test process. Let's stick to logging, as + // the test will fail either way. + Log.e(TAG, "Failed to send buffer", e); + } + } + + private void replyToRouterSolicitation(TapPacketReader reader, MacAddress dstMac) { + for (Map.Entry<Inet6Address, Pair<MacAddress, Boolean>> it : mNeighborMap.entrySet()) { + final boolean isRouter = it.getValue().second; + if (!isRouter) { + continue; + } + final ByteBuffer raResponse = buildRaPacket(it.getValue().first, dstMac, it.getKey()); + sendResponse(reader, raResponse); + } + } + + private void replyToNeighborSolicitation(TapPacketReader reader, MacAddress dstMac, + Inet6Address dstIp, Inet6Address targetIp) { + final Pair<MacAddress, Boolean> neighbor = mNeighborMap.get(targetIp); + if (neighbor == null) { + return; + } + + final MacAddress srcMac = neighbor.first; + final boolean isRouter = neighbor.second; + int flags = NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED | NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE; + if (isRouter) { + flags |= NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER; + } + + final ByteBuffer tlla = LlaOption.build((byte) ICMPV6_ND_OPTION_TLLA, srcMac); + final ByteBuffer naResponse = Ipv6Utils.buildNaPacket(srcMac, dstMac, targetIp, dstIp, + flags, targetIp, tlla); + sendResponse(reader, naResponse); + } + + @Override + protected void replyToPacket(byte[] packet, TapPacketReader reader) { + final ByteBuffer buf = ByteBuffer.wrap(packet); + // Messages are filtered by parent class, so it is safe to assume that packet is either an + // RS or NS. + final EthernetHeader ethHdr = Struct.parse(EthernetHeader.class, buf); + final Ipv6Header ipv6Hdr = Struct.parse(Ipv6Header.class, buf); + final Icmpv6Header icmpv6Header = Struct.parse(Icmpv6Header.class, buf); + + if (icmpv6Header.type == ICMPV6_ROUTER_SOLICITATION) { + replyToRouterSolicitation(reader, ethHdr.srcMac); + } else if (icmpv6Header.type == ICMPV6_NEIGHBOR_SOLICITATION) { + final NsHeader nsHeader = Struct.parse(NsHeader.class, buf); + replyToNeighborSolicitation(reader, ethHdr.srcMac, ipv6Hdr.srcIp, nsHeader.target); + } + } +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TapPacketReader.java b/staticlibs/testutils/devicetests/com/android/testutils/TapPacketReader.java new file mode 100644 index 0000000000000000000000000000000000000000..b25b9f214b7570a19e0df35c2ad6ea036eeb80d0 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/TapPacketReader.java @@ -0,0 +1,137 @@ +/* + * 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. + */ + +package com.android.testutils; + +import android.os.Handler; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.net.module.util.ArrayTrackRecord; +import com.android.net.module.util.PacketReader; + +import java.io.FileDescriptor; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.function.Predicate; + +import kotlin.Lazy; +import kotlin.LazyKt; + +/** + * A packet reader that runs on a TAP interface. + * + * It also implements facilities to reply to received packets. + */ +public class TapPacketReader extends PacketReader { + private final FileDescriptor mTapFd; + private final ArrayTrackRecord<byte[]> mReceivedPackets = new ArrayTrackRecord<>(); + private final Lazy<ArrayTrackRecord<byte[]>.ReadHead> mReadHead = + LazyKt.lazy(mReceivedPackets::newReadHead); + + public TapPacketReader(Handler h, FileDescriptor tapFd, int maxPacketSize) { + super(h, maxPacketSize); + mTapFd = tapFd; + } + + + /** + * Attempt to start the FdEventsReader on its handler thread. + * + * As opposed to {@link android.net.util.FdEventsReader#start()}, this method will not report + * failure to start, so it is only appropriate in tests that will fail later if that happens. + */ + public void startAsyncForTest() { + getHandler().post(this::start); + } + + @Override + protected FileDescriptor createFd() { + return mTapFd; + } + + @Override + protected void handlePacket(byte[] recvbuf, int length) { + final byte[] newPacket = Arrays.copyOf(recvbuf, length); + if (!mReceivedPackets.add(newPacket)) { + throw new AssertionError("More than " + Integer.MAX_VALUE + " packets outstanding!"); + } + } + + /** + * @deprecated This method does not actually "pop" (which generally means the last packet). + * Use {@link #poll(long)}, which has the same behavior, instead. + */ + @Nullable + @Deprecated + public byte[] popPacket(long timeoutMs) { + return poll(timeoutMs); + } + + /** + * @deprecated This method does not actually "pop" (which generally means the last packet). + * Use {@link #poll(long, Predicate)}, which has the same behavior, instead. + */ + @Nullable + @Deprecated + public byte[] popPacket(long timeoutMs, @NonNull Predicate<byte[]> filter) { + return poll(timeoutMs, filter); + } + + /** + * Get the next packet that was received on the interface. + */ + @Nullable + public byte[] poll(long timeoutMs) { + return mReadHead.getValue().poll(timeoutMs, packet -> true); + } + + /** + * Get the next packet that was received on the interface and matches the specified filter. + */ + @Nullable + public byte[] poll(long timeoutMs, @NonNull Predicate<byte[]> filter) { + return mReadHead.getValue().poll(timeoutMs, filter::test); + } + + /** + * Get the {@link ArrayTrackRecord} that records all packets received by the reader since its + * creation. + */ + public ArrayTrackRecord<byte[]> getReceivedPackets() { + return mReceivedPackets; + } + + /* + * Send a response on the TAP interface. + * + * The passed ByteBuffer is flipped after use. + * + * @param packet The packet to send. + * @throws IOException if the interface can't be written to. + */ + public void sendResponse(final ByteBuffer packet) throws IOException { + try (FileOutputStream out = new FileOutputStream(mTapFd)) { + byte[] packetBytes = new byte[packet.limit()]; + packet.get(packetBytes); + packet.flip(); // So we can reuse it in the future. + out.write(packetBytes); + } + } +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TapPacketReaderRule.kt b/staticlibs/testutils/devicetests/com/android/testutils/TapPacketReaderRule.kt new file mode 100644 index 0000000000000000000000000000000000000000..701666ca3f4d341a5f6d0b37e367463b822717e1 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/TapPacketReaderRule.kt @@ -0,0 +1,154 @@ +/* + * 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. + */ + +package com.android.testutils + +import android.Manifest.permission.MANAGE_TEST_NETWORKS +import android.net.TestNetworkInterface +import android.net.TestNetworkManager +import android.os.Handler +import android.os.HandlerThread +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement +import kotlin.test.assertFalse +import kotlin.test.fail + +private const val HANDLER_TIMEOUT_MS = 10_000L + +/** + * A [TestRule] that sets up a [TapPacketReader] on a [TestNetworkInterface] for use in the test. + * + * @param maxPacketSize Maximum size of packets read in the [TapPacketReader] buffer. + * @param autoStart Whether to initialize the interface and start the reader automatically for every + * test. If false, each test must either call start() and stop(), or be annotated + * with TapPacketReaderTest before using the reader or interface. + */ +class TapPacketReaderRule @JvmOverloads constructor( + private val maxPacketSize: Int = 1500, + private val autoStart: Boolean = true +) : TestRule { + // Use lateinit as the below members can't be initialized in the rule constructor (the + // InstrumentationRegistry may not be ready), but from the point of view of test cases using + // this rule with autoStart = true, the members are always initialized (in setup/test/teardown): + // tests cases should be able use them directly. + // lateinit also allows getting good exceptions detailing what went wrong if the members are + // referenced before they could be initialized (typically if autoStart is false and the test + // does not call start or use @TapPacketReaderTest). + lateinit var iface: TestNetworkInterface + lateinit var reader: TapPacketReader + + @Volatile + private var readerRunning = false + + /** + * Indicates that the [TapPacketReaderRule] should initialize its [TestNetworkInterface] and + * start the [TapPacketReader] before the test, and tear them down afterwards. + * + * For use when [TapPacketReaderRule] is created with autoStart = false. + */ + annotation class TapPacketReaderTest + + /** + * Initialize the tap interface and start the [TapPacketReader]. + * + * Tests using this method must also call [stop] before exiting. + * @param handler Handler to run the reader on. Callers are responsible for safely terminating + * the handler when the test ends. If null, a handler thread managed by the + * rule will be used. + */ + @JvmOverloads + fun start(handler: Handler? = null) { + if (this::iface.isInitialized) { + fail("${TapPacketReaderRule::class.java.simpleName} was already started") + } + + val ctx = InstrumentationRegistry.getInstrumentation().context + iface = runAsShell(MANAGE_TEST_NETWORKS) { + val tnm = ctx.getSystemService(TestNetworkManager::class.java) + ?: fail("Could not obtain the TestNetworkManager") + tnm.createTapInterface() + } + val usedHandler = handler ?: HandlerThread( + TapPacketReaderRule::class.java.simpleName).apply { start() }.threadHandler + reader = TapPacketReader(usedHandler, iface.fileDescriptor.fileDescriptor, maxPacketSize) + reader.startAsyncForTest() + readerRunning = true + } + + /** + * Stop the [TapPacketReader]. + * + * Tests calling [start] must call this method before exiting. If a handler was specified in + * [start], all messages on that handler must also be processed after calling this method and + * before exiting. + * + * If [start] was not called, calling this method is a no-op. + */ + fun stop() { + // The reader may not be initialized if the test case did not use the rule, even though + // other test cases in the same class may be using it (so test classes may call stop in + // tearDown even if start is not called for all test cases). + if (!this::reader.isInitialized) return + reader.handler.post { + reader.stop() + readerRunning = false + } + } + + override fun apply(base: Statement, description: Description): Statement { + return TapReaderStatement(base, description) + } + + private inner class TapReaderStatement( + private val base: Statement, + private val description: Description + ) : Statement() { + override fun evaluate() { + val shouldStart = autoStart || + description.getAnnotation(TapPacketReaderTest::class.java) != null + if (shouldStart) { + start() + } + + try { + base.evaluate() + } finally { + if (shouldStart) { + stop() + reader.handler.looper.apply { + quitSafely() + thread.join(HANDLER_TIMEOUT_MS) + assertFalse(thread.isAlive, + "HandlerThread did not exit within $HANDLER_TIMEOUT_MS ms") + } + } + + if (this@TapPacketReaderRule::iface.isInitialized) { + iface.fileDescriptor.close() + } + } + + assertFalse(readerRunning, + "stop() was not called, or the provided handler did not process the stop " + + "message before the test ended. If not using autostart, make sure to call " + + "stop() after the test. If a handler is specified in start(), make sure all " + + "messages are processed after calling stop(), before quitting (for example " + + "by using HandlerThread#quitSafely and HandlerThread#join).") + } + } +} \ No newline at end of file diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestBpfMap.java b/staticlibs/testutils/devicetests/com/android/testutils/TestBpfMap.java new file mode 100644 index 0000000000000000000000000000000000000000..733bd9807bd18ad235b0dac2604996364f0914e3 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/TestBpfMap.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.testutils; + +import android.system.ErrnoException; + +import androidx.annotation.NonNull; + +import com.android.net.module.util.IBpfMap; +import com.android.net.module.util.Struct; + +import java.io.IOException; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +/** + * + * Fake BPF map class for tests that have no privilege to access real BPF maps. TestBpfMap does not + * load JNI and all member functions do not access real BPF maps. + * + * Implements IBpfMap so that any class using IBpfMap can use this class in its tests. + * + * @param <K> the key type + * @param <V> the value type + */ +public class TestBpfMap<K extends Struct, V extends Struct> implements IBpfMap<K, V> { + private final ConcurrentHashMap<K, V> mMap = new ConcurrentHashMap<>(); + + // TODO: Remove this constructor + public TestBpfMap(final Class<K> key, final Class<V> value) { + } + + @Override + public void forEach(ThrowingBiConsumer<K, V> action) throws ErrnoException { + // TODO: consider using mocked #getFirstKey and #getNextKey to iterate. It helps to + // implement the entry deletion in the iteration if required. + for (Map.Entry<K, V> entry : mMap.entrySet()) { + action.accept(entry.getKey(), entry.getValue()); + } + } + + @Override + public void updateEntry(K key, V value) throws ErrnoException { + mMap.put(key, value); + } + + @Override + public void insertEntry(K key, V value) throws ErrnoException, + IllegalArgumentException { + // The entry is created if and only if it doesn't exist. See BpfMap#insertEntry. + if (mMap.get(key) != null) { + throw new IllegalArgumentException(key + " already exist"); + } + mMap.put(key, value); + } + + @Override + public void replaceEntry(K key, V value) throws ErrnoException, NoSuchElementException { + if (!mMap.containsKey(key)) throw new NoSuchElementException(); + mMap.put(key, value); + } + + @Override + public boolean insertOrReplaceEntry(K key, V value) throws ErrnoException { + // Returns true if inserted, false if replaced. + boolean ret = !mMap.containsKey(key); + mMap.put(key, value); + return ret; + } + + @Override + public boolean deleteEntry(Struct key) throws ErrnoException { + return mMap.remove(key) != null; + } + + @Override + public boolean isEmpty() throws ErrnoException { + return mMap.isEmpty(); + } + + @Override + public K getNextKey(@NonNull K key) { + // Expensive, but since this is only for tests... + Iterator<K> it = mMap.keySet().iterator(); + while (it.hasNext()) { + if (Objects.equals(it.next(), key)) { + return it.hasNext() ? it.next() : null; + } + } + return null; + } + + @Override + public K getFirstKey() { + for (K key : mMap.keySet()) { + return key; + } + return null; + } + + @Override + public boolean containsKey(@NonNull K key) throws ErrnoException { + return mMap.containsKey(key); + } + + @Override + public V getValue(@NonNull K key) throws ErrnoException { + // Return value for a given key. Otherwise, return null without an error ENOENT. + // BpfMap#getValue treats that the entry is not found as no error. + return mMap.get(key); + } + + @Override + public void clear() throws ErrnoException { + // TODO: consider using mocked #getFirstKey and #deleteEntry to implement. + mMap.clear(); + } + + @Override + public void close() throws IOException { + } +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestDnsServer.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestDnsServer.kt new file mode 100644 index 0000000000000000000000000000000000000000..e1b771b2f2dafe9d3abf1d50477956c4dd50ad1e --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/TestDnsServer.kt @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.testutils + +import android.net.Network +import android.util.Log +import com.android.internal.annotations.GuardedBy +import com.android.internal.annotations.VisibleForTesting +import com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE +import com.android.net.module.util.DnsPacket +import java.net.DatagramPacket +import java.net.DatagramSocket +import java.net.InetAddress +import java.net.InetSocketAddress +import java.net.SocketAddress +import java.net.SocketException +import java.util.ArrayList + +private const val TAG = "TestDnsServer" +private const val VDBG = true +@VisibleForTesting(visibility = PRIVATE) +const val MAX_BUF_SIZE = 8192 + +/** + * A simple implementation of Dns Server that can be bound on specific address and Network. + * + * The caller should use start() to make the server start a new thread to receive DNS queries + * on the bound address, [isAlive] to check status, and stop() for stopping. + * The server allows user to manipulate the records to be answered through + * [setAnswer] at runtime. + * + * This server runs on its own thread. Please make sure writing the query to the socket + * happens-after using [setAnswer] to guarantee the correct answer is returned. If possible, + * use [setAnswer] before calling [start] for simplicity. + */ +class TestDnsServer(network: Network, addr: InetSocketAddress) { + enum class Status { + NOT_STARTED, STARTED, STOPPED + } + @GuardedBy("thread") + private var status: Status = Status.NOT_STARTED + private val thread = ReceivingThread() + private val socket = DatagramSocket(addr).also { network.bindSocket(it) } + private val ansProvider = DnsAnswerProvider() + + // The buffer to store the received packet. They are being reused for + // efficiency and it's fine because they are only ever accessed + // on the server thread in a sequential manner. + private val buffer = ByteArray(MAX_BUF_SIZE) + private val packet = DatagramPacket(buffer, buffer.size) + + fun setAnswer(hostname: String, answer: List<InetAddress>) = + ansProvider.setAnswer(hostname, answer) + + private fun processPacket() { + // Blocking read and try construct a DnsQueryPacket object. + socket.receive(packet) + val q = DnsQueryPacket(packet.data) + handleDnsQuery(q, packet.socketAddress) + } + + // TODO: Add support to reply some error with a DNS reply packet with failure RCODE. + private fun handleDnsQuery(q: DnsQueryPacket, src: SocketAddress) { + val queryRecords = q.queryRecords + if (queryRecords.size != 1) { + throw IllegalArgumentException( + "Expected one dns query record but got ${queryRecords.size}" + ) + } + val answerRecords = queryRecords[0].let { ansProvider.getAnswer(it.dName, it.nsType) } + + if (VDBG) { + Log.v(TAG, "handleDnsPacket: " + + queryRecords.map { "${it.dName},${it.nsType}" }.joinToString() + + " ansCount=${answerRecords.size} socketAddress=$src") + } + + val bytes = q.getAnswerPacket(answerRecords).bytes + val reply = DatagramPacket(bytes, bytes.size, src) + socket.send(reply) + } + + fun start() { + synchronized(thread) { + if (status != Status.NOT_STARTED) { + throw IllegalStateException("unexpected status: $status") + } + thread.start() + status = Status.STARTED + } + } + fun stop() { + synchronized(thread) { + if (status != Status.STARTED) { + throw IllegalStateException("unexpected status: $status") + } + // The thread needs to be interrupted before closing the socket to prevent a data + // race where the thread tries to read from the socket while it's being closed. + // DatagramSocket is not thread-safe and running both concurrently can end up in + // getPort() returning -1 after it's been checked not to, resulting in a crash by + // IllegalArgumentException inside the DatagramSocket implementation. + thread.interrupt() + socket.close() + thread.join() + status = Status.STOPPED + } + } + val isAlive get() = thread.isAlive + val port get() = socket.localPort + + inner class ReceivingThread : Thread() { + override fun run() { + while (!interrupted() && !socket.isClosed) { + try { + processPacket() + } catch (e: InterruptedException) { + // The caller terminated the server, exit. + break + } catch (e: SocketException) { + // The caller terminated the server, exit. + break + } + } + Log.i(TAG, "exiting socket={$socket}") + } + } + + @VisibleForTesting(visibility = PRIVATE) + class DnsQueryPacket : DnsPacket { + constructor(data: ByteArray) : super(data) + constructor(header: DnsHeader, qd: List<DnsRecord>, an: List<DnsRecord>) : + super(header, qd, an) + + init { + if (mHeader.isResponse) { + throw ParseException("Not a query packet") + } + } + + val queryRecords: List<DnsRecord> + get() = mRecords[QDSECTION] + + fun getAnswerPacket(ar: List<DnsRecord>): DnsAnswerPacket { + // Set QR bit of flag to 1 for response packet according to RFC 1035 section 4.1.1. + val flags = 1 shl 15 + val qr = ArrayList(mRecords[QDSECTION]) + // Copy the query packet header id to the answer packet as RFC 1035 section 4.1.1. + val header = DnsHeader(mHeader.id, flags, qr.size, ar.size) + return DnsAnswerPacket(header, qr, ar) + } + } + + class DnsAnswerPacket : DnsPacket { + constructor(header: DnsHeader, qr: List<DnsRecord>, ar: List<DnsRecord>) : + super(header, qr, ar) + @VisibleForTesting(visibility = PRIVATE) + constructor(bytes: ByteArray) : super(bytes) + } +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestHttpServer.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestHttpServer.kt new file mode 100644 index 0000000000000000000000000000000000000000..740bf63a107f91614e1155f7041c4e4451378b56 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/TestHttpServer.kt @@ -0,0 +1,105 @@ +/* + * 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. + */ + +package com.android.testutils + +import android.net.Uri +import com.android.net.module.util.ArrayTrackRecord +import fi.iki.elonen.NanoHTTPD +import java.io.IOException + +/** + * A minimal HTTP server running on a random available port. + * + * @param host The host to listen to, or null to listen on all hosts + */ +class TestHttpServer(host: String? = null) : NanoHTTPD(host, 0 /* auto-select the port */) { + // Map of URL path -> HTTP response code + private val responses = HashMap<Request, Response>() + + /** + * A record of all requests received by the server since it was started. + */ + val requestsRecord = ArrayTrackRecord<Request>() + + /** + * A request received by the test server. + */ + data class Request( + val path: String, + val method: Method = Method.GET, + val queryParameters: String = "" + ) { + /** + * Returns whether the specified [Uri] matches parameters of this request. + */ + fun matches(uri: Uri) = (uri.path ?: "") == path && (uri.query ?: "") == queryParameters + } + + /** + * Add a response for GET requests with the path and query parameters of the specified [Uri]. + */ + fun addResponse( + uri: Uri, + statusCode: Response.IStatus, + headers: Map<String, String>? = null, + content: String = "" + ) { + addResponse(Request(uri.path + ?: "", Method.GET, uri.query ?: ""), + statusCode, headers, content) + } + + /** + * Add a response for the given request. + */ + fun addResponse( + request: Request, + statusCode: Response.IStatus, + headers: Map<String, String>? = null, + content: String = "" + ) { + val response = newFixedLengthResponse(statusCode, "text/plain", content) + headers?.forEach { + (key, value) -> response.addHeader(key, value) + } + responses[request] = response + } + + override fun serve(session: IHTTPSession): Response { + val request = Request(session.uri + ?: "", session.method, session.queryParameterString ?: "") + requestsRecord.add(request) + + // For PUT and POST, call parseBody to read InputStream before responding. + if (Method.PUT == session.method || Method.POST == session.method) { + try { + session.parseBody(HashMap()) + } catch (e: Exception) { + when (e) { + is IOException, is ResponseException -> e.toResponse() + else -> throw e + } + } + } + + // Default response is a 404 + return responses[request] ?: super.serve(session) + } + + fun Exception.toResponse() = + newFixedLengthResponse(Response.Status.INTERNAL_ERROR, "text/plain", this.toString()) +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestNetworkTracker.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestNetworkTracker.kt new file mode 100644 index 0000000000000000000000000000000000000000..84fb47bc6aa8c895d1b2090e30f35549f8731838 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/TestNetworkTracker.kt @@ -0,0 +1,158 @@ +/* + * 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. + */ + +package com.android.testutils + +import android.content.Context +import android.net.ConnectivityManager +import android.net.ConnectivityManager.NetworkCallback +import android.net.LinkAddress +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkRequest +import android.net.LinkProperties +import android.net.TestNetworkInterface +import android.net.TestNetworkManager +import android.os.Binder +import android.os.Build +import androidx.annotation.RequiresApi +import com.android.modules.utils.build.SdkLevel.isAtLeastR +import com.android.modules.utils.build.SdkLevel.isAtLeastS +import java.util.concurrent.CompletableFuture +import java.util.concurrent.TimeUnit +import kotlin.test.assertTrue + +/** + * Create a test network based on a TUN interface with a LinkAddress. + * + * TODO: remove this function after fixing all the callers to use a list of LinkAddresses. + * This method will block until the test network is available. Requires + * [android.Manifest.permission.CHANGE_NETWORK_STATE] and + * [android.Manifest.permission.MANAGE_TEST_NETWORKS]. + */ +fun initTestNetwork( + context: Context, + interfaceAddr: LinkAddress, + setupTimeoutMs: Long = 10_000L +): TestNetworkTracker { + return initTestNetwork(context, listOf(interfaceAddr), setupTimeoutMs) +} + +/** + * Create a test network based on a TUN interface with a LinkAddress list. + * + * This method will block until the test network is available. Requires + * [android.Manifest.permission.CHANGE_NETWORK_STATE] and + * [android.Manifest.permission.MANAGE_TEST_NETWORKS]. + */ +fun initTestNetwork( + context: Context, + linkAddrs: List<LinkAddress>, + setupTimeoutMs: Long = 10_000L +): TestNetworkTracker { + return initTestNetwork(context, linkAddrs, lp = null, setupTimeoutMs = setupTimeoutMs) +} + +/** + * Create a test network based on a TUN interface + * + * This method will block until the test network is available. Requires + * [android.Manifest.permission.CHANGE_NETWORK_STATE] and + * [android.Manifest.permission.MANAGE_TEST_NETWORKS]. + * + * This is only usable starting from R as [TestNetworkManager] has no support for specifying + * LinkProperties on Q. + */ +@RequiresApi(Build.VERSION_CODES.R) +fun initTestNetwork( + context: Context, + lp: LinkProperties, + setupTimeoutMs: Long = 10_000L +): TestNetworkTracker { + return initTestNetwork(context, lp.linkAddresses, lp, setupTimeoutMs) +} + +private fun initTestNetwork( + context: Context, + linkAddrs: List<LinkAddress>, + lp: LinkProperties?, + setupTimeoutMs: Long = 10_000L +): TestNetworkTracker { + val tnm = context.getSystemService(TestNetworkManager::class.java)!! + val iface = if (isAtLeastS()) tnm.createTunInterface(linkAddrs) + else tnm.createTunInterface(linkAddrs.toTypedArray()) + val lpWithIface = if (lp == null) null else LinkProperties(lp).apply { + interfaceName = iface.interfaceName + } + return TestNetworkTracker(context, iface, tnm, lpWithIface, setupTimeoutMs) +} + +/** + * Utility class to create and track test networks. + * + * This class is not thread-safe. + */ +class TestNetworkTracker internal constructor( + val context: Context, + val iface: TestNetworkInterface, + val tnm: TestNetworkManager, + val lp: LinkProperties?, + setupTimeoutMs: Long +) : TestableNetworkCallback.HasNetwork { + private val cm = context.getSystemService(ConnectivityManager::class.java)!! + private val binder = Binder() + + private val networkCallback: NetworkCallback + override val network: Network + val testIface: TestNetworkInterface + + init { + val networkFuture = CompletableFuture<Network>() + val networkRequest = NetworkRequest.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_TEST) + // Test networks do not have NOT_VPN or TRUSTED capabilities by default + .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) + .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED) + .setNetworkSpecifier(CompatUtil.makeTestNetworkSpecifier(iface.interfaceName)) + .build() + networkCallback = object : NetworkCallback() { + override fun onAvailable(network: Network) { + networkFuture.complete(network) + } + } + cm.requestNetwork(networkRequest, networkCallback) + + network = try { + if (lp != null) { + assertTrue(isAtLeastR(), "Cannot specify TestNetwork LinkProperties before R") + tnm.setupTestNetwork(lp, true /* isMetered */, binder) + } else { + tnm.setupTestNetwork(iface.interfaceName, binder) + } + networkFuture.get(setupTimeoutMs, TimeUnit.MILLISECONDS) + } catch (e: Throwable) { + cm.unregisterNetworkCallback(networkCallback) + throw e + } + + testIface = iface + } + + fun teardown() { + cm.unregisterNetworkCallback(networkCallback) + tnm.teardownTestNetwork(network) + } +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestPermissionUtil.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestPermissionUtil.kt new file mode 100644 index 0000000000000000000000000000000000000000..f571f64fe70524b82bd340fefc47e115785667ad --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/TestPermissionUtil.kt @@ -0,0 +1,95 @@ +/* + * 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. + */ + +@file:JvmName("TestPermissionUtil") + +package com.android.testutils + +import androidx.test.platform.app.InstrumentationRegistry +import com.android.modules.utils.build.SdkLevel +import com.android.testutils.FunctionalUtils.ThrowingRunnable +import com.android.testutils.FunctionalUtils.ThrowingSupplier + +/** + * Run the specified [task] with the specified [permissions] obtained through shell + * permission identity. + * + * Passing in an empty list of permissions can grant all shell permissions, but this is + * discouraged as it also causes the process to temporarily lose non-shell permissions. + */ +fun <T> runAsShell(vararg permissions: String, task: () -> T): T { + val autom = InstrumentationRegistry.getInstrumentation().uiAutomation + + // Calls to adoptShellPermissionIdentity do not nest, and dropShellPermissionIdentity drops all + // permissions. Thus, nesting calls will almost certainly cause test bugs, On S+, where we can + // detect this, refuse to do it. + // + // TODO: when R is deprecated, we could try to make this work instead. + // - Get the list of previously-adopted permissions. + // - Adopt the union of the previously-adopted and newly-requested permissions. + // - Run the task. + // - Adopt the previously-adopted permissions, dropping the ones just adopted. + // + // This would allow tests (and utility classes, such as the TestCarrierConfigReceiver attempted + // in aosp/2106007) to call runAsShell even within a test that has already adopted permissions. + if (SdkLevel.isAtLeastS() && !autom.getAdoptedShellPermissions().isNullOrEmpty()) { + throw IllegalStateException("adoptShellPermissionIdentity calls must not be nested") + } + + autom.adoptShellPermissionIdentity(*permissions) + try { + return task() + } finally { + autom.dropShellPermissionIdentity() + } +} + +/** + * Convenience overload of [runAsShell] that uses a [ThrowingSupplier] for Java callers, when + * only one/two/three permissions are needed. + */ +@JvmOverloads +fun <T> runAsShell( + perm1: String, + perm2: String = "", + perm3: String = "", + supplier: ThrowingSupplier<T> +): T = runAsShell(*getNonEmptyVarargs(perm1, perm2, perm3)) { supplier.get() } + +/** + * Convenience overload of [runAsShell] that uses a [ThrowingRunnable] for Java callers, when + * only one/two/three permissions are needed. + */ +@JvmOverloads +fun runAsShell( + perm1: String, + perm2: String = "", + perm3: String = "", + runnable: ThrowingRunnable +): Unit = runAsShell(*getNonEmptyVarargs(perm1, perm2, perm3)) { runnable.run() } + +/** + * Get an array containing the first consecutive non-empty arguments out of three arguments. + * + * The first argument is assumed to be non-empty. + */ +private fun getNonEmptyVarargs(arg1: String, arg2: String, arg3: String): Array<String> { + return when { + arg2 == "" -> arrayOf(arg1) + arg3 == "" -> arrayOf(arg1, arg2) + else -> arrayOf(arg1, arg2, arg3) + } +} \ No newline at end of file diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkAgent.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkAgent.kt new file mode 100644 index 0000000000000000000000000000000000000000..8dc1bc45f9c3fb530abaeb89b1a0ce4fa832f17e --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkAgent.kt @@ -0,0 +1,206 @@ +/* + * 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. + */ + +package com.android.testutils; + +import android.content.Context +import android.net.KeepalivePacketData +import android.net.LinkProperties +import android.net.NetworkAgent +import android.net.NetworkAgentConfig +import android.net.NetworkCapabilities +import android.net.NetworkProvider +import android.net.QosFilter +import android.net.Uri +import android.os.Looper +import com.android.net.module.util.ArrayTrackRecord +import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnAddKeepalivePacketFilter +import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnAutomaticReconnectDisabled +import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnBandwidthUpdateRequested +import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnDscpPolicyStatusUpdated +import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnNetworkCreated +import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnNetworkDestroyed +import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnNetworkUnwanted +import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnRegisterQosCallback +import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnRemoveKeepalivePacketFilter +import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnSaveAcceptUnvalidated +import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnSignalStrengthThresholdsUpdated +import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnStartSocketKeepalive +import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnStopSocketKeepalive +import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnUnregisterQosCallback +import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnValidationStatus +import java.time.Duration +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue +import org.junit.Assert.assertArrayEquals + +// Any legal score (0~99) for the test network would do, as it is going to be kept up by the +// requests filed by the test and should never match normal internet requests. 70 is the default +// score of Ethernet networks, it's as good a value as any other. +private const val TEST_NETWORK_SCORE = 70 + +private class Provider(context: Context, looper: Looper) : + NetworkProvider(context, looper, "NetworkAgentTest NetworkProvider") + +public open class TestableNetworkAgent( + context: Context, + looper: Looper, + val nc: NetworkCapabilities, + val lp: LinkProperties, + conf: NetworkAgentConfig +) : NetworkAgent(context, looper, TestableNetworkAgent::class.java.simpleName /* tag */, + nc, lp, TEST_NETWORK_SCORE, conf, Provider(context, looper)) { + + val DEFAULT_TIMEOUT_MS = 5000L + + val history = ArrayTrackRecord<CallbackEntry>().newReadHead() + + sealed class CallbackEntry { + object OnBandwidthUpdateRequested : CallbackEntry() + object OnNetworkUnwanted : CallbackEntry() + data class OnAddKeepalivePacketFilter( + val slot: Int, + val packet: KeepalivePacketData + ) : CallbackEntry() + data class OnRemoveKeepalivePacketFilter(val slot: Int) : CallbackEntry() + data class OnStartSocketKeepalive( + val slot: Int, + val interval: Int, + val packet: KeepalivePacketData + ) : CallbackEntry() + data class OnStopSocketKeepalive(val slot: Int) : CallbackEntry() + data class OnSaveAcceptUnvalidated(val accept: Boolean) : CallbackEntry() + object OnAutomaticReconnectDisabled : CallbackEntry() + data class OnValidationStatus(val status: Int, val uri: Uri?) : CallbackEntry() + data class OnSignalStrengthThresholdsUpdated(val thresholds: IntArray) : CallbackEntry() + object OnNetworkCreated : CallbackEntry() + object OnNetworkDestroyed : CallbackEntry() + data class OnDscpPolicyStatusUpdated(val policyId: Int, val status: Int) : CallbackEntry() + data class OnRegisterQosCallback( + val callbackId: Int, + val filter: QosFilter + ) : CallbackEntry() + data class OnUnregisterQosCallback(val callbackId: Int) : CallbackEntry() + } + + override fun onBandwidthUpdateRequested() { + history.add(OnBandwidthUpdateRequested) + } + + override fun onNetworkUnwanted() { + history.add(OnNetworkUnwanted) + } + + override fun onAddKeepalivePacketFilter(slot: Int, packet: KeepalivePacketData) { + history.add(OnAddKeepalivePacketFilter(slot, packet)) + } + + override fun onRemoveKeepalivePacketFilter(slot: Int) { + history.add(OnRemoveKeepalivePacketFilter(slot)) + } + + override fun onStartSocketKeepalive( + slot: Int, + interval: Duration, + packet: KeepalivePacketData + ) { + history.add(OnStartSocketKeepalive(slot, interval.seconds.toInt(), packet)) + } + + override fun onStopSocketKeepalive(slot: Int) { + history.add(OnStopSocketKeepalive(slot)) + } + + override fun onSaveAcceptUnvalidated(accept: Boolean) { + history.add(OnSaveAcceptUnvalidated(accept)) + } + + override fun onAutomaticReconnectDisabled() { + history.add(OnAutomaticReconnectDisabled) + } + + override fun onSignalStrengthThresholdsUpdated(thresholds: IntArray) { + history.add(OnSignalStrengthThresholdsUpdated(thresholds)) + } + + fun expectSignalStrengths(thresholds: IntArray? = intArrayOf()) { + expectCallback<OnSignalStrengthThresholdsUpdated>().let { + assertArrayEquals(thresholds, it.thresholds) + } + } + + override fun onQosCallbackRegistered(qosCallbackId: Int, filter: QosFilter) { + history.add(OnRegisterQosCallback(qosCallbackId, filter)) + } + + override fun onQosCallbackUnregistered(qosCallbackId: Int) { + history.add(OnUnregisterQosCallback(qosCallbackId)) + } + + override fun onValidationStatus(status: Int, uri: Uri?) { + history.add(OnValidationStatus(status, uri)) + } + + override fun onNetworkCreated() { + history.add(OnNetworkCreated) + } + + override fun onNetworkDestroyed() { + history.add(OnNetworkDestroyed) + } + + override fun onDscpPolicyStatusUpdated(policyId: Int, status: Int) { + history.add(OnDscpPolicyStatusUpdated(policyId, status)) + } + + // Expects the initial validation event that always occurs immediately after registering + // a NetworkAgent whose network does not require validation (which test networks do + // not, since they lack the INTERNET capability). It always contains the default argument + // for the URI. + fun expectValidationBypassedStatus() = expectCallback<OnValidationStatus>().let { + assertEquals(it.status, VALID_NETWORK) + // The returned Uri is parsed from the empty string, which means it's an + // instance of the (private) Uri.StringUri. There are no real good ways + // to check this, the least bad is to just convert it to a string and + // make sure it's empty. + assertEquals("", it.uri.toString()) + } + + inline fun <reified T : CallbackEntry> expectCallback(): T { + val foundCallback = history.poll(DEFAULT_TIMEOUT_MS) + assertTrue(foundCallback is T, "Expected ${T::class} but found $foundCallback") + return foundCallback + } + + inline fun <reified T : CallbackEntry> expectCallback(valid: (T) -> Boolean) { + val foundCallback = history.poll(DEFAULT_TIMEOUT_MS) + assertTrue(foundCallback is T, "Expected ${T::class} but found $foundCallback") + assertTrue(valid(foundCallback), "Unexpected callback : $foundCallback") + } + + inline fun <reified T : CallbackEntry> eventuallyExpect() = + history.poll(DEFAULT_TIMEOUT_MS) { it is T }.also { + assertNotNull(it, "Callback ${T::class} not received") + } as T + + fun assertNoCallback() { + assertTrue(waitForIdle(DEFAULT_TIMEOUT_MS), + "Handler didn't became idle after ${DEFAULT_TIMEOUT_MS}ms") + assertNull(history.peek()) + } +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt new file mode 100644 index 0000000000000000000000000000000000000000..df9c61ae2de8219fc5296bb1999bd332ba824b9b --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt @@ -0,0 +1,569 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.testutils + +import android.net.ConnectivityManager.NetworkCallback +import android.net.LinkProperties +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED +import android.util.Log +import com.android.net.module.util.ArrayTrackRecord +import com.android.testutils.RecorderCallback.CallbackEntry.Available +import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus +import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatusInt +import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged +import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged +import com.android.testutils.RecorderCallback.CallbackEntry.Losing +import com.android.testutils.RecorderCallback.CallbackEntry.Lost +import com.android.testutils.RecorderCallback.CallbackEntry.Resumed +import com.android.testutils.RecorderCallback.CallbackEntry.Suspended +import com.android.testutils.RecorderCallback.CallbackEntry.Unavailable +import kotlin.reflect.KClass +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.fail + +object NULL_NETWORK : Network(-1) +object ANY_NETWORK : Network(-2) +fun anyNetwork() = ANY_NETWORK + +open class RecorderCallback private constructor( + private val backingRecord: ArrayTrackRecord<CallbackEntry> +) : NetworkCallback() { + public constructor() : this(ArrayTrackRecord()) + protected constructor(src: RecorderCallback?) : this(src?.backingRecord ?: ArrayTrackRecord()) + + private val TAG = this::class.simpleName + + sealed class CallbackEntry { + // To get equals(), hashcode(), componentN() etc for free, the child classes of + // this class are data classes. But while data classes can inherit from other classes, + // they may only have visible members in the constructors, so they couldn't declare + // a constructor with a non-val arg to pass to CallbackEntry. Instead, force all + // subclasses to implement a `network' property, which can be done in a data class + // constructor by specifying override. + abstract val network: Network + + data class Available(override val network: Network) : CallbackEntry() + data class CapabilitiesChanged( + override val network: Network, + val caps: NetworkCapabilities + ) : CallbackEntry() + data class LinkPropertiesChanged( + override val network: Network, + val lp: LinkProperties + ) : CallbackEntry() + data class Suspended(override val network: Network) : CallbackEntry() + data class Resumed(override val network: Network) : CallbackEntry() + data class Losing(override val network: Network, val maxMsToLive: Int) : CallbackEntry() + data class Lost(override val network: Network) : CallbackEntry() + data class Unavailable private constructor( + override val network: Network + ) : CallbackEntry() { + constructor() : this(NULL_NETWORK) + } + data class BlockedStatus( + override val network: Network, + val blocked: Boolean + ) : CallbackEntry() + data class BlockedStatusInt( + override val network: Network, + val reason: Int + ) : CallbackEntry() + // Convenience constants for expecting a type + companion object { + @JvmField + val AVAILABLE = Available::class + @JvmField + val NETWORK_CAPS_UPDATED = CapabilitiesChanged::class + @JvmField + val LINK_PROPERTIES_CHANGED = LinkPropertiesChanged::class + @JvmField + val SUSPENDED = Suspended::class + @JvmField + val RESUMED = Resumed::class + @JvmField + val LOSING = Losing::class + @JvmField + val LOST = Lost::class + @JvmField + val UNAVAILABLE = Unavailable::class + @JvmField + val BLOCKED_STATUS = BlockedStatus::class + @JvmField + val BLOCKED_STATUS_INT = BlockedStatusInt::class + } + } + + val history = backingRecord.newReadHead() + val mark get() = history.mark + + override fun onAvailable(network: Network) { + Log.d(TAG, "onAvailable $network") + history.add(Available(network)) + } + + // PreCheck is not used in the tests today. For backward compatibility with existing tests that + // expect the callbacks not to record this, do not listen to PreCheck here. + + override fun onCapabilitiesChanged(network: Network, caps: NetworkCapabilities) { + Log.d(TAG, "onCapabilitiesChanged $network $caps") + history.add(CapabilitiesChanged(network, caps)) + } + + override fun onLinkPropertiesChanged(network: Network, lp: LinkProperties) { + Log.d(TAG, "onLinkPropertiesChanged $network $lp") + history.add(LinkPropertiesChanged(network, lp)) + } + + override fun onBlockedStatusChanged(network: Network, blocked: Boolean) { + Log.d(TAG, "onBlockedStatusChanged $network $blocked") + history.add(BlockedStatus(network, blocked)) + } + + // Cannot do: + // fun onBlockedStatusChanged(network: Network, blocked: Int) { + // because on S, that needs to be "override fun", and on R, that cannot be "override fun". + override fun onNetworkSuspended(network: Network) { + Log.d(TAG, "onNetworkSuspended $network $network") + history.add(Suspended(network)) + } + + override fun onNetworkResumed(network: Network) { + Log.d(TAG, "$network onNetworkResumed $network") + history.add(Resumed(network)) + } + + override fun onLosing(network: Network, maxMsToLive: Int) { + Log.d(TAG, "onLosing $network $maxMsToLive") + history.add(Losing(network, maxMsToLive)) + } + + override fun onLost(network: Network) { + Log.d(TAG, "onLost $network") + history.add(Lost(network)) + } + + override fun onUnavailable() { + Log.d(TAG, "onUnavailable") + history.add(Unavailable()) + } +} + +private const val DEFAULT_TIMEOUT = 30_000L // ms +private const val DEFAULT_NO_CALLBACK_TIMEOUT = 200L // ms +private val NOOP = Runnable {} + +/** + * See comments on the public constructor below for a description of the arguments. + */ +open class TestableNetworkCallback private constructor( + src: TestableNetworkCallback?, + val defaultTimeoutMs: Long = DEFAULT_TIMEOUT, + val defaultNoCallbackTimeoutMs: Long = DEFAULT_NO_CALLBACK_TIMEOUT, + val waiterFunc: Runnable = NOOP // "() -> Unit" would forbid calling with a void func from Java +) : RecorderCallback(src) { + /** + * Construct a testable network callback. + * @param timeoutMs the default timeout for expecting a callback. Default 30 seconds. This + * should be long in most cases, because the success case doesn't incur + * the wait. + * @param noCallbackTimeoutMs the timeout for expecting that no callback is received. Default + * 200ms. Because the success case does incur the timeout, this + * should be short in most cases, but not so short as to frequently + * time out before an incorrect callback is received. + * @param waiterFunc a function to use before asserting no callback. For some specific tests, + * it is useful to run test-specific code before asserting no callback to + * increase the likelihood that a spurious callback is correctly detected. + * As an example, a unit test using mock loopers may want to use this to + * make sure the loopers are drained before asserting no callback, since + * one of them may cause a callback to be called. @see ConnectivityServiceTest + * for such an example. + */ + @JvmOverloads + constructor( + timeoutMs: Long = DEFAULT_TIMEOUT, + noCallbackTimeoutMs: Long = DEFAULT_NO_CALLBACK_TIMEOUT, + waiterFunc: Runnable = NOOP + ) : this(null, timeoutMs, noCallbackTimeoutMs, waiterFunc) + + fun createLinkedCopy() = TestableNetworkCallback( + this, defaultTimeoutMs, defaultNoCallbackTimeoutMs, waiterFunc) + + // The last available network, or null if any network was lost since the last call to + // onAvailable. TODO : fix this by fixing the tests that rely on this behavior + val lastAvailableNetwork: Network? + get() = when (val it = history.lastOrNull { it is Available || it is Lost }) { + is Available -> it.network + else -> null + } + + /** + * Get the next callback or null if timeout. + * + * With no argument, this method waits out the default timeout. To wait forever, pass + * Long.MAX_VALUE. + */ + @JvmOverloads + fun poll(timeoutMs: Long = defaultTimeoutMs, predicate: (CallbackEntry) -> Boolean = { true }) = + history.poll(timeoutMs, predicate) + + /***** + * expect family of methods. + * These methods fetch the next callback and assert it matches the conditions : type, + * passed predicate. If no callback is received within the timeout, these methods fail. + */ + @JvmOverloads + fun <T : CallbackEntry> expect( + type: KClass<T>, + network: Network = ANY_NETWORK, + timeoutMs: Long = defaultTimeoutMs, + errorMsg: String? = null, + test: (T) -> Boolean = { true } + ) = expect<CallbackEntry>(network, timeoutMs, errorMsg) { + if (type.isInstance(it)) { + test(it as T) // Cast can't fail since type.isInstance(it) and type: KClass<T> + } else { + fail("Expected callback ${type.simpleName}, got $it") + } + } as T + + @JvmOverloads + fun <T : CallbackEntry> expect( + type: KClass<T>, + network: HasNetwork, + timeoutMs: Long = defaultTimeoutMs, + errorMsg: String? = null, + test: (T) -> Boolean = { true } + ) = expect(type, network.network, timeoutMs, errorMsg, test) + + // Java needs an explicit overload to let it omit arguments in the middle, so define these + // here. Note that @JvmOverloads give us the versions without the last arguments too, so + // there is no need to explicitly define versions without the test predicate. + // Without |network| + @JvmOverloads + fun <T : CallbackEntry> expect( + type: KClass<T>, + timeoutMs: Long, + errorMsg: String?, + test: (T) -> Boolean = { true } + ) = expect(type, ANY_NETWORK, timeoutMs, errorMsg, test) + + // Without |timeout|, in Network and HasNetwork versions + @JvmOverloads + fun <T : CallbackEntry> expect( + type: KClass<T>, + network: Network, + errorMsg: String?, + test: (T) -> Boolean = { true } + ) = expect(type, network, defaultTimeoutMs, errorMsg, test) + + @JvmOverloads + fun <T : CallbackEntry> expect( + type: KClass<T>, + network: HasNetwork, + errorMsg: String?, + test: (T) -> Boolean = { true } + ) = expect(type, network.network, defaultTimeoutMs, errorMsg, test) + + // Without |errorMsg|, in Network and HasNetwork versions + @JvmOverloads + fun <T : CallbackEntry> expect( + type: KClass<T>, + network: Network, + timeoutMs: Long, + test: (T) -> Boolean + ) = expect(type, network, timeoutMs, null, test) + + @JvmOverloads + fun <T : CallbackEntry> expect( + type: KClass<T>, + network: HasNetwork, + timeoutMs: Long, + test: (T) -> Boolean + ) = expect(type, network.network, timeoutMs, null, test) + + // Without |network| or |timeout| + @JvmOverloads + fun <T : CallbackEntry> expect( + type: KClass<T>, + errorMsg: String?, + test: (T) -> Boolean = { true } + ) = expect(type, ANY_NETWORK, defaultTimeoutMs, errorMsg, test) + + // Without |network| or |errorMsg| + @JvmOverloads + fun <T : CallbackEntry> expect( + type: KClass<T>, + timeoutMs: Long, + test: (T) -> Boolean = { true } + ) = expect(type, ANY_NETWORK, timeoutMs, null, test) + + // Without |timeout| or |errorMsg|, in Network and HasNetwork versions + @JvmOverloads + fun <T : CallbackEntry> expect( + type: KClass<T>, + network: Network, + test: (T) -> Boolean + ) = expect(type, network, defaultTimeoutMs, null, test) + + @JvmOverloads + fun <T : CallbackEntry> expect( + type: KClass<T>, + network: HasNetwork, + test: (T) -> Boolean + ) = expect(type, network.network, defaultTimeoutMs, null, test) + + // Without |network| or |timeout| or |errorMsg| + @JvmOverloads + fun <T : CallbackEntry> expect( + type: KClass<T>, + test: (T) -> Boolean + ) = expect(type, ANY_NETWORK, defaultTimeoutMs, null, test) + + // Kotlin reified versions. Don't call methods above, or the predicate would need to be noinline + inline fun <reified T : CallbackEntry> expect( + network: Network = ANY_NETWORK, + timeoutMs: Long = defaultTimeoutMs, + errorMsg: String? = null, + test: (T) -> Boolean = { true } + ) = (poll(timeoutMs) ?: fail("Did not receive ${T::class.simpleName} after ${timeoutMs}ms")) + .also { + if (it !is T) fail("Expected callback ${T::class.simpleName}, got $it") + if (ANY_NETWORK !== network && it.network != network) { + fail("Expected network $network for callback : $it") + } + if (!test(it)) { + fail("${errorMsg ?: "Callback doesn't match predicate"} : $it") + } + } as T + + inline fun <reified T : CallbackEntry> expect( + network: HasNetwork, + timeoutMs: Long = defaultTimeoutMs, + errorMsg: String? = null, + test: (T) -> Boolean = { true } + ) = expect(network.network, timeoutMs, errorMsg, test) + + /***** + * assertNoCallback family of methods. + * These methods make sure that no callback that matches the predicate was received. + * If no predicate is given, they make sure that no callback at all was received. + * These methods run the waiter func given in the constructor if any. + */ + @JvmOverloads + fun assertNoCallback( + timeoutMs: Long = defaultNoCallbackTimeoutMs, + valid: (CallbackEntry) -> Boolean = { true } + ) { + waiterFunc.run() + history.poll(timeoutMs) { valid(it) }?.let { fail("Expected no callback but got $it") } + } + + fun assertNoCallback(valid: (CallbackEntry) -> Boolean) = + assertNoCallback(defaultNoCallbackTimeoutMs, valid) + + /***** + * eventuallyExpect family of methods. + * These methods make sure a callback that matches the type/predicate is received eventually. + * Any callback of the wrong type, or doesn't match the optional predicate, is ignored. + * They fail if no callback matching the predicate is received within the timeout. + */ + inline fun <reified T : CallbackEntry> eventuallyExpect( + timeoutMs: Long = defaultTimeoutMs, + from: Int = mark, + crossinline predicate: (T) -> Boolean = { true } + ): T = history.poll(timeoutMs, from) { it is T && predicate(it) }.also { + assertNotNull(it, "Callback ${T::class} not received within ${timeoutMs}ms. " + + "Got ${history.backtrace()}") + } as T + + @JvmOverloads + fun <T : CallbackEntry> eventuallyExpect( + type: KClass<T>, + timeoutMs: Long = defaultTimeoutMs, + predicate: (cb: T) -> Boolean = { true } + ) = history.poll(timeoutMs) { type.java.isInstance(it) && predicate(it as T) }.also { + assertNotNull(it, "Callback ${type.java} not received within ${timeoutMs}ms. " + + "Got ${history.backtrace()}") + } as T + + fun <T : CallbackEntry> eventuallyExpect( + type: KClass<T>, + timeoutMs: Long = defaultTimeoutMs, + from: Int = mark, + predicate: (cb: T) -> Boolean = { true } + ) = history.poll(timeoutMs, from) { type.java.isInstance(it) && predicate(it as T) }.also { + assertNotNull(it, "Callback ${type.java} not received within ${timeoutMs}ms. " + + "Got ${history.backtrace()}") + } as T + + // Expects onAvailable and the callbacks that follow it. These are: + // - onSuspended, iff the network was suspended when the callbacks fire. + // - onCapabilitiesChanged. + // - onLinkPropertiesChanged. + // - onBlockedStatusChanged. + // + // @param network the network to expect the callbacks on. + // @param suspended whether to expect a SUSPENDED callback. + // @param validated the expected value of the VALIDATED capability in the + // onCapabilitiesChanged callback. + // @param tmt how long to wait for the callbacks. + @JvmOverloads + fun expectAvailableCallbacks( + net: Network, + suspended: Boolean = false, + validated: Boolean? = true, + blocked: Boolean = false, + tmt: Long = defaultTimeoutMs + ) { + expectAvailableCallbacksCommon(net, suspended, validated, tmt) + expect<BlockedStatus>(net, tmt) { it.blocked == blocked } + } + + fun expectAvailableCallbacks( + net: Network, + suspended: Boolean, + validated: Boolean, + blockedReason: Int, + tmt: Long + ) { + expectAvailableCallbacksCommon(net, suspended, validated, tmt) + expect<BlockedStatusInt>(net) { it.reason == blockedReason } + } + + private fun expectAvailableCallbacksCommon( + net: Network, + suspended: Boolean, + validated: Boolean?, + tmt: Long + ) { + expect<Available>(net, tmt) + if (suspended) { + expect<Suspended>(net, tmt) + } + expect<CapabilitiesChanged>(net, tmt) { + validated == null || validated == it.caps.hasCapability(NET_CAPABILITY_VALIDATED) + } + expect<LinkPropertiesChanged>(net, tmt) + } + + // Backward compatibility for existing Java code. Use named arguments instead and remove all + // these when there is no user left. + fun expectAvailableAndSuspendedCallbacks( + net: Network, + validated: Boolean, + tmt: Long = defaultTimeoutMs + ) = expectAvailableCallbacks(net, suspended = true, validated = validated, tmt = tmt) + + // Expects the available callbacks (where the onCapabilitiesChanged must contain the + // VALIDATED capability), plus another onCapabilitiesChanged which is identical to the + // one we just sent. + // TODO: this is likely a bug. Fix it and remove this method. + fun expectAvailableDoubleValidatedCallbacks(net: Network, tmt: Long = defaultTimeoutMs) { + val mark = history.mark + expectAvailableCallbacks(net, tmt = tmt) + val firstCaps = history.poll(tmt, mark) { it is CapabilitiesChanged } + assertEquals(firstCaps, expect<CapabilitiesChanged>(net, tmt)) + } + + // Expects the available callbacks where the onCapabilitiesChanged must not have validated, + // then expects another onCapabilitiesChanged that has the validated bit set. This is used + // when a network connects and satisfies a callback, and then immediately validates. + fun expectAvailableThenValidatedCallbacks(net: Network, tmt: Long = defaultTimeoutMs) { + expectAvailableCallbacks(net, validated = false, tmt = tmt) + expectCaps(net, tmt) { it.hasCapability(NET_CAPABILITY_VALIDATED) } + } + + fun expectAvailableThenValidatedCallbacks( + net: Network, + blockedReason: Int, + tmt: Long = defaultTimeoutMs + ) { + expectAvailableCallbacks(net, validated = false, suspended = false, + blockedReason = blockedReason, tmt = tmt) + expectCaps(net, tmt) { it.hasCapability(NET_CAPABILITY_VALIDATED) } + } + + // Temporary Java compat measure : have MockNetworkAgent implement this so that all existing + // calls with networkAgent can be routed through here without moving MockNetworkAgent. + // TODO: clean this up, remove this method. + interface HasNetwork { + val network: Network + } + + fun expectAvailableCallbacks( + n: HasNetwork, + suspended: Boolean, + validated: Boolean, + blocked: Boolean, + timeoutMs: Long + ) = expectAvailableCallbacks(n.network, suspended, validated, blocked, timeoutMs) + + fun expectAvailableAndSuspendedCallbacks(n: HasNetwork, expectValidated: Boolean) { + expectAvailableAndSuspendedCallbacks(n.network, expectValidated) + } + + fun expectAvailableCallbacksValidated(n: HasNetwork) { + expectAvailableCallbacks(n.network) + } + + fun expectAvailableCallbacksValidatedAndBlocked(n: HasNetwork) { + expectAvailableCallbacks(n.network, blocked = true) + } + + fun expectAvailableCallbacksUnvalidated(n: HasNetwork) { + expectAvailableCallbacks(n.network, validated = false) + } + + fun expectAvailableCallbacksUnvalidatedAndBlocked(n: HasNetwork) { + expectAvailableCallbacks(n.network, validated = false, blocked = true) + } + + fun expectAvailableDoubleValidatedCallbacks(n: HasNetwork) { + expectAvailableDoubleValidatedCallbacks(n.network, defaultTimeoutMs) + } + + fun expectAvailableThenValidatedCallbacks(n: HasNetwork) { + expectAvailableThenValidatedCallbacks(n.network, defaultTimeoutMs) + } + + @JvmOverloads + fun expectCaps( + n: HasNetwork, + tmt: Long = defaultTimeoutMs, + valid: (NetworkCapabilities) -> Boolean = { true } + ) = expect<CapabilitiesChanged>(n.network, tmt) { valid(it.caps) }.caps + + @JvmOverloads + fun expectCaps( + n: Network, + tmt: Long = defaultTimeoutMs, + valid: (NetworkCapabilities) -> Boolean + ) = expect<CapabilitiesChanged>(n, tmt) { valid(it.caps) }.caps + + fun expectCaps( + n: HasNetwork, + valid: (NetworkCapabilities) -> Boolean + ) = expect<CapabilitiesChanged>(n.network) { valid(it.caps) }.caps + + fun expectCaps( + tmt: Long, + valid: (NetworkCapabilities) -> Boolean + ) = expect<CapabilitiesChanged>(ANY_NETWORK, tmt) { valid(it.caps) }.caps +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkOfferCallback.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkOfferCallback.kt new file mode 100644 index 0000000000000000000000000000000000000000..21bd60c95da4e81cb8a8a0706fd0f5d07934ada9 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkOfferCallback.kt @@ -0,0 +1,75 @@ +/* + * 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. + */ + +package com.android.testutils + +import android.net.NetworkCapabilities +import android.net.NetworkProvider +import android.net.NetworkRequest +import android.util.Log +import com.android.net.module.util.ArrayTrackRecord +import kotlin.test.fail + +class TestableNetworkOfferCallback(val timeoutMs: Long, private val noCallbackTimeoutMs: Long) + : NetworkProvider.NetworkOfferCallback { + private val TAG = this::class.simpleName + val history = ArrayTrackRecord<CallbackEntry>().newReadHead() + + sealed class CallbackEntry { + data class OnNetworkNeeded(val request: NetworkRequest) : CallbackEntry() + data class OnNetworkUnneeded(val request: NetworkRequest) : CallbackEntry() + } + + /** + * Called by the system when a network for this offer is needed to satisfy some + * networking request. + */ + override fun onNetworkNeeded(request: NetworkRequest) { + Log.d(TAG, "onNetworkNeeded $request") + history.add(CallbackEntry.OnNetworkNeeded(request)) + } + + /** + * Called by the system when this offer is no longer valuable for this request. + */ + override fun onNetworkUnneeded(request: NetworkRequest) { + Log.d(TAG, "onNetworkUnneeded $request") + history.add(CallbackEntry.OnNetworkUnneeded(request)) + } + + inline fun <reified T : CallbackEntry> expectCallbackThat( + crossinline predicate: (T) -> Boolean + ) { + val event = history.poll(timeoutMs) + ?: fail("Did not receive callback after ${timeoutMs}ms") + if (event !is T || !predicate(event)) fail("Received unexpected callback $event") + } + + fun expectOnNetworkNeeded(capabilities: NetworkCapabilities) = + expectCallbackThat<CallbackEntry.OnNetworkNeeded> { + it.request.canBeSatisfiedBy(capabilities) + } + + fun expectOnNetworkUnneeded(capabilities: NetworkCapabilities) = + expectCallbackThat<CallbackEntry.OnNetworkUnneeded> { + it.request.canBeSatisfiedBy(capabilities) + } + + fun assertNoCallback() { + val cb = history.poll(noCallbackTimeoutMs) + if (null != cb) fail("Expected no callback but got $cb") + } +} \ No newline at end of file diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkStatsProvider.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkStatsProvider.kt new file mode 100644 index 0000000000000000000000000000000000000000..4a7b35134699e4e2347b1fd90b1c23d331e201dd --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkStatsProvider.kt @@ -0,0 +1,123 @@ +/* + * 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. + */ + +package com.android.testutils + +import android.net.netstats.provider.NetworkStatsProvider +import android.util.Log +import com.android.net.module.util.ArrayTrackRecord +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import kotlin.test.fail + +private const val DEFAULT_TIMEOUT_MS = 200L +const val TOKEN_ANY = -1 + +open class TestableNetworkStatsProvider( + val defaultTimeoutMs: Long = DEFAULT_TIMEOUT_MS +) : NetworkStatsProvider() { + sealed class CallbackType { + data class OnRequestStatsUpdate(val token: Int) : CallbackType() + data class OnSetWarningAndLimit( + val iface: String, + val warningBytes: Long, + val limitBytes: Long + ) : CallbackType() + data class OnSetLimit(val iface: String, val limitBytes: Long) : CallbackType() { + // Add getter for backward compatibility since old tests do not recognize limitBytes. + val quotaBytes: Long + get() = limitBytes + } + data class OnSetAlert(val quotaBytes: Long) : CallbackType() + } + + private val TAG = this::class.simpleName + val history = ArrayTrackRecord<CallbackType>().newReadHead() + // See ReadHead#mark + val mark get() = history.mark + + override fun onRequestStatsUpdate(token: Int) { + Log.d(TAG, "onRequestStatsUpdate $token") + history.add(CallbackType.OnRequestStatsUpdate(token)) + } + + override fun onSetWarningAndLimit(iface: String, warningBytes: Long, limitBytes: Long) { + Log.d(TAG, "onSetWarningAndLimit $iface $warningBytes $limitBytes") + history.add(CallbackType.OnSetWarningAndLimit(iface, warningBytes, limitBytes)) + } + + override fun onSetLimit(iface: String, quotaBytes: Long) { + Log.d(TAG, "onSetLimit $iface $quotaBytes") + history.add(CallbackType.OnSetLimit(iface, quotaBytes)) + } + + override fun onSetAlert(quotaBytes: Long) { + Log.d(TAG, "onSetAlert $quotaBytes") + history.add(CallbackType.OnSetAlert(quotaBytes)) + } + + fun expectOnRequestStatsUpdate(token: Int, timeout: Long = defaultTimeoutMs): Int { + val event = history.poll(timeout) + assertTrue(event is CallbackType.OnRequestStatsUpdate) + if (token != TOKEN_ANY) { + assertEquals(token, event.token) + } + return event.token + } + + fun expectOnSetLimit(iface: String, quotaBytes: Long, timeout: Long = defaultTimeoutMs) { + assertEquals(CallbackType.OnSetLimit(iface, quotaBytes), history.poll(timeout)) + } + + fun expectOnSetAlert(quotaBytes: Long, timeout: Long = defaultTimeoutMs) { + assertEquals(CallbackType.OnSetAlert(quotaBytes), history.poll(timeout)) + } + + fun pollForNextCallback(timeout: Long = defaultTimeoutMs) = + history.poll(timeout) ?: fail("Did not receive callback after ${timeout}ms") + + inline fun <reified T : CallbackType> expectCallback( + timeout: Long = defaultTimeoutMs, + predicate: (T) -> Boolean = { true } + ): T { + return pollForNextCallback(timeout).also { assertTrue(it is T && predicate(it)) } as T + } + + // Expects a callback of the specified type matching the predicate within the timeout. + // Any callback that doesn't match the predicate will be skipped. Fails only if + // no matching callback is received within the timeout. + // TODO : factorize the code for this with the identical call in TestableNetworkCallback. + // There should be a common superclass doing this generically. + // TODO : have a better error message to have this fail. Right now the failure when no + // matching callback arrives comes from the casting to a non-nullable T. + // TODO : in fact, completely removing this method and have clients use + // history.poll(timeout, index, predicate) directly might be simpler. + inline fun <reified T : CallbackType> eventuallyExpect( + timeoutMs: Long = defaultTimeoutMs, + from: Int = mark, + crossinline predicate: (T) -> Boolean = { true } + ) = history.poll(timeoutMs, from) { it is T && predicate(it) } as T + + fun drainCallbacks() { + history.mark = history.size + } + + @JvmOverloads + fun assertNoCallback(timeout: Long = defaultTimeoutMs) { + val cb = history.poll(timeout) + cb?.let { fail("Expected no callback but got $cb") } + } +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkStatsProviderBinder.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkStatsProviderBinder.kt new file mode 100644 index 0000000000000000000000000000000000000000..643346b2e6d030027ed2a74f29ccd348947cdf45 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkStatsProviderBinder.kt @@ -0,0 +1,68 @@ +/* + * 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. + */ + +package com.android.testutils + +import com.android.net.module.util.ArrayTrackRecord +import kotlin.test.assertEquals +import kotlin.test.fail + +private const val DEFAULT_TIMEOUT_MS = 200L + +open class TestableNetworkStatsProviderBinder : NetworkStatsProviderStubCompat() { + sealed class CallbackType { + data class OnRequestStatsUpdate(val token: Int) : CallbackType() + data class OnSetAlert(val quotaBytes: Long) : CallbackType() + data class OnSetWarningAndLimit( + val iface: String, + val warningBytes: Long, + val limitBytes: Long + ) : CallbackType() + } + + private val history = ArrayTrackRecord<CallbackType>().ReadHead() + + override fun onRequestStatsUpdate(token: Int) { + history.add(CallbackType.OnRequestStatsUpdate(token)) + } + + override fun onSetAlert(quotaBytes: Long) { + history.add(CallbackType.OnSetAlert(quotaBytes)) + } + + override fun onSetWarningAndLimit(iface: String, warningBytes: Long, limitBytes: Long) { + history.add(CallbackType.OnSetWarningAndLimit(iface, warningBytes, limitBytes)) + } + + fun expectOnRequestStatsUpdate(token: Int) { + assertEquals(CallbackType.OnRequestStatsUpdate(token), history.poll(DEFAULT_TIMEOUT_MS)) + } + + fun expectOnSetWarningAndLimit(iface: String, warningBytes: Long, limitBytes: Long) { + assertEquals(CallbackType.OnSetWarningAndLimit(iface, warningBytes, limitBytes), + history.poll(DEFAULT_TIMEOUT_MS)) + } + + fun expectOnSetAlert(quotaBytes: Long) { + assertEquals(CallbackType.OnSetAlert(quotaBytes), history.poll(DEFAULT_TIMEOUT_MS)) + } + + @JvmOverloads + fun assertNoCallback(timeout: Long = DEFAULT_TIMEOUT_MS) { + val cb = history.poll(timeout) + cb?.let { fail("Expected no callback but got $cb") } + } +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkStatsProviderCbBinder.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkStatsProviderCbBinder.kt new file mode 100644 index 0000000000000000000000000000000000000000..5547c90a48929e7908cd0ecc8842ba61c8dab5c9 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkStatsProviderCbBinder.kt @@ -0,0 +1,100 @@ +/* + * 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. + */ + +package com.android.testutils + +import android.net.NetworkStats +import com.android.net.module.util.ArrayTrackRecord +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import kotlin.test.fail + +private const val DEFAULT_TIMEOUT_MS = 3000L + +open class TestableNetworkStatsProviderCbBinder : NetworkStatsProviderCbStubCompat() { + sealed class CallbackType { + data class NotifyStatsUpdated( + val token: Int, + val ifaceStats: NetworkStats, + val uidStats: NetworkStats + ) : CallbackType() + object NotifyWarningReached : CallbackType() + object NotifyLimitReached : CallbackType() + object NotifyWarningOrLimitReached : CallbackType() + object NotifyAlertReached : CallbackType() + object Unregister : CallbackType() + } + + private val history = ArrayTrackRecord<CallbackType>().ReadHead() + + override fun notifyStatsUpdated(token: Int, ifaceStats: NetworkStats, uidStats: NetworkStats) { + history.add(CallbackType.NotifyStatsUpdated(token, ifaceStats, uidStats)) + } + + override fun notifyWarningReached() { + history.add(CallbackType.NotifyWarningReached) + } + + override fun notifyLimitReached() { + history.add(CallbackType.NotifyLimitReached) + } + + override fun notifyWarningOrLimitReached() { + // Older callback is split into notifyLimitReached and notifyWarningReached in T. + history.add(CallbackType.NotifyWarningOrLimitReached) + } + + override fun notifyAlertReached() { + history.add(CallbackType.NotifyAlertReached) + } + + override fun unregister() { + history.add(CallbackType.Unregister) + } + + fun expectNotifyStatsUpdated() { + val event = history.poll(DEFAULT_TIMEOUT_MS) + assertTrue(event is CallbackType.NotifyStatsUpdated) + } + + fun expectNotifyStatsUpdated(ifaceStats: NetworkStats, uidStats: NetworkStats) { + val event = history.poll(DEFAULT_TIMEOUT_MS)!! + if (event !is CallbackType.NotifyStatsUpdated) { + throw Exception("Expected NotifyStatsUpdated callback, but got ${event::class}") + } + // TODO: verify token. + assertNetworkStatsEquals(ifaceStats, event.ifaceStats) + assertNetworkStatsEquals(uidStats, event.uidStats) + } + + fun expectNotifyWarningReached() = + assertEquals(CallbackType.NotifyWarningReached, history.poll(DEFAULT_TIMEOUT_MS)) + + fun expectNotifyLimitReached() = + assertEquals(CallbackType.NotifyLimitReached, history.poll(DEFAULT_TIMEOUT_MS)) + + fun expectNotifyWarningOrLimitReached() = + assertEquals(CallbackType.NotifyWarningOrLimitReached, history.poll(DEFAULT_TIMEOUT_MS)) + + fun expectNotifyAlertReached() = + assertEquals(CallbackType.NotifyAlertReached, history.poll(DEFAULT_TIMEOUT_MS)) + + // Assert there is no callback in current queue. + fun assertNoCallback() { + val cb = history.poll(0) + cb?.let { fail("Expected no callback but got $cb") } + } +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/async/FakeOsAccess.java b/staticlibs/testutils/devicetests/com/android/testutils/async/FakeOsAccess.java new file mode 100644 index 0000000000000000000000000000000000000000..48b57d70be4f029997ed90c252481c4cb1f36819 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/async/FakeOsAccess.java @@ -0,0 +1,568 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.testutils.async; + +import android.os.ParcelFileDescriptor; +import android.system.StructPollfd; +import android.util.Log; + +import com.android.net.module.util.async.CircularByteBuffer; +import com.android.net.module.util.async.OsAccess; + +import java.io.FileDescriptor; +import java.io.InterruptedIOException; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.concurrent.TimeUnit; + +public class FakeOsAccess extends OsAccess { + public static final boolean ENABLE_FINE_DEBUG = true; + + public static final int DEFAULT_FILE_DATA_QUEUE_SIZE = 8 * 1024; + + private enum FileType { PAIR, PIPE } + + // Common poll() constants: + private static final short POLLIN = 0x0001; + private static final short POLLOUT = 0x0004; + private static final short POLLERR = 0x0008; + private static final short POLLHUP = 0x0010; + + private static final Constructor<FileDescriptor> FD_CONSTRUCTOR; + private static final Field FD_FIELD_DESCRIPTOR; + private static final Field PFD_FIELD_DESCRIPTOR; + private static final Field PFD_FIELD_GUARD; + private static final Method CLOSE_GUARD_METHOD_CLOSE; + + private final int mReadQueueSize = DEFAULT_FILE_DATA_QUEUE_SIZE; + private final int mWriteQueueSize = DEFAULT_FILE_DATA_QUEUE_SIZE; + private final HashMap<Integer, File> mFiles = new HashMap<>(); + private final byte[] mTmpBuffer = new byte[1024]; + private final long mStartTime; + private final String mLogTag; + private int mFileNumberGen = 3; + private boolean mHasRateLimitedData; + + public FakeOsAccess(String logTag) { + mLogTag = logTag; + mStartTime = monotonicTimeMillis(); + } + + @Override + public long monotonicTimeMillis() { + return System.nanoTime() / 1000000; + } + + @Override + public FileDescriptor getInnerFileDescriptor(ParcelFileDescriptor fd) { + try { + return (FileDescriptor) PFD_FIELD_DESCRIPTOR.get(fd); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void close(ParcelFileDescriptor fd) { + if (fd != null) { + close(getInnerFileDescriptor(fd)); + + try { + // Reduce CloseGuard warnings. + Object guard = PFD_FIELD_GUARD.get(fd); + CLOSE_GUARD_METHOD_CLOSE.invoke(guard); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + public synchronized void close(FileDescriptor fd) { + if (fd != null) { + File file = getFileOrNull(fd); + if (file != null) { + file.decreaseRefCount(); + mFiles.remove(getFileDescriptorNumber(fd)); + setFileDescriptorNumber(fd, -1); + notifyAll(); + } + } + } + + private File getFile(String func, FileDescriptor fd) throws IOException { + File file = getFileOrNull(fd); + if (file == null) { + throw newIOException(func, "Unknown file descriptor: " + getFileDebugName(fd)); + } + return file; + } + + private File getFileOrNull(FileDescriptor fd) { + return mFiles.get(getFileDescriptorNumber(fd)); + } + + @Override + public String getFileDebugName(ParcelFileDescriptor fd) { + return (fd != null ? getFileDebugName(getInnerFileDescriptor(fd)) : "null"); + } + + public String getFileDebugName(FileDescriptor fd) { + if (fd == null) { + return "null"; + } + + final int fdNumber = getFileDescriptorNumber(fd); + File file = mFiles.get(fdNumber); + + StringBuilder sb = new StringBuilder(); + if (file != null) { + if (file.name != null) { + sb.append(file.name); + sb.append("/"); + } + sb.append(file.type); + sb.append("/"); + } else { + sb.append("BADFD/"); + } + sb.append(fdNumber); + return sb.toString(); + } + + public synchronized void setFileName(FileDescriptor fd, String name) { + File file = getFileOrNull(fd); + if (file != null) { + file.name = name; + } + } + + @Override + public synchronized void setNonBlocking(FileDescriptor fd) throws IOException { + File file = getFile("fcntl", fd); + file.isBlocking = false; + } + + @Override + public synchronized int read(FileDescriptor fd, byte[] buffer, int pos, int len) + throws IOException { + checkBoundaries("read", buffer, pos, len); + + File file = getFile("read", fd); + if (file.readQueue == null) { + throw newIOException("read", "File not readable"); + } + file.checkNonBlocking("read"); + + if (len == 0) { + return 0; + } + + final int availSize = file.readQueue.size(); + if (availSize == 0) { + if (file.isEndOfStream) { + // Java convention uses -1 to indicate end of stream. + return -1; + } + return 0; // EAGAIN + } + + final int readCount = Math.min(len, availSize); + file.readQueue.readBytes(buffer, pos, readCount); + maybeTransferData(file); + return readCount; + } + + @Override + public synchronized int write(FileDescriptor fd, byte[] buffer, int pos, int len) + throws IOException { + checkBoundaries("write", buffer, pos, len); + + File file = getFile("write", fd); + if (file.writeQueue == null) { + throw newIOException("read", "File not writable"); + } + if (file.type == FileType.PIPE && file.sink.openCount == 0) { + throw newIOException("write", "The other end of pipe is closed"); + } + file.checkNonBlocking("write"); + + if (len == 0) { + return 0; + } + + final int originalFreeSize = file.writeQueue.freeSize(); + if (originalFreeSize == 0) { + return 0; // EAGAIN + } + + final int writeCount = Math.min(len, originalFreeSize); + file.writeQueue.writeBytes(buffer, pos, writeCount); + maybeTransferData(file); + + if (file.writeQueue.freeSize() < originalFreeSize) { + final int additionalQueuedCount = originalFreeSize - file.writeQueue.freeSize(); + Log.i(mLogTag, logStr("Delaying transfer of " + additionalQueuedCount + + " bytes, queued=" + file.writeQueue.size() + ", type=" + file.type + + ", src_red=" + file.outboundLimiter + ", dst_red=" + file.sink.inboundLimiter)); + } + + return writeCount; + } + + private void maybeTransferData(File file) { + boolean hasChanges = copyFileBuffers(file, file.sink); + hasChanges = copyFileBuffers(file.source, file) || hasChanges; + + if (hasChanges) { + // TODO(b/245971639): Avoid notifying if no-one is polling. + notifyAll(); + } + } + + private boolean copyFileBuffers(File src, File dst) { + if (src.writeQueue == null || dst.readQueue == null) { + return false; + } + + final int originalCopyCount = Math.min(mTmpBuffer.length, + Math.min(src.writeQueue.size(), dst.readQueue.freeSize())); + + final int allowedCopyCount = RateLimiter.limit( + src.outboundLimiter, dst.inboundLimiter, originalCopyCount); + + if (allowedCopyCount < originalCopyCount) { + if (ENABLE_FINE_DEBUG) { + Log.i(mLogTag, logStr("Delaying transfer of " + + (originalCopyCount - allowedCopyCount) + " bytes, original=" + + originalCopyCount + ", allowed=" + allowedCopyCount + + ", type=" + src.type)); + } + if (originalCopyCount > 0) { + mHasRateLimitedData = true; + } + if (allowedCopyCount == 0) { + return false; + } + } + + boolean hasChanges = false; + if (allowedCopyCount > 0) { + if (dst.readQueue.size() == 0 || src.writeQueue.freeSize() == 0) { + hasChanges = true; // Read queue had no data, or write queue was full. + } + src.writeQueue.readBytes(mTmpBuffer, 0, allowedCopyCount); + dst.readQueue.writeBytes(mTmpBuffer, 0, allowedCopyCount); + } + + if (!dst.isEndOfStream && src.openCount == 0 + && src.writeQueue.size() == 0 && dst.readQueue.size() == 0) { + dst.isEndOfStream = true; + hasChanges = true; + } + + return hasChanges; + } + + public void clearInboundRateLimit(FileDescriptor fd) { + setInboundRateLimit(fd, Integer.MAX_VALUE); + } + + public void clearOutboundRateLimit(FileDescriptor fd) { + setOutboundRateLimit(fd, Integer.MAX_VALUE); + } + + public synchronized void setInboundRateLimit(FileDescriptor fd, int bytesPerSecond) { + File file = getFileOrNull(fd); + if (file != null) { + file.inboundLimiter.setBytesPerSecond(bytesPerSecond); + maybeTransferData(file); + } + } + + public synchronized void setOutboundRateLimit(FileDescriptor fd, int bytesPerSecond) { + File file = getFileOrNull(fd); + if (file != null) { + file.outboundLimiter.setBytesPerSecond(bytesPerSecond); + maybeTransferData(file); + } + } + + public synchronized ParcelFileDescriptor[] socketpair() throws IOException { + int fdNumber1 = getNextFd("socketpair"); + int fdNumber2 = getNextFd("socketpair"); + + File file1 = new File(FileType.PAIR, mReadQueueSize, mWriteQueueSize); + File file2 = new File(FileType.PAIR, mReadQueueSize, mWriteQueueSize); + + return registerFilePair(fdNumber1, file1, fdNumber2, file2); + } + + @Override + public synchronized ParcelFileDescriptor[] pipe() throws IOException { + int fdNumber1 = getNextFd("pipe"); + int fdNumber2 = getNextFd("pipe"); + + File file1 = new File(FileType.PIPE, mReadQueueSize, 0); + File file2 = new File(FileType.PIPE, 0, mWriteQueueSize); + + return registerFilePair(fdNumber1, file1, fdNumber2, file2); + } + + private ParcelFileDescriptor[] registerFilePair( + int fdNumber1, File file1, int fdNumber2, File file2) { + file1.sink = file2; + file1.source = file2; + file2.sink = file1; + file2.source = file1; + + mFiles.put(fdNumber1, file1); + mFiles.put(fdNumber2, file2); + return new ParcelFileDescriptor[] { + newParcelFileDescriptor(fdNumber1), newParcelFileDescriptor(fdNumber2)}; + } + + @Override + public short getPollInMask() { + return POLLIN; + } + + @Override + public short getPollOutMask() { + return POLLOUT; + } + + @Override + public synchronized int poll(StructPollfd[] fds, int timeoutMs) throws IOException { + if (timeoutMs < 0) { + timeoutMs = (int) TimeUnit.HOURS.toMillis(1); // Make "infinite" equal to 1 hour. + } + + if (fds == null || fds.length > 1000) { + throw newIOException("poll", "Invalid fds param"); + } + for (StructPollfd pollFd : fds) { + getFile("poll", pollFd.fd); + } + + int waitCallCount = 0; + final long deadline = monotonicTimeMillis() + timeoutMs; + while (true) { + if (mHasRateLimitedData) { + mHasRateLimitedData = false; + for (File file : mFiles.values()) { + if (file.inboundLimiter.getLastRequestReduction() != 0) { + copyFileBuffers(file.source, file); + } + if (file.outboundLimiter.getLastRequestReduction() != 0) { + copyFileBuffers(file, file.sink); + } + } + } + + final int readyCount = calculateReadyCount(fds); + if (readyCount > 0) { + if (ENABLE_FINE_DEBUG) { + Log.v(mLogTag, logStr("Poll returns " + readyCount + + " after " + waitCallCount + " wait calls")); + } + return readyCount; + } + + long remainingTimeoutMs = deadline - monotonicTimeMillis(); + if (remainingTimeoutMs <= 0) { + if (ENABLE_FINE_DEBUG) { + Log.v(mLogTag, logStr("Poll timeout " + timeoutMs + + "ms after " + waitCallCount + " wait calls")); + } + return 0; + } + + if (mHasRateLimitedData) { + remainingTimeoutMs = Math.min(RateLimiter.BUCKET_DURATION_MS, remainingTimeoutMs); + } + + try { + wait(remainingTimeoutMs); + } catch (InterruptedException e) { + // Ignore and retry + } + waitCallCount++; + } + } + + private int calculateReadyCount(StructPollfd[] fds) { + int fdCount = 0; + for (StructPollfd pollFd : fds) { + pollFd.revents = 0; + + File file = getFileOrNull(pollFd.fd); + if (file == null) { + Log.w(mLogTag, logStr("Ignoring FD concurrently closed by a buggy app: " + + getFileDebugName(pollFd.fd))); + continue; + } + + if (ENABLE_FINE_DEBUG) { + Log.v(mLogTag, logStr("calculateReadyCount fd=" + getFileDebugName(pollFd.fd) + + ", events=" + pollFd.events + ", eof=" + file.isEndOfStream + + ", r=" + (file.readQueue != null ? file.readQueue.size() : -1) + + ", w=" + (file.writeQueue != null ? file.writeQueue.freeSize() : -1))); + } + + if ((pollFd.events & POLLIN) != 0) { + if (file.readQueue != null && file.readQueue.size() != 0) { + pollFd.revents |= POLLIN; + } + if (file.isEndOfStream) { + pollFd.revents |= POLLHUP; + } + } + + if ((pollFd.events & POLLOUT) != 0) { + if (file.type == FileType.PIPE && file.sink.openCount == 0) { + pollFd.revents |= POLLERR; + } + if (file.writeQueue != null && file.writeQueue.freeSize() != 0) { + pollFd.revents |= POLLOUT; + } + } + + if (pollFd.revents != 0) { + fdCount++; + } + } + return fdCount; + } + + private int getNextFd(String func) throws IOException { + if (mFileNumberGen > 100000) { + throw newIOException(func, "Too many files open"); + } + + return mFileNumberGen++; + } + + private static IOException newIOException(String func, String message) { + return new IOException(message + ", func=" + func); + } + + public static void checkBoundaries(String func, byte[] buffer, int pos, int len) + throws IOException { + if (((buffer.length | pos | len) < 0 || pos > buffer.length - len)) { + throw newIOException(func, "Invalid array bounds"); + } + } + + private ParcelFileDescriptor newParcelFileDescriptor(int fdNumber) { + try { + return new ParcelFileDescriptor(newFileDescriptor(fdNumber)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private FileDescriptor newFileDescriptor(int fdNumber) { + try { + return FD_CONSTRUCTOR.newInstance(Integer.valueOf(fdNumber)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public int getFileDescriptorNumber(FileDescriptor fd) { + try { + return (Integer) FD_FIELD_DESCRIPTOR.get(fd); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private void setFileDescriptorNumber(FileDescriptor fd, int fdNumber) { + try { + FD_FIELD_DESCRIPTOR.set(fd, Integer.valueOf(fdNumber)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private String logStr(String message) { + return "[FakeOs " + (monotonicTimeMillis() - mStartTime) + "] " + message; + } + + private class File { + final FileType type; + final CircularByteBuffer readQueue; + final CircularByteBuffer writeQueue; + final RateLimiter inboundLimiter = new RateLimiter(FakeOsAccess.this, Integer.MAX_VALUE); + final RateLimiter outboundLimiter = new RateLimiter(FakeOsAccess.this, Integer.MAX_VALUE); + String name; + int openCount = 1; + boolean isBlocking = true; + File sink; + File source; + boolean isEndOfStream; + + File(FileType type, int readQueueSize, int writeQueueSize) { + this.type = type; + readQueue = (readQueueSize > 0 ? new CircularByteBuffer(readQueueSize) : null); + writeQueue = (writeQueueSize > 0 ? new CircularByteBuffer(writeQueueSize) : null); + } + + void decreaseRefCount() { + if (openCount <= 0) { + throw new IllegalStateException(); + } + openCount--; + } + + void checkNonBlocking(String func) throws IOException { + if (isBlocking) { + throw newIOException(func, "File in blocking mode"); + } + } + } + + static { + try { + FD_CONSTRUCTOR = FileDescriptor.class.getDeclaredConstructor(int.class); + FD_CONSTRUCTOR.setAccessible(true); + + Field descriptorIntField; + try { + descriptorIntField = FileDescriptor.class.getDeclaredField("descriptor"); + } catch (NoSuchFieldException e) { + descriptorIntField = FileDescriptor.class.getDeclaredField("fd"); + } + FD_FIELD_DESCRIPTOR = descriptorIntField; + FD_FIELD_DESCRIPTOR.setAccessible(true); + + PFD_FIELD_DESCRIPTOR = ParcelFileDescriptor.class.getDeclaredField("mFd"); + PFD_FIELD_DESCRIPTOR.setAccessible(true); + + PFD_FIELD_GUARD = ParcelFileDescriptor.class.getDeclaredField("mGuard"); + PFD_FIELD_GUARD.setAccessible(true); + + CLOSE_GUARD_METHOD_CLOSE = Class.forName("dalvik.system.CloseGuard") + .getDeclaredMethod("close"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/async/RateLimiter.java b/staticlibs/testutils/devicetests/com/android/testutils/async/RateLimiter.java new file mode 100644 index 0000000000000000000000000000000000000000..d5cca0a4cd72e182de5134d8cb5891f25f57dc39 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/async/RateLimiter.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.testutils.async; + +import com.android.net.module.util.async.OsAccess; + +import java.util.Arrays; + +/** + * Limits the number of bytes processed to the given maximum of bytes per second. + * + * The limiter tracks the total for the past second, along with sums for each 10ms + * in the past second, allowing the total to be adjusted as the time passes. + */ +public final class RateLimiter { + private static final int PERIOD_DURATION_MS = 1000; + private static final int BUCKET_COUNT = 100; + + public static final int BUCKET_DURATION_MS = PERIOD_DURATION_MS / BUCKET_COUNT; + + private final OsAccess mOsAccess; + private final int[] mStatBuckets = new int[BUCKET_COUNT]; + private int mMaxPerPeriodBytes; + private int mMaxPerBucketBytes; + private int mRecordedPeriodBytes; + private long mLastLimitTimestamp; + private int mLastRequestReduction; + + public RateLimiter(OsAccess osAccess, int bytesPerSecond) { + mOsAccess = osAccess; + setBytesPerSecond(bytesPerSecond); + clear(); + } + + public int getBytesPerSecond() { + return mMaxPerPeriodBytes; + } + + public void setBytesPerSecond(int bytesPerSecond) { + mMaxPerPeriodBytes = bytesPerSecond; + mMaxPerBucketBytes = Math.max(1, (mMaxPerPeriodBytes / BUCKET_COUNT) * 2); + } + + public void clear() { + mLastLimitTimestamp = mOsAccess.monotonicTimeMillis(); + mRecordedPeriodBytes = 0; + Arrays.fill(mStatBuckets, 0); + } + + public static int limit(RateLimiter limiter1, RateLimiter limiter2, int requestedBytes) { + final long now = limiter1.mOsAccess.monotonicTimeMillis(); + final int allowedCount = Math.min(limiter1.calculateLimit(now, requestedBytes), + limiter2.calculateLimit(now, requestedBytes)); + limiter1.recordBytes(now, requestedBytes, allowedCount); + limiter2.recordBytes(now, requestedBytes, allowedCount); + return allowedCount; + } + + public int limit(int requestedBytes) { + final long now = mOsAccess.monotonicTimeMillis(); + final int allowedCount = calculateLimit(now, requestedBytes); + recordBytes(now, requestedBytes, allowedCount); + return allowedCount; + } + + public int getLastRequestReduction() { + return mLastRequestReduction; + } + + public boolean acceptAllOrNone(int requestedBytes) { + final long now = mOsAccess.monotonicTimeMillis(); + final int allowedCount = calculateLimit(now, requestedBytes); + if (allowedCount < requestedBytes) { + return false; + } + recordBytes(now, requestedBytes, allowedCount); + return true; + } + + private int calculateLimit(long now, int requestedBytes) { + // First remove all stale bucket data and adjust the total. + final long currentBucketAbsIdx = now / BUCKET_DURATION_MS; + final long staleCutoffIdx = currentBucketAbsIdx - BUCKET_COUNT; + for (long i = mLastLimitTimestamp / BUCKET_DURATION_MS; i < staleCutoffIdx; i++) { + final int idx = (int) (i % BUCKET_COUNT); + mRecordedPeriodBytes -= mStatBuckets[idx]; + mStatBuckets[idx] = 0; + } + + final int bucketIdx = (int) (currentBucketAbsIdx % BUCKET_COUNT); + final int maxAllowed = Math.min(mMaxPerPeriodBytes - mRecordedPeriodBytes, + Math.min(mMaxPerBucketBytes - mStatBuckets[bucketIdx], requestedBytes)); + return Math.max(0, maxAllowed); + } + + private void recordBytes(long now, int requestedBytes, int actualBytes) { + mStatBuckets[(int) ((now / BUCKET_DURATION_MS) % BUCKET_COUNT)] += actualBytes; + mRecordedPeriodBytes += actualBytes; + mLastRequestReduction = requestedBytes - actualBytes; + mLastLimitTimestamp = now; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("{max="); + sb.append(mMaxPerPeriodBytes); + sb.append(",max_bucket="); + sb.append(mMaxPerBucketBytes); + sb.append(",total="); + sb.append(mRecordedPeriodBytes); + sb.append(",last_red="); + sb.append(mLastRequestReduction); + sb.append('}'); + return sb.toString(); + } +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/async/ReadableDataAnswer.java b/staticlibs/testutils/devicetests/com/android/testutils/async/ReadableDataAnswer.java new file mode 100644 index 0000000000000000000000000000000000000000..4bf5527055861d60c3e3543df50799d492f68dc9 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/async/ReadableDataAnswer.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.testutils.async; + +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.util.ArrayList; + +public class ReadableDataAnswer implements Answer { + private final ArrayList<byte[]> mBuffers = new ArrayList<>(); + private int mBufferPos; + + public ReadableDataAnswer(byte[] ... buffers) { + for (byte[] buffer : buffers) { + addBuffer(buffer); + } + } + + public void addBuffer(byte[] buffer) { + if (buffer.length != 0) { + mBuffers.add(buffer); + } + } + + public int getRemainingSize() { + int totalSize = 0; + for (byte[] buffer : mBuffers) { + totalSize += buffer.length; + } + return totalSize - mBufferPos; + } + + private void cleanupBuffers() { + if (!mBuffers.isEmpty() && mBufferPos == mBuffers.get(0).length) { + mBuffers.remove(0); + mBufferPos = 0; + } + } + + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + cleanupBuffers(); + + if (mBuffers.isEmpty()) { + return Integer.valueOf(0); + } + + byte[] src = mBuffers.get(0); + + byte[] dst = invocation.<byte[]>getArgument(0); + int dstPos = invocation.<Integer>getArgument(1); + int dstLen = invocation.<Integer>getArgument(2); + + int copyLen = Math.min(dstLen, src.length - mBufferPos); + System.arraycopy(src, mBufferPos, dst, dstPos, copyLen); + mBufferPos += copyLen; + + cleanupBuffers(); + return Integer.valueOf(copyLen); + } +} diff --git a/staticlibs/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk30.kt b/staticlibs/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk30.kt new file mode 100644 index 0000000000000000000000000000000000000000..843c41e6fa25fbb90d46603084a208b615ca6f71 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk30.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.testutils.filters + +/** + * Only run this test in the CtsNetTestCasesMaxTargetSdk30 suite. + */ +annotation class CtsNetTestCasesMaxTargetSdk30(val reason: String) diff --git a/staticlibs/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk31.kt b/staticlibs/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk31.kt new file mode 100644 index 0000000000000000000000000000000000000000..be0103d6434dd259d36f92d4e7a9fb085adb5e9b --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk31.kt @@ -0,0 +1,22 @@ +/* + * 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. + */ + +package com.android.testutils.filters + +/** + * Only run this test in the CtsNetTestCasesMaxTargetSdk31 suite. + */ +annotation class CtsNetTestCasesMaxTargetSdk31(val reason: String) diff --git a/staticlibs/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk33.kt b/staticlibs/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk33.kt new file mode 100644 index 0000000000000000000000000000000000000000..5af890ff8bd8d15bcd607e70cf3a2cba91b77c77 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk33.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.testutils.filters + +/** + * Only run this test in the CtsNetTestCasesMaxTargetSdk33 suite. + */ +annotation class CtsNetTestCasesMaxTargetSdk33(val reason: String) diff --git a/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt b/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt new file mode 100644 index 0000000000000000000000000000000000000000..3fc74aa1a468010910468cab42679ad623f68e42 --- /dev/null +++ b/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt @@ -0,0 +1,116 @@ +/* + * 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. + */ + +package com.android.testutils + +import com.android.ddmlib.testrunner.TestResult +import com.android.tradefed.config.Option +import com.android.tradefed.invoker.TestInformation +import com.android.tradefed.result.CollectingTestListener +import com.android.tradefed.result.ddmlib.DefaultRemoteAndroidTestRunner +import com.android.tradefed.targetprep.BaseTargetPreparer +import com.android.tradefed.targetprep.TargetSetupError +import com.android.tradefed.targetprep.suite.SuiteApkInstaller + +private const val CONNECTIVITY_CHECKER_APK = "ConnectivityTestPreparer.apk" +private const val CONNECTIVITY_PKG_NAME = "com.android.testutils.connectivitypreparer" +private const val CONNECTIVITY_CHECK_CLASS = "$CONNECTIVITY_PKG_NAME.ConnectivityCheckTest" +// As per the <instrumentation> defined in the checker manifest +private const val CONNECTIVITY_CHECK_RUNNER_NAME = "androidx.test.runner.AndroidJUnitRunner" +private const val IGNORE_CONN_CHECK_OPTION = "ignore-connectivity-check" + +/** + * A target preparer that sets up and verifies a device for connectivity tests. + * + * For quick and dirty local testing, the connectivity check can be disabled by running tests with + * "atest -- \ + * --test-arg com.android.testutils.ConnectivityTestTargetPreparer:ignore-connectivity-check:true". + */ +open class ConnectivityTestTargetPreparer : BaseTargetPreparer() { + private val installer = SuiteApkInstaller() + + @Option(name = IGNORE_CONN_CHECK_OPTION, + description = "Disables the check for mobile data and wifi") + private var ignoreConnectivityCheck = false + + override fun setUp(testInformation: TestInformation) { + if (isDisabled) return + disableGmsUpdate(testInformation) + runPreparerApk(testInformation) + } + + private fun runPreparerApk(testInformation: TestInformation) { + installer.setCleanApk(true) + installer.addTestFileName(CONNECTIVITY_CHECKER_APK) + installer.setShouldGrantPermission(true) + installer.setUp(testInformation) + + val runner = DefaultRemoteAndroidTestRunner( + CONNECTIVITY_PKG_NAME, + CONNECTIVITY_CHECK_RUNNER_NAME, + testInformation.device.iDevice) + runner.runOptions = "--no-hidden-api-checks" + + val receiver = CollectingTestListener() + if (!testInformation.device.runInstrumentationTests(runner, receiver)) { + throw TargetSetupError("Device state check failed to complete", + testInformation.device.deviceDescriptor) + } + + val runResult = receiver.currentRunResults + if (runResult.isRunFailure) { + throw TargetSetupError("Failed to check device state before the test: " + + runResult.runFailureMessage, testInformation.device.deviceDescriptor) + } + + val ignoredTestClasses = mutableSetOf<String>() + if (ignoreConnectivityCheck) { + ignoredTestClasses.add(CONNECTIVITY_CHECK_CLASS) + } + + val errorMsg = runResult.testResults.mapNotNull { (testDescription, testResult) -> + if (TestResult.TestStatus.FAILURE != testResult.status || + ignoredTestClasses.contains(testDescription.className)) { + null + } else { + "$testDescription: ${testResult.stackTrace}" + } + }.joinToString("\n") + if (errorMsg.isBlank()) return + + throw TargetSetupError("Device setup checks failed. Check the test bench: \n$errorMsg", + testInformation.device.deviceDescriptor) + } + + private fun disableGmsUpdate(testInformation: TestInformation) { + // This will be a no-op on devices without root (su) or not using gservices, but that's OK. + testInformation.device.executeShellCommand("su 0 am broadcast " + + "-a com.google.gservices.intent.action.GSERVICES_OVERRIDE " + + "-e finsky.play_services_auto_update_enabled false") + } + + private fun clearGmsUpdateOverride(testInformation: TestInformation) { + testInformation.device.executeShellCommand("su 0 am broadcast " + + "-a com.google.gservices.intent.action.GSERVICES_OVERRIDE " + + "--esn finsky.play_services_auto_update_enabled") + } + + override fun tearDown(testInformation: TestInformation, e: Throwable?) { + if (isTearDownDisabled) return + installer.tearDown(testInformation, e) + clearGmsUpdateOverride(testInformation) + } +} diff --git a/staticlibs/testutils/host/com/android/testutils/DisableConfigSyncTargetPreparer.kt b/staticlibs/testutils/host/com/android/testutils/DisableConfigSyncTargetPreparer.kt new file mode 100644 index 0000000000000000000000000000000000000000..63f05a6cb14f596e396783888dead2f4b352b62a --- /dev/null +++ b/staticlibs/testutils/host/com/android/testutils/DisableConfigSyncTargetPreparer.kt @@ -0,0 +1,61 @@ +/* + * 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. + */ + +package com.android.testutils + +import com.android.tradefed.invoker.TestInformation +import com.android.tradefed.targetprep.BaseTargetPreparer + +/** + * A target preparer that disables DeviceConfig sync while running a test. + * + * Without this preparer, tests that rely on stable values of DeviceConfig flags, for example to + * test behavior when setting the flag and resetting it afterwards, may flake as the flags may + * be synced with remote servers during the test. + */ +class DisableConfigSyncTargetPreparer : BaseTargetPreparer() { + private var syncDisabledOriginalValue = "none" + + override fun setUp(testInfo: TestInformation) { + if (isDisabled) return + syncDisabledOriginalValue = readSyncDisabledOriginalValue(testInfo) + + // The setter is the same in current and legacy S versions + testInfo.exec("cmd device_config set_sync_disabled_for_tests until_reboot") + } + + override fun tearDown(testInfo: TestInformation, e: Throwable?) { + if (isTearDownDisabled) return + // May fail harmlessly if called before S + testInfo.exec("cmd device_config set_sync_disabled_for_tests $syncDisabledOriginalValue") + } + + private fun readSyncDisabledOriginalValue(testInfo: TestInformation): String { + return when (val reply = testInfo.exec("cmd device_config get_sync_disabled_for_tests")) { + "until_reboot", "persistent", "none" -> reply + // Reply does not match known modes, try legacy commands used on S and some T builds + else -> when (testInfo.exec("cmd device_config is_sync_disabled_for_tests")) { + // The legacy command just said "true" for "until_reboot" or "persistent". There is + // no way to know which one was used, so just reset to "until_reboot" to be + // conservative. + "true" -> "until_reboot" + else -> "none" + } + } + } +} + +private fun TestInformation.exec(cmd: String) = this.device.executeShellCommand(cmd) \ No newline at end of file diff --git a/staticlibs/testutils/hostdevice/com/android/net/module/util/TrackRecord.kt b/staticlibs/testutils/hostdevice/com/android/net/module/util/TrackRecord.kt new file mode 100644 index 0000000000000000000000000000000000000000..f24e4f184d1ca925f69c28694ea4396b3478b2af --- /dev/null +++ b/staticlibs/testutils/hostdevice/com/android/net/module/util/TrackRecord.kt @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util + +import java.util.concurrent.TimeUnit +import java.util.concurrent.locks.Condition +import java.util.concurrent.locks.ReentrantLock +import java.util.concurrent.locks.StampedLock +import kotlin.concurrent.withLock + +/** + * A List that additionally offers the ability to append via the add() method, and to retrieve + * an element by its index optionally waiting for it to become available. + */ +interface TrackRecord<E> : List<E> { + /** + * Adds an element to this queue, waking up threads waiting for one. Returns true, as + * per the contract for List. + */ + fun add(e: E): Boolean + + /** + * Returns the first element after {@param pos}, possibly blocking until one is available, or + * null if no such element can be found within the timeout. + * If a predicate is given, only elements matching the predicate are returned. + * + * @param timeoutMs how long, in milliseconds, to wait at most (best effort approximation). + * @param pos the position at which to start polling. + * @param predicate an optional predicate to filter elements to be returned. + * @return an element matching the predicate, or null if timeout. + */ + fun poll(timeoutMs: Long, pos: Int, predicate: (E) -> Boolean = { true }): E? +} + +/** + * A thread-safe implementation of TrackRecord that is backed by an ArrayList. + * + * This class also supports the creation of a read-head for easier single-thread access. + * Refer to the documentation of {@link ArrayTrackRecord.ReadHead}. + */ +class ArrayTrackRecord<E> : TrackRecord<E> { + private val lock = ReentrantLock() + private val condition = lock.newCondition() + // Backing store. This stores the elements in this ArrayTrackRecord. + private val elements = ArrayList<E>() + + // The list iterator for RecordingQueue iterates over a snapshot of the collection at the + // time the operator is created. Because TrackRecord is only ever mutated by appending, + // that makes this iterator thread-safe as it sees an effectively immutable List. + class ArrayTrackRecordIterator<E>( + private val list: ArrayList<E>, + start: Int, + private val end: Int + ) : ListIterator<E> { + var index = start + override fun hasNext() = index < end + override fun next() = list[index++] + override fun hasPrevious() = index > 0 + override fun nextIndex() = index + 1 + override fun previous() = list[--index] + override fun previousIndex() = index - 1 + } + + // List<E> implementation + override val size get() = lock.withLock { elements.size } + override fun contains(element: E) = lock.withLock { elements.contains(element) } + override fun containsAll(elements: Collection<E>) = lock.withLock { + this.elements.containsAll(elements) + } + override operator fun get(index: Int) = lock.withLock { elements[index] } + override fun indexOf(element: E): Int = lock.withLock { elements.indexOf(element) } + override fun lastIndexOf(element: E): Int = lock.withLock { elements.lastIndexOf(element) } + override fun isEmpty() = lock.withLock { elements.isEmpty() } + override fun listIterator(index: Int) = ArrayTrackRecordIterator(elements, index, size) + override fun listIterator() = listIterator(0) + override fun iterator() = listIterator() + override fun subList(fromIndex: Int, toIndex: Int): List<E> = lock.withLock { + elements.subList(fromIndex, toIndex) + } + + // TrackRecord<E> implementation + override fun add(e: E): Boolean { + lock.withLock { + elements.add(e) + condition.signalAll() + } + return true + } + override fun poll(timeoutMs: Long, pos: Int, predicate: (E) -> Boolean) = lock.withLock { + elements.getOrNull(pollForIndexReadLocked(timeoutMs, pos, predicate)) + } + + // For convenience + fun getOrNull(pos: Int, predicate: (E) -> Boolean) = lock.withLock { + if (pos < 0 || pos > size) null else elements.subList(pos, size).find(predicate) + } + + // Returns the index of the next element whose position is >= pos matching the predicate, if + // necessary waiting until such a time that such an element is available, with a timeout. + // If no such element is found within the timeout -1 is returned. + private fun pollForIndexReadLocked(timeoutMs: Long, pos: Int, predicate: (E) -> Boolean): Int { + val deadline = System.currentTimeMillis() + timeoutMs + var index = pos + do { + while (index < elements.size) { + if (predicate(elements[index])) return index + ++index + } + } while (condition.await(deadline - System.currentTimeMillis())) + return -1 + } + + /** + * Returns a ReadHead over this ArrayTrackRecord. The returned ReadHead is tied to the + * current thread. + */ + fun newReadHead() = ReadHead() + + /** + * ReadHead is an object that helps users of ArrayTrackRecord keep track of how far + * it has read this far in the ArrayTrackRecord. A ReadHead is always associated with + * a single instance of ArrayTrackRecord. Multiple ReadHeads can be created and used + * on the same instance of ArrayTrackRecord concurrently, and the ArrayTrackRecord + * instance can also be used concurrently. ReadHead maintains the current index that is + * the next to be read, and calls this the "mark". + * + * In a ReadHead, {@link poll(Long, (E) -> Boolean)} works similarly to a LinkedBlockingQueue. + * It can be called repeatedly and will return the elements as they arrive. + * + * Intended usage looks something like this : + * val TrackRecord<MyObject> record = ArrayTrackRecord().newReadHead() + * Thread().start { + * // do stuff + * record.add(something) + * // do stuff + * } + * + * val obj1 = record.poll(timeout) + * // do something with obj1 + * val obj2 = record.poll(timeout) + * // do something with obj2 + * + * The point is that the caller does not have to track the mark like it would have to if + * it was using ArrayTrackRecord directly. + * + * Thread safety : + * A ReadHead delegates all TrackRecord methods to its associated ArrayTrackRecord, and + * inherits its thread-safe properties for all the TrackRecord methods. + * + * Poll() operates under its own set of rules that only allow execution on multiple threads + * within constrained boundaries, and never concurrently or pseudo-concurrently. This is + * because concurrent calls to poll() fundamentally do not make sense. poll() will move + * the mark according to what events remained to be read by this read head, and therefore + * if multiple threads were calling poll() concurrently on the same ReadHead, what + * happens to the mark and the return values could not be useful because there is no way to + * provide either a guarantee not to skip objects nor a guarantee about the mark position at + * the exit of poll(). This is even more true in the presence of a predicate to filter + * returned elements, because one thread might be filtering out the events the other is + * interested in. For this reason, this class will fail-fast if any concurrent access is + * detected with ConcurrentAccessException. + * It is possible to use poll() on different threads as long as the following can be + * guaranteed : one thread must call poll() for the last time, then execute a write barrier, + * then the other thread must execute a read barrier before calling poll() for the first time. + * This allows in particular to call poll in @Before and @After methods in JUnit unit tests, + * because JUnit will enforce those barriers by creating the testing thread after executing + * @Before and joining the thread after executing @After. + * + * peek() can be used by multiple threads concurrently, but only if no thread is calling + * poll() outside of the boundaries above. For simplicity, it can be considered that peek() + * is safe to call only when poll() is safe to call. + * + * Polling concurrently from the same ArrayTrackRecord is supported by creating multiple + * ReadHeads on the same instance of ArrayTrackRecord (or of course by using ArrayTrackRecord + * directly). Each ReadHead is then guaranteed to see all events always and + * guarantees are made on the value of the mark upon return. {@see poll(Long, (E) -> Boolean)} + * for details. Be careful to create each ReadHead on the thread it is meant to be used on, or + * to have a clear synchronization point between creation and use. + * + * Users of a ReadHead can ask for the current position of the mark at any time, on a thread + * where it's safe to call peek(). This mark can be used later to replay the history of events + * either on this ReadHead, on the associated ArrayTrackRecord or on another ReadHead + * associated with the same ArrayTrackRecord. It might look like this in the reader thread : + * + * val markAtStart = record.mark + * // Start processing interesting events + * while (val element = record.poll(timeout) { it.isInteresting() }) { + * // Do something with element + * } + * // Look for stuff that happened while searching for interesting events + * val firstElementReceived = record.getOrNull(markAtStart) + * val firstSpecialElement = record.getOrNull(markAtStart) { it.isSpecial() } + * // Get the first special element since markAtStart, possibly blocking until one is available + * val specialElement = record.poll(timeout, markAtStart) { it.isSpecial() } + */ + inner class ReadHead : TrackRecord<E> by this@ArrayTrackRecord { + // This lock only controls access to the readHead member below. The ArrayTrackRecord + // object has its own synchronization following different (and more usual) semantics. + // See the comment on the ReadHead class for details. + private val slock = StampedLock() + private var readHead = 0 + + // A special mark used to track the start of the last poll() operation. + private var pollMark = 0 + + /** + * @return the current value of the mark. + */ + var mark + get() = checkThread { readHead } + set(v: Int) = rewind(v) + fun rewind(v: Int) { + val stamp = slock.tryWriteLock() + if (0L == stamp) concurrentAccessDetected() + readHead = v + pollMark = v + slock.unlockWrite(stamp) + } + + private fun <T> checkThread(r: (Long) -> T): T { + // tryOptimisticRead is a read barrier, guarantees writes from other threads are visible + // after it + val stamp = slock.tryOptimisticRead() + val result = r(stamp) + // validate also performs a read barrier, guaranteeing that if validate returns true, + // then any change either happens-before tryOptimisticRead, or happens-after validate. + if (!slock.validate(stamp)) concurrentAccessDetected() + return result + } + + private fun concurrentAccessDetected(): Nothing { + throw ConcurrentModificationException( + "ReadHeads can't be used concurrently. Check your threading model.") + } + + /** + * Returns the first element after the mark, optionally blocking until one is available, or + * null if no such element can be found within the timeout. + * If a predicate is given, only elements matching the predicate are returned. + * + * Upon return the mark will be set to immediately after the returned element, or after + * the last element in the queue if null is returned. This means this method will always + * skip elements that do not match the predicate, even if it returns null. + * + * This method can only be used by the thread that created this ManagedRecordingQueue. + * If used on another thread, this throws IllegalStateException. + * + * @param timeoutMs how long, in milliseconds, to wait at most (best effort approximation). + * @param predicate an optional predicate to filter elements to be returned. + * @return an element matching the predicate, or null if timeout. + */ + fun poll(timeoutMs: Long, predicate: (E) -> Boolean = { true }): E? { + val stamp = slock.tryWriteLock() + if (0L == stamp) concurrentAccessDetected() + pollMark = readHead + try { + lock.withLock { + val index = pollForIndexReadLocked(timeoutMs, readHead, predicate) + readHead = if (index < 0) size else index + 1 + return getOrNull(index) + } + } finally { + slock.unlockWrite(stamp) + } + } + + /** + * Returns a list of events that were observed since the last time poll() was called on this + * ReadHead. + * + * @return list of events since poll() was called. + */ + fun backtrace(): List<E> { + val stamp = slock.tryReadLock() + if (0L == stamp) concurrentAccessDetected() + + try { + lock.withLock { + return ArrayList(subList(pollMark, mark)) + } + } finally { + slock.unlockRead(stamp) + } + } + + /** + * Returns the first element after the mark or null. This never blocks. + * + * This method is subject to threading restrictions. It can be used concurrently on + * multiple threads but not if any other thread might be executing poll() at the same + * time. See the class comment for details. + */ + fun peek(): E? = checkThread { getOrNull(readHead) } + } +} + +// Private helper +private fun Condition.await(timeoutMs: Long) = this.await(timeoutMs, TimeUnit.MILLISECONDS) diff --git a/staticlibs/testutils/hostdevice/com/android/testutils/Cleanup.kt b/staticlibs/testutils/hostdevice/com/android/testutils/Cleanup.kt new file mode 100644 index 0000000000000000000000000000000000000000..9f282347f545a9d20628f0d1c03805e0b183941d --- /dev/null +++ b/staticlibs/testutils/hostdevice/com/android/testutils/Cleanup.kt @@ -0,0 +1,125 @@ +/* + * 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. + */ + +@file:JvmName("Cleanup") + +package com.android.testutils + +import com.android.testutils.FunctionalUtils.ThrowingRunnable +import com.android.testutils.FunctionalUtils.ThrowingSupplier +import javax.annotation.CheckReturnValue + +/** + * Utility to do cleanup in tests without replacing exceptions with those from a finally block. + * + * This utility is meant for tests that want to do cleanup after they execute their test + * logic, whether the test fails (and throws) or not. + * + * The usual way of doing this is to have a try{}finally{} block and put cleanup in finally{}. + * However, if any code in finally{} throws, the exception thrown in finally{} is thrown before + * any thrown in try{} ; that means errors reported from tests are from finally{} even if they + * have been caused by errors in try{}. This is unhelpful in tests, because it results in a + * stacktrace for a symptom rather than a stacktrace for a cause. + * + * To alleviate this, tests are encouraged to make sure the code in finally{} can't throw, or + * that the code in try{} can't cause it to fail. This is not always realistic ; not only does + * it require the developer thinks about complex interactions of code, test code often relies + * on bricks provided by other teams, not controlled by the team writing the test, which may + * start throwing with an update (see b/198998862 for an example). + * + * This utility allows a different approach : it offers a new construct, tryTest{}cleanup{} similar + * to try{}finally{}, but that will always throw the first exception that happens. In other words, + * if only tryTest{} throws or only cleanup{} throws, that exception will be thrown, but contrary + * to the standard try{}finally{}, if both throws, the construct throws the exception that happened + * in tryTest{} rather than the one that happened in cleanup{}. + * + * Kotlin usage is as try{}finally{}, but with multiple finally{} blocks : + * tryTest { + * testing code + * } cleanupStep { + * cleanup code 1 + * } cleanupStep { + * cleanup code 2 + * } cleanup { + * cleanup code 3 + * } + * Catch blocks can be added with the following syntax : + * tryTest { + * testing code + * }.catch<ExceptionType> { it -> + * do something to it + * } + * + * Java doesn't allow this kind of syntax, so instead a function taking lambdas is provided. + * testAndCleanup(() -> { + * testing code + * }, () -> { + * cleanup code 1 + * }, () -> { + * cleanup code 2 + * }); + */ + +@CheckReturnValue +fun <T> tryTest(block: () -> T) = TryExpr( + try { + Result.success(block()) + } catch (e: Throwable) { + Result.failure(e) + }) + +// Some downstream branches have an older kotlin that doesn't know about value classes. +// TODO : Change this to "value class" when aosp no longer merges into such branches. +@Suppress("INLINE_CLASS_DEPRECATED") +inline class TryExpr<T>(val result: Result<T>) { + inline infix fun <reified E : Throwable> catch(block: (E) -> T): TryExpr<T> { + val originalException = result.exceptionOrNull() + if (originalException !is E) return this + return TryExpr(try { + Result.success(block(originalException)) + } catch (e: Throwable) { + Result.failure(e) + }) + } + + @CheckReturnValue + inline infix fun cleanupStep(block: () -> Unit): TryExpr<T> { + try { + block() + } catch (e: Throwable) { + val originalException = result.exceptionOrNull() + return TryExpr(if (null == originalException) { + Result.failure(e) + } else { + originalException.addSuppressed(e) + Result.failure(originalException) + }) + } + return this + } + + inline infix fun cleanup(block: () -> Unit): T = cleanupStep(block).result.getOrThrow() +} + +// Java support +fun <T> testAndCleanup(tryBlock: ThrowingSupplier<T>, vararg cleanupBlock: ThrowingRunnable): T { + return cleanupBlock.fold(tryTest { tryBlock.get() }) { previousExpr, nextCleanup -> + previousExpr.cleanupStep { nextCleanup.run() } + }.cleanup {} +} +fun testAndCleanup(tryBlock: ThrowingRunnable, vararg cleanupBlock: ThrowingRunnable) { + return testAndCleanup(ThrowingSupplier { tryBlock.run() }, *cleanupBlock) +} diff --git a/staticlibs/testutils/hostdevice/com/android/testutils/ConcurrentUtils.kt b/staticlibs/testutils/hostdevice/com/android/testutils/ConcurrentUtils.kt new file mode 100644 index 0000000000000000000000000000000000000000..af4f96dce438f13c66ee683fddf87f4b524bee0b --- /dev/null +++ b/staticlibs/testutils/hostdevice/com/android/testutils/ConcurrentUtils.kt @@ -0,0 +1,28 @@ +/* + * 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. + */ + +@file:JvmName("ConcurrentUtils") + +package com.android.testutils + +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import kotlin.system.measureTimeMillis + +// For Java usage +fun durationOf(fn: Runnable) = measureTimeMillis { fn.run() } + +fun CountDownLatch.await(timeoutMs: Long): Boolean = await(timeoutMs, TimeUnit.MILLISECONDS) diff --git a/staticlibs/testutils/hostdevice/com/android/testutils/ConnectivityModuleTest.kt b/staticlibs/testutils/hostdevice/com/android/testutils/ConnectivityModuleTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..ec485fea550cada924d7c4a8accca77ed251bacf --- /dev/null +++ b/staticlibs/testutils/hostdevice/com/android/testutils/ConnectivityModuleTest.kt @@ -0,0 +1,27 @@ +/* + * 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. + */ + +package com.android.testutils + +/** + * Indicates that the test covers functionality that was rolled out in a connectivity module update. + * + * Annotated MTS tests will typically only be run in Connectivity/Tethering module MTS, and not when + * only other modules (such as NetworkStack) have been updated. + * Annotated CTS tests will always be run, as the Connectivity module should be at least newer than + * the CTS suite. + */ +annotation class ConnectivityModuleTest diff --git a/staticlibs/testutils/hostdevice/com/android/testutils/DnsResolverModuleTest.kt b/staticlibs/testutils/hostdevice/com/android/testutils/DnsResolverModuleTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..9e97d51e31f5b50cdd15202b6812c5a0f63d3233 --- /dev/null +++ b/staticlibs/testutils/hostdevice/com/android/testutils/DnsResolverModuleTest.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.testutils + +/** + * Indicates that the test covers functionality that was rolled out in a resolv module update. + */ +annotation class DnsResolverModuleTest diff --git a/staticlibs/testutils/hostdevice/com/android/testutils/FileUtils.kt b/staticlibs/testutils/hostdevice/com/android/testutils/FileUtils.kt new file mode 100644 index 0000000000000000000000000000000000000000..678f9774fdeb20194034a893560a40e800d08f8f --- /dev/null +++ b/staticlibs/testutils/hostdevice/com/android/testutils/FileUtils.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.testutils + +// This function is private because the 2 is hardcoded here, and is not correct if not called +// directly from __LINE__ or __FILE__. +private fun callerStackTrace(): StackTraceElement = try { + throw RuntimeException() +} catch (e: RuntimeException) { + e.stackTrace[2] // 0 is here, 1 is get() in __FILE__ or __LINE__ +} +val __FILE__: String get() = callerStackTrace().fileName +val __LINE__: Int get() = callerStackTrace().lineNumber diff --git a/staticlibs/testutils/hostdevice/com/android/testutils/FunctionalUtils.java b/staticlibs/testutils/hostdevice/com/android/testutils/FunctionalUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..da36e4de2ba4c5078613446bb7c49cd84fe60c04 --- /dev/null +++ b/staticlibs/testutils/hostdevice/com/android/testutils/FunctionalUtils.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.testutils; + +import java.util.function.Supplier; + +/** + * A class grouping some utilities to deal with exceptions. + */ +public class FunctionalUtils { + /** + * Like a Consumer, but declared to throw an exception. + * @param <T> + */ + @FunctionalInterface + public interface ThrowingConsumer<T> { + /** @see java.util.function.Consumer */ + void accept(T t) throws Exception; + } + + /** + * Like a Supplier, but declared to throw an exception. + * @param <T> + */ + @FunctionalInterface + public interface ThrowingSupplier<T> { + /** @see java.util.function.Supplier */ + T get() throws Exception; + } + + /** + * Like a Runnable, but declared to throw an exception. + */ + @FunctionalInterface + public interface ThrowingRunnable { + /** @see java.lang.Runnable */ + void run() throws Exception; + } + + /** + * Convert a supplier that throws into one that doesn't. + * + * The returned supplier returns null in cases where the source throws. + */ + public static <T> Supplier<T> ignoreExceptions(ThrowingSupplier<T> func) { + return () -> { + try { + return func.get(); + } catch (Exception e) { + return null; + } + }; + } + + /** + * Convert a runnable that throws into one that doesn't. + * + * All exceptions are ignored by the returned Runnable. + */ + public static Runnable ignoreExceptions(ThrowingRunnable r) { + return () -> { + try { + r.run(); + } catch (Exception e) { + } + }; + } + + // Java has Function<T, R> and BiFunction<T, U, V> but nothing for higher-arity functions. + // Function3 is what Kotlin and Scala use (they also have higher-arity variants, with + // FunctionN taking N arguments, as the JVM does not have variadic formal parameters) + /** + * A function with three arguments. + * @param <TArg1> Type of the first argument + * @param <TArg2> Type of the second argument + * @param <TArg3> Type of the third argument + * @param <TResult> Type of the return value + */ + public interface Function3<TArg1, TArg2, TArg3, TResult> { + /** + * Apply the function to the arguments + */ + TResult apply(TArg1 a1, TArg2 a2, TArg3 a3); + } +} diff --git a/staticlibs/testutils/hostdevice/com/android/testutils/MiscAsserts.kt b/staticlibs/testutils/hostdevice/com/android/testutils/MiscAsserts.kt new file mode 100644 index 0000000000000000000000000000000000000000..1883387c62b07b6da0286ea4d7895dea089a13a1 --- /dev/null +++ b/staticlibs/testutils/hostdevice/com/android/testutils/MiscAsserts.kt @@ -0,0 +1,121 @@ +/* + * 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. + */ + +@file:JvmName("MiscAsserts") + +package com.android.testutils + +import com.android.testutils.FunctionalUtils.ThrowingRunnable +import java.lang.reflect.Modifier +import kotlin.system.measureTimeMillis +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +private const val TAG = "Connectivity unit test" + +fun <T> assertEmpty(ts: Array<T>) = ts.size.let { len -> + assertEquals(0, len, "Expected empty array, but length was $len") +} + +fun <T> assertEmpty(ts: Collection<T>) = ts.size.let { len -> + assertEquals(0, len, "Expected empty collection, but length was $len") +} + +fun <T> assertLength(expected: Int, got: Array<T>) = got.size.let { len -> + assertEquals(expected, len, "Expected array of length $expected, but was $len for $got") +} + +fun <T> assertLength(expected: Int, got: List<T>) = got.size.let { len -> + assertEquals(expected, len, "Expected list of length $expected, but was $len for $got") +} + +// Bridge method to help write this in Java. If you're writing Kotlin, consider using +// kotlin.test.assertFailsWith instead, as that method is reified and inlined. +fun <T : Exception> assertThrows(expected: Class<T>, block: ThrowingRunnable): T { + return assertFailsWith(expected.kotlin) { block.run() } +} + +fun <T : Exception> assertThrows(msg: String, expected: Class<T>, block: ThrowingRunnable): T { + return assertFailsWith(expected.kotlin, msg) { block.run() } +} + +fun <T> assertEqualBothWays(o1: T, o2: T) { + assertTrue(o1 == o2) + assertTrue(o2 == o1) +} + +fun <T> assertNotEqualEitherWay(o1: T, o2: T) { + assertFalse(o1 == o2) + assertFalse(o2 == o1) +} + +fun assertStringContains(got: String, want: String) { + assertTrue(got.contains(want), "$got did not contain \"${want}\"") +} + +fun assertContainsExactly(actual: IntArray, vararg expected: Int) { + // IntArray#sorted() returns a list, so it's fine to test with equals() + assertEquals(actual.sorted(), expected.sorted(), + "$actual does not contain exactly $expected") +} + +fun assertContainsStringsExactly(actual: Array<String>, vararg expected: String) { + assertEquals(actual.sorted(), expected.sorted(), + "$actual does not contain exactly $expected") +} + +fun <T> assertContainsAll(list: Collection<T>, vararg elems: T) { + assertContainsAll(list, elems.asList()) +} + +fun <T> assertContainsAll(list: Collection<T>, elems: Collection<T>) { + elems.forEach { assertTrue(list.contains(it), "$it not in list") } +} + +fun assertRunsInAtMost(descr: String, timeLimit: Long, fn: Runnable) { + assertRunsInAtMost(descr, timeLimit) { fn.run() } +} + +fun assertRunsInAtMost(descr: String, timeLimit: Long, fn: () -> Unit) { + val timeTaken = measureTimeMillis(fn) + val msg = String.format("%s: took %dms, limit was %dms", descr, timeTaken, timeLimit) + assertTrue(timeTaken <= timeLimit, msg) +} + +/** + * Verifies that the number of nonstatic fields in a java class equals a given count. + * Note: this is essentially not useful for Kotlin code where fields are not really a thing. + * + * This assertion serves as a reminder to update test code around it if fields are added + * after the test is written. + * @param count Expected number of nonstatic fields in the class. + * @param clazz Class to test. + */ +fun <T> assertFieldCountEquals(count: Int, clazz: Class<T>) { + assertEquals(count, clazz.declaredFields.filter { + !Modifier.isStatic(it.modifiers) && !Modifier.isTransient(it.modifiers) + }.size) +} + +fun <T> assertSameElements(expected: List<T>, actual: List<T>) { + val expectedSet: HashSet<T> = HashSet(expected) + assertEquals(expectedSet.size, expected.size, "expected list contains duplicates") + val actualSet: HashSet<T> = HashSet(actual) + assertEquals(actualSet.size, actual.size, "actual list contains duplicates") + assertEquals(expectedSet, actualSet) +} \ No newline at end of file diff --git a/staticlibs/testutils/hostdevice/com/android/testutils/NetworkStackModuleTest.kt b/staticlibs/testutils/hostdevice/com/android/testutils/NetworkStackModuleTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..fe312a093cbf9b3dfa0903fafdc351645d2b305e --- /dev/null +++ b/staticlibs/testutils/hostdevice/com/android/testutils/NetworkStackModuleTest.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.testutils + +/** + * Indicates that the test covers functionality that was rolled out in a NetworkStack module update. + */ +annotation class NetworkStackModuleTest diff --git a/staticlibs/testutils/hostdevice/com/android/testutils/PacketFilter.kt b/staticlibs/testutils/hostdevice/com/android/testutils/PacketFilter.kt new file mode 100644 index 0000000000000000000000000000000000000000..1bb6d683e25ac1e417158f9ba5aad042c8692103 --- /dev/null +++ b/staticlibs/testutils/hostdevice/com/android/testutils/PacketFilter.kt @@ -0,0 +1,148 @@ +/* + * 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. + */ + +package com.android.testutils + +import java.net.Inet4Address +import java.util.function.Predicate + +// Some of the below constants are duplicated with NetworkStackConstants, but this is a hostdevice +// library usable for host-side tests, so device-side utils are not usable, and there is no +// host-side non-test library to host common constants. +private const val ETHER_TYPE_OFFSET = 12 +private const val ETHER_HEADER_LENGTH = 14 +private const val IPV4_PROTOCOL_OFFSET = ETHER_HEADER_LENGTH + 9 +private const val IPV6_PROTOCOL_OFFSET = ETHER_HEADER_LENGTH + 6 +private const val IPV4_CHECKSUM_OFFSET = ETHER_HEADER_LENGTH + 10 +private const val IPV4_DST_OFFSET = ETHER_HEADER_LENGTH + 16 +private const val IPV4_HEADER_LENGTH = 20 +private const val IPV6_HEADER_LENGTH = 40 +private const val IPV4_PAYLOAD_OFFSET = ETHER_HEADER_LENGTH + IPV4_HEADER_LENGTH +private const val IPV6_PAYLOAD_OFFSET = ETHER_HEADER_LENGTH + IPV6_HEADER_LENGTH +private const val UDP_HEADER_LENGTH = 8 +private const val BOOTP_OFFSET = IPV4_PAYLOAD_OFFSET + UDP_HEADER_LENGTH +private const val BOOTP_TID_OFFSET = BOOTP_OFFSET + 4 +private const val BOOTP_CLIENT_MAC_OFFSET = BOOTP_OFFSET + 28 +private const val DHCP_OPTIONS_OFFSET = BOOTP_OFFSET + 240 +private const val ARP_OPCODE_OFFSET = ETHER_HEADER_LENGTH + 6 + +/** + * A [Predicate] that matches a [ByteArray] if it contains the specified [bytes] at the specified + * [offset]. + */ +class OffsetFilter(val offset: Int, vararg val bytes: Byte) : Predicate<ByteArray> { + override fun test(packet: ByteArray) = + bytes.withIndex().all { it.value == packet[offset + it.index] } +} + +private class UdpPortFilter( + private val udpOffset: Int, + private val src: Short?, + private val dst: Short? +) : Predicate<ByteArray> { + override fun test(t: ByteArray): Boolean { + if (src != null && !OffsetFilter(udpOffset, + src.toInt().ushr(8).toByte(), src.toByte()).test(t)) { + return false + } + + if (dst != null && !OffsetFilter(udpOffset + 2, + dst.toInt().ushr(8).toByte(), dst.toByte()).test(t)) { + return false + } + return true + } +} + +/** + * A [Predicate] that matches ethernet-encapped packets that contain an UDP over IPv4 datagram. + */ +class IPv4UdpFilter @JvmOverloads constructor( + srcPort: Short? = null, + dstPort: Short? = null +) : Predicate<ByteArray> { + private val impl = OffsetFilter(ETHER_TYPE_OFFSET, 0x08, 0x00 /* IPv4 */).and( + OffsetFilter(IPV4_PROTOCOL_OFFSET, 17 /* UDP */)).and( + UdpPortFilter(IPV4_PAYLOAD_OFFSET, srcPort, dstPort)) + override fun test(t: ByteArray) = impl.test(t) +} + +/** + * A [Predicate] that matches ethernet-encapped packets that contain an UDP over IPv6 datagram. + */ +class IPv6UdpFilter @JvmOverloads constructor( + srcPort: Short? = null, + dstPort: Short? = null +) : Predicate<ByteArray> { + private val impl = OffsetFilter(ETHER_TYPE_OFFSET, 0x86.toByte(), 0xdd.toByte() /* IPv6 */).and( + OffsetFilter(IPV6_PROTOCOL_OFFSET, 17 /* UDP */)).and( + UdpPortFilter(IPV6_PAYLOAD_OFFSET, srcPort, dstPort)) + override fun test(t: ByteArray) = impl.test(t) +} + +/** + * A [Predicate] that matches ethernet-encapped packets sent to the specified IPv4 destination. + */ +class IPv4DstFilter(dst: Inet4Address) : Predicate<ByteArray> { + private val impl = OffsetFilter(IPV4_DST_OFFSET, *dst.address) + override fun test(t: ByteArray) = impl.test(t) +} + +/** + * A [Predicate] that matches ethernet-encapped ARP requests. + */ +class ArpRequestFilter : Predicate<ByteArray> { + private val impl = OffsetFilter(ETHER_TYPE_OFFSET, 0x08, 0x06 /* ARP */) + .and(OffsetFilter(ARP_OPCODE_OFFSET, 0x00, 0x01 /* request */)) + override fun test(t: ByteArray) = impl.test(t) +} + +/** + * A [Predicate] that matches ethernet-encapped DHCP packets sent from a DHCP client. + */ +class DhcpClientPacketFilter : Predicate<ByteArray> { + private val impl = IPv4UdpFilter(srcPort = 68, dstPort = 67) + override fun test(t: ByteArray) = impl.test(t) +} + +/** + * A [Predicate] that matches a [ByteArray] if it contains a ethernet-encapped DHCP packet that + * contains the specified option with the specified [bytes] as value. + */ +class DhcpOptionFilter(val option: Byte, vararg val bytes: Byte) : Predicate<ByteArray> { + override fun test(packet: ByteArray): Boolean { + val option = findDhcpOption(packet, option) ?: return false + return option.contentEquals(bytes) + } +} + +/** + * Find a DHCP option in a packet and return its value, if found. + */ +fun findDhcpOption(packet: ByteArray, option: Byte): ByteArray? = + findOptionOffset(packet, option, DHCP_OPTIONS_OFFSET)?.let { + val optionLen = packet[it + 1] + return packet.copyOfRange(it + 2 /* type, length bytes */, it + 2 + optionLen) + } + +private tailrec fun findOptionOffset(packet: ByteArray, option: Byte, searchOffset: Int): Int? { + if (packet.size <= searchOffset + 2 /* type, length bytes */) return null + + return if (packet[searchOffset] == option) searchOffset else { + val optionLen = packet[searchOffset + 1] + findOptionOffset(packet, option, searchOffset + 2 + optionLen) + } +} diff --git a/staticlibs/testutils/hostdevice/com/android/testutils/SkipMainlinePresubmit.kt b/staticlibs/testutils/hostdevice/com/android/testutils/SkipMainlinePresubmit.kt new file mode 100644 index 0000000000000000000000000000000000000000..5952365104725f27390806328a5dc4888b344b36 --- /dev/null +++ b/staticlibs/testutils/hostdevice/com/android/testutils/SkipMainlinePresubmit.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.testutils + +/** + * Skip the test in presubmit runs for the reason specified in [reason]. + * + * This annotation is typically used to document limitations that prevent a test from being + * executed in presubmit on older builds. + */ +annotation class SkipMainlinePresubmit(val reason: String) diff --git a/staticlibs/testutils/hostdevice/com/android/testutils/SkipPresubmit.kt b/staticlibs/testutils/hostdevice/com/android/testutils/SkipPresubmit.kt new file mode 100644 index 0000000000000000000000000000000000000000..69ed048f5fb86538756f93c6ee284f579c9639dd --- /dev/null +++ b/staticlibs/testutils/hostdevice/com/android/testutils/SkipPresubmit.kt @@ -0,0 +1,24 @@ +/* + * 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. + */ + +package com.android.testutils + +/** + * Skip the test in presubmit runs for the reason specified in [reason]. + * + * This annotation is typically used to document hardware or test bench limitations. + */ +annotation class SkipPresubmit(val reason: String) \ No newline at end of file