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:
Motomu Utsumi
2023-09-11 11:48:36 +09:00
552 changed files with 63165 additions and 6 deletions

View File

@@ -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",

View File

@@ -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",

View File

@@ -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
View 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
View 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"
}
]
}

View 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"
]
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View 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"],
}

View 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>

View File

@@ -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"));
}
}

View 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() + "}";
}
}

View 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();
}
}

View 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();
}
}

View 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();
}

View 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;
}
}

View 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.
}

View 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;
}

View 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;
}

View File

@@ -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;
}
}

View 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;
}
}

View File

@@ -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());
}
}

View File

@@ -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;
}

View 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;
}

View 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);
}
}

View 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";
}
}

View File

@@ -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);
}
}

View 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);
}
}

View File

@@ -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);
}
}

View 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());
}
}
}
}

View File

@@ -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() {}
}

View 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;
}
}
}

View 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;
}

View 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);
}
}
}

View File

@@ -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() {}
}

View File

@@ -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;
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}

View File

@@ -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;
}
}

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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));
}
}

View File

@@ -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));
}
}

View File

@@ -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;
}
}

View File

@@ -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) { }
}

View File

@@ -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: &lt;linux_src&gt;/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) + "}"
+ "}";
}
}

View File

@@ -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: &lt;linux_src&gt;/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 &lt;linux_src&gt;/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 &lt;linux_src&gt;/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()) + "} "
+ "}";
}
}

View File

@@ -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);
}

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View File

@@ -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()) + "} "
+ "}";
}
}

View 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.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;
}
}
}

View File

@@ -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() {}
}

View File

@@ -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) + "} "
+ "}";
}
}

View File

@@ -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 + "} "
+ "}";
}
}

View File

@@ -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: &lt;linux_src&gt;/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()) + "} "
+ "}";
}
}

View File

@@ -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()) + "} "
+ "}";
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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 &lt;linux_src&gt;/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 + "}, "
+ "}";
}
}

View File

@@ -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 &lt;linux_src&gt;/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")
+ "}";
}
}

View File

@@ -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 &lt;linux_src&gt;/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) + "}"
+ "}";
}
}

View File

@@ -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: &lt;linux_src&gt;/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 + "} "
+ "}";
}
}

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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: &lt;linux_src&gt;/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 + "} "
+ "}";
}
}

View File

@@ -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 &lt;linux_src&gt;/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) + "} "
+ "}";
}
}

View File

@@ -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: &lt;linux_src&gt;/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) + "}, "
+ "}";
}
}

View File

@@ -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 &lt;linux_src&gt;/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()) + "} "
+ "}";
}
}

View File

@@ -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 &lt;linux_src&gt;/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 + "} "
+ "}";
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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));
}
}

View File

@@ -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));
}
}

View File

@@ -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));
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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));
}
}

View File

@@ -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));
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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() {}
}

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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());
}
}

View File

@@ -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