uint8_t qname[255];
};
-struct QNameValue
+struct QNameAndQTypeKey
{
- uint64_t counter;
+ uint8_t qname[255];
uint16_t qtype;
};
-BPFFilter::Map::Map(const BPFFilter::MapConfiguration& config): d_config(config)
+struct QNameValue
+{
+ uint64_t counter{0};
+ uint16_t qtype{0};
+};
+
+struct CounterAndActionValue
+{
+ uint64_t counter{0};
+ BPFFilter::MatchAction action{BPFFilter::MatchAction::Pass};
+};
+
+BPFFilter::Map::Map(const BPFFilter::MapConfiguration& config, BPFFilter::MapFormat format): d_config(config)
{
if (d_config.d_type == BPFFilter::MapType::Filters) {
/* special case, this is a map of eBPF programs */
else {
int keySize = 0;
int valueSize = 0;
- switch (d_config.d_type) {
- case MapType::IPv4:
- keySize = sizeof(uint32_t);
- valueSize = sizeof(uint64_t);
- break;
- case MapType::IPv6:
- keySize = sizeof(KeyV6);
- valueSize = sizeof(uint64_t);
- break;
- case MapType::QNames:
- keySize = sizeof(QNameKey);
- valueSize = sizeof(QNameValue);
- break;
- default:
- throw std::runtime_error("Unsupported eBPF map type: " + std::to_string(static_cast<uint8_t>(d_config.d_type)));
+ if (format == MapFormat::Legacy) {
+ switch (d_config.d_type) {
+ case MapType::IPv4:
+ keySize = sizeof(uint32_t);
+ valueSize = sizeof(uint64_t);
+ break;
+ case MapType::IPv6:
+ keySize = sizeof(KeyV6);
+ valueSize = sizeof(uint64_t);
+ break;
+ case MapType::QNames:
+ keySize = sizeof(QNameKey);
+ valueSize = sizeof(QNameValue);
+ break;
+ default:
+ throw std::runtime_error("Unsupported eBPF map type: " + std::to_string(static_cast<uint8_t>(d_config.d_type)));
+ }
+ }
+ else {
+ switch (d_config.d_type) {
+ case MapType::IPv4:
+ keySize = sizeof(uint32_t);
+ valueSize = sizeof(CounterAndActionValue);
+ break;
+ case MapType::IPv6:
+ keySize = sizeof(KeyV6);
+ valueSize = sizeof(CounterAndActionValue);
+ break;
+ case MapType::QNames:
+ keySize = sizeof(QNameAndQTypeKey);
+ valueSize = sizeof(CounterAndActionValue);
+ break;
+ default:
+ throw std::runtime_error("Unsupported eBPF map type: " + std::to_string(static_cast<uint8_t>(d_config.d_type)));
+ }
}
if (!d_config.d_pinnedPath.empty()) {
}
}
else if (d_config.d_type == MapType::QNames) {
- QNameKey key;
- memset(&key, 0, sizeof(key));
- int res = bpf_get_next_key(d_fd.getHandle(), &key, &key);
- while (res == 0) {
- ++d_count;
- res = bpf_get_next_key(d_fd.getHandle(), &key, &key);
+ if (format == MapFormat::Legacy) {
+ QNameKey key;
+ memset(&key, 0, sizeof(key));
+ int res = bpf_get_next_key(d_fd.getHandle(), &key, &key);
+ while (res == 0) {
+ ++d_count;
+ res = bpf_get_next_key(d_fd.getHandle(), &key, &key);
+ }
+ }
+ else {
+ QNameAndQTypeKey key;
+ memset(&key, 0, sizeof(key));
+ int res = bpf_get_next_key(d_fd.getHandle(), &key, &key);
+ while (res == 0) {
+ ++d_count;
+ res = bpf_get_next_key(d_fd.getHandle(), &key, &key);
+ }
}
}
}
}
-BPFFilter::BPFFilter(const BPFFilter::MapConfiguration& v4, const BPFFilter::MapConfiguration& v6, const BPFFilter::MapConfiguration& qnames)
+BPFFilter::BPFFilter(const BPFFilter::MapConfiguration& v4, const BPFFilter::MapConfiguration& v6, const BPFFilter::MapConfiguration& qnames, BPFFilter::MapFormat format, bool external): d_mapFormat(format), d_external(external)
{
+ if (d_mapFormat != BPFFilter::MapFormat::Legacy && !d_external) {
+ throw std::runtime_error("Unsupported eBPF map format, the current internal implemenation only supports the legacy format");
+ }
+
auto maps = d_maps.lock();
- maps->d_v4 = BPFFilter::Map(v4);
- maps->d_v6 = BPFFilter::Map(v6);
- maps->d_qnames = BPFFilter::Map(qnames);
- BPFFilter::MapConfiguration filters;
- filters.d_maxItems = 1;
- filters.d_type = BPFFilter::MapType::Filters;
- maps->d_filters = BPFFilter::Map(filters);
+ maps->d_v4 = BPFFilter::Map(v4, d_mapFormat);
+ maps->d_v6 = BPFFilter::Map(v6, d_mapFormat);
+ maps->d_qnames = BPFFilter::Map(qnames, d_mapFormat);
+ if (!external) {
+ BPFFilter::MapConfiguration filters;
+ filters.d_maxItems = 1;
+ filters.d_type = BPFFilter::MapType::Filters;
+ maps->d_filters = BPFFilter::Map(filters, d_mapFormat);
- const struct bpf_insn main_filter[] = {
+ const struct bpf_insn main_filter[] = {
#include "bpf-filter.main.ebpf"
- };
+ };
- const struct bpf_insn qname_filter[] = {
+ const struct bpf_insn qname_filter[] = {
#include "bpf-filter.qname.ebpf"
- };
+ };
- try {
- d_mainfilter = loadProgram(main_filter,
- sizeof(main_filter));
- }
- catch (const std::exception& e) {
- throw std::runtime_error("Error load the main eBPF filter: " + std::string(e.what()));
- }
+ try {
+ d_mainfilter = loadProgram(main_filter,
+ sizeof(main_filter));
+ }
+ catch (const std::exception& e) {
+ throw std::runtime_error("Error load the main eBPF filter: " + std::string(e.what()));
+ }
- try {
- d_qnamefilter = loadProgram(qname_filter,
- sizeof(qname_filter));
- }
- catch (const std::exception& e) {
- throw std::runtime_error("Error load the qname eBPF filter: " + std::string(e.what()));
- }
+ try {
+ d_qnamefilter = loadProgram(qname_filter,
+ sizeof(qname_filter));
+ }
+ catch (const std::exception& e) {
+ throw std::runtime_error("Error load the qname eBPF filter: " + std::string(e.what()));
+ }
- uint32_t key = 0;
- int qnamefd = d_qnamefilter.getHandle();
- int res = bpf_update_elem(maps->d_filters.d_fd.getHandle(), &key, &qnamefd, BPF_ANY);
- if (res != 0) {
- throw std::runtime_error("Error updating BPF filters map: " + stringerror());
+ uint32_t key = 0;
+ int qnamefd = d_qnamefilter.getHandle();
+ int res = bpf_update_elem(maps->d_filters.d_fd.getHandle(), &key, &qnamefd, BPF_ANY);
+ if (res != 0) {
+ throw std::runtime_error("Error updating BPF filters map: " + stringerror());
+ }
}
}
}
}
-void BPFFilter::block(const ComboAddress& addr)
+void BPFFilter::block(const ComboAddress& addr, BPFFilter::MatchAction action)
{
- uint64_t counter = 0;
+ CounterAndActionValue value;
+ value.counter = 0;
+ value.action = action;
+
int res = 0;
if (addr.isIPv4()) {
uint32_t key = htonl(addr.sin4.sin_addr.s_addr);
throw std::runtime_error("Table full when trying to block " + addr.toString());
}
- res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &counter);
+ res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &value);
if (res != -1) {
throw std::runtime_error("Trying to block an already blocked address: " + addr.toString());
}
- res = bpf_update_elem(map.d_fd.getHandle(), &key, &counter, BPF_NOEXIST);
+ res = bpf_update_elem(map.d_fd.getHandle(), &key, &value, BPF_NOEXIST);
if (res == 0) {
++map.d_count;
}
throw std::runtime_error("Table full when trying to block " + addr.toString());
}
- res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &counter);
+ res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &value);
if (res != -1) {
throw std::runtime_error("Trying to block an already blocked address: " + addr.toString());
}
- res = bpf_update_elem(map.d_fd.getHandle(), key, &counter, BPF_NOEXIST);
+ res = bpf_update_elem(map.d_fd.getHandle(), key, &value, BPF_NOEXIST);
if (res == 0) {
map.d_count++;
}
}
}
-void BPFFilter::block(const DNSName& qname, uint16_t qtype)
+void BPFFilter::block(const DNSName& qname, BPFFilter::MatchAction action, uint16_t qtype)
{
- struct QNameKey key;
- struct QNameValue value;
+ CounterAndActionValue cadvalue;
+ QNameValue qvalue;
+ void* value = nullptr;
+
+ if (d_external) {
+ memset(&cadvalue, 0, sizeof(cadvalue));
+ cadvalue.counter = 0;
+ cadvalue.action = action;
+ value = &cadvalue;
+ }
+ else {
+ memset(&qvalue, 0, sizeof(qvalue));
+ qvalue.counter = 0;
+ qvalue.qtype = qtype;
+ value = &qvalue;
+ }
+
+ QNameAndQTypeKey key;
memset(&key, 0, sizeof(key));
- memset(&value, 0, sizeof(value));
- value.counter = 0;
- value.qtype = qtype;
std::string keyStr = qname.toDNSStringLC();
if (keyStr.size() > sizeof(key.qname)) {
throw std::runtime_error("Invalid QName to block " + qname.toLogString());
}
memcpy(key.qname, keyStr.c_str(), keyStr.size());
+ key.qtype = qtype;
{
auto maps = d_maps.lock();
void BPFFilter::unblock(const DNSName& qname, uint16_t qtype)
{
- struct QNameKey key = { { 0 } };
+ QNameAndQTypeKey key;
+ memset(&key, 0, sizeof(key));
std::string keyStr = qname.toDNSStringLC();
- (void) qtype;
if (keyStr.size() > sizeof(key.qname)) {
throw std::runtime_error("Invalid QName to block " + qname.toLogString());
}
memcpy(key.qname, keyStr.c_str(), keyStr.size());
+ key.qtype = qtype;
{
auto maps = d_maps.lock();
uint32_t v4Key = 0;
uint32_t nextV4Key;
- uint64_t value;
+ CounterAndActionValue value;
uint8_t v6Key[16];
uint8_t nextV6Key[16];
v4Key = nextV4Key;
if (bpf_lookup_elem(map.d_fd.getHandle(), &v4Key, &value) == 0) {
v4Addr.sin_addr.s_addr = ntohl(v4Key);
- result.emplace_back(ComboAddress(&v4Addr), value);
+ result.emplace_back(ComboAddress(&v4Addr), value.counter);
}
res = bpf_get_next_key(map.d_fd.getHandle(), &v4Key, &nextV4Key);
if (bpf_lookup_elem(map.d_fd.getHandle(), &nextV6Key, &value) == 0) {
memcpy(&v6Addr.sin6_addr.s6_addr, &nextV6Key, sizeof(nextV6Key));
- result.emplace_back(ComboAddress(&v6Addr), value);
+ result.emplace_back(ComboAddress(&v6Addr), value.counter);
}
res = bpf_get_next_key(map.d_fd.getHandle(), &nextV6Key, &nextV6Key);
{
std::vector<std::tuple<DNSName, uint16_t, uint64_t> > result;
- struct QNameKey key = { { 0 } };
- struct QNameKey nextKey = { { 0 } };
- struct QNameValue value;
+ if (d_mapFormat == MapFormat::Legacy) {
+ QNameKey key = { { 0 } };
+ QNameKey nextKey = { { 0 } };
+ QNameValue value;
- auto maps = d_maps.lock();
- auto& map = maps->d_qnames;
- result.reserve(map.d_count);
- int res = bpf_get_next_key(map.d_fd.getHandle(), &key, &nextKey);
+ auto maps = d_maps.lock();
+ auto& map = maps->d_qnames;
+ result.reserve(map.d_count);
+ int res = bpf_get_next_key(map.d_fd.getHandle(), &key, &nextKey);
- while (res == 0) {
- if (bpf_lookup_elem(map.d_fd.getHandle(), &nextKey, &value) == 0) {
- nextKey.qname[sizeof(nextKey.qname) - 1 ] = '\0';
- result.push_back(std::make_tuple(DNSName((const char*) nextKey.qname, sizeof(nextKey.qname), 0, false), value.qtype, value.counter));
+ while (res == 0) {
+ if (bpf_lookup_elem(map.d_fd.getHandle(), &nextKey, &value) == 0) {
+ nextKey.qname[sizeof(nextKey.qname) - 1 ] = '\0';
+ result.push_back(std::make_tuple(DNSName(reinterpret_cast<const char*>(nextKey.qname), sizeof(nextKey.qname), 0, false), value.qtype, value.counter));
+ }
+
+ res = bpf_get_next_key(map.d_fd.getHandle(), &nextKey, &nextKey);
}
+ }
+ else {
+ QNameAndQTypeKey key;
+ QNameAndQTypeKey nextKey;
+ memset(&key, 0, sizeof(key));
+ memset(&nextKey, 0, sizeof(nextKey));
+ CounterAndActionValue value;
+
+ auto maps = d_maps.lock();
+ auto& map = maps->d_qnames;
+ result.reserve(map.d_count);
+ int res = bpf_get_next_key(map.d_fd.getHandle(), &key, &nextKey);
- res = bpf_get_next_key(map.d_fd.getHandle(), &nextKey, &nextKey);
+ while (res == 0) {
+ if (bpf_lookup_elem(map.d_fd.getHandle(), &nextKey, &value) == 0) {
+ nextKey.qname[sizeof(nextKey.qname) - 1 ] = '\0';
+ result.push_back(std::make_tuple(DNSName(reinterpret_cast<const char*>(nextKey.qname), sizeof(nextKey.qname), 0, false), key.qtype, value.counter));
+ }
+
+ res = bpf_get_next_key(map.d_fd.getHandle(), &nextKey, &nextKey);
+ }
}
+
return result;
}
uint64_t BPFFilter::getHits(const ComboAddress& requestor)
{
- uint64_t counter = 0;
+ CounterAndActionValue counter;
+
if (requestor.isIPv4()) {
uint32_t key = htonl(requestor.sin4.sin_addr.s_addr);
auto& map = maps->d_v4;
int res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &counter);
if (res == 0) {
- return counter;
+ return counter.counter;
}
}
else if (requestor.isIPv6()) {
auto& map = maps->d_v6;
int res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &counter);
if (res == 0) {
- return counter;
+ return counter.counter;
}
}
#else
-BPFFilter::BPFFilter(const BPFFilter::MapConfiguration&, const BPFFilter::MapConfiguration&, const BPFFilter::MapConfiguration&)
+BPFFilter::BPFFilter(const BPFFilter::MapConfiguration&, const BPFFilter::MapConfiguration&, const BPFFilter::MapConfiguration&, BPFFilter::MapFormat, bool)
{
}
throw std::runtime_error("eBPF support not enabled");
}
-void BPFFilter::block(const ComboAddress&)
+void BPFFilter::block(const ComboAddress&, BPFFilter::MatchAction)
{
throw std::runtime_error("eBPF support not enabled");
}
throw std::runtime_error("eBPF support not enabled");
}
-void BPFFilter::block(const DNSName&, uint16_t)
+void BPFFilter::block(const DNSName&, BPFFilter::MatchAction, uint16_t)
{
throw std::runtime_error("eBPF support not enabled");
}
return 0;
}
#endif /* HAVE_EBPF */
+
+bool BPFFilter::supportsMatchAction(MatchAction action) const
+{
+#ifdef HAVE_EBPF
+ if (action == BPFFilter::MatchAction::Drop) {
+ return true;
+ }
+ return d_mapFormat == BPFFilter::MapFormat::WithActions;
+#endif /* HAVE_EBPF */
+ return false;
+}
+
+bool BPFFilter::isExternal() const
+{
+#ifdef HAVE_EBPF
+ return d_external;
+#endif /* HAVE_EBPF */
+ return false;
+}
/* BPF Filter */
#ifdef HAVE_EBPF
using bpfFilterMapParams = boost::variant<uint32_t, std::unordered_map<std::string, boost::variant<uint32_t, std::string>>>;
- luaCtx.writeFunction("newBPFFilter", [client](bpfFilterMapParams v4Params, bpfFilterMapParams v6Params, bpfFilterMapParams qnameParams) {
+ luaCtx.writeFunction("newBPFFilter", [client](bpfFilterMapParams v4Params, bpfFilterMapParams v6Params, bpfFilterMapParams qnameParams, boost::optional<bool> external) {
if (client) {
return std::shared_ptr<BPFFilter>(nullptr);
}
+
BPFFilter::MapConfiguration v4Config, v6Config, qnameConfig;
auto convertParamsToConfig = [](bpfFilterMapParams& params, BPFFilter::MapType type, BPFFilter::MapConfiguration& config) {
convertParamsToConfig(v6Params, BPFFilter::MapType::IPv6, v6Config);
convertParamsToConfig(qnameParams, BPFFilter::MapType::QNames, qnameConfig);
- return std::make_shared<BPFFilter>(v4Config, v6Config, qnameConfig);
+ BPFFilter::MapFormat format = BPFFilter::MapFormat::Legacy;
+ if (external && *external) {
+ format = BPFFilter::MapFormat::WithActions;
+ }
+
+ return std::make_shared<BPFFilter>(v4Config, v6Config, qnameConfig, format, external.value_or(false));
});
- luaCtx.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const ComboAddress& ca)>("block", [](std::shared_ptr<BPFFilter> bpf, const ComboAddress& ca) {
+ luaCtx.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const ComboAddress& ca, boost::optional<uint32_t> action)>("block", [](std::shared_ptr<BPFFilter> bpf, const ComboAddress& ca, boost::optional<uint32_t> action) {
if (bpf) {
- return bpf->block(ca);
+ if (!action) {
+ return bpf->block(ca, BPFFilter::MatchAction::Drop);
+ }
+ else {
+ BPFFilter::MatchAction match;
+
+ switch (*action) {
+ case 0:
+ match = BPFFilter::MatchAction::Pass;
+ break;
+ case 1:
+ match = BPFFilter::MatchAction::Drop;
+ break;
+ case 2:
+ match = BPFFilter::MatchAction::Truncate;
+ break;
+ default:
+ throw std::runtime_error("Unsupported action for BPFFilter::block");
+ }
+ return bpf->block(ca, match);
+ }
}
});
- luaCtx.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const DNSName& qname, boost::optional<uint16_t> qtype)>("blockQName", [](std::shared_ptr<BPFFilter> bpf, const DNSName& qname, boost::optional<uint16_t> qtype) {
+ luaCtx.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const DNSName& qname, boost::optional<uint16_t> qtype, boost::optional<uint32_t> action)>("blockQName", [](std::shared_ptr<BPFFilter> bpf, const DNSName& qname, boost::optional<uint16_t> qtype, boost::optional<uint32_t> action) {
if (bpf) {
- return bpf->block(qname, qtype ? *qtype : 255);
+ if (!action) {
+ return bpf->block(qname, BPFFilter::MatchAction::Drop, qtype.value_or(255));
+ }
+ else {
+ BPFFilter::MatchAction match;
+
+ switch (*action) {
+ case 0:
+ match = BPFFilter::MatchAction::Pass;
+ break;
+ case 1:
+ match = BPFFilter::MatchAction::Drop;
+ break;
+ case 2:
+ match = BPFFilter::MatchAction::Truncate;
+ break;
+ default:
+ throw std::runtime_error("Unsupported action for BPFFilter::block");
+ }
+ return bpf->block(qname, match, qtype.value_or(255));
+ }
}
});