/* * 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 #include #include #include #include #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 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 BpfMap { public: BpfMap() {}; protected: // flag must be within BPF_OBJ_FLAG_MASK, ie. 0, BPF_F_RDONLY, BPF_F_WRONLY BpfMap(const char* pathname, uint32_t flags) { int map_fd = mapRetrieve(pathname, flags); if (map_fd >= 0) mMapFd.reset(map_fd); } public: explicit BpfMap(const char* pathname) : BpfMap(pathname, 0) {} BpfMap(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 getFirstKey() const { Key firstKey; if (getFirstMapKey(mMapFd, &firstKey)) { return ErrnoErrorf("Get firstKey map {} failed", mMapFd.get()); } return firstKey; } base::Result 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 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 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 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 init(const char* path); // Iterate through the map and handle each key retrieved based on the filter // without modification of map content. base::Result iterate( const std::function(const Key& key, const BpfMap& map)>& filter) const; // Iterate through the map and get each pair, handle each pair based on the filter without modification of map content. base::Result iterateWithValue( const std::function(const Key& key, const Value& value, const BpfMap& map)>& filter) const; // Iterate through the map and handle each key retrieved based on the filter base::Result iterate( const std::function(const Key& key, BpfMap& map)>& filter); // Iterate through the map and get each pair, handle each pair based on the filter. base::Result iterateWithValue( const std::function(const Key& key, const Value& value, BpfMap& map)>& filter); const base::unique_fd& getMap() const { return mMapFd; }; // Copy assignment operator BpfMap& operator=(const BpfMap& other) { if (this != &other) mMapFd.reset(fcntl(other.mMapFd.get(), F_DUPFD_CLOEXEC, 0)); return *this; } // Move assignment operator BpfMap& operator=(BpfMap&& 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 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 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 base::Result BpfMap::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 base::Result BpfMap::iterate( const std::function(const Key& key, const BpfMap& map)>& filter) const { base::Result curKey = getFirstKey(); while (curKey.ok()) { const base::Result& nextKey = getNextKey(curKey.value()); base::Result status = filter(curKey.value(), *this); if (!status.ok()) return status; curKey = nextKey; } if (curKey.error().code() == ENOENT) return {}; return curKey.error(); } template base::Result BpfMap::iterateWithValue( const std::function(const Key& key, const Value& value, const BpfMap& map)>& filter) const { base::Result curKey = getFirstKey(); while (curKey.ok()) { const base::Result& nextKey = getNextKey(curKey.value()); base::Result curValue = readValue(curKey.value()); if (!curValue.ok()) return curValue.error(); base::Result status = filter(curKey.value(), curValue.value(), *this); if (!status.ok()) return status; curKey = nextKey; } if (curKey.error().code() == ENOENT) return {}; return curKey.error(); } template base::Result BpfMap::iterate( const std::function(const Key& key, BpfMap& map)>& filter) { base::Result curKey = getFirstKey(); while (curKey.ok()) { const base::Result& nextKey = getNextKey(curKey.value()); base::Result status = filter(curKey.value(), *this); if (!status.ok()) return status; curKey = nextKey; } if (curKey.error().code() == ENOENT) return {}; return curKey.error(); } template base::Result BpfMap::iterateWithValue( const std::function(const Key& key, const Value& value, BpfMap& map)>& filter) { base::Result curKey = getFirstKey(); while (curKey.ok()) { const base::Result& nextKey = getNextKey(curKey.value()); base::Result curValue = readValue(curKey.value()); if (!curValue.ok()) return curValue.error(); base::Result 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 BpfMapRO : public BpfMap { public: explicit BpfMapRO(const char* pathname) : BpfMap(pathname, BPF_F_RDONLY) {} }; } // namespace bpf } // namespace android