237 lines
8.1 KiB
C++
237 lines
8.1 KiB
C++
/*
|
|
* Copyright (C) 2018 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless requied 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 "BpfTest"
|
|
|
|
#include <arpa/inet.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <inttypes.h>
|
|
#include <linux/pfkeyv2.h>
|
|
#include <netinet/in.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <sys/types.h>
|
|
|
|
#include <thread>
|
|
|
|
#include <android-base/file.h>
|
|
#include <android-base/stringprintf.h>
|
|
#include <android-base/unique_fd.h>
|
|
#include <gtest/gtest.h>
|
|
#include <utils/Log.h>
|
|
|
|
#include "bpf/BpfMap.h"
|
|
#include "bpf/BpfUtils.h"
|
|
#include "kern.h"
|
|
#include "libbpf_android.h"
|
|
|
|
using android::base::unique_fd;
|
|
using namespace android::bpf;
|
|
|
|
namespace android {
|
|
|
|
TEST(BpfTest, bpfMapPinTest) {
|
|
EXPECT_EQ(0, setrlimitForTest());
|
|
const char* bpfMapPath = "/sys/fs/bpf/testMap";
|
|
int ret = access(bpfMapPath, F_OK);
|
|
if (!ret) {
|
|
ASSERT_EQ(0, remove(bpfMapPath));
|
|
} else {
|
|
ASSERT_EQ(errno, ENOENT);
|
|
}
|
|
|
|
android::base::unique_fd mapfd(createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t),
|
|
sizeof(uint32_t), 10,
|
|
BPF_F_NO_PREALLOC));
|
|
ASSERT_LT(0, mapfd) << "create map failed with error: " << strerror(errno);
|
|
ASSERT_EQ(0, bpfFdPin(mapfd, bpfMapPath))
|
|
<< "pin map failed with error: " << strerror(errno);
|
|
ASSERT_EQ(0, access(bpfMapPath, F_OK));
|
|
ASSERT_EQ(0, remove(bpfMapPath));
|
|
}
|
|
|
|
#define BPF_SRC_PATH "/data/local/tmp"
|
|
|
|
#if defined(__aarch64__) || defined(__x86_64__)
|
|
#define BPF_SRC_NAME "/64/kern.o"
|
|
#else
|
|
#define BPF_SRC_NAME "/32/kern.o"
|
|
#endif
|
|
|
|
#define BPF_PATH "/sys/fs/bpf"
|
|
#define TEST_PROG_PATH BPF_PATH "/prog_kern_skfilter_test"
|
|
#define TEST_STATS_MAP_A_PATH BPF_PATH "/map_kern_test_stats_map_A"
|
|
#define TEST_STATS_MAP_B_PATH BPF_PATH "/map_kern_test_stats_map_B"
|
|
#define TEST_CONFIGURATION_MAP_PATH BPF_PATH "/map_kern_test_configuration_map"
|
|
|
|
constexpr int ACTIVE_MAP_KEY = 1;
|
|
const int NUM_CPUS = sysconf(_SC_NPROCESSORS_ONLN);
|
|
const int NUM_SOCKETS = std::min(NUM_CPUS, MAX_NUM_SOCKETS);
|
|
|
|
class BpfRaceTest : public ::testing::Test {
|
|
protected:
|
|
BpfRaceTest() {}
|
|
BpfMap<uint64_t, stats_value> cookieStatsMap[2];
|
|
BpfMap<uint32_t, uint32_t> configurationMap;
|
|
bool stop;
|
|
std::thread *tds = new std::thread[NUM_SOCKETS];
|
|
|
|
static void workerThread(int prog_fd, bool *stop) {
|
|
struct sockaddr_in6 remote = {.sin6_family = AF_INET6};
|
|
struct sockaddr_in6 local;
|
|
uint64_t j = 0;
|
|
int recvSock, sendSock, recv_len;
|
|
char buf[strlen("msg: 18446744073709551615")];
|
|
int res;
|
|
socklen_t slen = sizeof(remote);
|
|
|
|
recvSock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
|
|
EXPECT_NE(-1, recvSock);
|
|
std::string address = android::base::StringPrintf("::1");
|
|
EXPECT_NE(0, inet_pton(AF_INET6, address.c_str(), &remote.sin6_addr));
|
|
EXPECT_NE(-1, bind(recvSock, (struct sockaddr *)&remote, sizeof(remote)));
|
|
EXPECT_EQ(0, getsockname(recvSock, (struct sockaddr *)&remote, &slen));
|
|
sendSock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
|
|
EXPECT_NE(-1, sendSock) << "send socket create failed!\n";
|
|
EXPECT_NE(-1, setsockopt(recvSock, SOL_SOCKET, SO_ATTACH_BPF, &prog_fd,
|
|
sizeof(prog_fd)))
|
|
<< "attach bpf program failed: "
|
|
<< android::base::StringPrintf("%s\n", strerror(errno));
|
|
|
|
// Keep sending and receiving packet until test end.
|
|
while (!*stop) {
|
|
std::string id = android::base::StringPrintf("msg: %" PRIu64 "\n", j);
|
|
res = sendto(sendSock, &id, id.length(), 0, (struct sockaddr *)&remote,
|
|
slen);
|
|
EXPECT_EQ(id.size(), res);
|
|
recv_len = recvfrom(recvSock, &buf, sizeof(buf), 0,
|
|
(struct sockaddr *)&local, &slen);
|
|
EXPECT_EQ(id.size(), recv_len);
|
|
}
|
|
}
|
|
|
|
void SetUp() {
|
|
EXPECT_EQ(0, setrlimitForTest());
|
|
int ret = access(TEST_PROG_PATH, R_OK);
|
|
// Always create a new program and remove the pinned program after program
|
|
// loading is done.
|
|
if (ret == 0) {
|
|
remove(TEST_PROG_PATH);
|
|
}
|
|
std::string progSrcPath = BPF_SRC_PATH BPF_SRC_NAME;
|
|
// 0 != 2 means ENOENT - ie. missing bpf program.
|
|
ASSERT_EQ(0, access(progSrcPath.c_str(), R_OK) ? errno : 0);
|
|
bool critical = false;
|
|
ASSERT_EQ(0, android::bpf::loadProg(progSrcPath.c_str(), &critical));
|
|
ASSERT_EQ(true, critical);
|
|
|
|
errno = 0;
|
|
int prog_fd = retrieveProgram(TEST_PROG_PATH);
|
|
EXPECT_EQ(0, errno);
|
|
ASSERT_LE(3, prog_fd);
|
|
|
|
EXPECT_RESULT_OK(cookieStatsMap[0].init(TEST_STATS_MAP_A_PATH));
|
|
EXPECT_RESULT_OK(cookieStatsMap[1].init(TEST_STATS_MAP_B_PATH));
|
|
EXPECT_RESULT_OK(configurationMap.init(TEST_CONFIGURATION_MAP_PATH));
|
|
EXPECT_TRUE(cookieStatsMap[0].isValid());
|
|
EXPECT_TRUE(cookieStatsMap[1].isValid());
|
|
EXPECT_TRUE(configurationMap.isValid());
|
|
EXPECT_RESULT_OK(configurationMap.writeValue(ACTIVE_MAP_KEY, 0, BPF_ANY));
|
|
|
|
// Start several threads to send and receive packets with an eBPF program
|
|
// attached to the socket.
|
|
stop = false;
|
|
|
|
for (int i = 0; i < NUM_SOCKETS; i++) {
|
|
tds[i] = std::thread(workerThread, prog_fd, &stop);
|
|
}
|
|
}
|
|
|
|
void TearDown() {
|
|
// Stop the threads and clean up the program.
|
|
stop = true;
|
|
for (int i = 0; i < NUM_SOCKETS; i++) {
|
|
if (tds[i].joinable()) tds[i].join();
|
|
}
|
|
delete [] tds;
|
|
remove(TEST_PROG_PATH);
|
|
remove(TEST_STATS_MAP_A_PATH);
|
|
remove(TEST_STATS_MAP_B_PATH);
|
|
remove(TEST_CONFIGURATION_MAP_PATH);
|
|
}
|
|
|
|
void swapAndCleanStatsMap(bool expectSynchronized, int seconds) {
|
|
uint64_t i = 0;
|
|
auto test_start = std::chrono::system_clock::now();
|
|
while ((std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
std::chrono::system_clock::now() - test_start)
|
|
.count() /
|
|
1000) < seconds) {
|
|
// Check if the vacant map is empty based on the current configuration.
|
|
auto isEmpty = cookieStatsMap[i].isEmpty();
|
|
ASSERT_RESULT_OK(isEmpty);
|
|
if (expectSynchronized) {
|
|
// The map should always be empty because synchronizeKernelRCU should
|
|
// ensure that the BPF programs running on all cores have seen the write
|
|
// to the configuration map that tells them to write to the other map.
|
|
// If it's not empty, fail.
|
|
ASSERT_TRUE(isEmpty.value())
|
|
<< "Race problem between stats clean and updates";
|
|
} else if (!isEmpty.value()) {
|
|
// We found a race condition, which is expected (eventually) because
|
|
// we're not calling synchronizeKernelRCU. Pass the test.
|
|
break;
|
|
}
|
|
|
|
// Change the configuration and wait for rcu grace period.
|
|
i ^= 1;
|
|
ASSERT_RESULT_OK(configurationMap.writeValue(ACTIVE_MAP_KEY, i, BPF_ANY));
|
|
if (expectSynchronized) {
|
|
EXPECT_EQ(0, synchronizeKernelRCU());
|
|
}
|
|
|
|
// Clean up the previous map after map swap.
|
|
EXPECT_RESULT_OK(cookieStatsMap[i].clear());
|
|
}
|
|
if (!expectSynchronized) {
|
|
auto test_end = std::chrono::system_clock::now();
|
|
auto diffSec = test_end - test_start;
|
|
auto msec =
|
|
std::chrono::duration_cast<std::chrono::milliseconds>(diffSec);
|
|
EXPECT_GE(seconds, (double)(msec.count() / 1000.0))
|
|
<< "Race problem didn't happen before time out";
|
|
}
|
|
}
|
|
};
|
|
|
|
// Verify the race problem disappear when the kernel call synchronize_rcu
|
|
// after changing the active map.
|
|
TEST_F(BpfRaceTest, testRaceWithBarrier) {
|
|
swapAndCleanStatsMap(true, 30);
|
|
}
|
|
|
|
// Confirm the race problem exists when the kernel doesn't call synchronize_rcu
|
|
// after changing the active map.
|
|
// This test is flaky. Race not triggering isn't really a bug per say...
|
|
// Maybe we should just outright delete this test...
|
|
TEST_F(BpfRaceTest, DISABLED_testRaceWithoutBarrier) {
|
|
swapAndCleanStatsMap(false, 240);
|
|
}
|
|
|
|
} // namespace android
|