forked from osquery/osquery
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Differential Revision: D13608479 fbshipit-source-id: c23c3ec8f5220e148986585c196ce3deb6ca9d1f
- Loading branch information
1 parent
fa024cc
commit 966c971
Showing
5 changed files
with
353 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
/** | ||
* Copyright (c) 2014-present, Facebook, Inc. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under both the Apache 2.0 license (found in the | ||
* LICENSE file in the root directory of this source tree) and the GPLv2 (found | ||
* in the COPYING file in the root directory of this source tree). | ||
* You may select, at your option, one of the above-listed licenses. | ||
*/ | ||
|
||
#include <osquery/utils/system/linux/ebpf/map.h> | ||
#include <osquery/utils/system/linux/ebpf/ebpf.h> | ||
|
||
#include <boost/io/detail/quoted_manip.hpp> | ||
|
||
#include <cerrno> | ||
#include <cstring> | ||
|
||
namespace osquery { | ||
namespace ebpf { | ||
|
||
namespace impl { | ||
|
||
Expected<int, MapError> mapCreate(enum bpf_map_type map_type, | ||
std::size_t key_size, | ||
std::size_t value_size, | ||
std::size_t max_entries) { | ||
union bpf_attr attr; | ||
memset(&attr, 0, sizeof(union bpf_attr)); | ||
attr.map_type = map_type; | ||
attr.key_size = static_cast<std::uint32_t>(key_size); | ||
attr.value_size = static_cast<std::uint32_t>(value_size); | ||
attr.max_entries = static_cast<std::uint32_t>(max_entries); | ||
auto exp_bpf = syscall(BPF_MAP_CREATE, &attr); | ||
if (exp_bpf.isError()) { | ||
return createError( | ||
MapError::Unknown, "Creating eBPF map failed", exp_bpf.takeError()); | ||
} | ||
return exp_bpf.take(); | ||
} | ||
|
||
ExpectedSuccess<MapError> mapUpdateElement(const int fd, | ||
void const* key, | ||
void const* value, | ||
unsigned long long flags) { | ||
union bpf_attr attr; | ||
memset(&attr, 0, sizeof(union bpf_attr)); | ||
attr.map_fd = fd; | ||
attr.key = reinterpret_cast<std::uint64_t>(key); | ||
attr.value = reinterpret_cast<std::uint64_t>(value); | ||
attr.flags = flags; | ||
auto exp_bpf = syscall(BPF_MAP_UPDATE_ELEM, &attr); | ||
if (exp_bpf.isError()) { | ||
return createError(MapError::Unknown, | ||
"Updating value in eBPF map failed", | ||
exp_bpf.takeError()); | ||
} | ||
return Success{}; | ||
} | ||
|
||
ExpectedSuccess<MapError> mapLookupElement(const int fd, | ||
void const* key, | ||
void* value) { | ||
union bpf_attr attr; | ||
memset(&attr, 0, sizeof(union bpf_attr)); | ||
attr.map_fd = fd; | ||
attr.key = reinterpret_cast<std::uint64_t>(key); | ||
attr.value = reinterpret_cast<std::uint64_t>(value); | ||
auto exp_bpf = syscall(BPF_MAP_LOOKUP_ELEM, &attr); | ||
if (exp_bpf.isError()) { | ||
if (exp_bpf.getErrorCode() == PosixError::NOENT) { | ||
return createError(MapError::NoSuchKey, | ||
"No such key in the eBPF map", | ||
exp_bpf.takeError()); | ||
} | ||
return createError( | ||
MapError::Unknown, "Look up in eBPF map failed", exp_bpf.takeError()); | ||
} | ||
return Success{}; | ||
} | ||
|
||
ExpectedSuccess<MapError> mapDeleteElement(const int fd, void const* key) { | ||
union bpf_attr attr; | ||
memset(&attr, 0, sizeof(union bpf_attr)); | ||
attr.map_fd = fd; | ||
attr.key = reinterpret_cast<std::uint64_t>(key); | ||
auto exp_bpf = syscall(BPF_MAP_DELETE_ELEM, &attr); | ||
if (exp_bpf.isError()) { | ||
if (exp_bpf.getErrorCode() == PosixError::NOENT) { | ||
return createError(MapError::NoSuchKey, | ||
"No such key in the eBPF map", | ||
exp_bpf.takeError()); | ||
} | ||
return createError(MapError::Unknown, | ||
"Deleting element from eBPF map failed", | ||
exp_bpf.takeError()); | ||
} | ||
return Success{}; | ||
} | ||
|
||
} // namespace impl | ||
|
||
} // namespace ebpf | ||
} // namespace osquery |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
/** | ||
* Copyright (c) 2014-present, Facebook, Inc. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under both the Apache 2.0 license (found in the | ||
* LICENSE file in the root directory of this source tree) and the GPLv2 (found | ||
* in the COPYING file in the root directory of this source tree). | ||
* You may select, at your option, one of the above-listed licenses. | ||
*/ | ||
|
||
#pragma once | ||
|
||
#include <osquery/utils/expected/expected.h> | ||
|
||
#include <linux/bpf.h> | ||
|
||
namespace osquery { | ||
namespace ebpf { | ||
|
||
enum class MapError { | ||
Unknown = 1, | ||
NoSuchKey = 2, | ||
}; | ||
|
||
namespace impl { | ||
/** | ||
* Do not use implementation functions directly, use Map class to create and | ||
* manage eBPF map | ||
*/ | ||
Expected<int, MapError> mapCreate(enum bpf_map_type map_type, | ||
std::size_t key_size, | ||
std::size_t value_size, | ||
std::size_t max_entries); | ||
ExpectedSuccess<MapError> mapUpdateElement(int fd, | ||
void const* key, | ||
void const* value, | ||
unsigned long long flags); | ||
ExpectedSuccess<MapError> mapLookupElement(int fd, | ||
void const* key, | ||
void* value); | ||
ExpectedSuccess<MapError> mapDeleteElement(int fd, void const* key); | ||
|
||
} // namespace impl | ||
|
||
/** | ||
* Proxy object for the eBPF map structure in kernel. | ||
*/ | ||
|
||
template <typename KeyType, typename ValueType, enum bpf_map_type map_type> | ||
class Map final { | ||
private: | ||
static_assert( | ||
std::is_pod<KeyType>::value && std::is_pod<ValueType>::value, | ||
"Both key type and value type must be a plain old data type (POD)"); | ||
|
||
/** | ||
* The only constructor of Map is private for purpose. Use createMap function | ||
* instead. Map should not be created in case of creating eBPF map failure. | ||
*/ | ||
explicit Map(int fd, std::size_t size) : fd_(fd), size_(size) {} | ||
|
||
public: | ||
~Map() { | ||
if (fd_ >= 0) { | ||
close(fd_); | ||
} | ||
} | ||
|
||
Map(Map const&) = delete; | ||
|
||
Map(Map && from) : fd_(from.fd_), size_(from.size_) { | ||
from.fd_ = -1; | ||
} | ||
|
||
Map& operator=(Map const&) = delete; | ||
|
||
Map& operator=(Map&& from) { | ||
if (fd_ >= 0) { | ||
close(fd_); | ||
fd_ = -1; | ||
} | ||
std::swap(fd_, from.fd_); | ||
std::swap(size_, from.size_); | ||
} | ||
|
||
Expected<ValueType, MapError> lookupElement(KeyType const& key) const { | ||
auto value = ValueType{}; | ||
auto exp = impl::mapLookupElement( | ||
fd_, static_cast<void const*>(&key), static_cast<void*>(&value)); | ||
if (exp.isError()) { | ||
return exp.takeError(); | ||
} | ||
return value; | ||
} | ||
|
||
ExpectedSuccess<MapError> updateElement(KeyType const& key, | ||
ValueType const& value, | ||
unsigned long long flags = BPF_ANY) { | ||
return impl::mapUpdateElement(fd_, | ||
static_cast<void const*>(&key), | ||
static_cast<void const*>(&value), | ||
flags); | ||
} | ||
|
||
ExpectedSuccess<MapError> deleteElement(KeyType const& key) { | ||
return impl::mapDeleteElement(fd_, static_cast<void const*>(&key)); | ||
} | ||
|
||
std::size_t size() const { | ||
return size_; | ||
} | ||
|
||
int fd() const { | ||
return fd_; | ||
} | ||
|
||
template <typename KType, typename VType, enum bpf_map_type type> | ||
friend Expected<Map<KType, VType, type>, MapError> createMap( | ||
std::size_t size); | ||
|
||
private: | ||
int fd_ = -1; | ||
std::size_t size_; | ||
}; | ||
|
||
template <typename KeyType, typename ValueType, enum bpf_map_type map_type> | ||
static Expected<Map<KeyType, ValueType, map_type>, MapError> createMap( | ||
std::size_t size) { | ||
auto exp = | ||
impl::mapCreate(map_type, sizeof(KeyType), sizeof(ValueType), size); | ||
if (exp.isError()) { | ||
return exp.takeError(); | ||
} | ||
return Map<KeyType, ValueType, map_type>(exp.take(), size); | ||
} | ||
|
||
} // namespace ebpf | ||
} // namespace osquery |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,7 @@ osquery_cxx_test( | |
LINUX, | ||
[ | ||
"ebpf.cpp", | ||
"map.cpp", | ||
], | ||
), | ||
], | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
/** | ||
* Copyright (c) 2014-present, Facebook, Inc. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under both the Apache 2.0 license (found in the | ||
* LICENSE file in the root directory of this source tree) and the GPLv2 (found | ||
* in the COPYING file in the root directory of this source tree). | ||
* You may select, at your option, one of the above-listed licenses. | ||
*/ | ||
|
||
#include <osquery/utils/system/linux/ebpf/map.h> | ||
#include <osquery/utils/system/linux/ebpf/ebpf.h> | ||
|
||
#include <osquery/logger.h> | ||
|
||
#include <gtest/gtest.h> | ||
|
||
namespace osquery { | ||
namespace { | ||
|
||
class EbpfMapTests : public testing::Test {}; | ||
|
||
TEST_F(EbpfMapTests, int_key_int_value) { | ||
if (!ebpf::isSupportedBySystem()) { | ||
LOG(WARNING) << "This system does not support eBPF of required vesion, " | ||
"test will be skipped"; | ||
return; | ||
} | ||
auto const size = std::size_t{12}; | ||
auto map_exp = ebpf::createMap<int, int, BPF_MAP_TYPE_HASH>(size); | ||
ASSERT_TRUE(map_exp.isValue()) | ||
<< map_exp.getError().getFullMessageRecursive(); | ||
auto map = map_exp.take(); | ||
ASSERT_EQ(map.size(), size); | ||
{ | ||
auto exp = map.lookupElement(0); | ||
ASSERT_TRUE(exp.isError()); | ||
EXPECT_EQ(exp.getError().getErrorCode(), ebpf::MapError::NoSuchKey); | ||
} | ||
{ | ||
auto exp = map.lookupElement(215); | ||
ASSERT_TRUE(exp.isError()); | ||
EXPECT_EQ(exp.getError().getErrorCode(), ebpf::MapError::NoSuchKey); | ||
} | ||
{ | ||
auto exp = map.updateElement(5, 53); | ||
ASSERT_TRUE(exp.isValue()) << exp.getError().getFullMessageRecursive(); | ||
} | ||
{ | ||
auto exp = map.lookupElement(5); | ||
ASSERT_TRUE(exp.isValue()) << exp.getError().getFullMessageRecursive(); | ||
ASSERT_EQ(exp.get(), 53); | ||
} | ||
{ | ||
// key could be greater a size, because it is a hash map | ||
auto exp = map.updateElement(207, 8042); | ||
ASSERT_TRUE(exp.isValue()) << exp.getError().getFullMessageRecursive(); | ||
} | ||
{ | ||
auto exp = map.lookupElement(207); | ||
ASSERT_TRUE(exp.isValue()) << exp.getError().getFullMessageRecursive(); | ||
ASSERT_EQ(exp.get(), 8042); | ||
} | ||
{ | ||
// let's try to delete some existing key | ||
auto exp = map.deleteElement(207); | ||
ASSERT_TRUE(exp.isValue()) << exp.getError().getFullMessageRecursive(); | ||
} | ||
{ | ||
auto exp = map.lookupElement(207); | ||
ASSERT_TRUE(exp.isError()); | ||
EXPECT_EQ(exp.getError().getErrorCode(), ebpf::MapError::NoSuchKey); | ||
} | ||
} | ||
|
||
TEST_F(EbpfMapTests, int_key_struct_value) { | ||
if (!ebpf::isSupportedBySystem()) { | ||
LOG(WARNING) << "This system does not support eBPF of required vesion, " | ||
"test will be skipped"; | ||
return; | ||
} | ||
struct Value { | ||
int left; | ||
int right; | ||
}; | ||
auto const size = std::size_t{128}; | ||
auto map_exp = ebpf::createMap<int, Value, BPF_MAP_TYPE_ARRAY>(size); | ||
ASSERT_TRUE(map_exp.isValue()); | ||
auto map = map_exp.take(); | ||
ASSERT_EQ(map.size(), size); | ||
{ | ||
auto const v = Value{ | ||
.left = -9287, | ||
.right = 2781, | ||
}; | ||
auto exp = map.updateElement(72, v); | ||
ASSERT_TRUE(exp.isValue()) << exp.getError().getFullMessageRecursive(); | ||
} | ||
{ | ||
auto exp = map.lookupElement(72); | ||
ASSERT_TRUE(exp.isValue()) << exp.getError().getFullMessageRecursive(); | ||
EXPECT_EQ(exp.get().left, -9287); | ||
EXPECT_EQ(exp.get().right, 2781); | ||
} | ||
} | ||
|
||
} // namespace | ||
} // namespace osquery |