BpfMap.h: introduce BpfMapRW

which allows writes, but not deletes.

This is technically what bpf map arrays are capable of.

Test: TreeHugger
Flag: EXEMPT mainline
Signed-off-by: Maciej Żenczykowski <maze@google.com>
Change-Id: I1f73bea5428c190af17442539c296282ce9e0786
This commit is contained in:
Maciej Żenczykowski 2025-08-08 14:34:32 -07:00
commit 8b96286200
9 changed files with 70 additions and 50 deletions

View file

@ -67,17 +67,17 @@ class BpfMapTest : public testing::Test {
}
}
void checkMapInvalid(BpfMap<uint32_t, uint32_t>& map) {
void checkMapInvalid(BpfMapRO<uint32_t, uint32_t>& map) {
EXPECT_FALSE(map.isValid());
EXPECT_EQ(-1, map.getMap().get());
}
void checkMapValid(BpfMap<uint32_t, uint32_t>& map) {
void checkMapValid(BpfMapRO<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) {
void writeToMapAndCheck(BpfMapRW<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));
@ -89,27 +89,39 @@ class BpfMapTest : public testing::Test {
ASSERT_EQ(refValue, value.value());
}
void populateMap(uint32_t total, BpfMap<uint32_t, uint32_t>& map) {
void populateMap(uint32_t total, BpfMapRW<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) {
void expectMapEmpty(BpfMapRO<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);
TEST_F(BpfMapTest, constructorRO) {
BpfMapRO<uint32_t, uint32_t> testMap;
checkMapInvalid(testMap);
}
BpfMap<uint32_t, uint32_t> testMap2;
ASSERT_RESULT_OK(testMap2.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC));
checkMapValid(testMap2);
TEST_F(BpfMapTest, constructorRW) {
BpfMapRW<uint32_t, uint32_t> testMap;
checkMapInvalid(testMap);
ASSERT_RESULT_OK(testMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC));
checkMapValid(testMap);
}
TEST_F(BpfMapTest, constructor) {
BpfMap<uint32_t, uint32_t> testMap;
checkMapInvalid(testMap);
ASSERT_RESULT_OK(testMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC));
checkMapValid(testMap);
}
TEST_F(BpfMapTest, basicHelpers) {
@ -128,7 +140,7 @@ TEST_F(BpfMapTest, basicHelpers) {
}
TEST_F(BpfMapTest, reset) {
BpfMap<uint32_t, uint32_t> testMap;
BpfMapRW<uint32_t, uint32_t> testMap;
ASSERT_RESULT_OK(testMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC));
uint32_t key = TEST_KEY1;
uint32_t value_write = TEST_VALUE1;
@ -141,9 +153,9 @@ TEST_F(BpfMapTest, reset) {
}
TEST_F(BpfMapTest, moveConstructor) {
BpfMap<uint32_t, uint32_t> testMap1;
BpfMapRW<uint32_t, uint32_t> testMap1;
ASSERT_RESULT_OK(testMap1.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC));
BpfMap<uint32_t, uint32_t> testMap2;
BpfMapRW<uint32_t, uint32_t> testMap2;
testMap2 = std::move(testMap1);
uint32_t key = TEST_KEY1;
checkMapInvalid(testMap1);
@ -153,12 +165,12 @@ TEST_F(BpfMapTest, moveConstructor) {
TEST_F(BpfMapTest, SetUpMap) {
EXPECT_NE(0, access(PINNED_MAP_PATH, R_OK));
BpfMap<uint32_t, uint32_t> testMap1;
BpfMapRW<uint32_t, uint32_t> testMap1;
ASSERT_RESULT_OK(testMap1.resetMap(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;
BpfMapRW<uint32_t, uint32_t> testMap2;
EXPECT_RESULT_OK(testMap2.init(PINNED_MAP_PATH));
checkMapValid(testMap2);
uint32_t key = TEST_KEY1;
@ -250,7 +262,7 @@ TEST_F(BpfMapTest, mapClear) {
}
TEST_F(BpfMapTest, testGTSbitmapTestOpen) {
BpfMap<int, uint64_t> bitmap;
BpfMapRW<int, uint64_t> bitmap;
ASSERT_RESULT_OK(bitmap.init("/sys/fs/bpf/tethering/map_test_bitmap"));
}

View file

@ -257,19 +257,15 @@ class BpfMapRO {
};
template <class Key, class Value>
class BpfMap : public BpfMapRO<Key, Value> {
class BpfMapRW : public BpfMapRO<Key, Value> {
protected:
using BpfMapRO<Key, Value>::mMapFd;
using BpfMapRO<Key, Value>::abortOnMismatch;
public:
using BpfMapRO<Key, Value>::getFirstKey;
using BpfMapRO<Key, Value>::getNextKey;
using BpfMapRO<Key, Value>::readValue;
using BpfMapRO<Key, Value>::BpfMapRO;
BpfMap<Key, Value>() {};
explicit BpfMap<Key, Value>(const char* pathname) {
explicit BpfMapRW<Key, Value>(const char* pathname) {
mMapFd.reset(mapRetrieveRW(pathname));
abortOnMismatch(/* writable */ true);
}
@ -286,6 +282,30 @@ class BpfMap : public BpfMapRO<Key, Value> {
return {};
}
#ifdef BPF_MAP_MAKE_VISIBLE_FOR_TESTING
[[clang::reinitializes]] Result<void> resetMap(bpf_map_type map_type,
uint32_t max_entries,
uint32_t map_flags = 0) {
if (map_flags & BPF_F_WRONLY) Abort(0, "map_flags is write-only");
if (map_flags & BPF_F_RDONLY) Abort(0, "map_flags is read-only");
mMapFd.reset(createMap(map_type, sizeof(Key), sizeof(Value), max_entries,
map_flags));
if (!mMapFd.ok()) return ErrnoErrorf("BpfMap::resetMap() failed");
abortOnMismatch(/* writable */ true);
return {};
}
#endif
};
template <class Key, class Value>
class BpfMap : public BpfMapRW<Key, Value> {
protected:
using BpfMapRW<Key, Value>::mMapFd;
public:
using BpfMapRW<Key, Value>::BpfMapRW;
using BpfMapRW<Key, Value>::getFirstKey;
Result<void> deleteValue(const Key& key) {
if (deleteMapEntry(mMapFd, &key)) {
return ErrnoErrorf("BpfMap::deleteValue() failed");
@ -309,20 +329,6 @@ class BpfMap : public BpfMapRO<Key, Value> {
}
}
}
#ifdef BPF_MAP_MAKE_VISIBLE_FOR_TESTING
[[clang::reinitializes]] Result<void> resetMap(bpf_map_type map_type,
uint32_t max_entries,
uint32_t map_flags = 0) {
if (map_flags & BPF_F_WRONLY) Abort(0, "map_flags is write-only");
if (map_flags & BPF_F_RDONLY) Abort(0, "map_flags is read-only");
mMapFd.reset(createMap(map_type, sizeof(Key), sizeof(Value), max_entries,
map_flags));
if (!mMapFd.ok()) return ErrnoErrorf("BpfMap::resetMap() failed");
abortOnMismatch(/* writable */ true);
return {};
}
#endif
};
} // namespace bpf

View file

@ -54,7 +54,7 @@ class BpfBasicTest : public NetNativeTestBase {
};
TEST_F(BpfBasicTest, TestTagSocket) {
BpfMap<uint64_t, UidTagValue> cookieTagMap(COOKIE_TAG_MAP_PATH);
BpfMapRO<uint64_t, UidTagValue> cookieTagMap(COOKIE_TAG_MAP_PATH);
ASSERT_TRUE(cookieTagMap.isValid());
int sock = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
ASSERT_LE(0, sock);
@ -72,7 +72,7 @@ TEST_F(BpfBasicTest, TestTagSocket) {
}
TEST_F(BpfBasicTest, TestCloseSocketWithoutUntag) {
BpfMap<uint64_t, UidTagValue> cookieTagMap(COOKIE_TAG_MAP_PATH);
BpfMapRO<uint64_t, UidTagValue> cookieTagMap(COOKIE_TAG_MAP_PATH);
ASSERT_TRUE(cookieTagMap.isValid());
int sock = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
ASSERT_LE(0, sock);

View file

@ -22,6 +22,7 @@
using android::bpf::BpfMap;
using android::bpf::BpfMapRO;
using android::bpf::BpfMapRW;
namespace android {
namespace net {

View file

@ -40,8 +40,8 @@ namespace bpf {
using base::Result;
BpfMap<uint32_t, IfaceValue>& getIfaceIndexNameMap() {
static BpfMap<uint32_t, IfaceValue> ifaceIndexNameMap(IFACE_INDEX_NAME_MAP_PATH);
BpfMapRW<uint32_t, IfaceValue>& getIfaceIndexNameMap() {
static BpfMapRW<uint32_t, IfaceValue> ifaceIndexNameMap(IFACE_INDEX_NAME_MAP_PATH);
return ifaceIndexNameMap;
}

View file

@ -72,10 +72,10 @@ class BpfNetworkStatsHelperTest : public testing::Test {
protected:
BpfNetworkStatsHelperTest() {}
BpfMap<uint64_t, UidTagValue> mFakeCookieTagMap;
BpfMap<uint32_t, StatsValue> mFakeAppUidStatsMap;
BpfMap<StatsKey, StatsValue> mFakeStatsMap;
BpfMap<uint32_t, IfaceValue> mFakeIfaceIndexNameMap;
BpfMap<uint32_t, StatsValue> mFakeIfaceStatsMap;
BpfMapRW<uint32_t, StatsValue> mFakeAppUidStatsMap;
BpfMapRW<StatsKey, StatsValue> mFakeStatsMap;
BpfMapRW<uint32_t, IfaceValue> mFakeIfaceIndexNameMap;
BpfMapRW<uint32_t, StatsValue> mFakeIfaceStatsMap;
IfIndexToNameFunc mIfIndex2Name = [this](const uint32_t ifindex){
return mFakeIfaceIndexNameMap.readValue(ifindex);
@ -108,7 +108,7 @@ class BpfNetworkStatsHelperTest : public testing::Test {
}
void populateFakeStats(uid_t uid, uint32_t tag, uint32_t ifaceIndex, uint32_t counterSet,
StatsValue value, BpfMap<StatsKey, StatsValue>& map) {
StatsValue value, BpfMapRW<StatsKey, StatsValue>& map) {
StatsKey key = {
.uid = (uint32_t)uid, .tag = tag, .counterSet = counterSet, .ifaceIndex = ifaceIndex};
EXPECT_RESULT_OK(map.writeValue(key, value, BPF_ANY));

View file

@ -57,7 +57,7 @@ bool operator<(const stats_line& lhs, const stats_line& rhs);
// This mirrors BpfMap.h's:
// Result<Value> readValue(const Key key) const
// for a BpfMap<uint32_t, IfaceValue>
// for a BpfMapRW<uint32_t, IfaceValue>
using IfIndexToNameFunc = std::function<Result<IfaceValue>(const uint32_t)>;
// For test only

View file

@ -88,7 +88,7 @@ class NetworkTracePoller {
std::unique_ptr<BpfRingbuf<PacketTrace>> mRingBuffer GUARDED_BY(mBufferMutex);
// The packet tracing config map (really a 1-element array).
BpfMap<uint32_t, bool> mConfigurationMap GUARDED_BY(mMutex);
BpfMapRW<uint32_t, bool> mConfigurationMap GUARDED_BY(mMutex);
// This must be the last member, causing it to be the first deleted. If it is
// not, members required for callbacks can be deleted before it's stopped.

View file

@ -24,6 +24,7 @@
using android::base::Result;
using android::bpf::BpfMap;
using android::bpf::BpfMapRW;
class Firewall {
public:
@ -37,8 +38,8 @@ class Firewall {
Result<bool> getDataSaverSetting();
Result<void> setDataSaver(bool enabled);
private:
BpfMap<uint32_t, uint32_t> mConfigurationMap GUARDED_BY(mMutex);
BpfMapRW<uint32_t, uint32_t> mConfigurationMap GUARDED_BY(mMutex);
BpfMap<uint32_t, UidOwnerValue> mUidOwnerMap GUARDED_BY(mMutex);
BpfMap<uint32_t, bool> mDataSaverEnabledMap GUARDED_BY(mMutex);
BpfMapRW<uint32_t, bool> mDataSaverEnabledMap GUARDED_BY(mMutex);
std::mutex mMutex;
};