/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; class BpfRaceTest : public ::testing::Test { protected: BpfRaceTest() {} BpfMap cookieStatsMap[2]; BpfMap configurationMap; bool stop; std::thread tds[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(); } 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::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(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, testRaceWithoutBarrier) { swapAndCleanStatsMap(false, 240); } } // namespace android