Vendor AIDL interface for port blocking via eBPF
New Connectivity Service exposed to vendor for restricting certain ports for use only in vendor. Bug: 179733303 Change-Id: Iad9aff6924498ede5a08cfa5482082f094c0a90b
This commit is contained in:
@@ -69,9 +69,10 @@ apex {
|
||||
],
|
||||
canned_fs_config: "canned_fs_config",
|
||||
bpfs: [
|
||||
"block.o",
|
||||
"clatd.o_mainline",
|
||||
"netd.o_mainline",
|
||||
"dscp_policy.o",
|
||||
"netd.o_mainline",
|
||||
"offload.o",
|
||||
"test.o",
|
||||
],
|
||||
|
||||
@@ -47,6 +47,7 @@ cc_library_headers {
|
||||
"//packages/modules/Connectivity/service/native/libs/libclat",
|
||||
"//packages/modules/Connectivity/Tethering",
|
||||
"//packages/modules/Connectivity/service/native",
|
||||
"//packages/modules/Connectivity/tests/native",
|
||||
"//packages/modules/Connectivity/service-t/native/libs/libnetworkstats",
|
||||
"//packages/modules/Connectivity/tests/unit/jni",
|
||||
"//system/netd/server",
|
||||
@@ -57,6 +58,16 @@ cc_library_headers {
|
||||
//
|
||||
// bpf kernel programs
|
||||
//
|
||||
bpf {
|
||||
name: "block.o",
|
||||
srcs: ["block.c"],
|
||||
cflags: [
|
||||
"-Wall",
|
||||
"-Werror",
|
||||
],
|
||||
sub_dir: "net_shared",
|
||||
}
|
||||
|
||||
bpf {
|
||||
name: "dscp_policy.o",
|
||||
srcs: ["dscp_policy.c"],
|
||||
|
||||
70
bpf_progs/block.c
Normal file
70
bpf_progs/block.c
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/bpf.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "bpf_helpers.h"
|
||||
|
||||
#define ALLOW 1
|
||||
#define DISALLOW 0
|
||||
|
||||
DEFINE_BPF_MAP_GRW(blocked_ports_map, ARRAY, int, uint64_t,
|
||||
1024 /* 64K ports -> 1024 u64s */, AID_SYSTEM)
|
||||
|
||||
static inline __always_inline int block_port(struct bpf_sock_addr *ctx) {
|
||||
if (!ctx->user_port) return ALLOW;
|
||||
|
||||
switch (ctx->protocol) {
|
||||
case IPPROTO_TCP:
|
||||
case IPPROTO_MPTCP:
|
||||
case IPPROTO_UDP:
|
||||
case IPPROTO_UDPLITE:
|
||||
case IPPROTO_DCCP:
|
||||
case IPPROTO_SCTP:
|
||||
break;
|
||||
default:
|
||||
return ALLOW; // unknown protocols are allowed
|
||||
}
|
||||
|
||||
int key = ctx->user_port >> 6;
|
||||
int shift = ctx->user_port & 63;
|
||||
|
||||
uint64_t *val = bpf_blocked_ports_map_lookup_elem(&key);
|
||||
// Lookup should never fail in reality, but if it does return here to keep the
|
||||
// BPF verifier happy.
|
||||
if (!val) return ALLOW;
|
||||
|
||||
if ((*val >> shift) & 1) return DISALLOW;
|
||||
return ALLOW;
|
||||
}
|
||||
|
||||
DEFINE_BPF_PROG_KVER("bind4/block_port", AID_ROOT, AID_SYSTEM,
|
||||
bind4_block_port, KVER(5, 4, 0))
|
||||
(struct bpf_sock_addr *ctx) {
|
||||
return block_port(ctx);
|
||||
}
|
||||
|
||||
DEFINE_BPF_PROG_KVER("bind6/block_port", AID_ROOT, AID_SYSTEM,
|
||||
bind6_block_port, KVER(5, 4, 0))
|
||||
(struct bpf_sock_addr *ctx) {
|
||||
return block_port(ctx);
|
||||
}
|
||||
|
||||
LICENSE("Apache 2.0");
|
||||
CRITICAL("ConnectivityNative");
|
||||
@@ -21,6 +21,7 @@ import android.util.Log;
|
||||
|
||||
import com.android.modules.utils.build.SdkLevel;
|
||||
import com.android.networkstack.apishim.ConstantsShim;
|
||||
import com.android.server.connectivity.ConnectivityNativeService;
|
||||
import com.android.server.ethernet.EthernetService;
|
||||
import com.android.server.ethernet.EthernetServiceImpl;
|
||||
import com.android.server.nearby.NearbyService;
|
||||
@@ -31,6 +32,7 @@ import com.android.server.nearby.NearbyService;
|
||||
*/
|
||||
public final class ConnectivityServiceInitializer extends SystemService {
|
||||
private static final String TAG = ConnectivityServiceInitializer.class.getSimpleName();
|
||||
private final ConnectivityNativeService mConnectivityNative;
|
||||
private final ConnectivityService mConnectivity;
|
||||
private final IpSecService mIpSecService;
|
||||
private final NsdService mNsdService;
|
||||
@@ -44,6 +46,7 @@ public final class ConnectivityServiceInitializer extends SystemService {
|
||||
mEthernetServiceImpl = createEthernetService(context);
|
||||
mConnectivity = new ConnectivityService(context);
|
||||
mIpSecService = createIpSecService(context);
|
||||
mConnectivityNative = createConnectivityNativeService(context);
|
||||
mNsdService = createNsdService(context);
|
||||
mNearbyService = createNearbyService(context);
|
||||
}
|
||||
@@ -65,6 +68,12 @@ public final class ConnectivityServiceInitializer extends SystemService {
|
||||
publishBinderService(Context.IPSEC_SERVICE, mIpSecService, /* allowIsolated= */ false);
|
||||
}
|
||||
|
||||
if (mConnectivityNative != null) {
|
||||
Log.i(TAG, "Registering " + ConnectivityNativeService.SERVICE_NAME);
|
||||
publishBinderService(ConnectivityNativeService.SERVICE_NAME, mConnectivityNative,
|
||||
/* allowIsolated= */ false);
|
||||
}
|
||||
|
||||
if (mNsdService != null) {
|
||||
Log.i(TAG, "Registering " + Context.NSD_SERVICE);
|
||||
publishBinderService(Context.NSD_SERVICE, mNsdService, /* allowIsolated= */ false);
|
||||
@@ -98,6 +107,19 @@ public final class ConnectivityServiceInitializer extends SystemService {
|
||||
return new IpSecService(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return ConnectivityNativeService instance, or null if current SDK is lower than T.
|
||||
*/
|
||||
private ConnectivityNativeService createConnectivityNativeService(final Context context) {
|
||||
if (!SdkLevel.isAtLeastT()) return null;
|
||||
try {
|
||||
return new ConnectivityNativeService(context);
|
||||
} catch (UnsupportedOperationException e) {
|
||||
Log.d(TAG, "Unable to get ConnectivityNative service", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Return NsdService instance or null if current SDK is lower than T */
|
||||
private NsdService createNsdService(final Context context) {
|
||||
if (!SdkLevel.isAtLeastT()) return null;
|
||||
|
||||
@@ -19,6 +19,54 @@ package {
|
||||
default_applicable_licenses: ["Android-Apache-2.0"],
|
||||
}
|
||||
|
||||
aidl_interface {
|
||||
name: "connectivity_native_aidl_interface",
|
||||
local_include_dir: "binder",
|
||||
vendor_available: true,
|
||||
srcs: [
|
||||
"binder/android/net/connectivity/aidl/*.aidl",
|
||||
],
|
||||
backend: {
|
||||
java: {
|
||||
apex_available: [
|
||||
"com.android.tethering",
|
||||
],
|
||||
min_sdk_version: "30",
|
||||
},
|
||||
ndk: {
|
||||
apex_available: [
|
||||
"com.android.tethering",
|
||||
],
|
||||
min_sdk_version: "30",
|
||||
},
|
||||
},
|
||||
versions: ["1"],
|
||||
|
||||
}
|
||||
|
||||
cc_library_static {
|
||||
name: "connectivity_native_aidl_interface-lateststable-ndk",
|
||||
min_sdk_version: "30",
|
||||
whole_static_libs: [
|
||||
"connectivity_native_aidl_interface-V1-ndk",
|
||||
],
|
||||
apex_available: [
|
||||
"com.android.tethering",
|
||||
],
|
||||
}
|
||||
|
||||
java_library {
|
||||
name: "connectivity_native_aidl_interface-lateststable-java",
|
||||
sdk_version: "system_current",
|
||||
min_sdk_version: "30",
|
||||
static_libs: [
|
||||
"connectivity_native_aidl_interface-V1-java",
|
||||
],
|
||||
apex_available: [
|
||||
"com.android.tethering",
|
||||
],
|
||||
}
|
||||
|
||||
// The library name match the service-connectivity jarjar rules that put the JNI utils in the
|
||||
// android.net.connectivity.com.android.net.module.util package.
|
||||
cc_library_shared {
|
||||
@@ -35,6 +83,7 @@ cc_library_shared {
|
||||
],
|
||||
static_libs: [
|
||||
"libnet_utils_device_common_bpfjni",
|
||||
"libnet_utils_device_common_bpfutils",
|
||||
],
|
||||
shared_libs: [
|
||||
"liblog",
|
||||
@@ -109,6 +158,7 @@ java_library {
|
||||
static_libs: [
|
||||
// Do not add libs here if they are already included
|
||||
// in framework-connectivity
|
||||
"connectivity_native_aidl_interface-lateststable-java",
|
||||
"dnsresolver_aidl_interface-V9-java",
|
||||
"modules-utils-shell-command-handler",
|
||||
"net-utils-device-common",
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
037b467eb02b172a3161e11bbc3dd691aebb5fce
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2022, The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
|
||||
// two cases:
|
||||
// 1). this is a frozen version file - do not edit this in any case.
|
||||
// 2). this is a 'current' file. If you make a backwards compatible change to
|
||||
// the interface (from the latest frozen version), the build system will
|
||||
// prompt you to update this file with `m <name>-update-api`.
|
||||
//
|
||||
// You must not make a backward incompatible change to any AIDL file built
|
||||
// with the aidl_interface module type with versions property set. The module
|
||||
// type is used to build AIDL files in a way that they can be used across
|
||||
// independently updatable components of the system. If a device is shipped
|
||||
// with such a backward incompatible change, it has a high risk of breaking
|
||||
// later when a module using the interface is updated, e.g., Mainline modules.
|
||||
|
||||
package android.net.connectivity.aidl;
|
||||
interface ConnectivityNative {
|
||||
void blockPortForBind(in int port);
|
||||
void unblockPortForBind(in int port);
|
||||
void unblockAllPortsForBind();
|
||||
int[] getPortsBlockedForBind();
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2022, The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
|
||||
// two cases:
|
||||
// 1). this is a frozen version file - do not edit this in any case.
|
||||
// 2). this is a 'current' file. If you make a backwards compatible change to
|
||||
// the interface (from the latest frozen version), the build system will
|
||||
// prompt you to update this file with `m <name>-update-api`.
|
||||
//
|
||||
// You must not make a backward incompatible change to any AIDL file built
|
||||
// with the aidl_interface module type with versions property set. The module
|
||||
// type is used to build AIDL files in a way that they can be used across
|
||||
// independently updatable components of the system. If a device is shipped
|
||||
// with such a backward incompatible change, it has a high risk of breaking
|
||||
// later when a module using the interface is updated, e.g., Mainline modules.
|
||||
|
||||
package android.net.connectivity.aidl;
|
||||
interface ConnectivityNative {
|
||||
void blockPortForBind(in int port);
|
||||
void unblockPortForBind(in int port);
|
||||
void unblockAllPortsForBind();
|
||||
int[] getPortsBlockedForBind();
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright (c) 2022, The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.net.connectivity.aidl;
|
||||
|
||||
interface ConnectivityNative {
|
||||
/**
|
||||
* Blocks a port from being assigned during bind(). The caller is responsible for updating
|
||||
* /proc/sys/net/ipv4/ip_local_port_range with the port being blocked so that calls to connect()
|
||||
* will not automatically assign one of the blocked ports.
|
||||
* Will return success even if port was already blocked.
|
||||
*
|
||||
* @param port Int corresponding to port number.
|
||||
*
|
||||
* @throws IllegalArgumentException if the port is invalid.
|
||||
* @throws SecurityException if the UID of the client doesn't have network stack permission.
|
||||
* @throws ServiceSpecificException in case of failure, with an error code corresponding to the
|
||||
* unix errno.
|
||||
*/
|
||||
void blockPortForBind(in int port);
|
||||
|
||||
/**
|
||||
* Unblocks a port that has previously been blocked.
|
||||
* Will return success even if port was already unblocked.
|
||||
*
|
||||
* @param port Int corresponding to port number.
|
||||
*
|
||||
* @throws IllegalArgumentException if the port is invalid.
|
||||
* @throws SecurityException if the UID of the client doesn't have network stack permission.
|
||||
* @throws ServiceSpecificException in case of failure, with an error code corresponding to the
|
||||
* unix errno.
|
||||
*/
|
||||
void unblockPortForBind(in int port);
|
||||
|
||||
/**
|
||||
* Unblocks all ports that have previously been blocked.
|
||||
*/
|
||||
void unblockAllPortsForBind();
|
||||
|
||||
/**
|
||||
* Gets the list of ports that have been blocked.
|
||||
*
|
||||
* @return List of blocked ports.
|
||||
*/
|
||||
int[] getPortsBlockedForBind();
|
||||
}
|
||||
@@ -21,6 +21,7 @@ namespace android {
|
||||
|
||||
int register_com_android_net_module_util_BpfMap(JNIEnv* env, char const* class_name);
|
||||
int register_com_android_net_module_util_TcUtils(JNIEnv* env, char const* class_name);
|
||||
int register_com_android_net_module_util_BpfUtils(JNIEnv* env, char const* class_name);
|
||||
|
||||
extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
|
||||
JNIEnv *env;
|
||||
@@ -35,6 +36,9 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
|
||||
if (register_com_android_net_module_util_TcUtils(env,
|
||||
"android/net/connectivity/com/android/net/module/util/TcUtils") < 0) return JNI_ERR;
|
||||
|
||||
if (register_com_android_net_module_util_BpfUtils(env,
|
||||
"android/net/connectivity/com/android/net/module/util/BpfUtils") < 0) return JNI_ERR;
|
||||
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* 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.server.connectivity;
|
||||
|
||||
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET4_BIND;
|
||||
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET6_BIND;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.net.connectivity.aidl.ConnectivityNative;
|
||||
import android.os.Binder;
|
||||
import android.os.Process;
|
||||
import android.os.ServiceSpecificException;
|
||||
import android.system.ErrnoException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.net.module.util.BpfBitmap;
|
||||
import com.android.net.module.util.BpfUtils;
|
||||
import com.android.net.module.util.CollectionUtils;
|
||||
import com.android.net.module.util.PermissionUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public class ConnectivityNativeService extends ConnectivityNative.Stub {
|
||||
public static final String SERVICE_NAME = "connectivity_native";
|
||||
|
||||
private static final String TAG = ConnectivityNativeService.class.getSimpleName();
|
||||
private static final String CGROUP_PATH = "/sys/fs/cgroup";
|
||||
private static final String V4_PROG_PATH =
|
||||
"/sys/fs/bpf/prog_block_bind4_block_port";
|
||||
private static final String V6_PROG_PATH =
|
||||
"/sys/fs/bpf/prog_block_bind6_block_port";
|
||||
private static final String BLOCKED_PORTS_MAP_PATH = "/sys/fs/bpf/map_block_blocked_ports_map";
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
// BPF map for port blocking. Exactly 65536 entries long, with one entry per port number
|
||||
@Nullable
|
||||
private final BpfBitmap mBpfBlockedPortsMap;
|
||||
|
||||
/**
|
||||
* Dependencies of ConnectivityNativeService, for injection in tests.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static class Dependencies {
|
||||
/** Get BPF maps. */
|
||||
@Nullable public BpfBitmap getBlockPortsMap() {
|
||||
try {
|
||||
return new BpfBitmap(BLOCKED_PORTS_MAP_PATH);
|
||||
} catch (ErrnoException e) {
|
||||
throw new UnsupportedOperationException("Failed to create blocked ports map: "
|
||||
+ e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void enforceBlockPortPermission() {
|
||||
final int uid = Binder.getCallingUid();
|
||||
if (uid == Process.ROOT_UID || uid == Process.PHONE_UID) return;
|
||||
PermissionUtils.enforceNetworkStackPermission(mContext);
|
||||
}
|
||||
|
||||
private void ensureValidPortNumber(int port) {
|
||||
if (port < 0 || port > 65535) {
|
||||
throw new IllegalArgumentException("Invalid port number " + port);
|
||||
}
|
||||
}
|
||||
|
||||
public ConnectivityNativeService(final Context context) {
|
||||
this(context, new Dependencies());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected ConnectivityNativeService(final Context context, @NonNull Dependencies deps) {
|
||||
mContext = context;
|
||||
mBpfBlockedPortsMap = deps.getBlockPortsMap();
|
||||
attachProgram();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void blockPortForBind(int port) {
|
||||
enforceBlockPortPermission();
|
||||
ensureValidPortNumber(port);
|
||||
try {
|
||||
mBpfBlockedPortsMap.set(port);
|
||||
} catch (ErrnoException e) {
|
||||
throw new ServiceSpecificException(e.errno, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unblockPortForBind(int port) {
|
||||
enforceBlockPortPermission();
|
||||
ensureValidPortNumber(port);
|
||||
try {
|
||||
mBpfBlockedPortsMap.unset(port);
|
||||
} catch (ErrnoException e) {
|
||||
throw new ServiceSpecificException(e.errno,
|
||||
"Could not unset bitmap value for (port: " + port + "): " + e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unblockAllPortsForBind() {
|
||||
enforceBlockPortPermission();
|
||||
try {
|
||||
mBpfBlockedPortsMap.clear();
|
||||
} catch (ErrnoException e) {
|
||||
throw new ServiceSpecificException(e.errno, "Could not clear map: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getPortsBlockedForBind() {
|
||||
enforceBlockPortPermission();
|
||||
|
||||
ArrayList<Integer> portMap = new ArrayList<Integer>();
|
||||
for (int i = 0; i <= 65535; i++) {
|
||||
try {
|
||||
if (mBpfBlockedPortsMap.get(i)) portMap.add(i);
|
||||
} catch (ErrnoException e) {
|
||||
Log.e(TAG, "Failed to get index " + i, e);
|
||||
}
|
||||
}
|
||||
return CollectionUtils.toIntArray(portMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInterfaceVersion() {
|
||||
return this.VERSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getInterfaceHash() {
|
||||
return this.HASH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach BPF program
|
||||
*/
|
||||
private void attachProgram() {
|
||||
try {
|
||||
BpfUtils.attachProgram(BPF_CGROUP_INET4_BIND, V4_PROG_PATH, CGROUP_PATH, 0);
|
||||
} catch (IOException e) {
|
||||
throw new UnsupportedOperationException("Unable to attach to BPF_CGROUP_INET4_BIND: "
|
||||
+ e);
|
||||
}
|
||||
try {
|
||||
BpfUtils.attachProgram(BPF_CGROUP_INET6_BIND, V6_PROG_PATH, CGROUP_PATH, 0);
|
||||
} catch (IOException e) {
|
||||
throw new UnsupportedOperationException("Unable to attach to BPF_CGROUP_INET6_BIND: "
|
||||
+ e);
|
||||
}
|
||||
Log.d(TAG, "Attached BPF_CGROUP_INET4_BIND and BPF_CGROUP_INET6_BIND programs");
|
||||
}
|
||||
}
|
||||
30
tests/native/Android.bp
Normal file
30
tests/native/Android.bp
Normal file
@@ -0,0 +1,30 @@
|
||||
cc_test {
|
||||
name: "connectivity_native_test",
|
||||
test_suites: [
|
||||
"general-tests",
|
||||
"mts-tethering",
|
||||
"vts",
|
||||
],
|
||||
min_sdk_version: "31",
|
||||
require_root: true,
|
||||
tidy: false,
|
||||
srcs: [
|
||||
"connectivity_native_test.cpp",
|
||||
],
|
||||
header_libs: ["bpf_connectivity_headers"],
|
||||
shared_libs: [
|
||||
"libbase",
|
||||
"libbinder_ndk",
|
||||
"liblog",
|
||||
"libnetutils",
|
||||
"libprocessgroup",
|
||||
],
|
||||
static_libs: [
|
||||
"connectivity_native_aidl_interface-lateststable-ndk",
|
||||
"libcutils",
|
||||
"libmodules-utils-build",
|
||||
"libutils",
|
||||
],
|
||||
compile_multilib: "first",
|
||||
defaults: ["connectivity-mainline-presubmit-cc-defaults"],
|
||||
}
|
||||
281
tests/native/connectivity_native_test.cpp
Normal file
281
tests/native/connectivity_native_test.cpp
Normal file
@@ -0,0 +1,281 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <android-modules-utils/sdk_level.h>
|
||||
#include <cutils/misc.h> // FIRST_APPLICATION_UID
|
||||
#include <gtest/gtest.h>
|
||||
#include <netinet/in.h>
|
||||
#include <android/binder_manager.h>
|
||||
#include <android/binder_process.h>
|
||||
|
||||
#include <aidl/android/net/connectivity/aidl/ConnectivityNative.h>
|
||||
|
||||
using aidl::android::net::connectivity::aidl::IConnectivityNative;
|
||||
|
||||
class ConnectivityNativeBinderTest : public ::testing::Test {
|
||||
public:
|
||||
std::vector<int32_t> mActualBlockedPorts;
|
||||
|
||||
ConnectivityNativeBinderTest() {
|
||||
AIBinder* binder = AServiceManager_getService("connectivity_native");
|
||||
ndk::SpAIBinder sBinder = ndk::SpAIBinder(binder);
|
||||
mService = aidl::android::net::connectivity::aidl::IConnectivityNative::fromBinder(sBinder);
|
||||
}
|
||||
|
||||
void SetUp() override {
|
||||
// Skip test case if not on T.
|
||||
if (!android::modules::sdklevel::IsAtLeastT()) GTEST_SKIP() <<
|
||||
"Should be at least T device.";
|
||||
|
||||
ASSERT_NE(nullptr, mService.get());
|
||||
|
||||
// If there are already ports being blocked on device unblockAllPortsForBind() store
|
||||
// the currently blocked ports and add them back at the end of the test. Do this for
|
||||
// every test case so additional test cases do not forget to add ports back.
|
||||
ndk::ScopedAStatus status = mService->getPortsBlockedForBind(&mActualBlockedPorts);
|
||||
EXPECT_TRUE(status.isOk()) << status.getDescription ();
|
||||
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
ndk::ScopedAStatus status;
|
||||
if (mActualBlockedPorts.size() > 0) {
|
||||
for (int i : mActualBlockedPorts) {
|
||||
mService->blockPortForBind(i);
|
||||
EXPECT_TRUE(status.isOk()) << status.getDescription ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
std::shared_ptr<IConnectivityNative> mService;
|
||||
|
||||
void runSocketTest (sa_family_t family, const int type, bool blockPort) {
|
||||
ndk::ScopedAStatus status;
|
||||
in_port_t port = 0;
|
||||
int sock, sock2;
|
||||
// Open two sockets with SO_REUSEADDR and expect they can both bind to port.
|
||||
sock = openSocket(&port, family, type, false /* expectBindFail */);
|
||||
sock2 = openSocket(&port, family, type, false /* expectBindFail */);
|
||||
|
||||
int blockedPort = 0;
|
||||
if (blockPort) {
|
||||
blockedPort = ntohs(port);
|
||||
status = mService->blockPortForBind(blockedPort);
|
||||
EXPECT_TRUE(status.isOk()) << status.getDescription ();
|
||||
}
|
||||
|
||||
int sock3 = openSocket(&port, family, type, blockPort /* expectBindFail */);
|
||||
|
||||
if (blockPort) {
|
||||
EXPECT_EQ(-1, sock3);
|
||||
status = mService->unblockPortForBind(blockedPort);
|
||||
EXPECT_TRUE(status.isOk()) << status.getDescription ();
|
||||
} else {
|
||||
EXPECT_NE(-1, sock3);
|
||||
}
|
||||
|
||||
close(sock);
|
||||
close(sock2);
|
||||
close(sock3);
|
||||
}
|
||||
|
||||
/*
|
||||
* Open the socket and update the port.
|
||||
*/
|
||||
int openSocket(in_port_t* port, sa_family_t family, const int type, bool expectBindFail) {
|
||||
int ret = 0;
|
||||
int enable = 1;
|
||||
const int sock = socket(family, type, 0);
|
||||
ret = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
|
||||
EXPECT_EQ(0, ret);
|
||||
|
||||
if (family == AF_INET) {
|
||||
struct sockaddr_in addr4 = { .sin_family = family, .sin_port = htons(*port) };
|
||||
ret = bind(sock, (struct sockaddr*) &addr4, sizeof(addr4));
|
||||
} else {
|
||||
struct sockaddr_in6 addr6 = { .sin6_family = family, .sin6_port = htons(*port) };
|
||||
ret = bind(sock, (struct sockaddr*) &addr6, sizeof(addr6));
|
||||
}
|
||||
|
||||
if (expectBindFail) {
|
||||
EXPECT_NE(0, ret);
|
||||
// If port is blocked, return here since the port is not needed
|
||||
// for subsequent sockets.
|
||||
close(sock);
|
||||
return -1;
|
||||
}
|
||||
EXPECT_EQ(0, ret) << "bind unexpectedly failed, errno: " << errno;
|
||||
|
||||
if (family == AF_INET) {
|
||||
struct sockaddr_in sin;
|
||||
socklen_t len = sizeof(sin);
|
||||
EXPECT_NE(-1, getsockname(sock, (struct sockaddr *)&sin, &len));
|
||||
EXPECT_NE(0, ntohs(sin.sin_port));
|
||||
if (*port != 0) EXPECT_EQ(*port, ntohs(sin.sin_port));
|
||||
*port = ntohs(sin.sin_port);
|
||||
} else {
|
||||
struct sockaddr_in6 sin;
|
||||
socklen_t len = sizeof(sin);
|
||||
EXPECT_NE(-1, getsockname(sock, (struct sockaddr *)&sin, &len));
|
||||
EXPECT_NE(0, ntohs(sin.sin6_port));
|
||||
if (*port != 0) EXPECT_EQ(*port, ntohs(sin.sin6_port));
|
||||
*port = ntohs(sin.sin6_port);
|
||||
}
|
||||
return sock;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ConnectivityNativeBinderTest, PortUnblockedV4Udp) {
|
||||
runSocketTest(AF_INET, SOCK_DGRAM, false);
|
||||
}
|
||||
|
||||
TEST_F(ConnectivityNativeBinderTest, PortUnblockedV4Tcp) {
|
||||
runSocketTest(AF_INET, SOCK_STREAM, false);
|
||||
}
|
||||
|
||||
TEST_F(ConnectivityNativeBinderTest, PortUnblockedV6Udp) {
|
||||
runSocketTest(AF_INET6, SOCK_DGRAM, false);
|
||||
}
|
||||
|
||||
TEST_F(ConnectivityNativeBinderTest, PortUnblockedV6Tcp) {
|
||||
runSocketTest(AF_INET6, SOCK_STREAM, false);
|
||||
}
|
||||
|
||||
TEST_F(ConnectivityNativeBinderTest, BlockPort4Udp) {
|
||||
runSocketTest(AF_INET, SOCK_DGRAM, true);
|
||||
}
|
||||
|
||||
TEST_F(ConnectivityNativeBinderTest, BlockPort4Tcp) {
|
||||
runSocketTest(AF_INET, SOCK_STREAM, true);
|
||||
}
|
||||
|
||||
TEST_F(ConnectivityNativeBinderTest, BlockPort6Udp) {
|
||||
runSocketTest(AF_INET6, SOCK_DGRAM, true);
|
||||
}
|
||||
|
||||
TEST_F(ConnectivityNativeBinderTest, BlockPort6Tcp) {
|
||||
runSocketTest(AF_INET6, SOCK_STREAM, true);
|
||||
}
|
||||
|
||||
TEST_F(ConnectivityNativeBinderTest, BlockPortTwice) {
|
||||
ndk::ScopedAStatus status = mService->blockPortForBind(5555);
|
||||
EXPECT_TRUE(status.isOk()) << status.getDescription ();
|
||||
status = mService->blockPortForBind(5555);
|
||||
EXPECT_TRUE(status.isOk()) << status.getDescription ();
|
||||
status = mService->unblockPortForBind(5555);
|
||||
EXPECT_TRUE(status.isOk()) << status.getDescription ();
|
||||
}
|
||||
|
||||
TEST_F(ConnectivityNativeBinderTest, GetBlockedPorts) {
|
||||
ndk::ScopedAStatus status;
|
||||
std::vector<int> blockedPorts{1, 100, 1220, 1333, 2700, 5555, 5600, 65000};
|
||||
for (int i : blockedPorts) {
|
||||
status = mService->blockPortForBind(i);
|
||||
EXPECT_TRUE(status.isOk()) << status.getDescription ();
|
||||
}
|
||||
std::vector<int32_t> actualBlockedPorts;
|
||||
status = mService->getPortsBlockedForBind(&actualBlockedPorts);
|
||||
EXPECT_TRUE(status.isOk()) << status.getDescription ();
|
||||
EXPECT_FALSE(actualBlockedPorts.empty());
|
||||
EXPECT_EQ(blockedPorts, actualBlockedPorts);
|
||||
|
||||
// Remove the ports we added.
|
||||
status = mService->unblockAllPortsForBind();
|
||||
EXPECT_TRUE(status.isOk()) << status.getDescription ();
|
||||
status = mService->getPortsBlockedForBind(&actualBlockedPorts);
|
||||
EXPECT_TRUE(status.isOk()) << status.getDescription ();
|
||||
EXPECT_TRUE(actualBlockedPorts.empty());
|
||||
}
|
||||
|
||||
TEST_F(ConnectivityNativeBinderTest, UnblockAllPorts) {
|
||||
ndk::ScopedAStatus status;
|
||||
std::vector<int> blockedPorts{1, 100, 1220, 1333, 2700, 5555, 5600, 65000};
|
||||
|
||||
if (mActualBlockedPorts.size() > 0) {
|
||||
status = mService->unblockAllPortsForBind();
|
||||
}
|
||||
|
||||
for (int i : blockedPorts) {
|
||||
status = mService->blockPortForBind(i);
|
||||
EXPECT_TRUE(status.isOk()) << status.getDescription ();
|
||||
}
|
||||
|
||||
std::vector<int32_t> actualBlockedPorts;
|
||||
status = mService->getPortsBlockedForBind(&actualBlockedPorts);
|
||||
EXPECT_TRUE(status.isOk()) << status.getDescription ();
|
||||
EXPECT_FALSE(actualBlockedPorts.empty());
|
||||
|
||||
status = mService->unblockAllPortsForBind();
|
||||
EXPECT_TRUE(status.isOk()) << status.getDescription ();
|
||||
status = mService->getPortsBlockedForBind(&actualBlockedPorts);
|
||||
EXPECT_TRUE(status.isOk()) << status.getDescription ();
|
||||
EXPECT_TRUE(actualBlockedPorts.empty());
|
||||
// If mActualBlockedPorts is not empty, ports will be added back in teardown.
|
||||
}
|
||||
|
||||
TEST_F(ConnectivityNativeBinderTest, BlockNegativePort) {
|
||||
int retry = 0;
|
||||
ndk::ScopedAStatus status;
|
||||
do {
|
||||
status = mService->blockPortForBind(-1);
|
||||
// TODO: find out why transaction failed is being thrown on the first attempt.
|
||||
} while (status.getExceptionCode() == EX_TRANSACTION_FAILED && retry++ < 5);
|
||||
EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode());
|
||||
}
|
||||
|
||||
TEST_F(ConnectivityNativeBinderTest, UnblockNegativePort) {
|
||||
int retry = 0;
|
||||
ndk::ScopedAStatus status;
|
||||
do {
|
||||
status = mService->unblockPortForBind(-1);
|
||||
// TODO: find out why transaction failed is being thrown on the first attempt.
|
||||
} while (status.getExceptionCode() == EX_TRANSACTION_FAILED && retry++ < 5);
|
||||
EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode());
|
||||
}
|
||||
|
||||
TEST_F(ConnectivityNativeBinderTest, BlockMaxPort) {
|
||||
int retry = 0;
|
||||
ndk::ScopedAStatus status;
|
||||
do {
|
||||
status = mService->blockPortForBind(65536);
|
||||
// TODO: find out why transaction failed is being thrown on the first attempt.
|
||||
} while (status.getExceptionCode() == EX_TRANSACTION_FAILED && retry++ < 5);
|
||||
EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode());
|
||||
}
|
||||
|
||||
TEST_F(ConnectivityNativeBinderTest, UnblockMaxPort) {
|
||||
int retry = 0;
|
||||
ndk::ScopedAStatus status;
|
||||
do {
|
||||
status = mService->unblockPortForBind(65536);
|
||||
// TODO: find out why transaction failed is being thrown on the first attempt.
|
||||
} while (status.getExceptionCode() == EX_TRANSACTION_FAILED && retry++ < 5);
|
||||
EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode());
|
||||
}
|
||||
|
||||
TEST_F(ConnectivityNativeBinderTest, CheckPermission) {
|
||||
int retry = 0;
|
||||
int curUid = getuid();
|
||||
EXPECT_EQ(0, seteuid(FIRST_APPLICATION_UID + 2000)) << "seteuid failed: " << strerror(errno);
|
||||
ndk::ScopedAStatus status;
|
||||
do {
|
||||
status = mService->blockPortForBind(5555);
|
||||
// TODO: find out why transaction failed is being thrown on the first attempt.
|
||||
} while (status.getExceptionCode() == EX_TRANSACTION_FAILED && retry++ < 5);
|
||||
EXPECT_EQ(EX_SECURITY, status.getExceptionCode());
|
||||
EXPECT_EQ(0, seteuid(curUid)) << "seteuid failed: " << strerror(errno);
|
||||
}
|
||||
Reference in New Issue
Block a user