/* * 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 "BpfSyscallWrappers.h" #include "bpf/BpfUtils.h" #include #include namespace android { namespace bpf { using base::Result; using base::unique_fd; using std::function; #ifdef BPF_MAP_MAKE_VISIBLE_FOR_TESTING #undef BPFMAP_VERBOSE_ABORT #define BPFMAP_VERBOSE_ABORT #endif [[noreturn]] __attribute__((__format__(__printf__, 2, 3))) static inline void Abort(int __unused error, const char* __unused fmt, ...) { #ifdef BPFMAP_VERBOSE_ABORT va_list va; va_start(va, fmt); fflush(stdout); vfprintf(stderr, fmt, va); if (error) fprintf(stderr, "; errno=%d [%s]", error, strerror(error)); putc('\n', stderr); fflush(stderr); va_end(va); #endif abort(); } // 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 BpfMapRO { public: BpfMapRO() {}; // explicitly force no copy constructor, since it would need to dup the fd // (later on, for testing, we still make available a copy assignment operator) BpfMapRO(const BpfMapRO&) = delete; protected: void abortOnMismatch(bool writable) const { if (!mMapFd.ok()) Abort(errno, "mMapFd %d is not valid", mMapFd.get()); if (isAtLeastKernelVersion(4, 14, 0)) { int flags = bpfGetFdMapFlags(mMapFd); if (flags < 0) Abort(errno, "bpfGetFdMapFlags fail: flags=%d", flags); if (flags & BPF_F_WRONLY) Abort(0, "map is write-only (flags=0x%X)", flags); if (writable && (flags & BPF_F_RDONLY)) Abort(0, "writable map is actually read-only (flags=0x%X)", flags); int keySize = bpfGetFdKeySize(mMapFd); if (keySize != sizeof(Key)) Abort(errno, "map key size mismatch (expected=%zu, actual=%d)", sizeof(Key), keySize); int valueSize = bpfGetFdValueSize(mMapFd); if (valueSize != sizeof(Value)) Abort(errno, "map value size mismatch (expected=%zu, actual=%d)", sizeof(Value), valueSize); } } public: explicit BpfMapRO(const char* pathname) { mMapFd.reset(mapRetrieveRO(pathname)); abortOnMismatch(/* writable */ false); } Result getFirstKey() const { Key firstKey; if (getFirstMapKey(mMapFd, &firstKey)) { return ErrnoErrorf("BpfMap::getFirstKey() failed"); } return firstKey; } Result getNextKey(const Key& key) const { Key nextKey; if (getNextMapKey(mMapFd, &key, &nextKey)) { return ErrnoErrorf("BpfMap::getNextKey() failed"); } return nextKey; } Result readValue(const Key key) const { Value value; if (findMapEntry(mMapFd, &key, &value)) { return ErrnoErrorf("BpfMap::readValue() failed"); } return value; } protected: [[clang::reinitializes]] Result init(const char* path, int fd, bool writable) { mMapFd.reset(fd); if (!mMapFd.ok()) { return ErrnoErrorf("Pinned map not accessible or does not exist: ({})", path); } // Normally we should return an error here instead of calling abort, // but this cannot happen at runtime without a massive code bug (K/V type mismatch) // and as such it's better to just blow the system up and let the developer fix it. // Crashes are much more likely to be noticed than logs and missing functionality. abortOnMismatch(writable); return {}; } public: // Function that tries to get map from a pinned path. [[clang::reinitializes]] Result init(const char* path) { return init(path, mapRetrieveRO(path), /* writable */ false); } // Iterate through the map and handle each key retrieved based on the filter // without modification of map content. Result iterate( const function(const Key& key, const BpfMapRO& map)>& filter) const; // Iterate through the map and get each pair, handle each pair based on the filter without modification of map content. Result iterateWithValue( const function(const Key& key, const Value& value, const BpfMapRO& map)>& filter) const; #ifdef BPF_MAP_MAKE_VISIBLE_FOR_TESTING const unique_fd& getMap() const { return mMapFd; }; // Copy assignment operator - due to need for fd duping, should not be used in non-test code. BpfMapRO& operator=(const BpfMapRO& other) { if (this != &other) mMapFd.reset(fcntl(other.mMapFd.get(), F_DUPFD_CLOEXEC, 0)); return *this; } #else BpfMapRO& operator=(const BpfMapRO&) = delete; #endif // Move assignment operator BpfMapRO& operator=(BpfMapRO&& other) noexcept { if (this != &other) { mMapFd = std::move(other.mMapFd); other.reset(); } return *this; } #ifdef BPF_MAP_MAKE_VISIBLE_FOR_TESTING // Note that unique_fd.reset() carefully saves and restores the errno, // and BpfMap.reset() won't touch the errno if passed in fd is negative either, // hence you can do something like BpfMap.reset(systemcall()) and then // check BpfMap.isValid() and look at errno and see why systemcall() failed. [[clang::reinitializes]] void reset(int fd) { mMapFd.reset(fd); if (mMapFd.ok()) abortOnMismatch(/* writable */ false); // false isn't ideal } // unique_fd has an implicit int conversion defined, which combined with the above // reset(int) would result in double ownership of the fd, hence we either need a custom // implementation of reset(unique_fd), or to delete it and thus cause compile failures // to catch this and prevent it. void reset(unique_fd fd) = delete; #endif [[clang::reinitializes]] void reset() { mMapFd.reset(); } bool isValid() const { return mMapFd.ok(); } Result isEmpty() const { auto key = getFirstKey(); if (key.ok()) return false; if (key.error().code() == ENOENT) return true; return key.error(); } protected: unique_fd mMapFd; }; template Result BpfMapRO::iterate( const function(const Key& key, const BpfMapRO& map)>& filter) const { Result curKey = getFirstKey(); while (curKey.ok()) { const Result& nextKey = getNextKey(curKey.value()); Result status = filter(curKey.value(), *this); if (!status.ok()) return status; curKey = nextKey; } if (curKey.error().code() == ENOENT) return {}; return curKey.error(); } template Result BpfMapRO::iterateWithValue( const function(const Key& key, const Value& value, const BpfMapRO& map)>& filter) const { Result curKey = getFirstKey(); while (curKey.ok()) { const Result& nextKey = getNextKey(curKey.value()); Result curValue = readValue(curKey.value()); if (!curValue.ok()) return curValue.error(); 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 BpfMap : public BpfMapRO { protected: using BpfMapRO::mMapFd; using BpfMapRO::abortOnMismatch; public: using BpfMapRO::getFirstKey; using BpfMapRO::getNextKey; using BpfMapRO::readValue; BpfMap() {}; explicit BpfMap(const char* pathname) { mMapFd.reset(mapRetrieveRW(pathname)); abortOnMismatch(/* writable */ true); } // Function that tries to get map from a pinned path. [[clang::reinitializes]] Result init(const char* path) { return BpfMapRO::init(path, mapRetrieveRW(path), /* writable */ true); } Result writeValue(const Key& key, const Value& value, uint64_t flags) { if (writeToMapEntry(mMapFd, &key, &value, flags)) { return ErrnoErrorf("BpfMap::writeValue() failed"); } return {}; } Result deleteValue(const Key& key) { if (deleteMapEntry(mMapFd, &key)) { return ErrnoErrorf("BpfMap::deleteValue() failed"); } return {}; } 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(); } } } #ifdef BPF_MAP_MAKE_VISIBLE_FOR_TESTING [[clang::reinitializes]] Result 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 // Iterate through the map and handle each key retrieved based on the filter // without modification of map content. Result iterate( const 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. Result iterateWithValue( const 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 Result iterate( const function(const Key& key, BpfMap& map)>& filter); // Iterate through the map and get each pair, handle each pair based on the filter. Result iterateWithValue( const function(const Key& key, const Value& value, BpfMap& map)>& filter); }; template Result BpfMap::iterate( const function(const Key& key, const BpfMap& map)>& filter) const { Result curKey = getFirstKey(); while (curKey.ok()) { const Result& nextKey = getNextKey(curKey.value()); Result status = filter(curKey.value(), *this); if (!status.ok()) return status; curKey = nextKey; } if (curKey.error().code() == ENOENT) return {}; return curKey.error(); } template Result BpfMap::iterateWithValue( const function(const Key& key, const Value& value, const BpfMap& map)>& filter) const { Result curKey = getFirstKey(); while (curKey.ok()) { const Result& nextKey = getNextKey(curKey.value()); Result curValue = readValue(curKey.value()); if (!curValue.ok()) return curValue.error(); 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 Result BpfMap::iterate( const function(const Key& key, BpfMap& map)>& filter) { Result curKey = getFirstKey(); while (curKey.ok()) { const Result& nextKey = getNextKey(curKey.value()); Result status = filter(curKey.value(), *this); if (!status.ok()) return status; curKey = nextKey; } if (curKey.error().code() == ENOENT) return {}; return curKey.error(); } template Result BpfMap::iterateWithValue( const function(const Key& key, const Value& value, BpfMap& map)>& filter) { Result curKey = getFirstKey(); while (curKey.ok()) { const Result& nextKey = getNextKey(curKey.value()); Result curValue = readValue(curKey.value()); if (!curValue.ok()) return curValue.error(); Result status = filter(curKey.value(), curValue.value(), *this); if (!status.ok()) return status; curKey = nextKey; } if (curKey.error().code() == ENOENT) return {}; return curKey.error(); } } // namespace bpf } // namespace android