Skip to content

Commit

Permalink
ebpf map cpp template wrapper
Browse files Browse the repository at this point in the history
Differential Revision: D13608479

fbshipit-source-id: c23c3ec8f5220e148986585c196ce3deb6ca9d1f
  • Loading branch information
akindyakov authored and facebook-github-bot committed Jan 16, 2019
1 parent fa024cc commit 966c971
Show file tree
Hide file tree
Showing 5 changed files with 353 additions and 0 deletions.
2 changes: 2 additions & 0 deletions osquery/utils/system/linux/ebpf/BUCK
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ osquery_cxx_library(
LINUX,
[
"ebpf.h",
"map.h",
],
),
],
Expand All @@ -27,6 +28,7 @@ osquery_cxx_library(
LINUX,
[
"ebpf.cpp",
"map.cpp",
],
),
],
Expand Down
104 changes: 104 additions & 0 deletions osquery/utils/system/linux/ebpf/map.cpp
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
138 changes: 138 additions & 0 deletions osquery/utils/system/linux/ebpf/map.h
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
1 change: 1 addition & 0 deletions osquery/utils/system/linux/ebpf/tests/BUCK
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ osquery_cxx_test(
LINUX,
[
"ebpf.cpp",
"map.cpp",
],
),
],
Expand Down
108 changes: 108 additions & 0 deletions osquery/utils/system/linux/ebpf/tests/map.cpp
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

0 comments on commit 966c971

Please sign in to comment.