[NETD-BPF#15] Move BPF map definition and utils to frameworks/libs/net/
1. Move BPF map definition and utilities to a common place that easy to
be referenced from both mainline module and platform code.
2. Create cc_library_headers bpf_map_utils which includes BPF map
definition and utils.
Bug: 202086915
Test: m; flash; boot
Test: cd system/netd/ && atest
Test: cd packages/modules/Connectivity && atest
Test: m gpuservice_unittest libtimeinstate_test bpf_module_test
CtsAppOpsTestCases libbpf_load_test VtsBootconfigTest
vts_test_binary_bpf_module bpf_benchmark libbpf_load_test
libbpf_android_test
Change-Id: I4d54b0d69029f593b8da3099d7ae16279692dabd
diff --git a/staticlibs/native/bpf_map_utils/Android.bp b/staticlibs/native/bpf_map_utils/Android.bp
new file mode 100644
index 0000000..521b682
--- /dev/null
+++ b/staticlibs/native/bpf_map_utils/Android.bp
@@ -0,0 +1,73 @@
+// 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.
+
+cc_library_headers {
+ name: "bpf_map_utils",
+ vendor_available: true,
+ host_supported: true,
+ native_bridge_supported: true,
+ export_include_dirs: ["include"],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ min_sdk_version: "30",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.art.debug",
+ ],
+ visibility: [
+ "//bootable/libbootloader/vts",
+ "//frameworks/base/services/core/jni",
+ "//frameworks/native/libs/cputimeinstate",
+ "//frameworks/native/services/gpuservice",
+ "//frameworks/native/services/gpuservice/gpumem",
+ "//frameworks/native/services/gpuservice/tests/unittests",
+ "//frameworks/native/services/gpuservice/tracing",
+ "//packages/modules/Connectivity/tests/unit/jni",
+ "//packages/modules/DnsResolver/tests",
+ "//system/bpf/bpfloader",
+ "//system/bpf/libbpf_android",
+ "//system/memory/libmeminfo",
+ "//system/netd/libnetdbpf",
+ "//system/netd/server",
+ "//system/netd/tests",
+ "//system/netd/tests/benchmarks",
+ "//test/vts-testcase/kernel/api/bpf_native_test",
+ ],
+}
+
+
+cc_test {
+ // TODO: Rename to bpf_map_test and modify .gcls as well.
+ name: "libbpf_android_test",
+ srcs: [
+ "BpfMapTest.cpp",
+ ],
+ defaults: ["bpf_defaults"],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-error=unused-variable",
+ ],
+ static_libs: ["libgmock"],
+ shared_libs: [
+ "libbpf_android",
+ "libbase",
+ "liblog",
+ "libutils",
+ ],
+ require_root: true,
+ test_suites: ["general-tests"],
+}
diff --git a/staticlibs/native/bpf_map_utils/BpfMapTest.cpp b/staticlibs/native/bpf_map_utils/BpfMapTest.cpp
new file mode 100644
index 0000000..d0737b0
--- /dev/null
+++ b/staticlibs/native/bpf_map_utils/BpfMapTest.cpp
@@ -0,0 +1,244 @@
+/*
+ * 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 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 <fstream>
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <linux/inet_diag.h>
+#include <linux/sock_diag.h>
+#include <net/if.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <gtest/gtest.h>
+
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+#include "bpf/BpfMap.h"
+#include "bpf/BpfUtils.h"
+
+using ::testing::Test;
+
+namespace android {
+namespace bpf {
+
+using base::Result;
+using base::unique_fd;
+
+constexpr uint32_t TEST_MAP_SIZE = 10;
+constexpr uint32_t TEST_KEY1 = 1;
+constexpr uint32_t TEST_VALUE1 = 10;
+constexpr const char PINNED_MAP_PATH[] = "/sys/fs/bpf/testMap";
+
+class BpfMapTest : public testing::Test {
+ protected:
+ BpfMapTest() {}
+
+ void SetUp() {
+ EXPECT_EQ(0, setrlimitForTest());
+ if (!access(PINNED_MAP_PATH, R_OK)) {
+ EXPECT_EQ(0, remove(PINNED_MAP_PATH));
+ }
+ }
+
+ void TearDown() {
+ if (!access(PINNED_MAP_PATH, R_OK)) {
+ EXPECT_EQ(0, remove(PINNED_MAP_PATH));
+ }
+ }
+
+ void checkMapInvalid(BpfMap<uint32_t, uint32_t>& map) {
+ EXPECT_FALSE(map.isValid());
+ EXPECT_EQ(-1, map.getMap().get());
+ }
+
+ void checkMapValid(BpfMap<uint32_t, uint32_t>& map) {
+ EXPECT_LE(0, map.getMap().get());
+ EXPECT_TRUE(map.isValid());
+ }
+
+ void writeToMapAndCheck(BpfMap<uint32_t, uint32_t>& map, uint32_t key, uint32_t value) {
+ ASSERT_RESULT_OK(map.writeValue(key, value, BPF_ANY));
+ uint32_t value_read;
+ ASSERT_EQ(0, findMapEntry(map.getMap(), &key, &value_read));
+ checkValueAndStatus(value, value_read);
+ }
+
+ void checkValueAndStatus(uint32_t refValue, Result<uint32_t> value) {
+ ASSERT_RESULT_OK(value);
+ ASSERT_EQ(refValue, value.value());
+ }
+
+ void populateMap(uint32_t total, BpfMap<uint32_t, uint32_t>& map) {
+ for (uint32_t key = 0; key < total; key++) {
+ uint32_t value = key * 10;
+ EXPECT_RESULT_OK(map.writeValue(key, value, BPF_ANY));
+ }
+ }
+
+ void expectMapEmpty(BpfMap<uint32_t, uint32_t>& map) {
+ Result<bool> isEmpty = map.isEmpty();
+ ASSERT_RESULT_OK(isEmpty);
+ ASSERT_TRUE(isEmpty.value());
+ }
+};
+
+TEST_F(BpfMapTest, constructor) {
+ BpfMap<uint32_t, uint32_t> testMap1;
+ checkMapInvalid(testMap1);
+
+ BpfMap<uint32_t, uint32_t> testMap2(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+ checkMapValid(testMap2);
+}
+
+TEST_F(BpfMapTest, basicHelpers) {
+ BpfMap<uint32_t, uint32_t> testMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+ uint32_t key = TEST_KEY1;
+ uint32_t value_write = TEST_VALUE1;
+ writeToMapAndCheck(testMap, key, value_write);
+ Result<uint32_t> value_read = testMap.readValue(key);
+ checkValueAndStatus(value_write, value_read);
+ Result<uint32_t> key_read = testMap.getFirstKey();
+ checkValueAndStatus(key, key_read);
+ ASSERT_RESULT_OK(testMap.deleteValue(key));
+ ASSERT_GT(0, findMapEntry(testMap.getMap(), &key, &value_read));
+ ASSERT_EQ(ENOENT, errno);
+}
+
+TEST_F(BpfMapTest, reset) {
+ BpfMap<uint32_t, uint32_t> testMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+ uint32_t key = TEST_KEY1;
+ uint32_t value_write = TEST_VALUE1;
+ writeToMapAndCheck(testMap, key, value_write);
+
+ testMap.reset(-1);
+ checkMapInvalid(testMap);
+ ASSERT_GT(0, findMapEntry(testMap.getMap(), &key, &value_write));
+ ASSERT_EQ(EBADF, errno);
+}
+
+TEST_F(BpfMapTest, moveConstructor) {
+ BpfMap<uint32_t, uint32_t> testMap1(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+ BpfMap<uint32_t, uint32_t> testMap2;
+ testMap2 = std::move(testMap1);
+ uint32_t key = TEST_KEY1;
+ checkMapInvalid(testMap1);
+ uint32_t value = TEST_VALUE1;
+ writeToMapAndCheck(testMap2, key, value);
+}
+
+TEST_F(BpfMapTest, SetUpMap) {
+ EXPECT_NE(0, access(PINNED_MAP_PATH, R_OK));
+ BpfMap<uint32_t, uint32_t> testMap1(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+ ASSERT_EQ(0, bpfFdPin(testMap1.getMap(), PINNED_MAP_PATH));
+ EXPECT_EQ(0, access(PINNED_MAP_PATH, R_OK));
+ checkMapValid(testMap1);
+ BpfMap<uint32_t, uint32_t> testMap2;
+ EXPECT_RESULT_OK(testMap2.init(PINNED_MAP_PATH));
+ checkMapValid(testMap2);
+ uint32_t key = TEST_KEY1;
+ uint32_t value = TEST_VALUE1;
+ writeToMapAndCheck(testMap1, key, value);
+ Result<uint32_t> value_read = testMap2.readValue(key);
+ checkValueAndStatus(value, value_read);
+}
+
+TEST_F(BpfMapTest, iterate) {
+ BpfMap<uint32_t, uint32_t> testMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+ populateMap(TEST_MAP_SIZE, testMap);
+ int totalCount = 0;
+ int totalSum = 0;
+ const auto iterateWithDeletion = [&totalCount, &totalSum](const uint32_t& key,
+ BpfMap<uint32_t, uint32_t>& map) {
+ EXPECT_GE((uint32_t)TEST_MAP_SIZE, key);
+ totalCount++;
+ totalSum += key;
+ return map.deleteValue(key);
+ };
+ EXPECT_RESULT_OK(testMap.iterate(iterateWithDeletion));
+ EXPECT_EQ((int)TEST_MAP_SIZE, totalCount);
+ EXPECT_EQ(((1 + TEST_MAP_SIZE - 1) * (TEST_MAP_SIZE - 1)) / 2, (uint32_t)totalSum);
+ expectMapEmpty(testMap);
+}
+
+TEST_F(BpfMapTest, iterateWithValue) {
+ BpfMap<uint32_t, uint32_t> testMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+ populateMap(TEST_MAP_SIZE, testMap);
+ int totalCount = 0;
+ int totalSum = 0;
+ const auto iterateWithDeletion = [&totalCount, &totalSum](const uint32_t& key,
+ const uint32_t& value,
+ BpfMap<uint32_t, uint32_t>& map) {
+ EXPECT_GE((uint32_t)TEST_MAP_SIZE, key);
+ EXPECT_EQ(value, key * 10);
+ totalCount++;
+ totalSum += value;
+ return map.deleteValue(key);
+ };
+ EXPECT_RESULT_OK(testMap.iterateWithValue(iterateWithDeletion));
+ EXPECT_EQ((int)TEST_MAP_SIZE, totalCount);
+ EXPECT_EQ(((1 + TEST_MAP_SIZE - 1) * (TEST_MAP_SIZE - 1)) * 5, (uint32_t)totalSum);
+ expectMapEmpty(testMap);
+}
+
+TEST_F(BpfMapTest, mapIsEmpty) {
+ BpfMap<uint32_t, uint32_t> testMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+ expectMapEmpty(testMap);
+ uint32_t key = TEST_KEY1;
+ uint32_t value_write = TEST_VALUE1;
+ writeToMapAndCheck(testMap, key, value_write);
+ Result<bool> isEmpty = testMap.isEmpty();
+ ASSERT_RESULT_OK(isEmpty);
+ ASSERT_FALSE(isEmpty.value());
+ ASSERT_RESULT_OK(testMap.deleteValue(key));
+ ASSERT_GT(0, findMapEntry(testMap.getMap(), &key, &value_write));
+ ASSERT_EQ(ENOENT, errno);
+ expectMapEmpty(testMap);
+ int entriesSeen = 0;
+ EXPECT_RESULT_OK(testMap.iterate(
+ [&entriesSeen](const unsigned int&,
+ const BpfMap<unsigned int, unsigned int>&) -> Result<void> {
+ entriesSeen++;
+ return {};
+ }));
+ EXPECT_EQ(0, entriesSeen);
+ EXPECT_RESULT_OK(testMap.iterateWithValue(
+ [&entriesSeen](const unsigned int&, const unsigned int&,
+ const BpfMap<unsigned int, unsigned int>&) -> Result<void> {
+ entriesSeen++;
+ return {};
+ }));
+ EXPECT_EQ(0, entriesSeen);
+}
+
+TEST_F(BpfMapTest, mapClear) {
+ BpfMap<uint32_t, uint32_t> testMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+ populateMap(TEST_MAP_SIZE, testMap);
+ Result<bool> isEmpty = testMap.isEmpty();
+ ASSERT_RESULT_OK(isEmpty);
+ ASSERT_FALSE(*isEmpty);
+ ASSERT_RESULT_OK(testMap.clear());
+ expectMapEmpty(testMap);
+}
+
+} // namespace bpf
+} // namespace android
diff --git a/staticlibs/native/bpf_map_utils/TEST_MAPPING b/staticlibs/native/bpf_map_utils/TEST_MAPPING
new file mode 100644
index 0000000..9ec8a40
--- /dev/null
+++ b/staticlibs/native/bpf_map_utils/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "libbpf_android_test"
+ }
+ ]
+}
diff --git a/staticlibs/native/bpf_map_utils/include/bpf/BpfMap.h b/staticlibs/native/bpf_map_utils/include/bpf/BpfMap.h
new file mode 100644
index 0000000..bdffc0f
--- /dev/null
+++ b/staticlibs/native/bpf_map_utils/include/bpf/BpfMap.h
@@ -0,0 +1,260 @@
+/*
+ * 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 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 <linux/bpf.h>
+
+#include <android-base/result.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+#include <utils/Log.h>
+#include "bpf/BpfUtils.h"
+
+namespace android {
+namespace bpf {
+
+// This is a class wrapper for eBPF maps. The eBPF map is a special in-kernel
+// data structure that stores data in <Key, Value> pairs. It can be read/write
+// from userspace by passing syscalls with the map file descriptor. This class
+// is used to generalize the procedure of interacting with eBPF maps and hide
+// the implementation detail from other process. Besides the basic syscalls
+// wrapper, it also provides some useful helper functions as well as an iterator
+// nested class to iterate the map more easily.
+//
+// NOTE: A kernel eBPF map may be accessed by both kernel and userspace
+// processes at the same time. Or if the map is pinned as a virtual file, it can
+// be obtained by multiple eBPF map class object and accessed concurrently.
+// Though the map class object and the underlying kernel map are thread safe, it
+// is not safe to iterate over a map while another thread or process is deleting
+// from it. In this case the iteration can return duplicate entries.
+template <class Key, class Value>
+class BpfMap {
+ public:
+ BpfMap<Key, Value>() {};
+
+ protected:
+ // flag must be within BPF_OBJ_FLAG_MASK, ie. 0, BPF_F_RDONLY, BPF_F_WRONLY
+ BpfMap<Key, Value>(const char* pathname, uint32_t flags) {
+ int map_fd = mapRetrieve(pathname, flags);
+ if (map_fd >= 0) mMapFd.reset(map_fd);
+ }
+
+ public:
+ explicit BpfMap<Key, Value>(const char* pathname) : BpfMap<Key, Value>(pathname, 0) {}
+
+ BpfMap<Key, Value>(bpf_map_type map_type, uint32_t max_entries, uint32_t map_flags = 0) {
+ int map_fd = createMap(map_type, sizeof(Key), sizeof(Value), max_entries, map_flags);
+ if (map_fd >= 0) mMapFd.reset(map_fd);
+ }
+
+ base::Result<Key> getFirstKey() const {
+ Key firstKey;
+ if (getFirstMapKey(mMapFd, &firstKey)) {
+ return ErrnoErrorf("Get firstKey map {} failed", mMapFd.get());
+ }
+ return firstKey;
+ }
+
+ base::Result<Key> getNextKey(const Key& key) const {
+ Key nextKey;
+ if (getNextMapKey(mMapFd, &key, &nextKey)) {
+ return ErrnoErrorf("Get next key of map {} failed", mMapFd.get());
+ }
+ return nextKey;
+ }
+
+ base::Result<void> writeValue(const Key& key, const Value& value, uint64_t flags) {
+ if (writeToMapEntry(mMapFd, &key, &value, flags)) {
+ return ErrnoErrorf("Write to map {} failed", mMapFd.get());
+ }
+ return {};
+ }
+
+ base::Result<Value> readValue(const Key key) const {
+ Value value;
+ if (findMapEntry(mMapFd, &key, &value)) {
+ return ErrnoErrorf("Read value of map {} failed", mMapFd.get());
+ }
+ return value;
+ }
+
+ base::Result<void> deleteValue(const Key& key) {
+ if (deleteMapEntry(mMapFd, &key)) {
+ return ErrnoErrorf("Delete entry from map {} failed", mMapFd.get());
+ }
+ return {};
+ }
+
+ // Function that tries to get map from a pinned path.
+ base::Result<void> init(const char* path);
+
+ // Iterate through the map and handle each key retrieved based on the filter
+ // without modification of map content.
+ base::Result<void> iterate(
+ const std::function<base::Result<void>(const Key& key, const BpfMap<Key, Value>& map)>&
+ filter) const;
+
+ // Iterate through the map and get each <key, value> pair, handle each <key,
+ // value> pair based on the filter without modification of map content.
+ base::Result<void> iterateWithValue(
+ const std::function<base::Result<void>(const Key& key, const Value& value,
+ const BpfMap<Key, Value>& map)>& filter) const;
+
+ // Iterate through the map and handle each key retrieved based on the filter
+ base::Result<void> iterate(
+ const std::function<base::Result<void>(const Key& key, BpfMap<Key, Value>& map)>&
+ filter);
+
+ // Iterate through the map and get each <key, value> pair, handle each <key,
+ // value> pair based on the filter.
+ base::Result<void> iterateWithValue(
+ const std::function<base::Result<void>(const Key& key, const Value& value,
+ BpfMap<Key, Value>& map)>& filter);
+
+ const base::unique_fd& getMap() const { return mMapFd; };
+
+ // Copy assignment operator
+ BpfMap<Key, Value>& operator=(const BpfMap<Key, Value>& other) {
+ if (this != &other) mMapFd.reset(fcntl(other.mMapFd.get(), F_DUPFD_CLOEXEC, 0));
+ return *this;
+ }
+
+ // Move assignment operator
+ BpfMap<Key, Value>& operator=(BpfMap<Key, Value>&& other) noexcept {
+ mMapFd = std::move(other.mMapFd);
+ other.reset(-1);
+ return *this;
+ }
+
+ void reset(base::unique_fd fd) = delete;
+
+ void reset(int fd) { mMapFd.reset(fd); }
+
+ bool isValid() const { return mMapFd != -1; }
+
+ base::Result<void> clear() {
+ while (true) {
+ auto key = getFirstKey();
+ if (!key.ok()) {
+ if (key.error().code() == ENOENT) return {}; // empty: success
+ return key.error(); // Anything else is an error
+ }
+ auto res = deleteValue(key.value());
+ if (!res.ok()) {
+ // Someone else could have deleted the key, so ignore ENOENT
+ if (res.error().code() == ENOENT) continue;
+ ALOGE("Failed to delete data %s", strerror(res.error().code()));
+ return res.error();
+ }
+ }
+ }
+
+ base::Result<bool> isEmpty() const {
+ auto key = getFirstKey();
+ if (!key.ok()) {
+ // Return error code ENOENT means the map is empty
+ if (key.error().code() == ENOENT) return true;
+ return key.error();
+ }
+ return false;
+ }
+
+ private:
+ base::unique_fd mMapFd;
+};
+
+template <class Key, class Value>
+base::Result<void> BpfMap<Key, Value>::init(const char* path) {
+ mMapFd = base::unique_fd(mapRetrieveRW(path));
+ if (mMapFd == -1) {
+ return ErrnoErrorf("Pinned map not accessible or does not exist: ({})", path);
+ }
+ return {};
+}
+
+template <class Key, class Value>
+base::Result<void> BpfMap<Key, Value>::iterate(
+ const std::function<base::Result<void>(const Key& key, const BpfMap<Key, Value>& map)>&
+ filter) const {
+ base::Result<Key> curKey = getFirstKey();
+ while (curKey.ok()) {
+ const base::Result<Key>& nextKey = getNextKey(curKey.value());
+ base::Result<void> status = filter(curKey.value(), *this);
+ if (!status.ok()) return status;
+ curKey = nextKey;
+ }
+ if (curKey.error().code() == ENOENT) return {};
+ return curKey.error();
+}
+
+template <class Key, class Value>
+base::Result<void> BpfMap<Key, Value>::iterateWithValue(
+ const std::function<base::Result<void>(const Key& key, const Value& value,
+ const BpfMap<Key, Value>& map)>& filter) const {
+ base::Result<Key> curKey = getFirstKey();
+ while (curKey.ok()) {
+ const base::Result<Key>& nextKey = getNextKey(curKey.value());
+ base::Result<Value> curValue = readValue(curKey.value());
+ if (!curValue.ok()) return curValue.error();
+ base::Result<void> status = filter(curKey.value(), curValue.value(), *this);
+ if (!status.ok()) return status;
+ curKey = nextKey;
+ }
+ if (curKey.error().code() == ENOENT) return {};
+ return curKey.error();
+}
+
+template <class Key, class Value>
+base::Result<void> BpfMap<Key, Value>::iterate(
+ const std::function<base::Result<void>(const Key& key, BpfMap<Key, Value>& map)>& filter) {
+ base::Result<Key> curKey = getFirstKey();
+ while (curKey.ok()) {
+ const base::Result<Key>& nextKey = getNextKey(curKey.value());
+ base::Result<void> status = filter(curKey.value(), *this);
+ if (!status.ok()) return status;
+ curKey = nextKey;
+ }
+ if (curKey.error().code() == ENOENT) return {};
+ return curKey.error();
+}
+
+template <class Key, class Value>
+base::Result<void> BpfMap<Key, Value>::iterateWithValue(
+ const std::function<base::Result<void>(const Key& key, const Value& value,
+ BpfMap<Key, Value>& map)>& filter) {
+ base::Result<Key> curKey = getFirstKey();
+ while (curKey.ok()) {
+ const base::Result<Key>& nextKey = getNextKey(curKey.value());
+ base::Result<Value> curValue = readValue(curKey.value());
+ if (!curValue.ok()) return curValue.error();
+ base::Result<void> status = filter(curKey.value(), curValue.value(), *this);
+ if (!status.ok()) return status;
+ curKey = nextKey;
+ }
+ if (curKey.error().code() == ENOENT) return {};
+ return curKey.error();
+}
+
+template <class Key, class Value>
+class BpfMapRO : public BpfMap<Key, Value> {
+ public:
+ explicit BpfMapRO<Key, Value>(const char* pathname)
+ : BpfMap<Key, Value>(pathname, BPF_F_RDONLY) {}
+};
+
+} // namespace bpf
+} // namespace android
diff --git a/staticlibs/native/bpf_map_utils/include/bpf/BpfUtils.h b/staticlibs/native/bpf_map_utils/include/bpf/BpfUtils.h
new file mode 100644
index 0000000..265d4b6
--- /dev/null
+++ b/staticlibs/native/bpf_map_utils/include/bpf/BpfUtils.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2017 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 <linux/if_ether.h>
+#include <linux/pfkeyv2.h>
+#include <net/if.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/resource.h>
+#include <sys/socket.h>
+#include <sys/utsname.h>
+
+#include <string>
+
+#include <android-base/unique_fd.h>
+#include <log/log.h>
+
+#include "BpfSyscallWrappers.h"
+
+// The buffer size for the buffer that records program loading logs, needs to be large enough for
+// the largest kernel program.
+
+namespace android {
+namespace bpf {
+
+constexpr const int OVERFLOW_COUNTERSET = 2;
+
+constexpr const uint64_t NONEXISTENT_COOKIE = 0;
+
+static inline uint64_t getSocketCookie(int sockFd) {
+ uint64_t sock_cookie;
+ socklen_t cookie_len = sizeof(sock_cookie);
+ int res = getsockopt(sockFd, SOL_SOCKET, SO_COOKIE, &sock_cookie, &cookie_len);
+ if (res < 0) {
+ res = -errno;
+ ALOGE("Failed to get socket cookie: %s\n", strerror(errno));
+ errno = -res;
+ // 0 is an invalid cookie. See sock_gen_cookie.
+ return NONEXISTENT_COOKIE;
+ }
+ return sock_cookie;
+}
+
+static inline int synchronizeKernelRCU() {
+ // This is a temporary hack for network stats map swap on devices running
+ // 4.9 kernels. The kernel code of socket release on pf_key socket will
+ // explicitly call synchronize_rcu() which is exactly what we need.
+ int pfSocket = socket(AF_KEY, SOCK_RAW | SOCK_CLOEXEC, PF_KEY_V2);
+
+ if (pfSocket < 0) {
+ int ret = -errno;
+ ALOGE("create PF_KEY socket failed: %s", strerror(errno));
+ return ret;
+ }
+
+ // When closing socket, synchronize_rcu() gets called in sock_release().
+ if (close(pfSocket)) {
+ int ret = -errno;
+ ALOGE("failed to close the PF_KEY socket: %s", strerror(errno));
+ return ret;
+ }
+ return 0;
+}
+
+static inline int setrlimitForTest() {
+ // Set the memory rlimit for the test process if the default MEMLOCK rlimit is not enough.
+ struct rlimit limit = {
+ .rlim_cur = 1073741824, // 1 GiB
+ .rlim_max = 1073741824, // 1 GiB
+ };
+ int res = setrlimit(RLIMIT_MEMLOCK, &limit);
+ if (res) {
+ ALOGE("Failed to set the default MEMLOCK rlimit: %s", strerror(errno));
+ }
+ return res;
+}
+
+#define KVER(a, b, c) (((a) << 24) + ((b) << 16) + (c))
+
+static inline unsigned kernelVersion() {
+ struct utsname buf;
+ int ret = uname(&buf);
+ if (ret) return 0;
+
+ unsigned kver_major;
+ unsigned kver_minor;
+ unsigned kver_sub;
+ char unused;
+ ret = sscanf(buf.release, "%u.%u.%u%c", &kver_major, &kver_minor, &kver_sub, &unused);
+ // Check the device kernel version
+ if (ret < 3) return 0;
+
+ return KVER(kver_major, kver_minor, kver_sub);
+}
+
+static inline bool isAtLeastKernelVersion(unsigned major, unsigned minor, unsigned sub) {
+ return kernelVersion() >= KVER(major, minor, sub);
+}
+
+#define SKIP_IF_BPF_SUPPORTED \
+ do { \
+ if (android::bpf::isAtLeastKernelVersion(4, 9, 0)) { \
+ GTEST_LOG_(INFO) << "This test is skipped since bpf is supported\n"; \
+ return; \
+ } \
+ } while (0)
+
+#define SKIP_IF_BPF_NOT_SUPPORTED \
+ do { \
+ if (!android::bpf::isAtLeastKernelVersion(4, 9, 0)) { \
+ GTEST_LOG_(INFO) << "This test is skipped since bpf is not supported\n"; \
+ return; \
+ } \
+ } while (0)
+
+#define SKIP_IF_EXTENDED_BPF_NOT_SUPPORTED \
+ do { \
+ if (!android::bpf::isAtLeastKernelVersion(4, 14, 0)) { \
+ GTEST_LOG_(INFO) << "This test is skipped since extended bpf feature" \
+ << "not supported\n"; \
+ return; \
+ } \
+ } while (0)
+
+#define SKIP_IF_XDP_NOT_SUPPORTED \
+ do { \
+ if (!android::bpf::isAtLeastKernelVersion(5, 9, 0)) { \
+ GTEST_LOG_(INFO) << "This test is skipped since xdp" \
+ << "not supported\n"; \
+ return; \
+ } \
+ } while (0)
+
+} // namespace bpf
+} // namespace android
diff --git a/staticlibs/native/bpf_map_utils/include/bpf/WaitForProgsLoaded.h b/staticlibs/native/bpf_map_utils/include/bpf/WaitForProgsLoaded.h
new file mode 100644
index 0000000..bc4168e
--- /dev/null
+++ b/staticlibs/native/bpf_map_utils/include/bpf/WaitForProgsLoaded.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ * Android BPF library - public API
+ *
+ * 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 <log/log.h>
+
+#include <android-base/properties.h>
+
+namespace android {
+namespace bpf {
+
+// Wait for bpfloader to load BPF programs.
+static inline void waitForProgsLoaded() {
+ // infinite loop until success with 5/10/20/40/60/60/60... delay
+ for (int delay = 5;; delay *= 2) {
+ if (delay > 60) delay = 60;
+ if (android::base::WaitForProperty("bpf.progs_loaded", "1", std::chrono::seconds(delay)))
+ return;
+ ALOGW("Waited %ds for bpf.progs_loaded, still waiting...", delay);
+ }
+}
+
+} // namespace bpf
+} // namespace android