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:
Tyler Wear
2021-10-01 13:22:00 -07:00
committed by Tyler Wear
parent dc1832cd83
commit b37f551287
13 changed files with 786 additions and 1 deletions

View File

@@ -69,9 +69,10 @@ apex {
], ],
canned_fs_config: "canned_fs_config", canned_fs_config: "canned_fs_config",
bpfs: [ bpfs: [
"block.o",
"clatd.o_mainline", "clatd.o_mainline",
"netd.o_mainline",
"dscp_policy.o", "dscp_policy.o",
"netd.o_mainline",
"offload.o", "offload.o",
"test.o", "test.o",
], ],

View File

@@ -47,6 +47,7 @@ cc_library_headers {
"//packages/modules/Connectivity/service/native/libs/libclat", "//packages/modules/Connectivity/service/native/libs/libclat",
"//packages/modules/Connectivity/Tethering", "//packages/modules/Connectivity/Tethering",
"//packages/modules/Connectivity/service/native", "//packages/modules/Connectivity/service/native",
"//packages/modules/Connectivity/tests/native",
"//packages/modules/Connectivity/service-t/native/libs/libnetworkstats", "//packages/modules/Connectivity/service-t/native/libs/libnetworkstats",
"//packages/modules/Connectivity/tests/unit/jni", "//packages/modules/Connectivity/tests/unit/jni",
"//system/netd/server", "//system/netd/server",
@@ -57,6 +58,16 @@ cc_library_headers {
// //
// bpf kernel programs // bpf kernel programs
// //
bpf {
name: "block.o",
srcs: ["block.c"],
cflags: [
"-Wall",
"-Werror",
],
sub_dir: "net_shared",
}
bpf { bpf {
name: "dscp_policy.o", name: "dscp_policy.o",
srcs: ["dscp_policy.c"], srcs: ["dscp_policy.c"],

70
bpf_progs/block.c Normal file
View 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");

View File

@@ -21,6 +21,7 @@ import android.util.Log;
import com.android.modules.utils.build.SdkLevel; import com.android.modules.utils.build.SdkLevel;
import com.android.networkstack.apishim.ConstantsShim; import com.android.networkstack.apishim.ConstantsShim;
import com.android.server.connectivity.ConnectivityNativeService;
import com.android.server.ethernet.EthernetService; import com.android.server.ethernet.EthernetService;
import com.android.server.ethernet.EthernetServiceImpl; import com.android.server.ethernet.EthernetServiceImpl;
import com.android.server.nearby.NearbyService; import com.android.server.nearby.NearbyService;
@@ -31,6 +32,7 @@ import com.android.server.nearby.NearbyService;
*/ */
public final class ConnectivityServiceInitializer extends SystemService { public final class ConnectivityServiceInitializer extends SystemService {
private static final String TAG = ConnectivityServiceInitializer.class.getSimpleName(); private static final String TAG = ConnectivityServiceInitializer.class.getSimpleName();
private final ConnectivityNativeService mConnectivityNative;
private final ConnectivityService mConnectivity; private final ConnectivityService mConnectivity;
private final IpSecService mIpSecService; private final IpSecService mIpSecService;
private final NsdService mNsdService; private final NsdService mNsdService;
@@ -44,6 +46,7 @@ public final class ConnectivityServiceInitializer extends SystemService {
mEthernetServiceImpl = createEthernetService(context); mEthernetServiceImpl = createEthernetService(context);
mConnectivity = new ConnectivityService(context); mConnectivity = new ConnectivityService(context);
mIpSecService = createIpSecService(context); mIpSecService = createIpSecService(context);
mConnectivityNative = createConnectivityNativeService(context);
mNsdService = createNsdService(context); mNsdService = createNsdService(context);
mNearbyService = createNearbyService(context); mNearbyService = createNearbyService(context);
} }
@@ -65,6 +68,12 @@ public final class ConnectivityServiceInitializer extends SystemService {
publishBinderService(Context.IPSEC_SERVICE, mIpSecService, /* allowIsolated= */ false); 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) { if (mNsdService != null) {
Log.i(TAG, "Registering " + Context.NSD_SERVICE); Log.i(TAG, "Registering " + Context.NSD_SERVICE);
publishBinderService(Context.NSD_SERVICE, mNsdService, /* allowIsolated= */ false); publishBinderService(Context.NSD_SERVICE, mNsdService, /* allowIsolated= */ false);
@@ -98,6 +107,19 @@ public final class ConnectivityServiceInitializer extends SystemService {
return new IpSecService(context); 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 */ /** Return NsdService instance or null if current SDK is lower than T */
private NsdService createNsdService(final Context context) { private NsdService createNsdService(final Context context) {
if (!SdkLevel.isAtLeastT()) return null; if (!SdkLevel.isAtLeastT()) return null;

View File

@@ -19,6 +19,54 @@ package {
default_applicable_licenses: ["Android-Apache-2.0"], 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 // 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. // android.net.connectivity.com.android.net.module.util package.
cc_library_shared { cc_library_shared {
@@ -35,6 +83,7 @@ cc_library_shared {
], ],
static_libs: [ static_libs: [
"libnet_utils_device_common_bpfjni", "libnet_utils_device_common_bpfjni",
"libnet_utils_device_common_bpfutils",
], ],
shared_libs: [ shared_libs: [
"liblog", "liblog",
@@ -109,6 +158,7 @@ java_library {
static_libs: [ static_libs: [
// Do not add libs here if they are already included // Do not add libs here if they are already included
// in framework-connectivity // in framework-connectivity
"connectivity_native_aidl_interface-lateststable-java",
"dnsresolver_aidl_interface-V9-java", "dnsresolver_aidl_interface-V9-java",
"modules-utils-shell-command-handler", "modules-utils-shell-command-handler",
"net-utils-device-common", "net-utils-device-common",

View File

@@ -0,0 +1 @@
037b467eb02b172a3161e11bbc3dd691aebb5fce

View File

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

View File

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

View File

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

View File

@@ -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_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_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*) { extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
JNIEnv *env; JNIEnv *env;
@@ -35,6 +36,9 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
if (register_com_android_net_module_util_TcUtils(env, if (register_com_android_net_module_util_TcUtils(env,
"android/net/connectivity/com/android/net/module/util/TcUtils") < 0) return JNI_ERR; "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; return JNI_VERSION_1_6;
} }

View File

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

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