[NETD-BPF#19] Mainline part of bpf code from netd
1. Add libnetd_updatable.so in com.android.tethering. The library is loaded by netd. Currently, it mainly targets on a few functions which access BPF maps. The functionality may extend in the future. 2. Attach gcroup progs from libnetd_updatable.so. 3. Move (privileged)TagSocket and untagSocket implementation to mainline module. Combine privilegedTagSocket and untagSocket into a single function. 4. Split related unit tests from netd_unit_test to libnetd_updatable_unit_test as well. Bug: 202086915 Test: cd system/netd; atest Test: atest TrafficStatsTest NetworkUsageStatsTest Change-Id: Ib556458103a4cbb643c1342d9b689ac692160de0
This commit is contained in:
parent
c5ad7cd775
commit
1647f60d66
11 changed files with 818 additions and 0 deletions
10
TEST_MAPPING
10
TEST_MAPPING
|
@ -18,6 +18,9 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "netd_updatable_unit_test"
|
||||
},
|
||||
{
|
||||
"name": "TetheringTests"
|
||||
},
|
||||
|
@ -39,6 +42,10 @@
|
|||
{
|
||||
"name": "bpf_existence_test"
|
||||
},
|
||||
{
|
||||
"name": "netd_updatable_unit_test",
|
||||
"keywords": ["netd-device-kernel-4.9", "netd-device-kernel-4.14"]
|
||||
},
|
||||
{
|
||||
"name": "libclat_test"
|
||||
},
|
||||
|
@ -62,6 +69,9 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "netd_updatable_unit_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
|
||||
},
|
||||
{
|
||||
"name": "ConnectivityCoverageTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
|
||||
},
|
||||
|
|
|
@ -55,6 +55,7 @@ apex {
|
|||
"libcom_android_connectivity_com_android_net_module_util_jni",
|
||||
"libtraffic_controller_jni",
|
||||
],
|
||||
native_shared_libs: ["libnetd_updatable"],
|
||||
},
|
||||
both: {
|
||||
jni_libs: ["libframework-connectivity-jni"],
|
||||
|
|
|
@ -42,6 +42,7 @@ cc_library_headers {
|
|||
// TODO: remove it when NetworkStatsService is moved into the mainline module and no more
|
||||
// calls to JNI in libservices.core.
|
||||
"//frameworks/base/services/core/jni",
|
||||
"//packages/modules/Connectivity/netd",
|
||||
"//packages/modules/Connectivity/service",
|
||||
"//packages/modules/Connectivity/service/native/libs/libclat",
|
||||
"//packages/modules/Connectivity/Tethering",
|
||||
|
|
81
netd/Android.bp
Normal file
81
netd/Android.bp
Normal file
|
@ -0,0 +1,81 @@
|
|||
//
|
||||
// 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.
|
||||
|
||||
cc_library {
|
||||
name: "libnetd_updatable",
|
||||
version_script: "libnetd_updatable.map.txt",
|
||||
stubs: {
|
||||
versions: [
|
||||
"1",
|
||||
],
|
||||
symbol_file: "libnetd_updatable.map.txt",
|
||||
},
|
||||
defaults: ["netd_defaults"],
|
||||
header_libs: [
|
||||
"bpf_connectivity_headers",
|
||||
"libcutils_headers",
|
||||
],
|
||||
srcs: [
|
||||
"BpfHandler.cpp",
|
||||
"NetdUpdatable.cpp",
|
||||
],
|
||||
static_libs: [
|
||||
"libnetdutils",
|
||||
],
|
||||
shared_libs: [
|
||||
"libbase",
|
||||
"liblog",
|
||||
],
|
||||
export_include_dirs: ["include"],
|
||||
header_abi_checker: {
|
||||
enabled: true,
|
||||
symbol_file: "libnetd_updatable.map.txt",
|
||||
},
|
||||
sanitize: {
|
||||
cfi: true,
|
||||
},
|
||||
apex_available: ["com.android.tethering"],
|
||||
min_sdk_version: "30",
|
||||
}
|
||||
|
||||
cc_test {
|
||||
name: "netd_updatable_unit_test",
|
||||
defaults: ["netd_defaults"],
|
||||
test_suites: ["general-tests"],
|
||||
require_root: true, // required by setrlimitForTest()
|
||||
header_libs: [
|
||||
"bpf_connectivity_headers",
|
||||
],
|
||||
srcs: [
|
||||
"BpfHandlerTest.cpp",
|
||||
],
|
||||
static_libs: [
|
||||
"libnetd_updatable",
|
||||
],
|
||||
shared_libs: [
|
||||
"libbase",
|
||||
"libcutils",
|
||||
"liblog",
|
||||
"libnetdutils",
|
||||
],
|
||||
multilib: {
|
||||
lib32: {
|
||||
suffix: "32",
|
||||
},
|
||||
lib64: {
|
||||
suffix: "64",
|
||||
},
|
||||
},
|
||||
}
|
212
netd/BpfHandler.cpp
Normal file
212
netd/BpfHandler.cpp
Normal file
|
@ -0,0 +1,212 @@
|
|||
/**
|
||||
* Copyright (c) 2022, The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#define LOG_TAG "BpfHandler"
|
||||
|
||||
#include "BpfHandler.h"
|
||||
|
||||
#include <linux/bpf.h>
|
||||
|
||||
#include <android-base/unique_fd.h>
|
||||
#include <bpf/WaitForProgsLoaded.h>
|
||||
#include <log/log.h>
|
||||
#include <netdutils/UidConstants.h>
|
||||
#include <private/android_filesystem_config.h>
|
||||
|
||||
#include "BpfSyscallWrappers.h"
|
||||
|
||||
namespace android {
|
||||
namespace net {
|
||||
|
||||
using base::unique_fd;
|
||||
using bpf::NONEXISTENT_COOKIE;
|
||||
using bpf::getSocketCookie;
|
||||
using bpf::retrieveProgram;
|
||||
using netdutils::Status;
|
||||
using netdutils::statusFromErrno;
|
||||
|
||||
constexpr int PER_UID_STATS_ENTRIES_LIMIT = 500;
|
||||
// At most 90% of the stats map may be used by tagged traffic entries. This ensures
|
||||
// that 10% of the map is always available to count untagged traffic, one entry per UID.
|
||||
// Otherwise, apps would be able to avoid data usage accounting entirely by filling up the
|
||||
// map with tagged traffic entries.
|
||||
constexpr int TOTAL_UID_STATS_ENTRIES_LIMIT = STATS_MAP_SIZE * 0.9;
|
||||
|
||||
static_assert(STATS_MAP_SIZE - TOTAL_UID_STATS_ENTRIES_LIMIT > 100,
|
||||
"The limit for stats map is to high, stats data may be lost due to overflow");
|
||||
|
||||
static Status attachProgramToCgroup(const char* programPath, const unique_fd& cgroupFd,
|
||||
bpf_attach_type type) {
|
||||
unique_fd cgroupProg(retrieveProgram(programPath));
|
||||
if (cgroupProg == -1) {
|
||||
int ret = errno;
|
||||
ALOGE("Failed to get program from %s: %s", programPath, strerror(ret));
|
||||
return statusFromErrno(ret, "cgroup program get failed");
|
||||
}
|
||||
if (android::bpf::attachProgram(type, cgroupProg, cgroupFd)) {
|
||||
int ret = errno;
|
||||
ALOGE("Program from %s attach failed: %s", programPath, strerror(ret));
|
||||
return statusFromErrno(ret, "program attach failed");
|
||||
}
|
||||
return netdutils::status::ok;
|
||||
}
|
||||
|
||||
static Status initPrograms(const char* cg2_path) {
|
||||
unique_fd cg_fd(open(cg2_path, O_DIRECTORY | O_RDONLY | O_CLOEXEC));
|
||||
if (cg_fd == -1) {
|
||||
int ret = errno;
|
||||
ALOGE("Failed to open the cgroup directory: %s", strerror(ret));
|
||||
return statusFromErrno(ret, "Open the cgroup directory failed");
|
||||
}
|
||||
RETURN_IF_NOT_OK(attachProgramToCgroup(BPF_EGRESS_PROG_PATH, cg_fd, BPF_CGROUP_INET_EGRESS));
|
||||
RETURN_IF_NOT_OK(attachProgramToCgroup(BPF_INGRESS_PROG_PATH, cg_fd, BPF_CGROUP_INET_INGRESS));
|
||||
|
||||
// For the devices that support cgroup socket filter, the socket filter
|
||||
// should be loaded successfully by bpfloader. So we attach the filter to
|
||||
// cgroup if the program is pinned properly.
|
||||
// TODO: delete the if statement once all devices should support cgroup
|
||||
// socket filter (ie. the minimum kernel version required is 4.14).
|
||||
if (!access(CGROUP_SOCKET_PROG_PATH, F_OK)) {
|
||||
RETURN_IF_NOT_OK(
|
||||
attachProgramToCgroup(CGROUP_SOCKET_PROG_PATH, cg_fd, BPF_CGROUP_INET_SOCK_CREATE));
|
||||
}
|
||||
return netdutils::status::ok;
|
||||
}
|
||||
|
||||
BpfHandler::BpfHandler()
|
||||
: mPerUidStatsEntriesLimit(PER_UID_STATS_ENTRIES_LIMIT),
|
||||
mTotalUidStatsEntriesLimit(TOTAL_UID_STATS_ENTRIES_LIMIT) {}
|
||||
|
||||
BpfHandler::BpfHandler(uint32_t perUidLimit, uint32_t totalLimit)
|
||||
: mPerUidStatsEntriesLimit(perUidLimit), mTotalUidStatsEntriesLimit(totalLimit) {}
|
||||
|
||||
Status BpfHandler::init(const char* cg2_path) {
|
||||
// Make sure BPF programs are loaded before doing anything
|
||||
android::bpf::waitForProgsLoaded();
|
||||
ALOGI("BPF programs are loaded");
|
||||
|
||||
RETURN_IF_NOT_OK(initPrograms(cg2_path));
|
||||
RETURN_IF_NOT_OK(initMaps());
|
||||
|
||||
return netdutils::status::ok;
|
||||
}
|
||||
|
||||
Status BpfHandler::initMaps() {
|
||||
std::lock_guard guard(mMutex);
|
||||
RETURN_IF_NOT_OK(mCookieTagMap.init(COOKIE_TAG_MAP_PATH));
|
||||
RETURN_IF_NOT_OK(mStatsMapA.init(STATS_MAP_A_PATH));
|
||||
RETURN_IF_NOT_OK(mStatsMapB.init(STATS_MAP_B_PATH));
|
||||
RETURN_IF_NOT_OK(mConfigurationMap.init(CONFIGURATION_MAP_PATH));
|
||||
RETURN_IF_NOT_OK(mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY, SELECT_MAP_A,
|
||||
BPF_ANY));
|
||||
RETURN_IF_NOT_OK(mUidPermissionMap.init(UID_PERMISSION_MAP_PATH));
|
||||
|
||||
return netdutils::status::ok;
|
||||
}
|
||||
|
||||
bool BpfHandler::hasUpdateDeviceStatsPermission(uid_t uid) {
|
||||
// This implementation is the same logic as method ActivityManager#checkComponentPermission.
|
||||
// It implies that the real uid can never be the same as PER_USER_RANGE.
|
||||
uint32_t appId = uid % PER_USER_RANGE;
|
||||
auto permission = mUidPermissionMap.readValue(appId);
|
||||
if (permission.ok() && (permission.value() & BPF_PERMISSION_UPDATE_DEVICE_STATS)) {
|
||||
return true;
|
||||
}
|
||||
return ((appId == AID_ROOT) || (appId == AID_SYSTEM) || (appId == AID_DNS));
|
||||
}
|
||||
|
||||
int BpfHandler::tagSocket(int sockFd, uint32_t tag, uid_t chargeUid, uid_t realUid) {
|
||||
std::lock_guard guard(mMutex);
|
||||
if (chargeUid != realUid && !hasUpdateDeviceStatsPermission(realUid)) {
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
uint64_t sock_cookie = getSocketCookie(sockFd);
|
||||
if (sock_cookie == NONEXISTENT_COOKIE) return -errno;
|
||||
UidTagValue newKey = {.uid = (uint32_t)chargeUid, .tag = tag};
|
||||
|
||||
uint32_t totalEntryCount = 0;
|
||||
uint32_t perUidEntryCount = 0;
|
||||
// Now we go through the stats map and count how many entries are associated
|
||||
// with chargeUid. If the uid entry hit the limit for each chargeUid, we block
|
||||
// the request to prevent the map from overflow. It is safe here to iterate
|
||||
// over the map since when mMutex is hold, system server cannot toggle
|
||||
// the live stats map and clean it. So nobody can delete entries from the map.
|
||||
const auto countUidStatsEntries = [chargeUid, &totalEntryCount, &perUidEntryCount](
|
||||
const StatsKey& key,
|
||||
const BpfMap<StatsKey, StatsValue>&) {
|
||||
if (key.uid == chargeUid) {
|
||||
perUidEntryCount++;
|
||||
}
|
||||
totalEntryCount++;
|
||||
return base::Result<void>();
|
||||
};
|
||||
auto configuration = mConfigurationMap.readValue(CURRENT_STATS_MAP_CONFIGURATION_KEY);
|
||||
if (!configuration.ok()) {
|
||||
ALOGE("Failed to get current configuration: %s, fd: %d",
|
||||
strerror(configuration.error().code()), mConfigurationMap.getMap().get());
|
||||
return -configuration.error().code();
|
||||
}
|
||||
if (configuration.value() != SELECT_MAP_A && configuration.value() != SELECT_MAP_B) {
|
||||
ALOGE("unknown configuration value: %d", configuration.value());
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
BpfMap<StatsKey, StatsValue>& currentMap =
|
||||
(configuration.value() == SELECT_MAP_A) ? mStatsMapA : mStatsMapB;
|
||||
base::Result<void> res = currentMap.iterate(countUidStatsEntries);
|
||||
if (!res.ok()) {
|
||||
ALOGE("Failed to count the stats entry in map %d: %s", currentMap.getMap().get(),
|
||||
strerror(res.error().code()));
|
||||
return -res.error().code();
|
||||
}
|
||||
|
||||
if (totalEntryCount > mTotalUidStatsEntriesLimit ||
|
||||
perUidEntryCount > mPerUidStatsEntriesLimit) {
|
||||
ALOGE("Too many stats entries in the map, total count: %u, chargeUid(%u) count: %u,"
|
||||
" blocking tag request to prevent map overflow",
|
||||
totalEntryCount, chargeUid, perUidEntryCount);
|
||||
return -EMFILE;
|
||||
}
|
||||
// Update the tag information of a socket to the cookieUidMap. Use BPF_ANY
|
||||
// flag so it will insert a new entry to the map if that value doesn't exist
|
||||
// yet. And update the tag if there is already a tag stored. Since the eBPF
|
||||
// program in kernel only read this map, and is protected by rcu read lock. It
|
||||
// should be fine to cocurrently update the map while eBPF program is running.
|
||||
res = mCookieTagMap.writeValue(sock_cookie, newKey, BPF_ANY);
|
||||
if (!res.ok()) {
|
||||
ALOGE("Failed to tag the socket: %s, fd: %d", strerror(res.error().code()),
|
||||
mCookieTagMap.getMap().get());
|
||||
return -res.error().code();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int BpfHandler::untagSocket(int sockFd) {
|
||||
std::lock_guard guard(mMutex);
|
||||
uint64_t sock_cookie = getSocketCookie(sockFd);
|
||||
|
||||
if (sock_cookie == NONEXISTENT_COOKIE) return -errno;
|
||||
base::Result<void> res = mCookieTagMap.deleteValue(sock_cookie);
|
||||
if (!res.ok()) {
|
||||
ALOGE("Failed to untag socket: %s\n", strerror(res.error().code()));
|
||||
return -res.error().code();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace net
|
||||
} // namespace android
|
83
netd/BpfHandler.h
Normal file
83
netd/BpfHandler.h
Normal file
|
@ -0,0 +1,83 @@
|
|||
/**
|
||||
* Copyright (c) 2022, The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <netdutils/Status.h>
|
||||
#include "bpf/BpfMap.h"
|
||||
#include "bpf_shared.h"
|
||||
|
||||
using android::bpf::BpfMap;
|
||||
|
||||
namespace android {
|
||||
namespace net {
|
||||
|
||||
class BpfHandler {
|
||||
public:
|
||||
BpfHandler();
|
||||
BpfHandler(const BpfHandler&) = delete;
|
||||
BpfHandler& operator=(const BpfHandler&) = delete;
|
||||
netdutils::Status init(const char* cg2_path);
|
||||
/*
|
||||
* Tag the socket with the specified tag and uid. In the qtaguid module, the
|
||||
* first tag request that grab the spinlock of rb_tree can update the tag
|
||||
* information first and other request need to wait until it finish. All the
|
||||
* tag request will be addressed in the order of they obtaining the spinlock.
|
||||
* In the eBPF implementation, the kernel will try to update the eBPF map
|
||||
* entry with the tag request. And the hashmap update process is protected by
|
||||
* the spinlock initialized with the map. So the behavior of two modules
|
||||
* should be the same. No additional lock needed.
|
||||
*/
|
||||
int tagSocket(int sockFd, uint32_t tag, uid_t chargeUid, uid_t realUid);
|
||||
|
||||
/*
|
||||
* The untag process is similar to tag socket and both old qtaguid module and
|
||||
* new eBPF module have spinlock inside the kernel for concurrent update. No
|
||||
* external lock is required.
|
||||
*/
|
||||
int untagSocket(int sockFd);
|
||||
|
||||
private:
|
||||
// For testing
|
||||
BpfHandler(uint32_t perUidLimit, uint32_t totalLimit);
|
||||
|
||||
netdutils::Status initMaps();
|
||||
bool hasUpdateDeviceStatsPermission(uid_t uid);
|
||||
|
||||
BpfMap<uint64_t, UidTagValue> mCookieTagMap;
|
||||
BpfMap<StatsKey, StatsValue> mStatsMapA;
|
||||
BpfMap<StatsKey, StatsValue> mStatsMapB;
|
||||
BpfMap<uint32_t, uint8_t> mConfigurationMap;
|
||||
BpfMap<uint32_t, uint8_t> mUidPermissionMap;
|
||||
|
||||
std::mutex mMutex;
|
||||
|
||||
// The limit on the number of stats entries a uid can have in the per uid stats map. BpfHandler
|
||||
// will block that specific uid from tagging new sockets after the limit is reached.
|
||||
const uint32_t mPerUidStatsEntriesLimit;
|
||||
|
||||
// The limit on the total number of stats entries in the per uid stats map. BpfHandler will
|
||||
// block all tagging requests after the limit is reached.
|
||||
const uint32_t mTotalUidStatsEntriesLimit;
|
||||
|
||||
// For testing
|
||||
friend class BpfHandlerTest;
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
} // namespace android
|
244
netd/BpfHandlerTest.cpp
Normal file
244
netd/BpfHandlerTest.cpp
Normal file
|
@ -0,0 +1,244 @@
|
|||
/*
|
||||
* Copyright 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* BpfHandlerTest.cpp - unit tests for BpfHandler.cpp
|
||||
*/
|
||||
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "BpfHandler.h"
|
||||
|
||||
using namespace android::bpf; // NOLINT(google-build-using-namespace): exempted
|
||||
|
||||
namespace android {
|
||||
namespace net {
|
||||
|
||||
using base::Result;
|
||||
|
||||
constexpr int TEST_MAP_SIZE = 10;
|
||||
constexpr int TEST_COOKIE = 1;
|
||||
constexpr uid_t TEST_UID = 10086;
|
||||
constexpr uid_t TEST_UID2 = 54321;
|
||||
constexpr uint32_t TEST_TAG = 42;
|
||||
constexpr uint32_t TEST_COUNTERSET = 1;
|
||||
constexpr uint32_t TEST_PER_UID_STATS_ENTRIES_LIMIT = 3;
|
||||
constexpr uint32_t TEST_TOTAL_UID_STATS_ENTRIES_LIMIT = 7;
|
||||
|
||||
#define ASSERT_VALID(x) ASSERT_TRUE((x).isValid())
|
||||
|
||||
class BpfHandlerTest : public ::testing::Test {
|
||||
protected:
|
||||
BpfHandlerTest()
|
||||
: mBh(TEST_PER_UID_STATS_ENTRIES_LIMIT, TEST_TOTAL_UID_STATS_ENTRIES_LIMIT) {}
|
||||
BpfHandler mBh;
|
||||
BpfMap<uint64_t, UidTagValue> mFakeCookieTagMap;
|
||||
BpfMap<StatsKey, StatsValue> mFakeStatsMapA;
|
||||
BpfMap<uint32_t, uint8_t> mFakeConfigurationMap;
|
||||
BpfMap<uint32_t, uint8_t> mFakeUidPermissionMap;
|
||||
|
||||
void SetUp() {
|
||||
std::lock_guard guard(mBh.mMutex);
|
||||
ASSERT_EQ(0, setrlimitForTest());
|
||||
|
||||
mFakeCookieTagMap.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(uint64_t), sizeof(UidTagValue),
|
||||
TEST_MAP_SIZE, 0));
|
||||
ASSERT_VALID(mFakeCookieTagMap);
|
||||
|
||||
mFakeStatsMapA.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(StatsKey), sizeof(StatsValue),
|
||||
TEST_MAP_SIZE, 0));
|
||||
ASSERT_VALID(mFakeStatsMapA);
|
||||
|
||||
mFakeConfigurationMap.reset(
|
||||
createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), 1, 0));
|
||||
ASSERT_VALID(mFakeConfigurationMap);
|
||||
|
||||
mFakeUidPermissionMap.reset(
|
||||
createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), TEST_MAP_SIZE, 0));
|
||||
ASSERT_VALID(mFakeUidPermissionMap);
|
||||
|
||||
mBh.mCookieTagMap.reset(dupFd(mFakeCookieTagMap.getMap()));
|
||||
ASSERT_VALID(mBh.mCookieTagMap);
|
||||
mBh.mStatsMapA.reset(dupFd(mFakeStatsMapA.getMap()));
|
||||
ASSERT_VALID(mBh.mStatsMapA);
|
||||
mBh.mConfigurationMap.reset(dupFd(mFakeConfigurationMap.getMap()));
|
||||
ASSERT_VALID(mBh.mConfigurationMap);
|
||||
// Always write to stats map A by default.
|
||||
ASSERT_RESULT_OK(mBh.mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY,
|
||||
SELECT_MAP_A, BPF_ANY));
|
||||
mBh.mUidPermissionMap.reset(dupFd(mFakeUidPermissionMap.getMap()));
|
||||
ASSERT_VALID(mBh.mUidPermissionMap);
|
||||
}
|
||||
|
||||
int dupFd(const android::base::unique_fd& mapFd) {
|
||||
return fcntl(mapFd.get(), F_DUPFD_CLOEXEC, 0);
|
||||
}
|
||||
|
||||
int setUpSocketAndTag(int protocol, uint64_t* cookie, uint32_t tag, uid_t uid,
|
||||
uid_t realUid) {
|
||||
int sock = socket(protocol, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
EXPECT_LE(0, sock);
|
||||
*cookie = getSocketCookie(sock);
|
||||
EXPECT_NE(NONEXISTENT_COOKIE, *cookie);
|
||||
EXPECT_EQ(0, mBh.tagSocket(sock, tag, uid, realUid));
|
||||
return sock;
|
||||
}
|
||||
|
||||
void expectUidTag(uint64_t cookie, uid_t uid, uint32_t tag) {
|
||||
Result<UidTagValue> tagResult = mFakeCookieTagMap.readValue(cookie);
|
||||
ASSERT_RESULT_OK(tagResult);
|
||||
EXPECT_EQ(uid, tagResult.value().uid);
|
||||
EXPECT_EQ(tag, tagResult.value().tag);
|
||||
}
|
||||
|
||||
void expectNoTag(uint64_t cookie) { EXPECT_FALSE(mFakeCookieTagMap.readValue(cookie).ok()); }
|
||||
|
||||
void populateFakeStats(uint64_t cookie, uint32_t uid, uint32_t tag, StatsKey* key) {
|
||||
UidTagValue cookieMapkey = {.uid = (uint32_t)uid, .tag = tag};
|
||||
EXPECT_RESULT_OK(mFakeCookieTagMap.writeValue(cookie, cookieMapkey, BPF_ANY));
|
||||
*key = {.uid = uid, .tag = tag, .counterSet = TEST_COUNTERSET, .ifaceIndex = 1};
|
||||
StatsValue statsMapValue = {.rxPackets = 1, .rxBytes = 100};
|
||||
EXPECT_RESULT_OK(mFakeStatsMapA.writeValue(*key, statsMapValue, BPF_ANY));
|
||||
key->tag = 0;
|
||||
EXPECT_RESULT_OK(mFakeStatsMapA.writeValue(*key, statsMapValue, BPF_ANY));
|
||||
// put tag information back to statsKey
|
||||
key->tag = tag;
|
||||
}
|
||||
|
||||
template <class Key, class Value>
|
||||
void expectMapEmpty(BpfMap<Key, Value>& map) {
|
||||
auto isEmpty = map.isEmpty();
|
||||
EXPECT_RESULT_OK(isEmpty);
|
||||
EXPECT_TRUE(isEmpty.value());
|
||||
}
|
||||
|
||||
void expectTagSocketReachLimit(uint32_t tag, uint32_t uid) {
|
||||
int sock = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
EXPECT_LE(0, sock);
|
||||
if (sock < 0) return;
|
||||
uint64_t sockCookie = getSocketCookie(sock);
|
||||
EXPECT_NE(NONEXISTENT_COOKIE, sockCookie);
|
||||
EXPECT_EQ(-EMFILE, mBh.tagSocket(sock, tag, uid, uid));
|
||||
expectNoTag(sockCookie);
|
||||
|
||||
// Delete stats entries then tag socket success
|
||||
StatsKey key = {.uid = uid, .tag = 0, .counterSet = TEST_COUNTERSET, .ifaceIndex = 1};
|
||||
ASSERT_RESULT_OK(mFakeStatsMapA.deleteValue(key));
|
||||
EXPECT_EQ(0, mBh.tagSocket(sock, tag, uid, uid));
|
||||
expectUidTag(sockCookie, uid, tag);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(BpfHandlerTest, TestTagSocketV4) {
|
||||
uint64_t sockCookie;
|
||||
int v4socket = setUpSocketAndTag(AF_INET, &sockCookie, TEST_TAG, TEST_UID, TEST_UID);
|
||||
expectUidTag(sockCookie, TEST_UID, TEST_TAG);
|
||||
ASSERT_EQ(0, mBh.untagSocket(v4socket));
|
||||
expectNoTag(sockCookie);
|
||||
expectMapEmpty(mFakeCookieTagMap);
|
||||
}
|
||||
|
||||
TEST_F(BpfHandlerTest, TestReTagSocket) {
|
||||
uint64_t sockCookie;
|
||||
int v4socket = setUpSocketAndTag(AF_INET, &sockCookie, TEST_TAG, TEST_UID, TEST_UID);
|
||||
expectUidTag(sockCookie, TEST_UID, TEST_TAG);
|
||||
ASSERT_EQ(0, mBh.tagSocket(v4socket, TEST_TAG + 1, TEST_UID + 1, TEST_UID + 1));
|
||||
expectUidTag(sockCookie, TEST_UID + 1, TEST_TAG + 1);
|
||||
}
|
||||
|
||||
TEST_F(BpfHandlerTest, TestTagTwoSockets) {
|
||||
uint64_t sockCookie1;
|
||||
uint64_t sockCookie2;
|
||||
int v4socket1 = setUpSocketAndTag(AF_INET, &sockCookie1, TEST_TAG, TEST_UID, TEST_UID);
|
||||
setUpSocketAndTag(AF_INET, &sockCookie2, TEST_TAG, TEST_UID, TEST_UID);
|
||||
expectUidTag(sockCookie1, TEST_UID, TEST_TAG);
|
||||
expectUidTag(sockCookie2, TEST_UID, TEST_TAG);
|
||||
ASSERT_EQ(0, mBh.untagSocket(v4socket1));
|
||||
expectNoTag(sockCookie1);
|
||||
expectUidTag(sockCookie2, TEST_UID, TEST_TAG);
|
||||
ASSERT_FALSE(mFakeCookieTagMap.getNextKey(sockCookie2).ok());
|
||||
}
|
||||
|
||||
TEST_F(BpfHandlerTest, TestTagSocketV6) {
|
||||
uint64_t sockCookie;
|
||||
int v6socket = setUpSocketAndTag(AF_INET6, &sockCookie, TEST_TAG, TEST_UID, TEST_UID);
|
||||
expectUidTag(sockCookie, TEST_UID, TEST_TAG);
|
||||
ASSERT_EQ(0, mBh.untagSocket(v6socket));
|
||||
expectNoTag(sockCookie);
|
||||
expectMapEmpty(mFakeCookieTagMap);
|
||||
}
|
||||
|
||||
TEST_F(BpfHandlerTest, TestTagInvalidSocket) {
|
||||
int invalidSocket = -1;
|
||||
ASSERT_GT(0, mBh.tagSocket(invalidSocket, TEST_TAG, TEST_UID, TEST_UID));
|
||||
expectMapEmpty(mFakeCookieTagMap);
|
||||
}
|
||||
|
||||
TEST_F(BpfHandlerTest, TestTagSocketWithoutPermission) {
|
||||
int sock = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
ASSERT_NE(-1, sock);
|
||||
ASSERT_EQ(-EPERM, mBh.tagSocket(sock, TEST_TAG, TEST_UID, TEST_UID2));
|
||||
expectMapEmpty(mFakeCookieTagMap);
|
||||
}
|
||||
|
||||
TEST_F(BpfHandlerTest, TestTagSocketWithPermission) {
|
||||
// Grant permission to real uid. In practice, the uid permission map will be updated by
|
||||
// TrafficController::setPermissionForUids().
|
||||
uid_t realUid = TEST_UID2;
|
||||
ASSERT_RESULT_OK(mFakeUidPermissionMap.writeValue(realUid,
|
||||
BPF_PERMISSION_UPDATE_DEVICE_STATS, BPF_ANY));
|
||||
|
||||
// Tag a socket to a different uid other then realUid.
|
||||
uint64_t sockCookie;
|
||||
int v6socket = setUpSocketAndTag(AF_INET6, &sockCookie, TEST_TAG, TEST_UID, realUid);
|
||||
expectUidTag(sockCookie, TEST_UID, TEST_TAG);
|
||||
EXPECT_EQ(0, mBh.untagSocket(v6socket));
|
||||
expectNoTag(sockCookie);
|
||||
expectMapEmpty(mFakeCookieTagMap);
|
||||
}
|
||||
|
||||
TEST_F(BpfHandlerTest, TestUntagInvalidSocket) {
|
||||
int invalidSocket = -1;
|
||||
ASSERT_GT(0, mBh.untagSocket(invalidSocket));
|
||||
int v4socket = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
ASSERT_GT(0, mBh.untagSocket(v4socket));
|
||||
expectMapEmpty(mFakeCookieTagMap);
|
||||
}
|
||||
|
||||
TEST_F(BpfHandlerTest, TestTagSocketReachLimitFail) {
|
||||
uid_t uid = TEST_UID;
|
||||
StatsKey tagStatsMapKey[3];
|
||||
for (int i = 0; i < 3; i++) {
|
||||
uint64_t cookie = TEST_COOKIE + i;
|
||||
uint32_t tag = TEST_TAG + i;
|
||||
populateFakeStats(cookie, uid, tag, &tagStatsMapKey[i]);
|
||||
}
|
||||
expectTagSocketReachLimit(TEST_TAG, TEST_UID);
|
||||
}
|
||||
|
||||
TEST_F(BpfHandlerTest, TestTagSocketReachTotalLimitFail) {
|
||||
StatsKey tagStatsMapKey[4];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
uint64_t cookie = TEST_COOKIE + i;
|
||||
uint32_t tag = TEST_TAG + i;
|
||||
uid_t uid = TEST_UID + i;
|
||||
populateFakeStats(cookie, uid, tag, &tagStatsMapKey[i]);
|
||||
}
|
||||
expectTagSocketReachLimit(TEST_TAG, TEST_UID);
|
||||
}
|
||||
|
||||
} // namespace net
|
||||
} // namespace android
|
61
netd/NetdUpdatable.cpp
Normal file
61
netd/NetdUpdatable.cpp
Normal 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.
|
||||
*/
|
||||
|
||||
#define LOG_TAG "NetdUpdatable"
|
||||
|
||||
#include "NetdUpdatable.h"
|
||||
|
||||
#include <android-base/logging.h>
|
||||
#include <netdutils/Status.h>
|
||||
|
||||
#include "NetdUpdatablePublic.h"
|
||||
|
||||
int libnetd_updatable_init(const char* cg2_path) {
|
||||
android::base::InitLogging(/*argv=*/nullptr);
|
||||
LOG(INFO) << __func__ << ": Initializing";
|
||||
|
||||
android::net::gNetdUpdatable = android::net::NetdUpdatable::getInstance();
|
||||
android::netdutils::Status ret = android::net::gNetdUpdatable->mBpfHandler.init(cg2_path);
|
||||
if (!android::netdutils::isOk(ret)) {
|
||||
LOG(ERROR) << __func__ << ": BPF handler init failed";
|
||||
return -ret.code();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int libnetd_updatable_tagSocket(int sockFd, uint32_t tag, uid_t chargeUid, uid_t realUid) {
|
||||
if (android::net::gNetdUpdatable == nullptr) return -EPERM;
|
||||
return android::net::gNetdUpdatable->mBpfHandler.tagSocket(sockFd, tag, chargeUid, realUid);
|
||||
}
|
||||
|
||||
int libnetd_updatable_untagSocket(int sockFd) {
|
||||
if (android::net::gNetdUpdatable == nullptr) return -EPERM;
|
||||
return android::net::gNetdUpdatable->mBpfHandler.untagSocket(sockFd);
|
||||
}
|
||||
|
||||
namespace android {
|
||||
namespace net {
|
||||
|
||||
NetdUpdatable* gNetdUpdatable = nullptr;
|
||||
|
||||
NetdUpdatable* NetdUpdatable::getInstance() {
|
||||
// Instantiated on first use.
|
||||
static NetdUpdatable instance;
|
||||
return &instance;
|
||||
}
|
||||
|
||||
} // namespace net
|
||||
} // namespace android
|
37
netd/NetdUpdatable.h
Normal file
37
netd/NetdUpdatable.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* Copyright (c) 2022, The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BpfHandler.h"
|
||||
|
||||
namespace android {
|
||||
namespace net {
|
||||
|
||||
class NetdUpdatable {
|
||||
public:
|
||||
NetdUpdatable() = default;
|
||||
NetdUpdatable(const NetdUpdatable&) = delete;
|
||||
NetdUpdatable& operator=(const NetdUpdatable&) = delete;
|
||||
static NetdUpdatable* getInstance();
|
||||
|
||||
BpfHandler mBpfHandler;
|
||||
};
|
||||
|
||||
extern NetdUpdatable* gNetdUpdatable;
|
||||
|
||||
} // namespace net
|
||||
} // namespace android
|
61
netd/include/NetdUpdatablePublic.h
Normal file
61
netd/include/NetdUpdatablePublic.h
Normal 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sys/cdefs.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
__BEGIN_DECLS
|
||||
|
||||
/*
|
||||
* Initial function for libnetd_updatable library.
|
||||
*
|
||||
* The function uses |cg2_path| as cgroup v2 mount location to attach BPF programs so that the
|
||||
* kernel can record packet number, size, etc. in BPF maps when packets pass through, and let user
|
||||
* space retrieve statistics.
|
||||
*
|
||||
* Returns 0 on success, or a negative POSIX error code (see errno.h) on
|
||||
* failure.
|
||||
*/
|
||||
int libnetd_updatable_init(const char* cg2_path);
|
||||
|
||||
/*
|
||||
* Set the socket tag and owning UID for traffic statistics on the specified socket. Permission
|
||||
* check is performed based on the |realUid| before socket tagging.
|
||||
*
|
||||
* The |sockFd| is a file descriptor of the socket that needs to tag. The |tag| is the mark to tag.
|
||||
* It can be an arbitrary value in uint32_t range. The |chargeUid| is owning uid which will be
|
||||
* tagged along with the |tag|. The |realUid| is an effective uid of the calling process, which is
|
||||
* used for permission check before socket tagging.
|
||||
*
|
||||
* Returns 0 on success, or a negative POSIX error code (see errno.h) on failure.
|
||||
*/
|
||||
int libnetd_updatable_tagSocket(int sockFd, uint32_t tag, uid_t chargeUid,
|
||||
uid_t realUid);
|
||||
|
||||
/*
|
||||
* Untag a network socket. Future traffic on this socket will no longer be associated with any
|
||||
* previously configured tag and uid.
|
||||
*
|
||||
* The |sockFd| is a file descriptor of the socket that wants to untag.
|
||||
*
|
||||
* Returns 0 on success, or a negative POSIX error code (see errno.h) on failure.
|
||||
*/
|
||||
int libnetd_updatable_untagSocket(int sockFd);
|
||||
|
||||
__END_DECLS
|
27
netd/libnetd_updatable.map.txt
Normal file
27
netd/libnetd_updatable.map.txt
Normal file
|
@ -0,0 +1,27 @@
|
|||
#
|
||||
# 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 lists the entry points visible to applications that use the libnetd_updatable
|
||||
# library. Other entry points present in the library won't be usable.
|
||||
|
||||
LIBNETD_UPDATABLE {
|
||||
global:
|
||||
libnetd_updatable_init; # apex
|
||||
libnetd_updatable_tagSocket; # apex
|
||||
libnetd_updatable_untagSocket; # apex
|
||||
local:
|
||||
*;
|
||||
};
|
Loading…
Reference in a new issue