Merge remote-tracking branch 'remotes/aosp/tmp_libs_net_move' into libs_net_move_merge
frameworks/libs/net/common -> packages/modules/Connectivity/staticlibs frameworks/libs/net/client-libs -> packages/modules/Connectivity/staticlbs/client-libs Test: TH Bug: 296014682 Change-Id: I5dc78f0c4653e20312ab3d488b1e69262dbb9840
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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__",
|
||||
|
||||
434
staticlibs/Android.bp
Normal file
434
staticlibs/Android.bp
Normal file
@@ -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",
|
||||
],
|
||||
}
|
||||
28
staticlibs/TEST_MAPPING
Normal file
28
staticlibs/TEST_MAPPING
Normal file
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
26
staticlibs/client-libs/Android.bp
Normal file
26
staticlibs/client-libs/Android.bp
Normal file
@@ -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"
|
||||
]
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
44
staticlibs/client-libs/tests/unit/Android.bp
Normal file
44
staticlibs/client-libs/tests/unit/Android.bp
Normal file
@@ -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"],
|
||||
}
|
||||
28
staticlibs/client-libs/tests/unit/AndroidManifest.xml
Normal file
28
staticlibs/client-libs/tests/unit/AndroidManifest.xml
Normal file
@@ -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>
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
215
staticlibs/device/android/net/NetworkFactory.java
Normal file
215
staticlibs/device/android/net/NetworkFactory.java
Normal file
@@ -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() + "}";
|
||||
}
|
||||
}
|
||||
322
staticlibs/device/android/net/NetworkFactoryImpl.java
Normal file
322
staticlibs/device/android/net/NetworkFactoryImpl.java
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
397
staticlibs/device/android/net/NetworkFactoryLegacyImpl.java
Normal file
397
staticlibs/device/android/net/NetworkFactoryLegacyImpl.java
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
71
staticlibs/device/android/net/NetworkFactoryShim.java
Normal file
71
staticlibs/device/android/net/NetworkFactoryShim.java
Normal file
@@ -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();
|
||||
}
|
||||
126
staticlibs/device/com/android/net/module/util/BpfBitmap.java
Normal file
126
staticlibs/device/com/android/net/module/util/BpfBitmap.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
136
staticlibs/device/com/android/net/module/util/BpfDump.java
Normal file
136
staticlibs/device/com/android/net/module/util/BpfDump.java
Normal file
@@ -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.
|
||||
}
|
||||
312
staticlibs/device/com/android/net/module/util/BpfMap.java
Normal file
312
staticlibs/device/com/android/net/module/util/BpfMap.java
Normal file
@@ -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;
|
||||
}
|
||||
69
staticlibs/device/com/android/net/module/util/BpfUtils.java
Normal file
69
staticlibs/device/com/android/net/module/util/BpfUtils.java
Normal file
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
143
staticlibs/device/com/android/net/module/util/DomainUtils.java
Normal file
143
staticlibs/device/com/android/net/module/util/DomainUtils.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
82
staticlibs/device/com/android/net/module/util/IBpfMap.java
Normal file
82
staticlibs/device/com/android/net/module/util/IBpfMap.java
Normal file
@@ -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;
|
||||
}
|
||||
187
staticlibs/device/com/android/net/module/util/Ipv6Utils.java
Normal file
187
staticlibs/device/com/android/net/module/util/Ipv6Utils.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
35
staticlibs/device/com/android/net/module/util/JniUtil.java
Normal file
35
staticlibs/device/com/android/net/module/util/JniUtil.java
Normal file
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
302
staticlibs/device/com/android/net/module/util/PacketBuilder.java
Normal file
302
staticlibs/device/com/android/net/module/util/PacketBuilder.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
286
staticlibs/device/com/android/net/module/util/SharedLog.java
Normal file
286
staticlibs/device/com/android/net/module/util/SharedLog.java
Normal file
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() {}
|
||||
}
|
||||
774
staticlibs/device/com/android/net/module/util/Struct.java
Normal file
774
staticlibs/device/com/android/net/module/util/Struct.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
104
staticlibs/device/com/android/net/module/util/TcUtils.java
Normal file
104
staticlibs/device/com/android/net/module/util/TcUtils.java
Normal file
@@ -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;
|
||||
}
|
||||
171
staticlibs/device/com/android/net/module/util/arp/ArpPacket.java
Normal file
171
staticlibs/device/com/android/net/module/util/arp/ArpPacket.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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) { }
|
||||
}
|
||||
@@ -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) + "}"
|
||||
+ "}";
|
||||
}
|
||||
}
|
||||
@@ -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()) + "} "
|
||||
+ "}";
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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()) + "} "
|
||||
+ "}";
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -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) + "} "
|
||||
+ "}";
|
||||
}
|
||||
}
|
||||
@@ -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 + "} "
|
||||
+ "}";
|
||||
}
|
||||
}
|
||||
@@ -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()) + "} "
|
||||
+ "}";
|
||||
}
|
||||
}
|
||||
@@ -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()) + "} "
|
||||
+ "}";
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 + "}, "
|
||||
+ "}";
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
+ "}";
|
||||
}
|
||||
}
|
||||
@@ -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) + "}"
|
||||
+ "}";
|
||||
}
|
||||
}
|
||||
@@ -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 + "} "
|
||||
+ "}";
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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 + "} "
|
||||
+ "}";
|
||||
}
|
||||
}
|
||||
@@ -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) + "} "
|
||||
+ "}";
|
||||
}
|
||||
}
|
||||
@@ -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) + "}, "
|
||||
+ "}";
|
||||
}
|
||||
}
|
||||
@@ -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()) + "} "
|
||||
+ "}";
|
||||
}
|
||||
}
|
||||
@@ -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 + "} "
|
||||
+ "}";
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user