diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 21:11:59 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 21:11:59 +0000 |
commit | 3cd01b932e1c85394272ae64fae67ebeda92fb00 (patch) | |
tree | c5a3115d710afc1879ddea5349362a2bc651733c /bpf-filter.cc | |
parent | Initial commit. (diff) | |
download | dnsdist-3cd01b932e1c85394272ae64fae67ebeda92fb00.tar.xz dnsdist-3cd01b932e1c85394272ae64fae67ebeda92fb00.zip |
Adding upstream version 1.8.3.upstream/1.8.3
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'bpf-filter.cc')
-rw-r--r-- | bpf-filter.cc | 982 |
1 files changed, 982 insertions, 0 deletions
diff --git a/bpf-filter.cc b/bpf-filter.cc new file mode 100644 index 0000000..bb0c58e --- /dev/null +++ b/bpf-filter.cc @@ -0,0 +1,982 @@ +/* + * This file is part of PowerDNS or dnsdist. + * Copyright -- PowerDNS.COM B.V. and its contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * In addition, for the avoidance of any doubt, permission is granted to + * link this program with OpenSSL and to (re)distribute the binaries + * produced as the result of such linking. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include "bpf-filter.hh" +#include "iputils.hh" +#include "dolog.hh" + +#ifdef HAVE_EBPF + +#include <sys/syscall.h> +#include <sys/resource.h> +#include <linux/bpf.h> + +#include "ext/libbpf/libbpf.h" + +#include "misc.hh" + +static __u64 ptr_to_u64(void *ptr) +{ + return (__u64) (unsigned long) ptr; +} + +/* these can be static as they are not declared in libbpf.h: */ +static int bpf_pin_map(int fd, const std::string& path) +{ + union bpf_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.bpf_fd = fd; + attr.pathname = ptr_to_u64(const_cast<char*>(path.c_str())); + return syscall(SYS_bpf, BPF_OBJ_PIN, &attr, sizeof(attr)); +} + +static int bpf_load_pinned_map(const std::string& path) +{ + union bpf_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.pathname = ptr_to_u64(const_cast<char*>(path.c_str())); + return syscall(SYS_bpf, BPF_OBJ_GET, &attr, sizeof(attr)); +} + +static void bpf_check_map_sizes(int fd, uint32_t expectedKeySize, uint32_t expectedValueSize) +{ + struct bpf_map_info info; + uint32_t info_len = sizeof(info); + memset(&info, 0, sizeof(info)); + + union bpf_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.info.bpf_fd = fd; + attr.info.info_len = info_len; + attr.info.info = ptr_to_u64(&info); + + int err = syscall(SYS_bpf, BPF_OBJ_GET_INFO_BY_FD, &attr, sizeof(attr)); + if (err != 0) { + throw std::runtime_error("Error checking the size of eBPF map: " + stringerror()); + } + if (info_len != sizeof(info)) { + throw std::runtime_error("Error checking the size of eBPF map: invalid info size returned"); + } + if (info.key_size != expectedKeySize) { + throw std::runtime_error("Error checking the size of eBPF map: key size mismatch (" + std::to_string(info.key_size) + " VS " + std::to_string(expectedKeySize) + ")"); + } + if (info.value_size != expectedValueSize) { + throw std::runtime_error("Error checking the size of eBPF map: value size mismatch (" + std::to_string(info.value_size) + " VS " + std::to_string(expectedValueSize) + ")"); + } +} + +int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size, + int max_entries, int map_flags) +{ + union bpf_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.map_type = map_type; + attr.key_size = key_size; + attr.value_size = value_size; + attr.max_entries = max_entries; + attr.map_flags = map_flags; + return syscall(SYS_bpf, BPF_MAP_CREATE, &attr, sizeof(attr)); +} + +int bpf_update_elem(int fd, void *key, void *value, unsigned long long flags) +{ + union bpf_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.map_fd = fd; + attr.key = ptr_to_u64(key); + attr.value = ptr_to_u64(value); + attr.flags = flags; + return syscall(SYS_bpf, BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr)); +} + +int bpf_lookup_elem(int fd, void *key, void *value) +{ + union bpf_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.map_fd = fd; + attr.key = ptr_to_u64(key); + attr.value = ptr_to_u64(value); + return syscall(SYS_bpf, BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr)); +} + +int bpf_delete_elem(int fd, void *key) +{ + union bpf_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.map_fd = fd; + attr.key = ptr_to_u64(key); + return syscall(SYS_bpf, BPF_MAP_DELETE_ELEM, &attr, sizeof(attr)); +} + +int bpf_get_next_key(int fd, void *key, void *next_key) +{ + union bpf_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.map_fd = fd; + attr.key = ptr_to_u64(key); + attr.next_key = ptr_to_u64(next_key); + return syscall(SYS_bpf, BPF_MAP_GET_NEXT_KEY, &attr, sizeof(attr)); +} + +int bpf_prog_load(enum bpf_prog_type prog_type, + const struct bpf_insn *insns, int prog_len, + const char *license, int kern_version) +{ + char log_buf[65535]; + union bpf_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.prog_type = prog_type; + attr.insns = ptr_to_u64((void *) insns); + attr.insn_cnt = prog_len / sizeof(struct bpf_insn); + attr.license = ptr_to_u64((void *) license); + attr.log_buf = ptr_to_u64(log_buf); + attr.log_size = sizeof(log_buf); + attr.log_level = 1; + /* assign one field outside of struct init to make sure any + * padding is zero initialized + */ + attr.kern_version = kern_version; + + long res = syscall(SYS_bpf, BPF_PROG_LOAD, &attr, sizeof(attr)); + if (res == -1) { + if (errno == ENOSPC) { + /* not enough space in the log buffer */ + attr.log_level = 0; + attr.log_size = 0; + attr.log_buf = ptr_to_u64(nullptr); + res = syscall(SYS_bpf, BPF_PROG_LOAD, &attr, sizeof(attr)); + if (res != -1) { + return res; + } + } + throw std::runtime_error("Error loading BPF program: (" + stringerror() + "):\n" + std::string(log_buf)); + } + return res; +} + +struct KeyV6 +{ + uint8_t src[16]; +}; + +struct QNameKey +{ + uint8_t qname[255]; +}; + +struct QNameAndQTypeKey +{ + uint8_t qname[255]; + uint16_t qtype; +}; + +struct QNameValue +{ + uint64_t counter{0}; + uint16_t qtype{0}; +}; + + +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 */ + d_fd = FDWrapper(bpf_create_map(BPF_MAP_TYPE_PROG_ARRAY, sizeof(uint32_t), sizeof(uint32_t), d_config.d_maxItems, 0)); + if (d_fd.getHandle() == -1) { + throw std::runtime_error("Error creating a BPF program map of size " + std::to_string(d_config.d_maxItems) + ": " + stringerror()); + } + } + else { + int keySize = 0; + int valueSize = 0; + int flags = 0; + bpf_map_type type = BPF_MAP_TYPE_HASH; + 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)) + " for legacy eBPF, perhaps you are trying to use an external program instead?"); + } + } + 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::CIDR4: + keySize = sizeof(CIDR4); + valueSize = sizeof(CounterAndActionValue); + flags = BPF_F_NO_PREALLOC; + type = BPF_MAP_TYPE_LPM_TRIE; + break; + case MapType::CIDR6: + keySize = sizeof(CIDR6); + valueSize = sizeof(CounterAndActionValue); + flags = BPF_F_NO_PREALLOC; + type = BPF_MAP_TYPE_LPM_TRIE; + 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()) { + /* try to load */ + d_fd = FDWrapper(bpf_load_pinned_map(d_config.d_pinnedPath)); + if (d_fd.getHandle() != -1) { + /* sanity checks: key and value size */ + bpf_check_map_sizes(d_fd.getHandle(), keySize, valueSize); + switch (d_config.d_type) { + case MapType::IPv4: { + uint32_t key = 0; + while (bpf_get_next_key(d_fd.getHandle(), &key, &key) == 0) { + ++d_count; + } + break; + } + case MapType::IPv6: { + KeyV6 key; + memset(&key, 0, sizeof(key)); + while (bpf_get_next_key(d_fd.getHandle(), &key, &key) == 0) { + ++d_count; + } + break; + } + case MapType::CIDR4: { + CIDR4 key; + memset(&key, 0, sizeof(key)); + while (bpf_get_next_key(d_fd.getHandle(), &key, &key) == 0) { + ++d_count; + } + break; + } + case MapType::CIDR6: { + CIDR6 key; + memset(&key, 0, sizeof(key)); + while (bpf_get_next_key(d_fd.getHandle(), &key, &key) == 0) { + ++d_count; + } + break; + } + case MapType::QNames: { + if (format == MapFormat::Legacy) { + QNameKey key; + memset(&key, 0, sizeof(key)); + while (bpf_get_next_key(d_fd.getHandle(), &key, &key) == 0) { + ++d_count; + } + } + else { + QNameAndQTypeKey key; + memset(&key, 0, sizeof(key)); + while (bpf_get_next_key(d_fd.getHandle(), &key, &key) == 0) { + ++d_count; + } + } + break; + } + + default: + throw std::runtime_error("Unsupported eBPF map type: " + std::to_string(static_cast<uint8_t>(d_config.d_type))); + } + } + } + + if (d_fd.getHandle() == -1) { + d_fd = FDWrapper(bpf_create_map(type, keySize, valueSize, static_cast<int>(d_config.d_maxItems), flags)); + if (d_fd.getHandle() == -1) { + throw std::runtime_error("Error creating a BPF map of size " + std::to_string(d_config.d_maxItems) + ": " + stringerror()); + } + + if (!d_config.d_pinnedPath.empty()) { + if (bpf_pin_map(d_fd.getHandle(), d_config.d_pinnedPath) != 0) { + throw std::runtime_error("Unable to pin map to path '" + d_config.d_pinnedPath + "': " + stringerror()); + } + } + } + } +} + +static FDWrapper loadProgram(const struct bpf_insn* filter, size_t filterSize) +{ + auto fd = FDWrapper(bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, + filter, + filterSize, + "GPL", + 0)); + if (fd.getHandle() == -1) { + throw std::runtime_error("error loading BPF filter: " + stringerror()); + } + return fd; +} + + +BPFFilter::BPFFilter(std::unordered_map<std::string, MapConfiguration>& configs, 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"); + } + + struct rlimit old_limit; + if (getrlimit(RLIMIT_MEMLOCK, &old_limit) != 0) { + throw std::runtime_error("Unable to get memory lock limit: " + stringerror()); + } + + const rlim_t new_limit_size = 1024 * 1024; + + /* Check if the current soft memlock limit is at least the limit */ + if (old_limit.rlim_cur < new_limit_size) { + infolog("The current limit of locked memory (soft: %d, hard: %d) is too low for eBPF, trying to raise it to %d", old_limit.rlim_cur, old_limit.rlim_max, new_limit_size); + + struct rlimit new_limit; + new_limit.rlim_cur = new_limit_size; + new_limit.rlim_max = new_limit_size; + + if (setrlimit(RLIMIT_MEMLOCK, &new_limit) != 0) { + warnlog("Unable to raise the maximum amount of locked memory for eBPF from %d to %d, consider raising RLIMIT_MEMLOCK or setting LimitMEMLOCK in the systemd unit: %d", old_limit.rlim_cur, new_limit.rlim_cur, stringerror()); + } + } + + auto maps = d_maps.lock(); + + maps->d_v4 = BPFFilter::Map(configs["ipv4"], d_mapFormat); + maps->d_v6 = BPFFilter::Map(configs["ipv6"], d_mapFormat); + maps->d_qnames = BPFFilter::Map(configs["qnames"], d_mapFormat); + + if (d_mapFormat != BPFFilter::MapFormat::Legacy) { + maps->d_cidr4 = BPFFilter::Map(configs["cidr4"], d_mapFormat); + maps->d_cidr6 = BPFFilter::Map(configs["cidr6"], 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[] = { +#include "bpf-filter.main.ebpf" + }; + + 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_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()); + } + } +} + +void BPFFilter::addSocket(int sock) +{ + int fd = d_mainfilter.getHandle(); + int res = setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, &fd, sizeof(fd)); + + if (res != 0) { + throw std::runtime_error("Error attaching BPF filter to this socket: " + stringerror()); + } +} + +void BPFFilter::removeSocket(int sock) +{ + int fd = d_mainfilter.getHandle(); + int res = setsockopt(sock, SOL_SOCKET, SO_DETACH_BPF, &fd, sizeof(fd)); + + if (res != 0) { + throw std::runtime_error("Error detaching BPF filter from this socket: " + stringerror()); + } +} + +void BPFFilter::block(const ComboAddress& addr, BPFFilter::MatchAction action) +{ + CounterAndActionValue value; + value.counter = 0; + value.action = action; + + int res = 0; + if (addr.isIPv4()) { + uint32_t key = htonl(addr.sin4.sin_addr.s_addr); + auto maps = d_maps.lock(); + auto& map = maps->d_v4; + if (map.d_count >= map.d_config.d_maxItems) { + throw std::runtime_error("Table full when trying to block " + addr.toString()); + } + + 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, &value, BPF_NOEXIST); + if (res == 0) { + ++map.d_count; + } + } + else if (addr.isIPv6()) { + uint8_t key[16]; + static_assert(sizeof(addr.sin6.sin6_addr.s6_addr) == sizeof(key), "POSIX mandates s6_addr to be an array of 16 uint8_t"); + for (size_t idx = 0; idx < sizeof(key); idx++) { + key[idx] = addr.sin6.sin6_addr.s6_addr[idx]; + } + + auto maps = d_maps.lock(); + auto& map = maps->d_v6; + if (map.d_count >= map.d_config.d_maxItems) { + throw std::runtime_error("Table full when trying to block " + addr.toString()); + } + + 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, &value, BPF_NOEXIST); + if (res == 0) { + map.d_count++; + } + } + + if (res != 0) { + throw std::runtime_error("Error adding blocked address " + addr.toString() + ": " + stringerror()); + } +} + +void BPFFilter::unblock(const ComboAddress& addr) +{ + int res = 0; + if (addr.isIPv4()) { + uint32_t key = htonl(addr.sin4.sin_addr.s_addr); + auto maps = d_maps.lock(); + auto& map = maps->d_v4; + res = bpf_delete_elem(map.d_fd.getHandle(), &key); + if (res == 0) { + --map.d_count; + } + } + else if (addr.isIPv6()) { + uint8_t key[16]; + static_assert(sizeof(addr.sin6.sin6_addr.s6_addr) == sizeof(key), "POSIX mandates s6_addr to be an array of 16 uint8_t"); + for (size_t idx = 0; idx < sizeof(key); idx++) { + key[idx] = addr.sin6.sin6_addr.s6_addr[idx]; + } + + auto maps = d_maps.lock(); + auto& map = maps->d_v6; + res = bpf_delete_elem(map.d_fd.getHandle(), key); + if (res == 0) { + --map.d_count; + } + } + + if (res != 0) { + throw std::runtime_error("Error removing blocked address " + addr.toString() + ": " + stringerror()); + } +} + +void BPFFilter::addRangeRule(const Netmask& addr, bool force, BPFFilter::MatchAction action) +{ + CounterAndActionValue value; + + int res = 0; + if (addr.isIPv4()) { + CIDR4 key(addr); + auto maps = d_maps.lock(); + auto& map = maps->d_cidr4; + if (map.d_fd.getHandle() == -1) { + throw std::runtime_error("Trying to use an unsupported map type, likely adding a range to a legacy eBPF program"); + } + if (map.d_count >= map.d_config.d_maxItems) { + throw std::runtime_error("Table full when trying to add this rule: " + addr.toString()); + } + + res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &value); + if (((res != -1 && value.action == action) || (res == -1 && value.action == BPFFilter::MatchAction::Pass)) && !force) { + throw std::runtime_error("Trying to add a useless rule: " + addr.toString()); + } + + value.counter = 0; + value.action = action; + + res = bpf_update_elem(map.d_fd.getHandle(), &key, &value, force ? BPF_ANY : BPF_NOEXIST); + if (res == 0) { + ++map.d_count; + } + } + else if (addr.isIPv6()) { + CIDR6 key(addr); + + auto maps = d_maps.lock(); + auto& map = maps->d_cidr6; + if (map.d_fd.getHandle() == -1) { + throw std::runtime_error("Trying to use an unsupported map type, likely adding a range to a legacy eBPF program"); + } + if (map.d_count >= map.d_config.d_maxItems) { + throw std::runtime_error("Table full when trying to add this rule: " + addr.toString()); + } + + res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &value); + if (((res != -1 && value.action == action) || (res == -1 && value.action == BPFFilter::MatchAction::Pass)) && !force) { + throw std::runtime_error("Trying to add a useless rule: " + addr.toString()); + } + + value.counter = 0; + value.action = action; + + res = bpf_update_elem(map.d_fd.getHandle(), &key, &value, BPF_NOEXIST); + if (res == 0) { + map.d_count++; + } + } + + if (res != 0) { + throw std::runtime_error("Error adding this rule: " + addr.toString() + ": " + stringerror()); + } +} + +void BPFFilter::rmRangeRule(const Netmask& addr) +{ + int res = 0; + CounterAndActionValue value; + value.counter = 0; + value.action = MatchAction::Pass; + if (addr.isIPv4()) { + CIDR4 key(addr); + auto maps = d_maps.lock(); + auto& map = maps->d_cidr4; + if (map.d_fd.getHandle() == -1) { + throw std::runtime_error("Trying to use an unsupported map type, likely adding a range to a legacy eBPF program"); + } + res = bpf_delete_elem(map.d_fd.getHandle(), &key); + if (res == 0) { + --map.d_count; + } + else { + throw std::runtime_error("Cannot remove '" + addr.toString() + "': No such rule"); + } + } + else if (addr.isIPv6()) { + CIDR6 key(addr); + + auto maps = d_maps.lock(); + auto& map = maps->d_cidr6; + if (map.d_fd.getHandle() == -1) { + throw std::runtime_error("Trying to use an unsupported map type, likely adding a range to a legacy eBPF program"); + } + res = bpf_delete_elem(map.d_fd.getHandle(), &key); + if (res == 0) { + --map.d_count; + } + else { + throw std::runtime_error("Cannot remove '" + addr.toString() + "': No such rule"); + } + } + + if (res != 0) { + throw std::runtime_error("Error removing this rule: " + addr.toString() + ": " + stringerror()); + } +} + +void BPFFilter::block(const DNSName& qname, BPFFilter::MatchAction action, uint16_t qtype) +{ + CounterAndActionValue cadvalue; + QNameValue qvalue; + void* value = nullptr; + + if (d_external) { + cadvalue.counter = 0; + cadvalue.action = action; + value = &cadvalue; + } + else { + qvalue.counter = 0; + qvalue.qtype = qtype; + value = &qvalue; + } + + QNameAndQTypeKey key; + memset(&key, 0, sizeof(key)); + + 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(); + auto& map = maps->d_qnames; + if (map.d_count >= map.d_config.d_maxItems) { + throw std::runtime_error("Table full when trying to block " + qname.toLogString()); + } + + int res = bpf_lookup_elem(map.d_fd.getHandle(), &key, value); + if (res != -1) { + throw std::runtime_error("Trying to block an already blocked qname: " + qname.toLogString()); + } + res = bpf_update_elem(map.d_fd.getHandle(), &key, value, BPF_NOEXIST); + if (res == 0) { + ++map.d_count; + } + + if (res != 0) { + throw std::runtime_error("Error adding blocked qname " + qname.toLogString() + ": " + stringerror()); + } + } +} + +void BPFFilter::unblock(const DNSName& qname, uint16_t qtype) +{ + QNameAndQTypeKey key; + memset(&key, 0, sizeof(key)); + 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(); + auto& map = maps->d_qnames; + int res = bpf_delete_elem(map.d_fd.getHandle(), &key); + if (res == 0) { + --map.d_count; + } + else { + throw std::runtime_error("Error removing qname address " + qname.toLogString() + ": " + stringerror()); + } + } +} + +std::vector<std::pair<ComboAddress, uint64_t> > BPFFilter::getAddrStats() +{ + std::vector<std::pair<ComboAddress, uint64_t> > result; + { + auto maps = d_maps.lock(); + result.reserve(maps->d_v4.d_count + maps->d_v6.d_count); + } + + sockaddr_in v4Addr; + memset(&v4Addr, 0, sizeof(v4Addr)); + v4Addr.sin_family = AF_INET; + + uint32_t v4Key = 0; + uint32_t nextV4Key; + CounterAndActionValue value; + + uint8_t v6Key[16]; + uint8_t nextV6Key[16]; + sockaddr_in6 v6Addr; + memset(&v6Addr, 0, sizeof(v6Addr)); + v6Addr.sin6_family = AF_INET6; + + static_assert(sizeof(v6Addr.sin6_addr.s6_addr) == sizeof(v6Key), "POSIX mandates s6_addr to be an array of 16 uint8_t"); + memset(&v6Key, 0, sizeof(v6Key)); + + auto maps = d_maps.lock(); + + { + auto& map = maps->d_v4; + int res = bpf_get_next_key(map.d_fd.getHandle(), &v4Key, &nextV4Key); + + while (res == 0) { + 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.counter); + } + + res = bpf_get_next_key(map.d_fd.getHandle(), &v4Key, &nextV4Key); + } + } + + { + auto& map = maps->d_v6; + int res = bpf_get_next_key(map.d_fd.getHandle(), &v6Key, &nextV6Key); + + while (res == 0) { + 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.counter); + } + + res = bpf_get_next_key(map.d_fd.getHandle(), &nextV6Key, &nextV6Key); + } + } + + return result; +} + +std::vector<std::pair<Netmask, CounterAndActionValue>> BPFFilter::getRangeRule() +{ + CIDR4 cidr4[2]; + CIDR6 cidr6[2]; + std::vector<std::pair<Netmask, CounterAndActionValue>> result; + + sockaddr_in v4Addr; + sockaddr_in6 v6Addr; + CounterAndActionValue value; + + memset(cidr4, 0, sizeof(cidr4)); + memset(cidr6, 0, sizeof(cidr6)); + memset(&v4Addr, 0, sizeof(v4Addr)); + memset(&v6Addr, 0, sizeof(v6Addr)); + v4Addr.sin_family = AF_INET; + v6Addr.sin6_family = AF_INET6; + auto maps = d_maps.lock(); + result.reserve(maps->d_cidr4.d_count + maps->d_cidr6.d_count); + { + auto& map = maps->d_cidr4; + int res = bpf_get_next_key(map.d_fd.getHandle(), &cidr4[0], &cidr4[1]); + while (res == 0) { + if (bpf_lookup_elem(map.d_fd.getHandle(), &cidr4[1], &value) == 0) { + v4Addr.sin_addr.s_addr = cidr4[1].addr.s_addr; + result.emplace_back(Netmask(&v4Addr, cidr4[1].cidr), value); + } + + res = bpf_get_next_key(map.d_fd.getHandle(), &cidr4[1], &cidr4[1]); + } + } + + { + auto& map = maps->d_cidr6; + int res = bpf_get_next_key(map.d_fd.getHandle(), &cidr6[0], &cidr6[1]); + while (res == 0) { + if (bpf_lookup_elem(map.d_fd.getHandle(), &cidr6[1], &value) == 0) { + v6Addr.sin6_addr = cidr6[1].addr; + result.emplace_back(Netmask(&v6Addr, cidr6[1].cidr), value); + } + + res = bpf_get_next_key(map.d_fd.getHandle(), &cidr6[1], &cidr6[1]); + } + } + return result; +} + +std::vector<std::tuple<DNSName, uint16_t, uint64_t> > BPFFilter::getQNameStats() +{ + std::vector<std::tuple<DNSName, uint16_t, uint64_t> > result; + + 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); + + 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); + + 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) +{ + CounterAndActionValue counter; + + if (requestor.isIPv4()) { + uint32_t key = htonl(requestor.sin4.sin_addr.s_addr); + + auto maps = d_maps.lock(); + auto& map = maps->d_v4; + int res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &counter); + if (res == 0) { + return counter.counter; + } + } + else if (requestor.isIPv6()) { + uint8_t key[16]; + static_assert(sizeof(requestor.sin6.sin6_addr.s6_addr) == sizeof(key), "POSIX mandates s6_addr to be an array of 16 uint8_t"); + for (size_t idx = 0; idx < sizeof(key); idx++) { + key[idx] = requestor.sin6.sin6_addr.s6_addr[idx]; + } + + auto maps = d_maps.lock(); + auto& map = maps->d_v6; + int res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &counter); + if (res == 0) { + return counter.counter; + } + } + + return 0; +} + +#else + +BPFFilter::BPFFilter(std::unordered_map<std::string, MapConfiguration>& configs, BPFFilter::MapFormat format, bool external) +{ +} + +void BPFFilter::addSocket(int) +{ + throw std::runtime_error("eBPF support not enabled"); +} + +void BPFFilter::removeSocket(int) +{ + throw std::runtime_error("eBPF support not enabled"); +} + +void BPFFilter::block(const ComboAddress&, BPFFilter::MatchAction) +{ + throw std::runtime_error("eBPF support not enabled"); +} + +void BPFFilter::unblock(const ComboAddress&) +{ + throw std::runtime_error("eBPF support not enabled"); +} + +void BPFFilter::block(const DNSName&, BPFFilter::MatchAction, uint16_t) +{ + throw std::runtime_error("eBPF support not enabled"); +} + +void BPFFilter::unblock(const DNSName&, uint16_t) +{ + throw std::runtime_error("eBPF support not enabled"); +} + +void BPFFilter::addRangeRule(const Netmask&, bool, BPFFilter::MatchAction) +{ + throw std::runtime_error("eBPF support not enabled"); +} +void BPFFilter::rmRangeRule(const Netmask&) +{ + throw std::runtime_error("eBPF support not enabled"); +} + +std::vector<std::pair<Netmask, CounterAndActionValue>> BPFFilter::getRangeRule(){ + std::vector<std::pair<Netmask, CounterAndActionValue>> result; + return result; +} +std::vector<std::pair<ComboAddress, uint64_t> > BPFFilter::getAddrStats() +{ + std::vector<std::pair<ComboAddress, uint64_t> > result; + return result; +} + +std::vector<std::tuple<DNSName, uint16_t, uint64_t> > BPFFilter::getQNameStats() +{ + std::vector<std::tuple<DNSName, uint16_t, uint64_t> > result; + return result; +} + +uint64_t BPFFilter::getHits(const ComboAddress&) +{ + 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; +} |