C++程序  |  224行  |  7.36 KB

/*
 * 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) {
  SKIP_IF_BPF_NOT_SUPPORTED;

  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<uint64_t, stats_value> cookieStatsMap[2];
  BpfMap<uint32_t, uint32_t> configurationMap;
  BpfProgInfo program;
  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() {
    SKIP_IF_BPF_NOT_SUPPORTED;
    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;
    ASSERT_EQ(0, android::bpf::loadProg(progSrcPath.c_str()));

    EXPECT_TRUE(isOk(cookieStatsMap[0].init(TEST_STATS_MAP_A_PATH)));
    EXPECT_TRUE(isOk(cookieStatsMap[1].init(TEST_STATS_MAP_B_PATH)));
    EXPECT_TRUE(isOk(configurationMap.init(TEST_CONFIGURATION_MAP_PATH)));
    EXPECT_TRUE(cookieStatsMap[0].isValid());
    EXPECT_TRUE(cookieStatsMap[1].isValid());
    EXPECT_TRUE(configurationMap.isValid());
    // Start several threads to send and receive packets with an eBPF program
    // attached to the socket.
    stop = false;
    int prog_fd = bpfFdGet(TEST_PROG_PATH, 0);
    EXPECT_OK(configurationMap.writeValue(ACTIVE_MAP_KEY, 0, BPF_ANY));

    for (int i = 0; i < NUM_SOCKETS; i++) {
      tds[i] = std::thread(workerThread, prog_fd, &stop);
    }
  }

  void TearDown() {
    SKIP_IF_BPF_NOT_SUPPORTED;

    // Stop the threads and clean up the program.
    stop = true;
    for (int i = 0; i < NUM_SOCKETS; i++) {
      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 start = std::clock();
    while (((double)(std::clock() - start) / CLOCKS_PER_SEC) < seconds) {
      // Check if the vacant map is empty based on the current configuration.
      auto isEmpty = cookieStatsMap[i].isEmpty();
      EXPECT_TRUE(isOk(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;
      EXPECT_OK(configurationMap.writeValue(ACTIVE_MAP_KEY, i, BPF_ANY));
      if (expectSynchronized) {
        EXPECT_EQ(0, synchronizeKernelRCU());
      }

      // Clean up the previous map after map swap.
      EXPECT_OK(cookieStatsMap[i].clear());
    }
    if (!expectSynchronized) {
      EXPECT_GE(seconds, (double)(std::clock() - start) / CLOCKS_PER_SEC)
          << "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) {
  SKIP_IF_BPF_NOT_SUPPORTED;

  swapAndCleanStatsMap(true, 60);
}

// Confirm the race problem exists when the kernel doesn't call synchronize_rcu
// after changing the active map.
TEST_F(BpfRaceTest, testRaceWithoutBarrier) {
  SKIP_IF_BPF_NOT_SUPPORTED;

  swapAndCleanStatsMap(false, 20);
}

}  // namespace android